diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000000..b75130124b --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,7 @@ +[alias] +xtask = "run --manifest-path xtask/Cargo.toml" + +[build] +rustflags = [ +"--cfg=web_sys_unstable_apis" +] diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 0000000000..b8dbfe9528 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,9 @@ +# None of our tests should take longer than 45s, and if they've gone 2x that, +# terminate them to prevent infinite run-on. +[profile.default] +slow-timeout = { period = "45s", terminate-after = 2 } + +# Use two threads for tests with "2_threads" in their name +[[profile.default.overrides]] +filter = 'test(~2_threads)' +threads-required = 2 diff --git a/.deny.toml b/.deny.toml index 5c214bbc28..9c8f96c6b4 100644 --- a/.deny.toml +++ b/.deny.toml @@ -1,12 +1,11 @@ [bans] multiple-versions = "deny" skip-tree = [ - { name = "cts_runner" }, - { name = "player" }, - { name = "wgpu-info" }, + { name = "windows-sys", version = "0.45" }, + { name = "winit", version = "0.27.5" }, + { name = "rustc_version", version = "0.2.3" }, ] skip = [ - { name = "wgpu" } ] wildcards = "deny" @@ -19,14 +18,19 @@ allow = [ "CC0-1.0", "ISC", "MIT", - "MPL-2.0", + "MIT-0", "Unicode-DFS-2016", "Zlib", ] [sources] allow-git = [ + # Waiting on releases; used in examples only + "https://github.com/SiegeEngine/ddsfile", + "https://github.com/Razaekel/noise-rs", + "https://github.com/grovesNL/glow", + "https://github.com/gfx-rs/metal-rs", ] unknown-registry = "deny" unknown-git = "deny" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7fefad320c..009cd30564 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,10 @@ -/cts_runner/ @crowlKats -/deno_webgpu/ @crowlKats +* @gfx-rs/wgpu + +/cts_runner/ @gfx-rs/deno @gfx-rs/wgpu +/deno_webgpu/ @gfx-rs/deno @gfx-rs/wgpu +/naga/ @gfx-rs/naga +/naga-cli/ @gfx-rs/naga + +# We leave the codeowners empty for the changelog, so naga changes +# don't trigger wgpu reviews and vise versa. +/CHANGELOG.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index bbcfe868fa..dc45ceff32 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,5 @@ blank_issues_enabled: false contact_links: - - name: Issues with shaders - url: https://github.com/gfx-rs/naga/issues/new/choose - about: Issues with or enhancements for the shader translation. - name: Question about wgpu url: https://github.com/gfx-rs/wgpu/discussions/new about: Any questions about how to use wgpu should go here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 74b048acfa..3958eade2c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,9 +4,24 @@ updates: - package-ecosystem: cargo directory: / schedule: - interval: daily + interval: weekly + # This allows dependabot to update _all_ lockfile packages. + # + # These will be grouped into the existing group update PRs, so shoudn't generate additional jobs. + allow: + # Allow both direct and indirect updates for all packages + - dependency-type: "all" + # Waiting on https://github.com/dependabot/dependabot-core/issues/4009 to be resolved, shouldn't be long. + # versioning-strategy: increase-if-necessary + groups: + patch-updates: + patterns: + - "*" + update-types: + - "minor" + - "patch" - package-ecosystem: github-actions directory: / schedule: - interval: daily + interval: weekly diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 14e09dde05..95e8ed603e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,3 @@ -**Checklist** - -- [ ] Run `cargo clippy`. -- [ ] Run `cargo clippy --target wasm32-unknown-unknown` if applicable. -- [ ] Add change to CHANGELOG.md. See simple instructions inside file. - **Connections** _Link to the issues addressed by this PR, or dependent PRs in other repositories_ @@ -12,3 +6,19 @@ _Describe what problem this is solving, and how it's solved._ **Testing** _Explain how this change is tested._ + + + +**Checklist** + +- [ ] Run `cargo fmt`. +- [ ] Run `cargo clippy`. If applicable, add: + - [ ] `--target wasm32-unknown-unknown` + - [ ] `--target wasm32-unknown-emscripten` +- [ ] Run `cargo xtask test` to run tests. +- [ ] Add change to `CHANGELOG.md`. See simple instructions inside file. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06ae299b77..f6d9a3e366 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,14 +8,56 @@ on: merge_group: env: + # + # Dependency versioning + # + + # Sourced from https://vulkan.lunarg.com/sdk/home#linux + VULKAN_SDK_VERSION: "1.3.268" + # Sourced from https://www.nuget.org/packages/Microsoft.Direct3D.WARP + WARP_VERSION: "1.0.8" + + # Sourced from https://github.com/microsoft/DirectXShaderCompiler/releases + # + # Must also be changed in shaders.yaml + DXC_RELEASE: "v1.7.2308" + DXC_FILENAME: "dxc_2023_08_14.zip" + + # Sourced from https://archive.mesa3d.org/. Bumping this requires + # updating the mesa build in https://github.com/gfx-rs/ci-build and creating a new release. + MESA_VERSION: "23.3.1" + # Corresponds to https://github.com/gfx-rs/ci-build/releases + CI_BINARY_BUILD: "build18" + + # We sometimes need nightly to use special things in CI. + # + # In order to prevent CI regressions, we pin the nightly version. + NIGHTLY_VERSION: "nightly-2023-12-17" + # Version of rust used to build the docs with. + # + # This needs to be newer to work around https://github.com/gfx-rs/wgpu/issues/4905. + # + # Once 1.76 coes out, we can use that instead of nightly. + DOCS_RUST_VERSION: "nightly-2023-12-17" + # This is the MSRV used by `wgpu` itself and all surrounding infrastructure. + REPO_MSRV: "1.71" + # This is the MSRV used by the `wgpu-core`, `wgpu-hal`, and `wgpu-types` crates, + # to ensure that they can be used with firefox. + CORE_MSRV: "1.70" + + # + # Environment variables + # + CARGO_INCREMENTAL: false CARGO_TERM_COLOR: always + WGPU_DX12_COMPILER: dxc RUST_LOG: info RUST_BACKTRACE: full - MSRV: 1.65 PKG_CONFIG_ALLOW_CROSS: 1 # allow android to work RUSTFLAGS: --cfg=web_sys_unstable_apis -D warnings RUSTDOCFLAGS: -Dwarnings + WASM_BINDGEN_TEST_TIMEOUT: 300 # 5 minutes CACHE_SUFFIX: c # cache busting # We distinguish the following kinds of builds: @@ -32,7 +74,10 @@ env: # It adds overhead to the build and another point of failure. jobs: - check-msrv: + check: + # runtime is normally 2-8 minutes + timeout-minutes: 15 + strategy: fail-fast: false matrix: @@ -93,12 +138,19 @@ jobs: steps: - name: checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: Install MSRV toolchain + - name: Install Repo MSRV toolchain + run: | + rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy --target ${{ matrix.target }} + rustup override set ${{ env.REPO_MSRV }} + cargo -V + + # Use special toolchain for rustdoc, see https://github.com/gfx-rs/wgpu/issues/4905 + - name: Install Rustdoc Toolchain run: | - rustup toolchain install ${{ env.MSRV }} --no-self-update --profile=minimal --component clippy --target ${{ matrix.target }} - rustup default ${{ env.MSRV }} + rustup toolchain install ${{ env.DOCS_RUST_VERSION }} --no-self-update --profile=minimal --component rust-docs --target ${{ matrix.target }} + cargo +${{ env.DOCS_RUST_VERSION }} -V - name: disable debug shell: bash @@ -109,11 +161,12 @@ jobs: debug = false" >> .cargo/config.toml - name: caching - uses: Swatinem/rust-cache@v2 + # Pin to 2.7.1 due to a bug in github actions cache action https://github.com/Swatinem/rust-cache/issues/182 + uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 with: key: clippy-${{ matrix.target }}-${{ matrix.kind }}-${{ env.CACHE_SUFFIX }} - - name: install aarch64-linux-gnu g++ + - name: (linux aarch64) install aarch64-linux-gnu g++ if: matrix.target == 'aarch64-unknown-linux-gnu' run: | set -e @@ -122,7 +175,7 @@ jobs: sudo apt-get install g++-aarch64-linux-gnu - - name: add android apk to path + - name: (android) add android apk to path if: matrix.target == 'aarch64-linux-android' run: | # clang++ will be detected correctly by CC from path @@ -140,11 +193,11 @@ jobs: # build for WebGPU cargo clippy --target ${{ matrix.target }} --tests --features glsl,spirv,fragile-send-sync-non-atomic-wasm cargo clippy --target ${{ matrix.target }} --tests --features glsl,spirv - cargo doc --target ${{ matrix.target }} --no-deps --features glsl,spirv + cargo +${{ env.DOCS_RUST_VERSION }} doc --target ${{ matrix.target }} --no-deps --features glsl,spirv # all features cargo clippy --target ${{ matrix.target }} --tests --all-features - cargo doc --target ${{ matrix.target }} --no-deps --all-features + cargo +${{ env.DOCS_RUST_VERSION }} doc --target ${{ matrix.target }} --no-deps --all-features - name: check em if: matrix.kind == 'em' @@ -174,14 +227,137 @@ jobs: cargo clippy --target ${{ matrix.target }} --tests --all-features # build docs - cargo doc --target ${{ matrix.target }} --all-features --no-deps + cargo +${{ env.DOCS_RUST_VERSION }} doc --target ${{ matrix.target }} --all-features --no-deps + + # We run minimal checks on the MSRV of the core crates, ensuring that + # its dependency tree does not cause issues for firefox. + # + # We don't test all platforms, just ones with different dependency stacks. + check-core-msrv: + # runtime is normally 1-3 minutes + timeout-minutes: 10 + + strategy: + fail-fast: false + matrix: + include: + # Windows + - name: Windows x86_64 + os: windows-2022 + target: x86_64-pc-windows-msvc + + # MacOS + - name: MacOS x86_64 + os: macos-12 + target: x86_64-apple-darwin + + # Linux + - name: Linux x86_64 + os: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + + name: MSRV Check ${{ matrix.name }} + runs-on: ${{ matrix.os }} + + steps: + - name: checkout repo + uses: actions/checkout@v4 + + - name: Install Core MSRV toolchain + run: | + rustup toolchain install ${{ env.CORE_MSRV }} --no-self-update --profile=minimal --component clippy --target ${{ matrix.target }} + rustup override set ${{ env.CORE_MSRV }} + cargo -V + + - name: disable debug + shell: bash + run: | + mkdir -p .cargo + echo """ + [profile.dev] + debug = false" >> .cargo/config.toml + + - name: caching + # Pin to 2.7.1 due to a bug in github actions cache action https://github.com/Swatinem/rust-cache/issues/182 + uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 + with: + key: msrv-check-${{ matrix.target }}-${{ env.CACHE_SUFFIX }} + + - name: check native + shell: bash + run: | + set -e + + # check wgpu-core with all features. This will also get wgpu-hal and wgpu-types. + cargo check --target ${{ matrix.target }} --all-features -p wgpu-core + + naga-minimal-versions: + # runtime is normally 2 minutes + timeout-minutes: 10 + + name: MSRV naga Minimal Versions + runs-on: ubuntu-22.04 + + steps: + - name: checkout repo + uses: actions/checkout@v4 + + - name: Install Core MSRV toolchain + run: | + rustup toolchain install ${{ env.CORE_MSRV }} --no-self-update --profile=minimal --component clippy + rustup override set ${{ env.CORE_MSRV }} + cargo -V + + # Use special toolchain for rustdoc, see https://github.com/gfx-rs/wgpu/issues/4905 + - name: Install Nightly Toolchain + run: | + rustup toolchain install ${{ env.NIGHTLY_VERSION }} --no-self-update --profile=minimal --component clippy + cargo +${{ env.NIGHTLY_VERSION }} -V + + - name: Install cargo-hack + uses: taiki-e/install-action@v2 + with: + tool: cargo-hack + + - name: disable debug + shell: bash + run: | + mkdir -p .cargo + echo """ + [profile.dev] + debug = false" >> .cargo/config.toml + + - name: Set Minimal Versions + shell: bash + run: | + set -e + + cargo +${{ env.NIGHTLY_VERSION }} hack generate-lockfile --remove-dev-deps -Z minimal-versions -p naga -p naga-cli + + - name: Clippy + shell: bash + run: | + set -e + + cargo clippy --all-features -p naga -p naga-cli wasm-test: + # runtime is normally 2 minutes + timeout-minutes: 10 + name: Test WebAssembly runs-on: ubuntu-latest + needs: [check] + steps: - name: checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Install Repo MSRV toolchain + run: | + rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy --target wasm32-unknown-unknown + rustup override set ${{ env.REPO_MSRV }} + cargo -V - name: Install wasm-pack uses: taiki-e/install-action@v2 @@ -191,9 +367,12 @@ jobs: - name: execute tests run: | cd wgpu - wasm-pack test --headless --chrome --features webgl --workspace + wasm-pack test --headless --chrome --no-default-features --features wgsl,webgl --workspace gpu-test: + # runtime is normally 5-15 minutes + timeout-minutes: 30 + strategy: fail-fast: false matrix: @@ -201,43 +380,90 @@ jobs: # Windows - name: Windows x86_64 os: windows-2022 - backends: dx12 # Mac - name: Mac aarch64 - os: [self-hosted, macOS] - backends: vulkan metal + os: macos-13-xlarge # Linux - name: Linux x86_64 os: ubuntu-22.04 - backends: vulkan gl - name: Test ${{ matrix.name }} runs-on: ${{ matrix.os }} + needs: [check] steps: - name: checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Install Repo MSRV toolchain + run: | + rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal -c llvm-tools + cargo -V - name: Install cargo-nextest and cargo-llvm-cov uses: taiki-e/install-action@v2 with: tool: cargo-nextest,cargo-llvm-cov - - name: install swiftshader - if: matrix.os == 'ubuntu-22.04' + # Cache step must go before warp and mesa install on windows as they write into the + # target directory, and rust-cache will overwrite the entirety of the target directory. + - name: caching + # Pin to 2.7.1 due to a bug in github actions cache action https://github.com/Swatinem/rust-cache/issues/182 + uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 + with: + key: test-${{ matrix.os }}-${{ env.CACHE_SUFFIX }} + workspaces: | + . -> target + xtask -> xtask/target + + - name: (windows) install dxc + if: matrix.os == 'windows-2022' + shell: bash + run: | + set -e + + curl.exe -L --retry 5 https://github.com/microsoft/DirectXShaderCompiler/releases/download/$DXC_RELEASE/$DXC_FILENAME -o dxc.zip + 7z.exe e dxc.zip -odxc bin/x64/{dxc.exe,dxcompiler.dll,dxil.dll} + + # We need to use cygpath to convert PWD to a windows path as we're using bash. + cygpath --windows "$PWD/dxc" >> "$GITHUB_PATH" + + - name: (windows) install warp + if: matrix.os == 'windows-2022' + shell: bash + run: | + set -e + + # Make sure dxc is in path. + dxc --version + + curl.exe -L --retry 5 https://www.nuget.org/api/v2/package/Microsoft.Direct3D.WARP/$WARP_VERSION -o warp.zip + 7z.exe e warp.zip -owarp build/native/amd64/d3d10warp.dll + + mkdir -p target/llvm-cov-target/debug/deps + + cp -v warp/d3d10warp.dll target/llvm-cov-target/debug/ + cp -v warp/d3d10warp.dll target/llvm-cov-target/debug/deps + + - name: (windows) install mesa + if: matrix.os == 'windows-2022' shell: bash run: | set -e - mkdir -p swiftshader - curl -LsSf https://github.com/gfx-rs/ci-build/releases/latest/download/swiftshader-linux-x86_64.tar.xz | tar -xf - -C swiftshader + curl.exe -L --retry 5 https://github.com/pal1000/mesa-dist-win/releases/download/$MESA_VERSION/mesa3d-$MESA_VERSION-release-msvc.7z -o mesa.7z + 7z.exe e mesa.7z -omesa x64/{opengl32.dll,libgallium_wgl.dll,libglapi.dll,vulkan_lvp.dll,lvp_icd.x86_64.json} + + cp -v mesa/* target/llvm-cov-target/debug/ + cp -v mesa/* target/llvm-cov-target/debug/deps - echo "VK_ICD_FILENAMES=$PWD/swiftshader/vk_swiftshader_icd.json" >> $GITHUB_ENV + # We need to use cygpath to convert PWD to a windows path as we're using bash. + echo "VK_DRIVER_FILES=`cygpath --windows $PWD/mesa/lvp_icd.x86_64.json`" >> "$GITHUB_ENV" + echo "GALLIUM_DRIVER=llvmpipe" >> "$GITHUB_ENV" - - name: install llvmpipe, vulkan sdk + - name: (linux) install vulkan sdk if: matrix.os == 'ubuntu-22.04' shell: bash run: | @@ -247,10 +473,37 @@ jobs: # vulkan sdk wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add - - sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-jammy.list https://packages.lunarg.com/vulkan/lunarg-vulkan-jammy.list + sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-$VULKAN_SDK_VERSION-jammy.list https://packages.lunarg.com/vulkan/$VULKAN_SDK_VERSION/lunarg-vulkan-$VULKAN_SDK_VERSION-jammy.list sudo apt-get update - sudo apt install -y libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev vulkan-sdk + sudo apt install -y vulkan-sdk + + - name: (linux) install mesa + if: matrix.os == 'ubuntu-22.04' + shell: bash + run: | + set -e + + curl -L --retry 5 https://github.com/gfx-rs/ci-build/releases/download/$CI_BINARY_BUILD/mesa-$MESA_VERSION-linux-x86_64.tar.xz -o mesa.tar.xz + mkdir mesa + tar xpf mesa.tar.xz -C mesa + + # The ICD provided by the mesa build is hardcoded to the build environment. + # + # We write out our own ICD file to point to the mesa vulkan + cat <<- EOF > icd.json + { + "ICD": { + "api_version": "1.1.255", + "library_path": "$PWD/mesa/lib/x86_64-linux-gnu/libvulkan_lvp.so" + }, + "file_format_version": "1.0.0" + } + EOF + + echo "VK_DRIVER_FILES=$PWD/icd.json" >> "$GITHUB_ENV" + echo "LD_LIBRARY_PATH=$PWD/mesa/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH" >> "$GITHUB_ENV" + echo "LIBGL_DRIVERS_PATH=$PWD/mesa/lib/x86_64-linux-gnu/dri" >> "$GITHUB_ENV" - name: disable debug shell: bash @@ -260,39 +513,38 @@ jobs: [profile.dev] debug = 1" >> .cargo/config.toml - - name: caching - uses: Swatinem/rust-cache@v2 - if: matrix.os[0] != 'self-hosted' - with: - key: test-${{ matrix.os }}-${{ env.CACHE_SUFFIX }} - - name: run wgpu-info shell: bash run: | - set -e + echo "$PATH" + + export RUST_LOG=trace - cargo llvm-cov --no-cfg-coverage run --bin wgpu-info --no-report --features vulkan-portability + # This needs to match the command in xtask/tests.rs + cargo llvm-cov --no-cfg-coverage --no-report run --bin wgpu-info -- -vv - name: run tests shell: bash run: | set -e - for backend in ${{ matrix.backends }}; do - echo "======= NATIVE TESTS $backend ======"; - WGPU_BACKEND=$backend cargo llvm-cov --no-cfg-coverage nextest --no-fail-fast --no-report --features vulkan-portability - done + cargo xtask test --llvm-cov - - uses: actions/upload-artifact@v3 + - name: check naga snapshots + run: git diff --exit-code -- naga/tests/out + + - uses: actions/upload-artifact@v4 if: always() # We want artifacts even if the tests fail. with: - name: comparison-images + name: comparison-images-${{ matrix.os }} path: | **/*-actual.png **/*-difference.png - name: generate coverage report + id: coverage shell: bash + continue-on-error: true run: | set -e @@ -300,16 +552,26 @@ jobs: - name: upload coverage report to codecov uses: codecov/codecov-action@v3 + if: steps.coverage.outcome == 'success' with: files: lcov.info doctest: + # runtime is normally 2 minutes + timeout-minutes: 10 + name: Doctest runs-on: ubuntu-latest steps: - name: checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Install Repo MSRV toolchain + run: | + rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component rustfmt + rustup override set ${{ env.REPO_MSRV }} + cargo -V - name: disable debug shell: bash @@ -320,9 +582,10 @@ jobs: debug = 1" >> .cargo/config.toml - name: caching - uses: Swatinem/rust-cache@v2 + # Pin to 2.7.1 due to a bug in github actions cache action https://github.com/Swatinem/rust-cache/issues/182 + uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 with: - key: clippy-${{ matrix.target }}-${{ matrix.kind }}-${{ env.CACHE_SUFFIX }} + key: doctests-${{ env.CACHE_SUFFIX }} - name: run doctests shell: bash @@ -332,27 +595,41 @@ jobs: cargo test --doc fmt: + # runtime is normally 15 seconds + timeout-minutes: 2 + name: Format runs-on: ubuntu-latest steps: - name: checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Install Repo MSRV toolchain + run: | + rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component rustfmt + rustup override set ${{ env.REPO_MSRV }} + cargo -V - name: run rustfmt run: | cargo fmt -- --check + cargo fmt --manifest-path xtask/Cargo.toml -- --check + + check-cts-runner: + # runtime is normally 2 minutes + timeout-minutes: 10 - check-msrv-cts_runner: name: Clippy cts_runner runs-on: ubuntu-latest steps: - name: checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install MSRV toolchain run: | - rustup toolchain install ${{ env.MSRV }} --no-self-update --profile=minimal --component clippy - rustup default ${{ env.MSRV }} + rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal --component clippy + rustup override set ${{ env.REPO_MSRV }} + cargo -V - name: disable debug shell: bash @@ -363,38 +640,48 @@ jobs: debug = 1" >> .cargo/config.toml - name: caching - uses: Swatinem/rust-cache@v2 + # Pin to 2.7.1 due to a bug in github actions cache action https://github.com/Swatinem/rust-cache/issues/182 + uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 with: - key: cts_runner-${{ env.CACHE_SUFFIX }} + key: cts-runner-${{ env.CACHE_SUFFIX }} - name: build Deno run: | cargo clippy --manifest-path cts_runner/Cargo.toml + # Separate job so that new advisories don't block CI. + # + # This job is not required to pass for PRs to be merged. cargo-deny-check-advisories: - name: "Run `cargo deny check advisories`" + # runtime is normally 1 minute + timeout-minutes: 5 + + name: "cargo-deny advisories" runs-on: ubuntu-latest steps: - name: checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run `cargo deny check` uses: EmbarkStudios/cargo-deny-action@v1 with: command: check advisories arguments: --all-features --workspace - rust-version: ${{ env.MSRV }} + rust-version: ${{ env.REPO_MSRV }} cargo-deny-check-rest: - name: "Run `cargo deny check`" + # runtime is normally 1 minute + timeout-minutes: 5 + + name: "cargo-deny" runs-on: ubuntu-latest steps: - name: checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run `cargo deny check` uses: EmbarkStudios/cargo-deny-action@v1 with: command: check bans licenses sources arguments: --all-features --workspace - rust-version: ${{ env.MSRV }} + rust-version: ${{ env.REPO_MSRV }} diff --git a/.github/workflows/cts.yml b/.github/workflows/cts.yml deleted file mode 100644 index 70479533cf..0000000000 --- a/.github/workflows/cts.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: CTS - -on: - push: - branches: [master, staging] - tags: [v0.*] - pull_request: - types: [labeled, opened, synchronize] - -env: - CARGO_INCREMENTAL: false - CARGO_TERM_COLOR: always - RUST_BACKTRACE: full - MSRV: 1.65 - -jobs: - cts: - # Only run if we add this label - if: contains(github.event.pull_request.labels.*.name, 'needs testing') - - strategy: - fail-fast: false - matrix: - include: - # Windows - - name: Windows x86_64 - os: windows-2019 - target: x86_64-pc-windows-msvc - backends: dx12 # dx11 - - # Linux - #- name: Linux x86_64 - # os: ubuntu-20.04 - # target: x86_64-unknown-linux-gnu - # backends: vulkan # gl - - name: CTS ${{ matrix.name }} - runs-on: ${{ matrix.os }} - - steps: - - name: checkout repo - uses: actions/checkout@v3 - with: - path: wgpu - - - name: checkout cts - run: | - git clone https://github.com/gpuweb/cts.git - cd cts - git checkout $(cat ../wgpu/cts_runner/revision.txt) - - - name: Install MSRV toolchain - run: | - rustup toolchain install ${{ env.MSRV }} --no-self-update --profile=minimal --target ${{ matrix.target }} - rustup default ${{ env.MSRV }} - - - name: caching - uses: Swatinem/rust-cache@v2 - with: - key: cts-a # suffix for cache busting - working-directory: wgpu/cts_runner - target-dir: wgpu/cts_runner/target - - - name: install llvmpipe and lavapipe - if: matrix.os == 'ubuntu-20.04' - run: | - sudo apt-get update -y -qq - sudo add-apt-repository ppa:oibaf/graphics-drivers -y - sudo apt-get update - sudo apt install -y libxcb-xfixes0-dev mesa-vulkan-drivers - # libegl1-mesa libgl1-mesa-dri for gl - - # We enable line numbers for panics, but that's it - - name: disable debug - shell: bash - run: | - mkdir wgpu/.cargo - echo """[profile.dev] - debug = 1" > wgpu/.cargo/config.toml - - - name: build CTS runner - run: | - cargo build --manifest-path wgpu/cts_runner/Cargo.toml - - - name: run CTS - shell: bash - run: | - cd cts; - for backend in ${{ matrix.backends }}; do - echo "======= CTS TESTS $backend ======"; - grep -v '^//' ../wgpu/cts_runner/test.lst | while IFS=$' \t\r\n' read test; do - echo "=== Running $test ==="; - DENO_WEBGPU_BACKEND=$backend cargo run --manifest-path ../wgpu/cts_runner/Cargo.toml --frozen -- ./tools/run_deno --verbose "$test"; - done - done diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 11d8d9e962..ceecbb703f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,11 +1,18 @@ -name: Documentation +name: Docs on: + pull_request: + paths: + - '.github/workflows/docs.yml' push: branches: - - master + - trunk env: + # We need to use nightly for various features + # when building docs.rs style docs. + NIGHTLY_VERSION: nightly-2023-12-17 + CARGO_INCREMENTAL: false CARGO_TERM_COLOR: always RUST_BACKTRACE: full @@ -16,21 +23,16 @@ jobs: steps: - name: Checkout the code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: persist-credentials: false - - name: Install nightly toolchain - run: rustup toolchain install nightly --no-self-update --profile=minimal - - - name: Add EGL for OpenGL - run: | - sudo apt-get update -y -qq - sudo apt-get install -y -qq libegl1-mesa-dev + - name: Install documentation toolchain + run: rustup toolchain install ${{ env.NIGHTLY_VERSION }} --no-self-update --profile=minimal - name: Build the docs (nightly) run: | - cargo +nightly doc --no-deps --lib + cargo +${{ env.NIGHTLY_VERSION }} doc --no-deps --lib env: RUSTDOCFLAGS: --cfg docsrs @@ -39,7 +41,8 @@ jobs: if: ${{ failure() }} - name: Deploy the docs - uses: JamesIves/github-pages-deploy-action@v4.4.3 + uses: JamesIves/github-pages-deploy-action@v4.5.0 + if: github.ref == 'refs/heads/trunk' with: token: ${{ secrets.WEB_DEPLOY }} folder: target/doc diff --git a/.github/workflows/lazy.yml b/.github/workflows/lazy.yml new file mode 100644 index 0000000000..0872326268 --- /dev/null +++ b/.github/workflows/lazy.yml @@ -0,0 +1,155 @@ +# Lazy jobs running on trunk post merges. +name: Lazy +on: + pull_request: + paths: + - '.github/workflows/lazy.yml' + push: + branches: [trunk] + +env: + CARGO_INCREMENTAL: false + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +jobs: + parse-dota2: + name: "Validate Shaders: Dota2" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - run: mkdir naga/data + + - name: Download shaders + run: curl https://user.fm/files/v2-5573e18b9f03f42c6ae53c392083da35/dota2-shaders.zip -o naga/data/all.zip + + - name: Unpack shaders + run: | + cd naga/data + unzip all.zip + + - name: Build Naga + run: | + cd naga + cargo build --release -p naga-cli + + - name: Convert shaders + run: | + cd naga + for file in data/*.spv ; do echo "Translating" ${file} && ../target/release/naga --validate 27 ${file} ${file}.metal; done + + parse-vulkan-tutorial-shaders: + name: "Validate Shaders: Sascha Willems Vulkan Tutorial" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download shaders + run: cd naga && git clone https://github.com/SaschaWillems/Vulkan.git + + - name: Build Naga + run: | + cd naga + cargo build --release -p naga-cli + + - name: Convert metal shaders + run: | + # No needed to stop workflow if we can't validate one file + set +e + cd naga + touch counter + SUCCESS_RESULT_COUNT=0 + FILE_COUNT=0 + mkdir -p out + find "Vulkan/data/shaders/glsl/" -name '*.spv' | while read fname; + do + echo "Convert: $fname" + FILE_COUNT=$((FILE_COUNT+1)) + ../target/release/naga --validate 27 $(realpath ${fname}) out/$(basename ${fname}).metal + if [[ $? -eq 0 ]]; then + SUCCESS_RESULT_COUNT=$((SUCCESS_RESULT_COUNT + 1)) + fi + echo "Result: $(expr $FILE_COUNT - $SUCCESS_RESULT_COUNT) / $FILE_COUNT" > counter + done + cat counter + + dneto0_spirv-samples: + name: "Validate Shaders: dneto0 spirv-samples" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download shaders + run: | + cd naga + git clone https://github.com/dneto0/spirv-samples.git + + - name: Build Naga + run: | + cargo build --release -p naga-cli + + - name: Install spirv-tools + run: | + cd naga + wget -q https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/linux-clang-release/continuous/1489/20210629-121459/install.tgz + tar zxf install.tgz + ./install/bin/spirv-as --version + + - name: Compile spv from spvasm + run: | + cd naga/spirv-samples + mkdir -p spv + + find "./spvasm" -name '*.spvasm' | while read fname; + do + echo "Convert to spv with spirv-as: $fname" + ../install/bin/spirv-as --target-env spv1.3 $(realpath ${fname}) -o ./spv/$(basename ${fname}).spv + done; + + - name: Validate spv and generate wgsl + run: | + set +e + cd naga/spirv-samples + SUCCESS_RESULT_COUNT=0 + FILE_COUNT=0 + mkdir -p spv + mkdir -p wgsl + + echo "==== Validate spv and generate wgsl ====" + rm -f counter + touch counter + + find "./spv" -name '*.spv' | while read fname; + do + echo "Convert: $fname" + FILE_COUNT=$((FILE_COUNT+1)) + ../../target/release/naga --validate 27 $(realpath ${fname}) ./wgsl/$(basename ${fname}).wgsl + if [[ $? -eq 0 ]]; then + SUCCESS_RESULT_COUNT=$((SUCCESS_RESULT_COUNT + 1)) + fi + echo "Result: $(expr $FILE_COUNT - $SUCCESS_RESULT_COUNT) / $FILE_COUNT" > counter + done + cat counter + + - name: Validate output wgsl + run: | + set +e + cd naga/spirv-samples + SUCCESS_RESULT_COUNT=0 + FILE_COUNT=0 + + rm -f counter + touch counter + + find "./wgsl" -name '*.wgsl' | while read fname; + do + echo "Validate: $fname" + FILE_COUNT=$((FILE_COUNT+1)) + ../../target/release/naga --validate 27 $(realpath ${fname}) + if [[ $? -eq 0 ]]; then + SUCCESS_RESULT_COUNT=$((SUCCESS_RESULT_COUNT + 1)) + fi + echo "Result: $(expr $FILE_COUNT - $SUCCESS_RESULT_COUNT) / $FILE_COUNT" > counter + done + cat counter diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 81a2a7b407..f45224779c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,10 +1,12 @@ name: Publish on: - push: - branches: ["*"] pull_request: - merge_group: + paths: + - '.github/workflows/publish.yml' + push: + branches: + - trunk env: CARGO_INCREMENTAL: false @@ -17,58 +19,33 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: persist-credentials: false - name: Install Rust WASM target run: rustup target add wasm32-unknown-unknown - - name: Install wasm-bindgen-cli - run: cargo +stable install wasm-bindgen-cli --version=0.2.87 - - - name: Pin wasm-bindgen version - run: cargo update -p wasm-bindgen --precise 0.2.87 - - - name: Build WebGPU examples - run: cargo build --release --target wasm32-unknown-unknown - - - name: Generate JS bindings for WebGPU examples + - name: Get wasm-bindgen version run: | - for i in target/wasm32-unknown-unknown/release/*.wasm; - do - wasm-bindgen --no-typescript --out-dir target/generated-gpu --web "$i"; - done + WASM_BINDGEN_VERSION=$(cargo metadata --format-version 1 --all-features | jq '.packages[] | select(.name == "wasm-bindgen") | .version' | tr -d '"') - - name: Deploy WebGPU examples - uses: JamesIves/github-pages-deploy-action@v4.4.3 - if: github.ref == 'refs/heads/trunk' - with: - token: ${{ secrets.WEB_DEPLOY }} - folder: target/generated-gpu - repository-name: gfx-rs/wgpu-rs.github.io - branch: master - target-folder: examples-gpu/wasm + echo $WASM_BINDGEN_VERSION - - name: Clean the build - run: cargo clean + echo "WASM_BINDGEN_VERSION=$WASM_BINDGEN_VERSION" >> "$GITHUB_ENV" - - name: Build WebGL examples - run: cargo build --release --target wasm32-unknown-unknown --features webgl + - name: Install wasm-bindgen + run: cargo +stable install wasm-bindgen-cli --version=$WASM_BINDGEN_VERSION - - name: Generate JS bindings for WebGL examples - run: | - for i in target/wasm32-unknown-unknown/release/*.wasm; - do - wasm-bindgen --no-typescript --out-dir target/generated-gl --web "$i"; - done + - name: Build examples + run: cargo xtask run-wasm --no-serve - - name: Deploy WebGL examples - uses: JamesIves/github-pages-deploy-action@v4.4.3 + - name: Deploy WebGPU examples + uses: JamesIves/github-pages-deploy-action@v4.5.0 if: github.ref == 'refs/heads/trunk' with: token: ${{ secrets.WEB_DEPLOY }} - folder: target/generated-gl + folder: target/generated repository-name: gfx-rs/wgpu-rs.github.io branch: master - target-folder: examples-gl/wasm + target-folder: examples/ diff --git a/.github/workflows/shaders.yml b/.github/workflows/shaders.yml new file mode 100644 index 0000000000..7e4fe1aa72 --- /dev/null +++ b/.github/workflows/shaders.yml @@ -0,0 +1,101 @@ +name: Shaders + +on: + push: + branches: ["*"] + tags: [v0.*] + pull_request: + merge_group: + +env: + # Sourced from https://github.com/microsoft/DirectXShaderCompiler/releases + # + # Must also be changed in ci.yaml + DXC_RELEASE: "v1.7.2308" + DXC_FILENAME: "dxc_2023_08_14.zip" + +jobs: + naga-validate-windows: + name: "Validate: HLSL" + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + # Pin to 2.7.1 due to a bug in github actions cache action https://github.com/Swatinem/rust-cache/issues/182 + - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 + with: + workspaces: | + naga/xtask -> naga/xtask/target + + # We must have the FXC job before the DXC job, so the DXC PATH has priority + # over the FXC PATH. This is because the windows kits also include an older + # version of DXC, which we don't want to use. + - name: Setup FXC + run: | + Get-Childitem -Path "C:\Program Files (x86)\Windows Kits\10\bin\**\x64\fxc.exe" ` + | Sort-Object -Property LastWriteTime -Descending ` + | Select-Object -First 1 ` + | Split-Path -Parent ` + | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append + shell: powershell + + - name: Setup DXC + shell: bash + run: | + set -e + + curl.exe -L --retry 5 https://github.com/microsoft/DirectXShaderCompiler/releases/download/$DXC_RELEASE/$DXC_FILENAME -o dxc.zip + 7z.exe e dxc.zip -odxc bin/x64/{dxc.exe,dxcompiler.dll,dxil.dll} + + # We need to use cygpath to convert PWD to a windows path as we're using bash. + cygpath --windows "$PWD/dxc" >> "$GITHUB_PATH" + + - name: Validate + shell: bash + run: | + set -e + + dxc --version + + cd naga + cargo xtask validate hlsl dxc + cargo xtask validate hlsl fxc + + naga-validate-macos: + name: "Validate: MSL" + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + # Pin to 2.7.1 due to a bug in github actions cache action https://github.com/Swatinem/rust-cache/issues/182 + - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 + with: + workspaces: | + naga/xtask -> naga/xtask/target + + - run: | + cd naga + cargo xtask validate msl + + naga-validate-linux: + name: "Validate: SPIR-V/GLSL/DOT/WGSL" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install tools + run: sudo apt-get install spirv-tools glslang-tools graphviz + + # Pin to 2.7.1 due to a bug in github actions cache action https://github.com/Swatinem/rust-cache/issues/182 + - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 + with: + workspaces: | + naga/xtask -> naga/xtask/target + + - run: cd naga; cargo xtask validate spv + + - run: cd naga; cargo xtask validate glsl + + - run: cd naga; cargo xtask validate dot + + - run: cd naga; cargo xtask validate wgsl diff --git a/.gitignore b/.gitignore index 6192f9dede..089a7f2e19 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ cts/ # Readme says to put angle in working directory *.dll + +# Cached GPU config +.gpuconfig diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ca35c7cfd..7bdb11de29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Please add your PR to the changelog! Choose from a top level and bottom level category, then write your changes like follows: -- Describe your change in a user friendly format by @yourslug in [#9999](https://github.com/gfx-rs/wgpu/pull/2488) +- Describe your change in a user friendly format by @yourslug in [#99999](https://github.com/gfx-rs/wgpu/pull/99999) You can add additional user facing information if it's a major breaking change. You can use the following to help: @@ -31,7 +31,6 @@ Bottom level categories: - DX12 - Vulkan - Metal -- DX11 - GLES - WebGPU - Emscripten @@ -40,33 +39,399 @@ Bottom level categories: ## Unreleased -### Major changes +### Direct3D 11 backend removal + +This backend had no functionality, and with the recent support for GL on Desktop, which allows wgpu to run on older devices, there is no need to keep the backend. + +### `WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER` environment variable + +This adds a way to allow a Vulkan driver which is non-compliant per VK_KHR_driver_properties to be enumerated. This is intended for testing new Vulkan drivers which are not Vulkan compliant yet. + +### `DeviceExt::create_texture_with_data` Allows Mip-Major Data + +Previously, `DeviceExt::create_texture_with_data` only allowed data to be provided in layer major order. There is now a `order` parameter which allows you to specify if the data is in layer major or mip major order. + +### `expose-ids` feature now available unconditionally + +This feature allowed you to call `global_id` on any wgpu opaque handle to get a unique hashable identity for the given resource. This is now available without the feature flag. By @cwfitzgerald in [#4841](https://github.com/gfx-rs/wgpu/pull/4841) + +### `dx12` and `metal` backend crate features + +Wgpu now exposes backend feature for the Direct3D 12 (`dx12`) and Metal (`metal`) backend. These are enabled by default, but don't do anything when not targetting the corresponding OS. By @daxpedda in [#4815](https://github.com/gfx-rs/wgpu/pull/4815) + +### Unified surface creation + +Previously, there were various specialized surface creation functions for various platform specific handles. +Now, `wgpu::Instance::create_surface` & `wgpu::Instance::create_surface_unsafe` instead each take a value that can be converted to the unified `wgpu::SurfaceTarget`/`wgpu::SurfaceTargetUnsafe` enums. +Conversion to `wgpu::SurfaceTarget` is automatic for anything implementing `raw-window-handle`'s `HasWindowHandle` & `HasDisplayHandle` traits, +meaning that you can continue to e.g. pass references to winit windows as before. +By @wumpf in [#4984](https://github.com/gfx-rs/wgpu/pull/4984) + +### WebGPU & WebGL in the same binary + +Enabling `webgl` no longer removes the `webgpu` backend. +Instead, there's a new (default enabled) `webgpu` feature that allows to explicitly opt-out of `webgpu` if so desired. +If both `webgl` & `webgpu` are enabled, `wgpu::Instance` decides upon creation whether to target wgpu-core/WebGL or WebGPU. +This means that adapter selection is not handled as with regular adapters, but still allows to decide at runtime whether +`webgpu` or the `webgl` backend should be used using a single wasm binary. +By @wumpf in [#5044](https://github.com/gfx-rs/wgpu/pull/5044) + +### New Features + +#### General +- Added `DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW` to know if `@builtin(vertex_index)` and `@builtin(instance_index)` will respect the `first_vertex` / `first_instance` in indirect calls. If this is not present, both will always start counting from 0. Currently enabled on all backends except DX12. By @cwfitzgerald in [#4722](https://github.com/gfx-rs/wgpu/pull/4722) +- No longer validate surfaces against their allowed extent range on configure. This caused warnings that were almost impossible to avoid. As before, the resulting behavior depends on the compositor. By @wumpf in [#4796](https://github.com/gfx-rs/wgpu/pull/4796) +- Added support for the float32-filterable feature. By @almarklein in [#4759](https://github.com/gfx-rs/wgpu/pull/4759) +- GPU buffer memory is released during "lose the device". By @bradwerth in [#4851](https://github.com/gfx-rs/wgpu/pull/4851) +- wgpu and wgpu-core features are now documented on docs.rs. By @wumpf in [#4886](https://github.com/gfx-rs/wgpu/pull/4886) +- DeviceLostClosure is guaranteed to be invoked exactly once. By @bradwerth in [#4862](https://github.com/gfx-rs/wgpu/pull/4862) + +#### OpenGL +- `@builtin(instance_index)` now properly reflects the range provided in the draw call instead of always counting from 0. By @cwfitzgerald in [#4722](https://github.com/gfx-rs/wgpu/pull/4722). +- Desktop GL now supports `POLYGON_MODE_LINE` and `POLYGON_MODE_POINT`. By @valaphee in [#4836](https://github.com/gfx-rs/wgpu/pull/4836) + +#### Naga + +- Naga's WGSL front end now allows operators to produce values with abstract types, rather than concretizing thir operands. By @jimblandy in [#4850](https://github.com/gfx-rs/wgpu/pull/4850) and [#4870](https://github.com/gfx-rs/wgpu/pull/4870). + +- Naga's WGSL front and back ends now have experimental support for 64-bit floating-point literals: `1.0lf` denotes an `f64` value. There has been experimental support for an `f64` type for a while, but until now there was no syntax for writing literals with that type. As before, Naga module validation rejects `f64` values unless `naga::valid::Capabilities::FLOAT64` is requested. By @jimblandy in [#4747](https://github.com/gfx-rs/wgpu/pull/4747). + +- Naga constant evaluation can now process binary operators whose operands are both vectors. By @jimblandy in [#4861](https://github.com/gfx-rs/wgpu/pull/4861). + +- Add `--bulk-validate` option to Naga CLI. By @jimblandy in [#4871](https://github.com/gfx-rs/wgpu/pull/4871). + +- Naga's `cargo xtask validate` now runs validation jobs in parallel, using the [jobserver](https://crates.io/crates/jobserver) protocol to limit concurrency, and offers a `validate all` subcommand, which runs all available validation types. By @jimblandy in [#4902](https://github.com/gfx-rs/wgpu/pull/4902). + +### Changes + +- Arcanization of wgpu core resources: By @gents83 in [#3626](https://github.com/gfx-rs/wgpu/pull/3626) and thanks also to @jimblandy, @nical, @Wumpf, @Elabajaba & @cwfitzgerald + - Removed Token and LifeTime related management + - Removed RefCount and MultiRefCount in favour of using only Arc internal reference count + - Removing mut from resources and added instead internal members locks on demand or atomics operations + - Resources now implement Drop and destroy stuff when last Arc resources is released + - Resources hold an Arc in order to be able to implement Drop + - Resources have an utility to retrieve the id of the resource itself + - Remove all guards and just retrive the Arc needed on-demand to unlock registry of resources asap + - Verify correct resources release when unused or not needed + - Check Web and Metal compliation (thanks to @niklaskorz) + - Fix tests on all platforms + - Test a multithreaded scenario + - Storage is now holding only user-land resources, but Arc is keeping refcount for resources + - When user unregister a resource, it's not dropped if still in use due to refcount inside wgpu + - IdentityManager is now unique and free is called on resource drop instead of storage unregister + - Identity changes due to Arcanization and Registry being just the user reference + - Added MemLeaks test and fixing mem leaks + +#### General + +- Log vulkan validation layer messages during instance creation and destruction: By @exrook in [#4586](https://github.com/gfx-rs/wgpu/pull/4586) +- `TextureFormat::block_size` is deprecated, use `TextureFormat::block_copy_size` instead: By @wumpf in [#4647](https://github.com/gfx-rs/wgpu/pull/4647) +- Rename of `DispatchIndirect`, `DrawIndexedIndirect`, and `DrawIndirect` types in the `wgpu::util` module to `DispatchIndirectArgs`, `DrawIndexedIndirectArgs`, and `DrawIndirectArgs`. By @cwfitzgerald in [#4723](https://github.com/gfx-rs/wgpu/pull/4723). +- Make the size parameter of `encoder.clear_buffer` an `Option` instead of `Option>`. By @nical in [#4737](https://github.com/gfx-rs/wgpu/pull/4737) +- Reduce the `info` log level noise. By @nical in [#4769](https://github.com/gfx-rs/wgpu/pull/4769), [#4711](https://github.com/gfx-rs/wgpu/pull/4711) and [#4772](https://github.com/gfx-rs/wgpu/pull/4772) +- Rename `features` & `limits` fields of `DeviceDescriptor` to `required_features` & `required_limits`. By @teoxoy in [#4803](https://github.com/gfx-rs/wgpu/pull/4803) + +#### Safe `Surface` creation + +It is now possible to safely create a `wgpu::Surface` with `Surface::create_surface()` by letting `Surface` hold a lifetime to `window`. + +Passing an owned value `window` to `Surface` will return a `Surface<'static>`. Shared ownership over `window` can still be achieved with e.g. an `Arc`. Alternatively a reference could be passed, which will return a `Surface<'window>`. + +`Surface::create_surface_from_raw()` can be used to continue producing a `Surface<'static>` without any lifetime requirements over `window`, which also remains `unsafe`. + +#### Naga + +- Remove `span` and `validate` features. Always fully validate shader modules, and always track source positions for use in error messages. By @teoxoy in [#4706](https://github.com/gfx-rs/wgpu/pull/4706) +- Introduce a new `Scalar` struct type for use in Naga's IR, and update all frontend, middle, and backend code appropriately. By @jimblandy in [#4673](https://github.com/gfx-rs/wgpu/pull/4673). +- Add more metal keywords. By @fornwall in [#4707](https://github.com/gfx-rs/wgpu/pull/4707). + +- Add partial support for WGSL abstract types (@jimblandy in [#4743](https://github.com/gfx-rs/wgpu/pull/4743), [#4755](https://github.com/gfx-rs/wgpu/pull/4755)). + + Abstract types make numeric literals easier to use, by + automatically converting literals and other constant expressions + from abstract numeric types to concrete types when safe and + necessary. For example, to build a vector of floating-point + numbers, Naga previously made you write: + + vec3(1.0, 2.0, 3.0) + + With this change, you can now simply write: + + vec3(1, 2, 3) + + Even though the literals are abstract integers, Naga recognizes + that it is safe and necessary to convert them to `f32` values in + order to build the vector. You can also use abstract values as + initializers for global constants and global and local variables, + like this: + + var unit_x: vec2 = vec2(1, 0); + + The literals `1` and `0` are abstract integers, and the expression + `vec2(1, 0)` is an abstract vector. However, Naga recognizes that + it can convert that to the concrete type `vec2` to satisfy + the given type of `unit_x`. + + The WGSL specification permits abstract integers and + floating-point values in almost all contexts, but Naga's support + for this is still incomplete. Many WGSL operators and builtin + functions are specified to produce abstract results when applied + to abstract inputs, but for now Naga simply concretizes them all + before applying the operation. We will expand Naga's abstract type + support in subsequent pull requests. + + As part of this work, the public types `naga::ScalarKind` and + `naga::Literal` now have new variants, `AbstractInt` and `AbstractFloat`. + +- Add a new `naga::Literal` variant, `I64`, for signed 64-bit literals. [#4711](https://github.com/gfx-rs/wgpu/pull/4711) + +- Emit and init `struct` member padding always. By @ErichDonGubler in [#4701](https://github.com/gfx-rs/wgpu/pull/4701). + +- In WGSL output, always include the `i` suffix on `i32` literals. By @jimblandy in [#4863](https://github.com/gfx-rs/wgpu/pull/4863). + +- In WGSL output, always include the `f` suffix on `f32` literals. By @jimblandy in [#4869](https://github.com/gfx-rs/wgpu/pull/4869). + +### Bug Fixes + +#### General + +- `BufferMappedRange` trait is now `WasmNotSendSync`, i.e. it is `Send`/`Sync` if not on wasm or `fragile-send-sync-non-atomic-wasm` is enabled. By @wumpf in [#4818](https://github.com/gfx-rs/wgpu/pull/4818) +- Align `wgpu_types::CompositeAlphaMode` serde serialization to spec. By @littledivy in [#4940](https://github.com/gfx-rs/wgpu/pull/4940) +- Fix error message of `ConfigureSurfaceError::TooLarge`. By @Dinnerbone in [#4960](https://github.com/gfx-rs/wgpu/pull/4960) +- Fix dropping of `DeviceLostCallbackC` params. By @bradwerth in [#5032](https://github.com/gfx-rs/wgpu/pull/5032) +- Fixed a number of panics. by @nical in [#4999](https://github.com/gfx-rs/wgpu/pull/4999), [#5014](https://github.com/gfx-rs/wgpu/pull/5014), [#5024](https://github.com/gfx-rs/wgpu/pull/5024), [#5025](https://github.com/gfx-rs/wgpu/pull/5025), [#5026](https://github.com/gfx-rs/wgpu/pull/5026), [#5027](https://github.com/gfx-rs/wgpu/pull/5027), [#5028](https://github.com/gfx-rs/wgpu/pull/5028) and [#5042](https://github.com/gfx-rs/wgpu/pull/5042). + +#### DX12 + +- Fixed D3D12_SUBRESOURCE_FOOTPRINT calculation for block compressed textures which caused a crash with `Queue::write_texture` on DX12. By @DTZxPorter in [#4990](https://github.com/gfx-rs/wgpu/pull/4990) + +#### Vulkan + +- Use `VK_EXT_robustness2` only when not using an outdated intel iGPU driver. By @TheoDulka in [#4602](https://github.com/gfx-rs/wgpu/pull/4602). + +#### WebGPU + +- Allow calling `BufferSlice::get_mapped_range` multiple times on the same buffer slice (instead of throwing a Javascript exception): By @DouglasDwyer in [#4726](https://github.com/gfx-rs/wgpu/pull/4726) + +#### WGL + +- Create a hidden window per `wgpu::Instance` instead of sharing a global one. + +#### Naga + +- Make module compaction preserve the module's named types, even if they are unused. By @jimblandy in [#4734](https://github.com/gfx-rs/wgpu/pull/4734). + +- Improve algorithm used by module compaction. By @jimblandy in [#4662](https://github.com/gfx-rs/wgpu/pull/4662). + +- When reading GLSL, fix the argument types of the double-precision floating-point overloads of the `dot`, `reflect`, `distance`, and `ldexp` builtin functions. Correct the WGSL generated for constructing 64-bit floating-point matrices. Add tests for all the above. By @jimblandy in [#4684](https://github.com/gfx-rs/wgpu/pull/4684). + +- Allow Naga's IR types to represent matrices with elements elements of any scalar kind. This makes it possible for Naga IR types to represent WGSL abstract matrices. By @jimblandy in [#4735](https://github.com/gfx-rs/wgpu/pull/4735). + +- When evaluating const-expressions and generating SPIR-V, properly handle `Compose` expressions whose operands are `Splat` expressions. Such expressions are created and marked as constant by the constant evaluator. By @jimblandy in [#4695](https://github.com/gfx-rs/wgpu/pull/4695). + +- Preserve the source spans for constants and expressions correctly across module compaction. By @jimblandy in [#4696](https://github.com/gfx-rs/wgpu/pull/4696). + +- Record the names of WGSL `alias` declarations in Naga IR `Type`s. By @jimblandy in [#4733](https://github.com/gfx-rs/wgpu/pull/4733). + +#### Metal + +- Allow the `COPY_SRC` usage flag in surface configuration. By @Toqozz in [#4852](https://github.com/gfx-rs/wgpu/pull/4852). + +### Examples + +- remove winit dependency from hello-compute example by @psvri in [#4699](https://github.com/gfx-rs/wgpu/pull/4699) +- hello-compute example fix failure with "wgpu error: Validation Error" if arguments are missing by @vilcans in [#4939](https://github.com/gfx-rs/wgpu/pull/4939) +- Made the examples page not crash on Chrome on Android, and responsive to screen sizes by @Dinnerbone in [#4958](https://github.com/gfx-rs/wgpu/pull/4958) + +## v0.18.1 (2023-11-15) + +(naga version 0.14.1) + +### Bug Fixes + +#### General +- Fix panic in `Surface::configure` in debug builds. By @cwfitzgerald in [#4635](https://github.com/gfx-rs/wgpu/pull/4635) +- Fix crash when all the following are true: By @teoxoy in #[#4642](https://github.com/gfx-rs/wgpu/pull/4642) + - Passing a naga module directly to `Device::create_shader_module`. + - `InstanceFlags::DEBUG` is enabled. + +#### DX12 +- Always use HLSL 2018 when using DXC to compile HLSL shaders. By @daxpedda in [#4629](https://github.com/gfx-rs/wgpu/pull/4629) + +#### Metal +- In Metal Shading Language output, fix issue where local variables were sometimes using variable names from previous functions. By @DJMcNab in [#4594](https://github.com/gfx-rs/wgpu/pull/4594) + +## v0.18.0 (2023-10-25) + +For naga changelogs at or before v0.14.0. See [naga's changelog](naga/CHANGELOG.md). + +### Desktop OpenGL 3.3+ Support on Windows -#### Pass timestamp queries +We now support OpenGL on Windows! This brings support for a vast majority of the hardware that used to be covered by our DX11 backend. As of this writing we support OpenGL 3.3+, though there are efforts to reduce that further. -Addition of `TimestampWrites` to compute and render passes to allow profiling. -This brings us in line with the spec. +This allows us to cover the last 12 years of Intel GPUs (starting with Ivy Bridge; aka 3xxx), and the last 16 years of AMD (starting with Terascale; aka HD 2000) / NVidia GPUs (starting with Tesla; aka GeForce 8xxx). -Added new example to demonstrate the various kinds of timestamps. +By @Zoxc in [#4248](https://github.com/gfx-rs/wgpu/pull/4248) + +### Timestamp Queries Supported on Metal and OpenGL + +Timestamp queries are now supported on both Metal and Desktop OpenGL. On Apple chips on Metal, they only support timestamp queries in command buffers or in the renderpass descriptor, +they do not support them inside a pass. + +Metal: By @Wumpf in [#4008](https://github.com/gfx-rs/wgpu/pull/4008) +OpenGL: By @Zoxc in [#4267](https://github.com/gfx-rs/wgpu/pull/4267) + +### Render/Compute Pass Query Writes + +Addition of the `TimestampWrites` type to compute and render pass descriptors to allow profiling on tilers which do not support timestamps inside passes. + +Added [an example](https://github.com/gfx-rs/wgpu/tree/trunk/examples/timestamp-queries) to demonstrate the various kinds of timestamps. + +Additionally, metal now supports timestamp queries! By @FL33TW00D & @wumpf in [#3636](https://github.com/gfx-rs/wgpu/pull/3636). -#### Occlusion Query Support +### Occlusion Queries + +We now support binary occlusion queries! This allows you to determine if any of the draw calls within the query drew any pixels. -The `occlusion_query_set` value defines where the occlusion query results will be stored for this pass. +Use the new `occlusion_query_set` field on `RenderPassDescriptor` to give a query set that occlusion queries will write to. ```diff -let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - // ... -+ occlusion_query_set: None, +let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + // ... ++ occlusion_query_set: Some(&my_occlusion_query_set), }); ``` +Within the renderpass do the following to write the occlusion query results to the query set at the given index: + +```rust +rpass.begin_occlusion_query(index); +rpass.draw(...); +rpass.draw(...); +rpass.end_occlusion_query(); +``` + +These are binary occlusion queries, so the result will be either 0 or an unspecified non-zero value. + By @Valaphee in [#3402](https://github.com/gfx-rs/wgpu/pull/3402) +### Shader Improvements + +```rust +// WGSL constant expressions are now supported! +const BLAH: u32 = 1u + 1u; + +// `rgb10a2uint` and `bgra8unorm` can now be used as a storage image format. +var image: texture_storage_2d; +var image: texture_storage_2d; + +// You can now use dual source blending! +struct FragmentOutput{ + @location(0) source1: vec4, + @location(0) @second_blend_source source2: vec4, +} + +// `modf`/`frexp` now return structures +let result = modf(1.5); +result.fract == 0.5; +result.whole == 1.0; + +let result = frexp(1.5); +result.fract == 0.75; +result.exponent == 2i; + +// `modf`/`frexp` are currently disabled on GLSL and SPIR-V input. +``` + +### Shader Validation Improvements + +```rust +// Cannot get pointer to a workgroup variable +fn func(p: ptr); // ERROR + +// Cannot create Inf/NaN through constant expressions +const INF: f32 = 3.40282347e+38 + 1.0; // ERROR +const NAN: f32 = 0.0 / 0.0; // ERROR + +// `outerProduct` function removed + +// Error on repeated or missing `@workgroup_size()` +@workgroup_size(1) @workgroup_size(2) // ERROR +fn compute_main() {} + +// Error on repeated attributes. +fn fragment_main(@location(0) @location(0) location_0: f32) // ERROR +``` + +### RenderPass `StoreOp` is now Enumeration + +`wgpu::Operations::store` used to be an underdocumented boolean value, +causing misunderstandings of the effect of setting it to `false`. + +The API now more closely resembles WebGPU which distinguishes between `store` and `discard`, +see [WebGPU spec on GPUStoreOp](https://gpuweb.github.io/gpuweb/#enumdef-gpustoreop). + +```diff +// ... +depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), +- store: false, ++ store: wgpu::StoreOp::Discard, +}), +// ... +``` + +By @wumpf in [#4147](https://github.com/gfx-rs/wgpu/pull/4147) + +### Instance Descriptor Settings + +The instance descriptor grew two more fields: `flags` and `gles_minor_version`. + +`flags` allow you to toggle the underlying api validation layers, debug information about shaders and objects in capture programs, and the ability to discard lables + +`gles_minor_version` is a rather niche feature that allows you to force the GLES backend to use a specific minor version, this is useful to get ANGLE to enable more than GLES 3.0. + +```diff +let instance = wgpu::Instance::new(InstanceDescriptor { + ... ++ flags: wgpu::InstanceFlags::default() ++ gles_minor_version: wgpu::Gles3MinorVersion::Automatic, +}); +``` + +`gles_minor_version`: By @PJB3005 in [#3998](https://github.com/gfx-rs/wgpu/pull/3998) +`flags`: By @nical in [#4230](https://github.com/gfx-rs/wgpu/pull/4230) + +### Many New Examples! + +- Added the following examples: By @JustAnotherCodemonkey in [#3885](https://github.com/gfx-rs/wgpu/pull/3885). + - [repeated-compute](https://github.com/gfx-rs/wgpu/tree/trunk/examples/repeated-compute) + - [storage-texture](https://github.com/gfx-rs/wgpu/tree/trunk/examples/storage-texture) + - [render-to-texture](https://github.com/gfx-rs/wgpu/tree/trunk/examples/render-to-texture) + - [uniform-values](https://github.com/gfx-rs/wgpu/tree/trunk/examples/uniform-values) + - [hello-workgroups](https://github.com/gfx-rs/wgpu/tree/trunk/examples/hello-workgroups) + - [hello-synchronization](https://github.com/gfx-rs/wgpu/tree/trunk/examples/hello-synchronization) + +### Revamped Testing Suite + +Our testing harness was completely revamped and now automatically runs against all gpus in the system, shows the expected status of every test, and is tolerant to flakes. + +Additionally, we have filled out our CI to now run the latest versions of WARP and Mesa. This means we can test even more features on CI than before. + +By @cwfitzgerald in [#3873](https://github.com/gfx-rs/wgpu/pull/3873) + +### The GLES backend is now optional on macOS + +The `angle` feature flag has to be set for the GLES backend to be enabled on Windows & macOS. + +By @teoxoy in [#4185](https://github.com/gfx-rs/wgpu/pull/4185) + ### Added/New Features -- Add `gles_minor_version` field to `wgpu::InstanceDescriptor`. By @PJB3005 in [#3998](https://github.com/gfx-rs/wgpu/pull/3998) +- Re-export Naga. By @exrook in [#4172](https://github.com/gfx-rs/wgpu/pull/4172) +- Add WinUI 3 SwapChainPanel support. By @ddrboxman in [#4191](https://github.com/gfx-rs/wgpu/pull/4191) ### Changes @@ -75,16 +440,34 @@ By @Valaphee in [#3402](https://github.com/gfx-rs/wgpu/pull/3402) - Omit texture store bound checks since they are no-ops if out of bounds on all APIs. By @teoxoy in [#3975](https://github.com/gfx-rs/wgpu/pull/3975) - Validate `DownlevelFlags::READ_ONLY_DEPTH_STENCIL`. By @teoxoy in [#4031](https://github.com/gfx-rs/wgpu/pull/4031) - Add validation in accordance with WebGPU `setViewport` valid usage for `x`, `y` and `this.[[attachment_size]]`. By @James2022-rgb in [#4058](https://github.com/gfx-rs/wgpu/pull/4058) -- `wgpu::CreateSurfaceError` now gives details of the failure, but no longer implements `PartialEq`. By @kpreid in [#4066](https://github.com/gfx-rs/wgpu/pull/4066) +- `wgpu::CreateSurfaceError` and `wgpu::RequestDeviceError` now give details of the failure, but no longer implement `PartialEq` and cannot be constructed. By @kpreid in [#4066](https://github.com/gfx-rs/wgpu/pull/4066) and [#4145](https://github.com/gfx-rs/wgpu/pull/4145) - Make `WGPU_POWER_PREF=none` a valid value. By @fornwall in [4076](https://github.com/gfx-rs/wgpu/pull/4076) +- Support dual source blending in OpenGL ES, Metal, Vulkan & DX12. By @freqmod in [4022](https://github.com/gfx-rs/wgpu/pull/4022) +- Add stub support for device destroy and device validity. By @bradwerth in [4163](https://github.com/gfx-rs/wgpu/pull/4163) and in [4212](https://github.com/gfx-rs/wgpu/pull/4212) +- Add trace-level logging for most entry points in wgpu-core By @nical in [4183](https://github.com/gfx-rs/wgpu/pull/4183) +- Add `Rgb10a2Uint` format. By @teoxoy in [4199](https://github.com/gfx-rs/wgpu/pull/4199) +- Validate that resources are used on the right device. By @nical in [4207](https://github.com/gfx-rs/wgpu/pull/4207) +- Expose instance flags. +- Add support for the bgra8unorm-storage feature. By @jinleili and @nical in [#4228](https://github.com/gfx-rs/wgpu/pull/4228) +- Calls to lost devices now return `DeviceError::Lost` instead of `DeviceError::Invalid`. By @bradwerth in [#4238]([https://github.com/gfx-rs/wgpu/pull/4238]) +- Let the `"strict_asserts"` feature enable check that wgpu-core's lock-ordering tokens are unique per thread. By @jimblandy in [#4258]([https://github.com/gfx-rs/wgpu/pull/4258]) +- Allow filtering labels out before they are passed to GPU drivers by @nical in [https://github.com/gfx-rs/wgpu/pull/4246](4246) +- `DeviceLostClosure` callback mechanism provided so user agents can resolve `GPUDevice.lost` Promises at the appropriate time by @bradwerth in [#4645](https://github.com/gfx-rs/wgpu/pull/4645) + #### Vulkan +- Rename `wgpu_hal::vulkan::Instance::required_extensions` to `desired_extensions`. By @jimblandy in [#4115](https://github.com/gfx-rs/wgpu/pull/4115) - Don't bother calling `vkFreeCommandBuffers` when `vkDestroyCommandPool` will take care of that for us. By @jimblandy in [#4059](https://github.com/gfx-rs/wgpu/pull/4059) +#### DX12 + +- Bump `gpu-allocator` to 0.23. By @Elabajaba in [#4198](https://github.com/gfx-rs/wgpu/pull/4198) ### Documentation -- Use WGSL for VertexFormat example types. By @ScanMountGoat in [#4305](https://github.com/gfx-rs/wgpu/pull/4035) + +- Use WGSL for VertexFormat example types. By @ScanMountGoat in [#4035](https://github.com/gfx-rs/wgpu/pull/4035) +- Fix description of `Features::TEXTURE_COMPRESSION_ASTC_HDR` in [#4157](https://github.com/gfx-rs/wgpu/pull/4157) ### Bug Fixes @@ -93,13 +476,17 @@ By @Valaphee in [#3402](https://github.com/gfx-rs/wgpu/pull/3402) - Derive storage bindings via `naga::StorageAccess` instead of `naga::GlobalUse`. By @teoxoy in [#3985](https://github.com/gfx-rs/wgpu/pull/3985). - `Queue::on_submitted_work_done` callbacks will now always be called after all previous `BufferSlice::map_async` callbacks, even when there are no active submissions. By @cwfitzgerald in [#4036](https://github.com/gfx-rs/wgpu/pull/4036). - Fix `clear` texture views being leaked when `wgpu::SurfaceTexture` is dropped before it is presented. By @rajveermalviya in [#4057](https://github.com/gfx-rs/wgpu/pull/4057). +- Add `Feature::SHADER_UNUSED_VERTEX_OUTPUT` to allow unused vertex shader outputs. By @Aaron1011 in [#4116](https://github.com/gfx-rs/wgpu/pull/4116). +- Fix a panic in `surface_configure`. By @nical in [#4220](https://github.com/gfx-rs/wgpu/pull/4220) and [#4227](https://github.com/gfx-rs/wgpu/pull/4227) +- Pipelines register their implicit layouts in error cases. By @bradwerth in [#4624](https://github.com/gfx-rs/wgpu/pull/4624) +- Better handle explicit destruction of textures and buffers. By @nical in [#4657](https://github.com/gfx-rs/wgpu/pull/4657) #### Vulkan -- Fix enabling `wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY` not being actually enabled in vulkan backend. By @39ali in[#3772](https://github.com/gfx-rs/wgpu/pull/3772). +- Fix enabling `wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY` not being actually enabled in vulkan backend. By @39ali in[#3772](https://github.com/gfx-rs/wgpu/pull/3772). - Don't pass `vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR` unless the `VK_KHR_portability_enumeration` extension is available. By @jimblandy in[#4038](https://github.com/gfx-rs/wgpu/pull/4038). - - Enhancement of [#4038], using ash's definition instead of hard-coded c_str. By @hybcloud in[#4044](https://github.com/gfx-rs/wgpu/pull/4044). +- Enable vulkan presentation on (Linux) Intel Mesa >= v21.2. By @flukejones in[#4110](https://github.com/gfx-rs/wgpu/pull/4110) #### DX12 @@ -113,15 +500,46 @@ By @Valaphee in [#3402](https://github.com/gfx-rs/wgpu/pull/3402) #### WebGPU - Ensure that limit requests and reporting is done correctly. By @OptimisticPeach in [#4107](https://github.com/gfx-rs/wgpu/pull/4107) +- Validate usage of polygon mode. By @teoxoy in [#4196](https://github.com/gfx-rs/wgpu/pull/4196) -#### Testing +#### GLES -- Skip `test_multithreaded_compute` on MoltenVK. By @jimblandy in [#4096](https://github.com/gfx-rs/wgpu/pull/4096). +- enable/disable blending per attachment only when available (on ES 3.2 or higher). By @teoxoy in [#4234](https://github.com/gfx-rs/wgpu/pull/4234) ### Documentation - Add an overview of `RenderPass` and how render state works. By @kpreid in [#4055](https://github.com/gfx-rs/wgpu/pull/4055) +### Examples + +- Created `wgpu-example::utils` module to contain misc functions and such that are common code but aren't part of the example framework. Add to it the functions `output_image_wasm` and `output_image_native`, both for outputting `Vec` RGBA images either to the disc or the web page. By @JustAnotherCodemonkey in [#3885](https://github.com/gfx-rs/wgpu/pull/3885). +- Removed `capture` example as it had issues (did not run on wasm) and has been replaced by `render-to-texture` (see above). By @JustAnotherCodemonkey in [#3885](https://github.com/gfx-rs/wgpu/pull/3885). + +## v0.17.2 (2023-10-03) + +### Bug Fixes + +#### Vulkan + +- Fix x11 hang while resizing on vulkan. @Azorlogh in [#4184](https://github.com/gfx-rs/wgpu/pull/4184). + +## v0.17.1 (2023-09-27) + +### Added/New Features + +- Add `get_mapped_range_as_array_buffer` for faster buffer read-backs in wasm builds. By @ryankaplan in [#4042] (https://github.com/gfx-rs/wgpu/pull/4042). + +### Bug Fixes + +#### DX12 + +- Fix panic on resize when using DX12. By @cwfitzgerald in [#4106](https://github.com/gfx-rs/wgpu/pull/4106) + +#### Vulkan + +- Suppress validation error caused by OBS layer. This was also fixed upstream. By @cwfitzgerald in [#4002](https://github.com/gfx-rs/wgpu/pull/4002) +- Work around bug in nvidia's vkCmdFillBuffer implementation. By @cwfitzgerald in [#4132](https://github.com/gfx-rs/wgpu/pull/4132). + ## v0.17.0 (2023-07-20) This is the first release that featured `wgpu-info` as a binary crate for getting information about what devices wgpu sees in your system. It can dump the information in both human readable format and json. @@ -172,6 +590,8 @@ By @fornwall in [#3904](https://github.com/gfx-rs/wgpu/pull/3904) and [#3905](ht ### Added/New Features +#### General + - Empty scissor rects are allowed now, matching the specification. by @PJB3005 in [#3863](https://github.com/gfx-rs/wgpu/pull/3863). - Add back components info to `TextureFormat`s. By @teoxoy in [#3843](https://github.com/gfx-rs/wgpu/pull/3843). - Add `get_mapped_range_as_array_buffer` for faster buffer read-backs in wasm builds. By @ryankaplan in [#4042] (https://github.com/gfx-rs/wgpu/pull/4042). @@ -370,7 +790,7 @@ By @cwfitzgerald in [#3671](https://github.com/gfx-rs/wgpu/pull/3671). - Implemented basic ray-tracing api for acceleration structures, and ray-queries @daniel-keitel (started by @expenses) in [#3507](https://github.com/gfx-rs/wgpu/pull/3507) -#### Hal +#### Hal - Added basic ray-tracing api for acceleration structures, and ray-queries @daniel-keitel (started by @expenses) in [#3507](https://github.com/gfx-rs/wgpu/pull/3507) @@ -787,6 +1207,7 @@ By @jimblandy in [#3254](https://github.com/gfx-rs/wgpu/pull/3254). - Move `ResourceMetadata` into its own module. By @jimblandy in [#3213](https://github.com/gfx-rs/wgpu/pull/3213) - Add WebAssembly testing infrastructure. By @haraldreingruber in [#3238](https://github.com/gfx-rs/wgpu/pull/3238) - Error message when you forget to use cargo-nextest. By @cwfitzgerald in [#3293](https://github.com/gfx-rs/wgpu/pull/3293) +- Fix all suggestions from `cargo clippy` ## wgpu-0.14.2 (2022-11-28) @@ -877,7 +1298,7 @@ both `raw_window_handle::HasRawWindowHandle` and `raw_window_handle::HasRawDispl #### Vulkan -- Fix `astc_hdr` formats support by @jinleili in [#2971]](https://github.com/gfx-rs/wgpu/pull/2971) +- Fix `astc_hdr` formats support by @jinleili in [#2971]](https://github.com/gfx-rs/wgpu/pull/2971) - Update to Naga b209d911 (2022-9-1) to avoid generating SPIR-V that violates Vulkan valid usage rules `VUID-StandaloneSpirv-Flat-06202` and `VUID-StandaloneSpirv-Flat-04744`. By @jimblandy in diff --git a/Cargo.lock b/Cargo.lock index 09080b1849..75347d7808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.21" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5110f1c78cf582855d895ecd0746b653db010cec6d9f5575293f27934d980a39" +checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -35,40 +35,51 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ - "getrandom 0.2.10", + "cfg-if", + "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-activity" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" +checksum = "39b801912a977c3fd52d80511fe1c0c8480c6f957f21ae2ce1b92ffe970cf4b9" dependencies = [ "android-properties", - "bitflags 1.3.2", + "bitflags 2.4.1", "cc", + "cesu8", + "jni", "jni-sys", "libc", "log", - "ndk", + "ndk 0.8.0", "ndk-context", - "ndk-sys", - "num_enum 0.6.1", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum 0.7.1", + "thiserror", ] [[package]] @@ -86,11 +97,105 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "argh" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "argh_shared" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +dependencies = [ + "serde", +] [[package]] name = "arrayref" @@ -113,6 +218,12 @@ dependencies = [ "serde", ] +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + [[package]] name = "ash" version = "0.37.3+1.3.251" @@ -122,46 +233,23 @@ dependencies = [ "libloading 0.7.4", ] -[[package]] -name = "async-executor" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -178,16 +266,16 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide 0.7.1", + "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "base64" -version = "0.21.2" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" [[package]] name = "base64-simd" @@ -199,6 +287,15 @@ dependencies = [ "vsimd", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -222,10 +319,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" dependencies = [ + "arbitrary", "serde", ] @@ -237,60 +335,60 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-sys" -version = "0.1.0-beta.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" dependencies = [ "objc-sys", ] [[package]] name = "block2" -version = "0.2.0-alpha.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" dependencies = [ "block-sys", - "objc2-encode", + "objc2", ] [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", ] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "calloop" @@ -306,6 +404,38 @@ dependencies = [ "vec_map", ] +[[package]] +name = "calloop" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b50b5a44d59a98c55a9eeb518f39bf7499ba19fd98ee7d22618687f3f10adbf" +dependencies = [ + "bitflags 2.4.1", + "log", + "polling", + "rustix", + "slab", + "thiserror", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop 0.12.3", + "rustix", + "wayland-backend", + "wayland-client 0.31.1", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.83" @@ -316,6 +446,12 @@ dependencies = [ "libc", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -337,6 +473,73 @@ dependencies = [ "libc", ] +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + [[package]] name = "cmake" version = "0.1.50" @@ -356,23 +559,38 @@ dependencies = [ "block", "cocoa-foundation", "core-foundation", - "core-graphics", + "core-graphics 0.22.3", "foreign-types 0.3.2", "libc", "objc", ] +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics 0.23.1", + "foreign-types 0.5.0", + "libc", + "objc", +] + [[package]] name = "cocoa-foundation" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", "core-foundation", "core-graphics-types", - "foreign-types 0.3.2", "libc", "objc", ] @@ -394,16 +612,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] -name = "com-rs" -version = "0.2.1" +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] @@ -428,6 +687,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "const_panic" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" + [[package]] name = "convert_case" version = "0.4.0" @@ -436,9 +701,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -446,9 +711,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" @@ -463,11 +728,24 @@ dependencies = [ "libc", ] +[[package]] +name = "core-graphics" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + [[package]] name = "core-graphics-types" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -476,44 +754,96 @@ dependencies = [ [[package]] name = "core-text" -version = "19.2.0" +version = "20.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" dependencies = [ "core-foundation", - "core-graphics", - "foreign-types 0.3.2", + "core-graphics 0.23.1", + "foreign-types 0.5.0", "libc", ] [[package]] -name = "crc32fast" -version = "1.3.2" +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "cfg-if", + "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crossfont" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21fd3add36ea31aba1520aa5288714dd63be506106753226d0eb387a93bc9c45" +checksum = "3eb5a3822b594afc99b503cc1859b94686d3c3efdd60507a28587dab80ee1071" dependencies = [ - "cocoa", + "cocoa 0.25.0", "core-foundation", "core-foundation-sys", - "core-graphics", + "core-graphics 0.23.1", "core-text", "dwrote", "foreign-types 0.5.0", @@ -527,6 +857,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "ctor" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" +dependencies = [ + "quote", + "syn 2.0.48", +] + [[package]] name = "cts_runner" version = "0.1.0" @@ -547,14 +887,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + [[package]] name = "d3d12" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16e44ab292b1dddfdaf7be62cfd8877df52f2f3fde5858d95bab606be259f20" dependencies = [ - "bitflags 2.4.0", - "libloading 0.8.0", + "bitflags 2.4.1", + "libloading 0.8.1", "winapi", ] @@ -595,20 +939,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" - -[[package]] -name = "ddsfile" -version = "0.5.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "594ecd967c2f40db5dde8da4c356975fc1fe030e951c7c3962f6dc2e80042e87" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "enum_primitive", -] +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "debugid" @@ -622,27 +955,26 @@ dependencies = [ [[package]] name = "deno_console" -version = "0.106.0" +version = "0.125.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e7857cc8b133aed57e5f1dcfac6c0db308e70540727dfe0637af5281299758" +checksum = "92543d4f4d82f2350123bd4b60e97a73aba1a9bbca8c931e827459096dedabba" dependencies = [ "deno_core", ] [[package]] name = "deno_core" -version = "0.188.0" +version = "0.232.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83573c39d4045b6c2e056a2ad87e49c43e57b64b54ee4381894c030390fa1f76" +checksum = "229ffd108e028b148a1a5a6122f771bc7c37094170226f44b8b93b3a9b79d114" dependencies = [ "anyhow", "bytes", "deno_ops", + "deno_unsync", "futures", - "indexmap 1.9.3", "libc", "log", - "once_cell", "parking_lot", "pin-project", "serde", @@ -657,43 +989,52 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.66.0" +version = "0.108.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0446caff6cdb14fbf6c5e85fc042e3102aa6c618fa19a2ef47b67fc2657c0e8e" +checksum = "f7dde627916f8539f3f0d2e754dda40810c8ca4d655f2eaac1ef54785a12fd27" dependencies = [ - "lazy-regex", - "once_cell", - "pmutil", - "proc-macro-crate", + "proc-macro-rules", "proc-macro2", "quote", - "regex", - "syn 1.0.109", + "strum", + "strum_macros", + "syn 2.0.48", + "thiserror", +] + +[[package]] +name = "deno_unsync" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30dff7e03584dbae188dae96a0f1876740054809b2ad0cf7c9fc5d361f20e739" +dependencies = [ + "tokio", ] [[package]] name = "deno_url" -version = "0.106.0" +version = "0.125.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae0fa17e1fc70d8bb1f59d64a952b790afd774d3499524d7a760812eec07486" +checksum = "25ec92af225230fe4a429de0b5891f35b1ba5f143f8c1605bb7b9d1cb767ac73" dependencies = [ "deno_core", "serde", - "serde_repr", "urlpattern", ] [[package]] name = "deno_web" -version = "0.137.0" +version = "0.156.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a10df5503ffec0b1a59541e2587f84873676d714e5d935ecc4d2792685827198" +checksum = "3aeef7522f46b3442e24a750ef914ca54aade2110d6540a66e4ea17b4eb68bb7" dependencies = [ "async-trait", "base64-simd", + "bytes", "deno_core", "encoding_rs", "flate2", + "futures", "serde", "tokio", "uuid", @@ -705,22 +1046,34 @@ name = "deno_webgpu" version = "0.85.0" dependencies = [ "deno_core", - "raw-window-handle 0.5.2", + "raw-window-handle 0.6.0", "serde", "tokio", "wgpu-core", + "wgpu-hal", "wgpu-types", ] [[package]] name = "deno_webidl" -version = "0.106.0" +version = "0.125.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980a92f4619ace414abd464ad417ae362c7be05020009dfd4c4f1794ed21c71f" +checksum = "74b1a86e9a1dec0dc5d4dc132faee72ac50297f41e30f7cab57dd52dda380eed" dependencies = [ "deno_core", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -734,6 +1087,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "dispatch" version = "0.2.0" @@ -746,7 +1105,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.0", + "libloading 0.8.1", ] [[package]] @@ -776,28 +1135,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] -name = "encoding_rs" -version = "0.8.31" +name = "encase" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "95ed933078d2e659745df651f4c180511cd582e5b9414ff896e7d50d207e3103" dependencies = [ - "cfg-if", + "const_panic", + "encase_derive", + "glam", + "thiserror", ] [[package]] -name = "enum_primitive" -version = "0.1.1" +name = "encase_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ce1449c7d19eba6cc0abd231150ad81620a8dce29601d7f8d236e5d431d72a" +dependencies = [ + "encase_derive_impl", +] + +[[package]] +name = "encase_derive_impl" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92959a9e8d13eaa13b8ae8c7b583c3bf1669ca7a8e7708a088d12587ba86effc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ - "num-traits 0.1.43", + "cfg-if", ] [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -814,31 +1196,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "expat-sys" version = "2.1.6" @@ -851,22 +1216,28 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fdeflate" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd" dependencies = [ "simd-adler32", ] +[[package]] +name = "fern" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +dependencies = [ + "log", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -875,12 +1246,24 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", - "miniz_oxide 0.5.4", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", ] [[package]] @@ -916,7 +1299,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", ] [[package]] @@ -933,9 +1316,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -974,9 +1357,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -989,9 +1372,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -999,81 +1382,68 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", "futures-util", ] -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" -version = "1.13.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" dependencies = [ "fastrand", "futures-core", "futures-io", - "memchr", "parking", "pin-project-lite", - "waker-fn", ] [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1088,32 +1458,33 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.1.16" +name = "gethostname" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ - "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "windows-targets 0.48.5", ] [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", + "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gl_generator" @@ -1128,21 +1499,14 @@ dependencies = [ [[package]] name = "glam" -version = "0.21.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" - -[[package]] -name = "glam" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42218cb640844e3872cc3c153dc975229e080a6c4733b34709ef445610550226" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" [[package]] name = "glow" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +version = "0.13.0" +source = "git+https://github.com/grovesNL/glow.git?rev=29ff917a2b2ff7ce0a81b2cc5681de6d4735b36e#29ff917a2b2ff7ce0a81b2cc5681de6d4735b36e" dependencies = [ "js-sys", "slotmap", @@ -1157,12 +1521,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444c9ad294fdcaf20ccf6726b78f380b5450275540c9b68ab62f49726ad1c713" dependencies = [ "cgl", - "cocoa", + "cocoa 0.24.1", "core-foundation", "glutin_egl_sys", "glutin_gles2_sys", "glutin_glx_sys", - "glutin_wgl_sys", + "glutin_wgl_sys 0.1.5", "libloading 0.7.4", "log", "objc", @@ -1170,7 +1534,7 @@ dependencies = [ "osmesa-sys", "parking_lot", "raw-window-handle 0.5.2", - "wayland-client", + "wayland-client 0.29.5", "wayland-egl", "winapi", "winit 0.27.5", @@ -1215,13 +1579,22 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + [[package]] name = "gpu-alloc" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "gpu-alloc-types", ] @@ -1231,17 +1604,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", ] [[package]] name = "gpu-allocator" -version = "0.22.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" dependencies = [ - "backtrace", "log", + "presser", "thiserror", "winapi", "windows", @@ -1249,59 +1622,66 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "gpu-descriptor-types", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] name = "gpu-descriptor-types" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "half" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "hassle-rs" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 1.3.2", - "com-rs", + "bitflags 2.4.1", + "com", "libc", - "libloading 0.7.4", + "libloading 0.8.1", "thiserror", "widestring", "winapi", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hexf-parse" @@ -1309,12 +1689,40 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hlsl-snapshots" +version = "0.1.0" +dependencies = [ + "anyhow", + "nanoserde", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "icrate" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" +dependencies = [ + "block2", + "dispatch", + "objc2", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1323,9 +1731,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1347,28 +1755,19 @@ dependencies = [ "byteorder", "color_quant", "num-rational", - "num-traits 0.2.16", + "num-traits", "png", ] [[package]] name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ + "arbitrary", "equivalent", - "hashbrown 0.14.0", + "hashbrown", "serde", ] @@ -1386,20 +1785,45 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" dependencies = [ "hermit-abi", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jni" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] [[package]] name = "jni-sys" @@ -1409,30 +1833,30 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] [[package]] name = "khronos-egl" -version = "4.1.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.7.4", + "libloading 0.8.1", "pkg-config", ] @@ -1443,26 +1867,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] -name = "lazy-regex" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff63c423c68ea6814b7da9e88ce585f793c87ddd9e78f646970891769c8235d4" -dependencies = [ - "lazy-regex-proc_macros", - "once_cell", - "regex", -] - -[[package]] -name = "lazy-regex-proc_macros" -version = "2.4.1" +name = "ktx2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8edfc11b8f56ce85e207e62ea21557cfa09bb24a8f6b04ae181b086ff8611c22" +checksum = "87d65e08a9ec02e409d27a0139eaa6b9756b4d81fe7cde71f6941a83730ce838" dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 1.0.109", + "bitflags 1.3.2", ] [[package]] @@ -1473,9 +1883,20 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] [[package]] name = "libloading" @@ -1489,25 +1910,47 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "libtest-mimic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d8de370f98a6cb8a4606618e53e802f93b094ddec0f96988eaec2c27e6e9ce7" +dependencies = [ + "clap", + "termcolor", + "threadpool", +] + [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1515,12 +1958,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "malloc_buf" @@ -1533,9 +1973,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -1546,6 +1986,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -1555,13 +2004,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623b5e6cefd76e58f774bd3cc0c6f5c7615c58c03a97815245a25c3c9bdee318" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "block", "core-graphics-types", "foreign-types 0.5.0", @@ -1570,21 +2028,6 @@ dependencies = [ "paste", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1597,43 +2040,89 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] [[package]] name = "naga" -version = "0.13.0" -source = "git+https://github.com/gfx-rs/naga?rev=7a19f3af909202c7eafd36633b5584bfbb353ecb#7a19f3af909202c7eafd36633b5584bfbb353ecb" +version = "0.14.2" dependencies = [ + "arbitrary", + "bincode", "bit-set", - "bitflags 2.4.0", + "bitflags 2.4.1", "codespan-reporting", + "criterion", + "diff", + "env_logger", "hexf-parse", - "indexmap 2.0.0", + "hlsl-snapshots", + "indexmap", "log", - "num-traits 0.2.16", + "num-traits", "petgraph", "pp-rs", + "ron", + "rspirv", "rustc-hash", "serde", - "spirv", + "spirv 0.3.0+sdk-1.3.268.0", "termcolor", "thiserror", "unicode-xid", ] +[[package]] +name = "naga-cli" +version = "0.14.0" +dependencies = [ + "argh", + "bincode", + "codespan-reporting", + "env_logger", + "log", + "naga", +] + +[[package]] +name = "naga-fuzz" +version = "0.0.0" +dependencies = [ + "arbitrary", + "libfuzzer-sys", + "naga", +] + [[package]] name = "nanorand" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + +[[package]] +name = "nanoserde" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a983d0b19ed0fcd803c4f04f9b20d5e6dd17e06d44d98742a0985ac45dab1bc" +dependencies = [ + "nanoserde-derive", +] + +[[package]] +name = "nanoserde-derive" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4dc96541767a4279572fdcf9f95af9cc1c9b2a2254e7a079203c81e206a9059" [[package]] name = "ndk" @@ -1643,12 +2132,27 @@ checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" dependencies = [ "bitflags 1.3.2", "jni-sys", - "ndk-sys", + "ndk-sys 0.4.1+23.1.7779620", "num_enum 0.5.11", "raw-window-handle 0.5.2", "thiserror", ] +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.4.1", + "jni-sys", + "log", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum 0.7.1", + "raw-window-handle 0.6.0", + "thiserror", +] + [[package]] name = "ndk-context" version = "0.1.1" @@ -1663,10 +2167,10 @@ checksum = "0434fabdd2c15e0aab768ca31d5b7b333717f03cf02037d5a0a3ff3c278ed67f" dependencies = [ "libc", "log", - "ndk", + "ndk 0.7.0", "ndk-context", "ndk-macro", - "ndk-sys", + "ndk-sys 0.4.1+23.1.7779620", "once_cell", "parking_lot", ] @@ -1678,7 +2182,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" dependencies = [ "darling", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -1693,6 +2197,15 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + [[package]] name = "nix" version = "0.24.3" @@ -1702,7 +2215,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] @@ -1715,28 +2228,29 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] -name = "noise" -version = "0.8.2" +name = "nix" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba869e17168793186c10ca82c7079a4ffdeac4f1a7d9e755b9491c028180e40" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ - "num-traits 0.2.16", - "rand 0.7.3", - "rand_xorshift", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", ] [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +name = "noise" +version = "0.8.2" +source = "git+https://github.com/Razaekel/noise-rs.git?rev=c6942d4fb70af26db4441edcf41f90fa115333f2#c6942d4fb70af26db4441edcf41f90fa115333f2" dependencies = [ - "memchr", - "minimal-lexical", + "num-traits", + "rand", + "rand_xorshift", ] [[package]] @@ -1747,8 +2261,8 @@ checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", - "num-traits 0.2.16", - "rand 0.8.5", + "num-traits", + "rand", ] [[package]] @@ -1758,7 +2272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", - "num-traits 0.2.16", + "num-traits", ] [[package]] @@ -1769,23 +2283,14 @@ checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-integer", - "num-traits 0.2.16", -] - -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -dependencies = [ - "num-traits 0.2.16", + "num-traits", ] [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -1811,11 +2316,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" dependencies = [ - "num_enum_derive 0.6.1", + "num_enum_derive 0.7.1", ] [[package]] @@ -1824,7 +2329,7 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -1832,14 +2337,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 2.0.1", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", ] [[package]] @@ -1878,29 +2383,25 @@ dependencies = [ [[package]] name = "objc-sys" -version = "0.2.0-beta.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" +checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459" [[package]] name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "block2", "objc-sys", "objc2-encode", ] [[package]] name = "objc2-encode" -version = "2.0.0-pre.2" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" -dependencies = [ - "objc-sys", -] +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" [[package]] name = "objc_exception" @@ -1913,26 +2414,32 @@ dependencies = [ [[package]] name = "object" -version = "0.32.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "orbclient" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8378ac0dfbd4e7895f2d2c1f1345cab3836910baf3a300b000d04250f0c8428f" +checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "redox_syscall", + "libredox", ] [[package]] @@ -1952,18 +2459,18 @@ checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" [[package]] name = "owned_ttf_parser" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" +checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" dependencies = [ "ttf-parser", ] [[package]] name = "parking" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" @@ -1977,14 +2484,17 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ + "backtrace", "cfg-if", "libc", - "redox_syscall", + "petgraph", + "redox_syscall 0.4.1", "smallvec", + "thread-id", "windows-targets 0.48.5", ] @@ -1996,9 +2506,9 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" @@ -2007,7 +2517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.0.0", + "indexmap", ] [[package]] @@ -2033,7 +2543,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", ] [[package]] @@ -2050,33 +2560,50 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "player" -version = "0.17.0" +version = "0.18.0" dependencies = [ "env_logger", "log", - "raw-window-handle 0.5.2", + "raw-window-handle 0.6.0", "ron", "serde", "wgpu-core", "wgpu-types", - "winit 0.28.6", + "winit 0.29.9", ] [[package]] -name = "pmutil" -version = "0.5.3" +name = "plotters" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", ] [[package]] @@ -2089,7 +2616,21 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.1", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" +dependencies = [ + "cfg-if", + "concurrent-queue", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", ] [[package]] @@ -2108,10 +2649,10 @@ dependencies = [ ] [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "presser" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "proc-macro-crate" @@ -2120,72 +2661,82 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] -name = "proc-macro2" -version = "1.0.66" +name = "proc-macro-crate" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" dependencies = [ - "unicode-ident", + "toml_datetime", + "toml_edit 0.20.2", ] [[package]] -name = "profiling" -version = "1.0.10" +name = "proc-macro-rules" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f10e75d83c7aec79a6aa46f897075890e156b105eebe51cfa0abce51af025f" +checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" +dependencies = [ + "proc-macro-rules-macros", + "proc-macro2", + "syn 2.0.48", +] [[package]] -name = "quote" -version = "1.0.33" +name = "proc-macro-rules-macros" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "207fffb0fe655d1d47f6af98cc2793405e85929bdbc420d685554ff07be27ac7" dependencies = [ + "once_cell", "proc-macro2", + "quote", + "syn 2.0.48", ] [[package]] -name = "rand" -version = "0.7.3" +name = "proc-macro2" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha", - "rand_core 0.5.1", - "rand_hc", + "unicode-ident", ] [[package]] -name = "rand" -version = "0.8.5" +name = "profiling" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "d135ede8821cf6376eb7a64148901e1690b788c11ae94dc297ae917dbc91dc0e" + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ - "rand_core 0.6.4", + "memchr", ] [[package]] -name = "rand_chacha" -version = "0.2.2" +name = "quote" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "proc-macro2", ] [[package]] -name = "rand_core" -version = "0.5.1" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -2194,22 +2745,13 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "rand_xorshift" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.5.1", + "rand_core", ] [[package]] @@ -2234,43 +2776,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] -name = "ray-cube-compute" -version = "0.17.0" -dependencies = [ - "bytemuck", - "glam 0.24.1", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", -] +name = "raw-window-handle" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" [[package]] -name = "ray-cube-fragment" -version = "0.17.0" +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ - "bytemuck", - "glam 0.24.1", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", + "either", + "rayon-core", ] [[package]] -name = "ray-scene" -version = "0.17.0" +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "bytemuck", - "glam 0.24.1", - "obj", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] @@ -2282,11 +2810,20 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" -version = "1.9.3" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -2296,9 +2833,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -2307,9 +2844,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "renderdoc-sys" @@ -2324,11 +2861,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64", - "bitflags 2.4.0", + "bitflags 2.4.1", "serde", "serde_derive", ] +[[package]] +name = "rspirv" +version = "0.11.0+sdk-1.2.198" +source = "git+https://github.com/gfx-rs/rspirv?rev=b969f175d5663258b4891e44b76c1544da9661ab#b969f175d5663258b4891e44b76c1544da9661ab" +dependencies = [ + "rustc-hash", + "spirv 0.2.0+sdk-1.2.198", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2356,35 +2902,50 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.18", + "semver 1.0.21", ] [[package]] name = "rustix" -version = "0.38.9" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "safe_arch" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] [[package]] -name = "safe_arch" -version = "0.5.2" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "bytemuck", + "winapi-util", ] [[package]] @@ -2407,21 +2968,21 @@ checksum = "61270629cc6b4d77ec1907db1033d5c2e1a404c412743621981a871dc9c12339" dependencies = [ "crossfont", "log", - "smithay-client-toolkit", + "smithay-client-toolkit 0.16.1", "tiny-skia 0.7.0", ] [[package]] name = "sctk-adwaita" -version = "0.5.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +checksum = "82b2eaf3a5b264a521b988b2e73042e742df700c4f962cde845d1541adb46550" dependencies = [ "ab_glyph", "log", - "memmap2", - "smithay-client-toolkit", - "tiny-skia 0.8.4", + "memmap2 0.9.3", + "smithay-client-toolkit 0.18.0", + "tiny-skia 0.11.3", ] [[package]] @@ -2435,9 +2996,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "semver-parser" @@ -2447,67 +3008,46 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ - "indexmap 2.0.0", + "indexmap", "itoa", "ryu", "serde", ] -[[package]] -name = "serde_repr" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "serde_v8" -version = "0.99.0" +version = "0.141.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abcb15f793aba70da4d29b2015c9b70943bd7f6970cab7963fcf83c19bbab1c9" +checksum = "bc689cb316d67b200e9f7449ce76cceb7e483e0f828d1a9c3d057c4367b6c26e" dependencies = [ "bytes", "derive_more", "num-bigint", "serde", - "serde_bytes", "smallvec", "thiserror", "v8", @@ -2570,43 +3110,77 @@ dependencies = [ [[package]] name = "slotmap" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ "version_check", ] [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smithay-client-toolkit" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" dependencies = [ "bitflags 1.3.2", - "calloop", + "calloop 0.10.6", "dlib", "lazy_static", "log", - "memmap2", + "memmap2 0.5.10", "nix 0.24.3", "pkg-config", - "wayland-client", - "wayland-cursor", - "wayland-protocols", + "wayland-client 0.29.5", + "wayland-cursor 0.29.5", + "wayland-protocols 0.29.5", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f" +dependencies = [ + "bitflags 2.4.1", + "calloop 0.12.3", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2 0.9.3", + "rustix", + "thiserror", + "wayland-backend", + "wayland-client 0.31.1", + "wayland-csd-frame", + "wayland-cursor 0.31.0", + "wayland-protocols 0.31.0", + "wayland-protocols-wlr", + "wayland-scanner 0.31.0", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", ] [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", @@ -2614,9 +3188,9 @@ dependencies = [ [[package]] name = "sourcemap" -version = "6.4.1" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4cbf65ca7dc576cf50e21f8d0712d96d4fcfd797389744b7b222a85cdf5bd90" +checksum = "10da010a590ed2fa9ca8467b00ce7e9c5a8017742c0c09c45450efc172208c4b" dependencies = [ "data-encoding", "debugid", @@ -2629,13 +3203,30 @@ dependencies = [ ] [[package]] -name = "spirv" -version = "0.2.0+1.5.4" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.2.0+sdk-1.2.198" +source = "git+https://github.com/gfx-rs/rspirv?rev=b969f175d5663258b4891e44b76c1544da9661ab#b969f175d5663258b4891e44b76c1544da9661ab" dependencies = [ "bitflags 1.3.2", - "num-traits 0.2.16", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.4.1", + "serde", ] [[package]] @@ -2656,6 +3247,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.48", +] + [[package]] name = "syn" version = "1.0.109" @@ -2669,9 +3282,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -2680,31 +3293,50 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", +] + +[[package]] +name = "thread-id" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", ] [[package]] @@ -2724,16 +3356,16 @@ dependencies = [ [[package]] name = "tiny-skia" -version = "0.8.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +checksum = "b6a067b809476893fce6a254cf285850ff69c847e6cfbade6a20b655b6c7e80d" dependencies = [ "arrayref", "arrayvec 0.7.4", "bytemuck", "cfg-if", - "png", - "tiny-skia-path 0.8.4", + "log", + "tiny-skia-path 0.11.3", ] [[package]] @@ -2748,15 +3380,25 @@ dependencies = [ [[package]] name = "tiny-skia-path" -version = "0.8.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +checksum = "5de35e8a90052baaaf61f171680ac2f8e925a1e43ea9d2e3a00514772250e541" dependencies = [ "arrayref", "bytemuck", "strict-num", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2774,9 +3416,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -2793,13 +3435,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", ] [[package]] @@ -2810,20 +3452,47 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.0.0", + "indexmap", "toml_datetime", "winnow", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" + [[package]] name = "ttf-parser" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a464a4b34948a5f67fddd2b823c62d9d92e44be75058b99939eae6c5b6960b33" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" [[package]] name = "unic-char-property" @@ -2868,21 +3537,21 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-id" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" +checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -2893,11 +3562,17 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" @@ -2907,9 +3582,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "url" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -2930,21 +3605,27 @@ dependencies = [ "url", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" -version = "1.4.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ - "getrandom 0.2.10", + "getrandom", "serde", ] [[package]] name = "v8" -version = "0.72.0" +version = "0.81.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5c1d09f66ab7f69e36211c5488d47f683fef6b65b83a627cfd75ed9cef254e6" +checksum = "b75f5f378b9b54aff3b10da8170d26af4cfd217f644cf671badcd13af5db4beb" dependencies = [ "bitflags 1.3.2", "fslock", @@ -2971,16 +3652,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +name = "walkdir" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] name = "wasi" @@ -2990,9 +3669,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3000,24 +3679,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -3027,9 +3706,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3037,28 +3716,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-bindgen-test" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403" dependencies = [ "console_error_panic_hook", "js-sys", @@ -3070,12 +3749,27 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" dependencies = [ "proc-macro2", "quote", + "syn 2.0.48", +] + +[[package]] +name = "wayland-backend" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4" +dependencies = [ + "cc", + "downcast-rs", + "nix 0.26.4", + "scoped-tls", + "smallvec", + "wayland-sys 0.31.1", ] [[package]] @@ -3090,8 +3784,20 @@ dependencies = [ "nix 0.24.3", "scoped-tls", "wayland-commons", - "wayland-scanner", - "wayland-sys", + "wayland-scanner 0.29.5", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-client" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" +dependencies = [ + "bitflags 2.4.1", + "nix 0.26.4", + "wayland-backend", + "wayland-scanner 0.31.0", ] [[package]] @@ -3103,7 +3809,18 @@ dependencies = [ "nix 0.24.3", "once_cell", "smallvec", - "wayland-sys", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.4.1", + "cursor-icon", + "wayland-backend", ] [[package]] @@ -3113,7 +3830,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" dependencies = [ "nix 0.24.3", - "wayland-client", + "wayland-client 0.29.5", + "xcursor", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44aa20ae986659d6c77d64d808a046996a932aa763913864dc40c359ef7ad5b" +dependencies = [ + "nix 0.26.4", + "wayland-client 0.31.1", "xcursor", ] @@ -3123,8 +3851,8 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402de949f81a012926d821a2d659f930694257e76dd92b6e0042ceb27be4107d" dependencies = [ - "wayland-client", - "wayland-sys", + "wayland-client 0.29.5", + "wayland-sys 0.29.5", ] [[package]] @@ -3134,9 +3862,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" dependencies = [ "bitflags 1.3.2", - "wayland-client", + "wayland-client 0.29.5", "wayland-commons", - "wayland-scanner", + "wayland-scanner 0.29.5", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c" +dependencies = [ + "bitflags 2.4.1", + "wayland-backend", + "wayland-client 0.31.1", + "wayland-scanner 0.31.0", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +dependencies = [ + "bitflags 2.4.1", + "wayland-backend", + "wayland-client 0.31.1", + "wayland-protocols 0.31.0", + "wayland-scanner 0.31.0", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.4.1", + "wayland-backend", + "wayland-client 0.31.1", + "wayland-protocols 0.31.0", + "wayland-scanner 0.31.0", ] [[package]] @@ -3150,6 +3916,17 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "wayland-scanner" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + [[package]] name = "wayland-sys" version = "0.29.5" @@ -3162,108 +3939,77 @@ dependencies = [ ] [[package]] -name = "web-sys" -version = "0.3.64" +name = "wayland-sys" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" dependencies = [ - "js-sys", - "wasm-bindgen", + "dlib", + "log", + "once_cell", + "pkg-config", ] [[package]] -name = "wgpu" -version = "0.17.0" +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ - "arrayvec 0.7.4", - "cfg-if", "js-sys", - "log", - "naga", - "parking_lot", - "profiling", - "raw-window-handle 0.5.2", - "serde", - "smallvec", - "static_assertions", "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-boids-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "nanorand", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-bunnymark-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "glam 0.24.1", - "nanorand", - "png", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", ] [[package]] -name = "wgpu-capture-example" -version = "0.17.0" +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" dependencies = [ - "bytemuck", - "console_error_panic_hook", - "console_log", - "env_logger", - "futures-intrusive", - "png", - "pollster", - "wasm-bindgen-futures", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "winit 0.28.6", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wgpu-conservative-raster-example" -version = "0.17.0" +name = "wgpu" +version = "0.18.0" dependencies = [ - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", + "arrayvec 0.7.4", + "cfg-if", + "cfg_aliases", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle 0.6.0", + "serde", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", ] [[package]] name = "wgpu-core" -version = "0.17.0" +version = "0.18.0" dependencies = [ "arrayvec 0.7.4", "bit-vec", - "bitflags 2.4.0", + "bitflags 2.4.1", + "cfg_aliases", "codespan-reporting", + "indexmap", "log", "naga", + "once_cell", "parking_lot", "profiling", - "raw-window-handle 0.5.2", + "raw-window-handle 0.6.0", "ron", "rustc-hash", "serde", @@ -3275,56 +4021,57 @@ dependencies = [ ] [[package]] -name = "wgpu-cube-example" -version = "0.17.0" +name = "wgpu-examples" +version = "0.18.0" dependencies = [ "bytemuck", - "glam 0.24.1", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-example" -version = "0.17.0" -dependencies = [ - "async-executor", + "cfg-if", "console_error_panic_hook", "console_log", + "encase", "env_logger", + "fern", + "flume", + "getrandom", + "glam", "js-sys", + "ktx2", "log", + "nanorand", + "noise", + "obj", "png", "pollster", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-bindgen-test", "web-sys", + "web-time", "wgpu", "wgpu-hal", "wgpu-test", - "winit 0.28.6", + "winit 0.29.9", ] [[package]] name = "wgpu-hal" -version = "0.17.0" +version = "0.18.0" dependencies = [ "android_system_properties", "arrayvec 0.7.4", "ash", "bit-set", - "bitflags 2.4.0", + "bitflags 2.4.1", "block", "cfg-if", + "cfg_aliases", "core-graphics-types", "d3d12", "env_logger", - "glam 0.21.3", + "glam", "glow", "glutin", + "glutin_wgl_sys 0.5.0", "gpu-alloc", "gpu-allocator", "gpu-descriptor", @@ -3332,15 +4079,16 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.0", + "libloading 0.8.1", "log", "metal", "naga", "objc", + "once_cell", "parking_lot", "profiling", "range-alloc", - "raw-window-handle 0.5.2", + "raw-window-handle 0.6.0", "renderdoc-sys", "rustc-hash", "smallvec", @@ -3349,75 +4097,15 @@ dependencies = [ "web-sys", "wgpu-types", "winapi", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-hello-compute-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "console_error_panic_hook", - "console_log", - "env_logger", - "futures-intrusive", - "log", - "pollster", - "wasm-bindgen-futures", - "wasm-bindgen-test", - "wgpu", - "wgpu-test", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-hello-example" -version = "0.17.0" -dependencies = [ - "console_error_panic_hook", - "console_log", - "env_logger", - "glam 0.24.1", - "log", - "pollster", - "wasm-bindgen-futures", - "wgpu", - "wgpu-test", -] - -[[package]] -name = "wgpu-hello-triangle-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "console_error_panic_hook", - "console_log", - "env_logger", - "pollster", - "wasm-bindgen-futures", - "web-sys", - "wgpu", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-hello-windows-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "console_error_panic_hook", - "env_logger", - "pollster", - "wgpu", - "winit 0.28.6", + "winit 0.29.9", ] [[package]] name = "wgpu-info" -version = "0.17.0" +version = "0.18.0" dependencies = [ "anyhow", - "bitflags 2.4.0", + "bitflags 2.4.1", "env_logger", "pico-args", "serde", @@ -3427,165 +4115,72 @@ dependencies = [ ] [[package]] -name = "wgpu-mipmap-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "glam 0.24.1", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-msaa-line-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "glam 0.24.1", - "log", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-shadow-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "glam 0.24.1", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-skybox-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "ddsfile", - "glam 0.24.1", - "log", - "obj", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-stencil-triangle-example" -version = "0.17.0" +name = "wgpu-macros" +version = "0.18.0" dependencies = [ - "bytemuck", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", + "heck", + "quote", + "syn 2.0.48", ] [[package]] name = "wgpu-test" -version = "0.17.0" +version = "0.18.0" dependencies = [ - "bitflags 2.4.0", + "anyhow", + "arrayvec 0.7.4", + "bitflags 2.4.1", "bytemuck", "cfg-if", "console_log", + "ctor", "env_logger", - "glam 0.24.1", + "futures-lite", + "glam", + "heck", "image", "js-sys", + "libtest-mimic", "log", "naga", "nv-flip", "parking_lot", "png", "pollster", - "raw-window-handle 0.5.2", + "profiling", + "raw-window-handle 0.6.0", + "serde", + "serde_json", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", "web-sys", "wgpu", + "wgpu-macros", "wgpu-types", ] -[[package]] -name = "wgpu-texture-arrays-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", -] - -[[package]] -name = "wgpu-timestamp-queries-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "console_error_panic_hook", - "console_log", - "env_logger", - "futures-intrusive", - "log", - "pollster", - "wasm-bindgen-futures", - "wasm-bindgen-test", - "wgpu", - "wgpu-test", - "winit 0.28.6", -] - [[package]] name = "wgpu-types" -version = "0.17.0" +version = "0.18.0" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "js-sys", "serde", "serde_json", "web-sys", ] -[[package]] -name = "wgpu-water-example" -version = "0.17.0" -dependencies = [ - "bytemuck", - "glam 0.24.1", - "nanorand", - "noise", - "wasm-bindgen-test", - "wgpu", - "wgpu-example", - "wgpu-test", - "winit 0.28.6", -] - [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -3612,9 +4207,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -3627,11 +4222,21 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.44.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-targets 0.42.2", + "windows-core", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -3665,6 +4270,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3695,6 +4309,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3707,6 +4336,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -3725,6 +4360,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -3743,6 +4384,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -3761,6 +4408,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -3779,6 +4432,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3791,6 +4450,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -3809,6 +4474,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winit" version = "0.27.5" @@ -3816,15 +4487,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb796d6fbd86b2fd896c9471e6f04d39d750076ebe5680a3958f00f5ab97657c" dependencies = [ "bitflags 1.3.2", - "cocoa", + "cocoa 0.24.1", "core-foundation", - "core-graphics", + "core-graphics 0.22.3", "dispatch", "instant", "libc", "log", "mio", - "ndk", + "ndk 0.7.0", "ndk-glue", "objc", "once_cell", @@ -3833,10 +4504,10 @@ dependencies = [ "raw-window-handle 0.4.3", "raw-window-handle 0.5.2", "sctk-adwaita 0.4.3", - "smithay-client-toolkit", + "smithay-client-toolkit 0.16.1", "wasm-bindgen", - "wayland-client", - "wayland-protocols", + "wayland-client 0.29.5", + "wayland-protocols 0.29.5", "web-sys", "windows-sys 0.36.1", "x11-dl", @@ -3844,44 +4515,57 @@ dependencies = [ [[package]] name = "winit" -version = "0.28.6" +version = "0.29.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866db3f712fffba75d31bf0cdecf357c8aeafd158c5b7ab51dba2a2b2d47f196" +checksum = "c2376dab13e09c01ad8b679f0dbc7038af4ec43d9a91344338e37bd686481550" dependencies = [ + "ahash", "android-activity", - "bitflags 1.3.2", + "atomic-waker", + "bitflags 2.4.1", + "bytemuck", + "calloop 0.12.3", "cfg_aliases", "core-foundation", - "core-graphics", - "dispatch", - "instant", + "core-graphics 0.23.1", + "cursor-icon", + "icrate", + "js-sys", "libc", "log", - "mio", - "ndk", + "memmap2 0.9.3", + "ndk 0.8.0", + "ndk-sys 0.5.0+25.2.9519653", "objc2", "once_cell", "orbclient", "percent-encoding", - "raw-window-handle 0.5.2", - "redox_syscall", - "sctk-adwaita 0.5.4", - "smithay-client-toolkit", + "raw-window-handle 0.6.0", + "redox_syscall 0.3.5", + "rustix", + "sctk-adwaita 0.8.1", + "smithay-client-toolkit 0.18.0", + "smol_str", + "unicode-segmentation", "wasm-bindgen", - "wayland-client", - "wayland-commons", - "wayland-protocols", - "wayland-scanner", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client 0.31.1", + "wayland-protocols 0.31.0", + "wayland-protocols-plasma", "web-sys", - "windows-sys 0.45.0", + "web-time", + "windows-sys 0.48.0", "x11-dl", + "x11rb", + "xkbcommon-dl", ] [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" dependencies = [ "memchr", ] @@ -3906,17 +4590,74 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x11rb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading 0.8.1", + "once_cell", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" + [[package]] name = "xcursor" -version = "0.3.4" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699" dependencies = [ - "nom", + "bitflags 2.4.1", + "dlib", + "log", + "once_cell", + "xkeysym", ] +[[package]] +name = "xkeysym" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" + [[package]] name = "xml-rs" -version = "0.8.16" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] diff --git a/Cargo.toml b/Cargo.toml index 9455290b3d..bb6be1f1c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,149 +3,177 @@ resolver = "2" members = [ "cts_runner", "deno_webgpu", + + # default members + "d3d12", + "examples/", + "naga-cli", + "naga", + "naga/fuzz", + "naga/hlsl-snapshots", "player", - "examples/*", - "wgpu", + "tests", "wgpu-core", "wgpu-hal", "wgpu-info", + "wgpu-macros", "wgpu-types", - "tests", + "wgpu", ] exclude = [] default-members = [ - "examples/*", + "d3d12", + "examples/", + "naga-cli", + "naga", + "naga/fuzz", + "naga/hlsl-snapshots", "player", - "wgpu", + "tests", "wgpu-core", "wgpu-hal", "wgpu-info", + "wgpu-macros", "wgpu-types", - "tests" + "wgpu", ] [workspace.package] edition = "2021" -rust-version = "1.65" +rust-version = "1.70" keywords = ["graphics"] license = "MIT OR Apache-2.0" homepage = "https://wgpu.rs/" repository = "https://github.com/gfx-rs/wgpu" -version = "0.17.0" -authors = ["wgpu developers"] +version = "0.18.0" +authors = ["gfx-rs developers"] [workspace.dependencies.wgc] package = "wgpu-core" path = "./wgpu-core" -version = "0.17" +version = "0.18.0" [workspace.dependencies.wgt] package = "wgpu-types" path = "./wgpu-types" -version = "0.17" +version = "0.18.0" [workspace.dependencies.hal] package = "wgpu-hal" path = "./wgpu-hal" -version = "0.17" +version = "0.18.0" [workspace.dependencies.naga] -git = "https://github.com/gfx-rs/naga" -rev = "7a19f3af909202c7eafd36633b5584bfbb353ecb" -version = "0.13.0" +path = "./naga" +version = "0.14.0" [workspace.dependencies] anyhow = "1.0" arrayvec = "0.7" -async-executor = "1" -bitflags = "2" bit-vec = "0.6" -bytemuck = { version = "1.13", features = ["derive"] } +bitflags = "2" +bytemuck = { version = "1.14", features = ["derive"] } cfg_aliases = "0.1" cfg-if = "1" codespan-reporting = "0.11" -ddsfile = "0.5" +ctor = "0.2" +encase = "0.7" env_logger = "0.10" -futures-intrusive = "0.5" -rustc-hash = "1.1.0" -glam = "0.24.1" +fern = "0.6" +flume = "0.11" +futures-lite = "2" +getrandom = "0.2" +glam = "0.25" +heck = "0.4.0" image = { version = "0.24", default-features = false, features = ["png"] } -# libloading 0.8 switches from `winapi` to `windows-sys`; permit either -libloading = ">=0.7,<0.9" +ktx2 = "0.3" libc = "0.2" +# libloading 0.8 switches from `winapi` to `windows-sys`; permit either +libloading = ">=0.7, <0.9" +libtest-mimic = "0.6" log = "0.4" nanorand = { version = "0.7", default-features = false, features = ["wyrand"] } -nv-flip = "0.1" +# https://github.com/Razaekel/noise-rs/issues/335 (Updated dependencies) +noise = { version = "0.8", git = "https://github.com/Razaekel/noise-rs.git", rev = "c6942d4fb70af26db4441edcf41f90fa115333f2" } num-traits = { version = "0.2" } -noise = "0.8" +nv-flip = "0.1" obj = "0.10" -# parking_lot 0.12 switches from `winapi` to `windows`; permit either -parking_lot = ">=0.11,<0.13" -pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] } +once_cell = "1" +parking_lot = ">=0.11,<0.13" # parking_lot 0.12 switches from `winapi` to `windows`; permit either +pico-args = { version = "0.5.0", features = [ + "eq-separator", + "short-space-opt", + "combined-flags", +] } png = "0.17.10" pollster = "0.3" profiling = { version = "1", default-features = false } -raw-window-handle = "0.5" +raw-window-handle = "0.6" renderdoc-sys = "1.0.0" ron = "0.8" +rustc-hash = "1.1.0" serde = "1" -serde_json = "1.0.105" +serde_json = "1.0.111" smallvec = "1" static_assertions = "1.1.0" thiserror = "1" -wgpu = { version = "0.17.0", path = "./wgpu" } -wgpu-core = { version = "0.17.0", path = "./wgpu-core" } -wgpu-example = { version = "0.17.0", path = "./examples/common" } -wgpu-test = { version = "0.17", path = "./tests"} -wgpu-types = { version = "0.17.0", path = "./wgpu-types" } -winit = { version = "0.28.6", features = [ "android-native-activity" ] } +wgpu = { version = "0.18.0", path = "./wgpu" } +wgpu-core = { version = "0.18.0", path = "./wgpu-core" } +wgpu-example = { version = "0.18.0", path = "./examples/common" } +wgpu-macros = { version = "0.18.0", path = "./wgpu-macros" } +wgpu-test = { version = "0.18.0", path = "./tests" } +wgpu-types = { version = "0.18.0", path = "./wgpu-types" } +winit = { version = "0.29", features = ["android-native-activity"] } # Metal dependencies block = "0.1" -metal = "0.26.0" -objc = "0.2.5" core-graphics-types = "0.1" +metal = "0.27.0" +objc = "0.2.5" # Vulkan dependencies +android_system_properties = "0.1.1" ash = "0.37.3" gpu-alloc = "0.6" gpu-descriptor = "0.2" -android_system_properties = "0.1.1" # DX dependencies bit-set = "0.5" -gpu-allocator = { version = "0.21", default_features = false, features = ["d3d12", "windows", "public-winapi"] } -d3d12 = "0.7.0" +gpu-allocator = { version = "0.25", default_features = false, features = [ + "d3d12", + "public-winapi", +] } +d3d12 = { version = "0.7.0", path = "./d3d12/" } range-alloc = "0.1" winapi = "0.3" -hassle-rs = "0.10.0" +hassle-rs = "0.11.0" # Gles dependencies -khronos-egl = "4.1" +khronos-egl = "6" glow = "0.12.3" glutin = "0.29.1" # wasm32 dependencies console_error_panic_hook = "0.1.7" console_log = "1" -js-sys = "0.3.64" +js-sys = "0.3.66" wasm-bindgen = "0.2.87" -wasm-bindgen-futures = "0.4.34" +wasm-bindgen-futures = "0.4.39" wasm-bindgen-test = "0.3" -web-sys = "0.3.64" +web-sys = "0.3.66" +web-time = "0.2.4" # deno dependencies -deno_console = "0.106.0" -deno_core = "0.188.0" -deno_url = "0.106.0" -deno_web = "0.137.0" -deno_webidl = "0.106.0" -deno_webgpu = { path = "./deno_webgpu" } -tokio = "1.32.0" -termcolor = "1.2.0" +deno_console = "0.125.0" +deno_core = "0.232.0" +deno_url = "0.125.0" +deno_web = "0.156.0" +deno_webidl = "0.125.0" +deno_webgpu = { version = "0.85.0", path = "./deno_webgpu" } +tokio = "1.35.1" +termcolor = "1.4.1" [patch."https://github.com/gfx-rs/naga"] -#naga = { path = "../naga" } [patch."https://github.com/zakarumych/gpu-descriptor"] #gpu-descriptor = { path = "../gpu-descriptor/gpu-descriptor" } @@ -154,10 +182,7 @@ termcolor = "1.2.0" #gpu-alloc = { path = "../gpu-alloc/gpu-alloc" } [patch.crates-io] -#naga = { path = "../naga" } #glow = { path = "../glow" } -#d3d12 = { path = "../d3d12-rs" } -#metal = { path = "../metal-rs" } #web-sys = { path = "../wasm-bindgen/crates/web-sys" } #js-sys = { path = "../wasm-bindgen/crates/js-sys" } #wasm-bindgen = { path = "../wasm-bindgen" } diff --git a/README.md b/README.md index 44e036a2b2..4dbcd243ef 100644 --- a/README.md +++ b/README.md @@ -6,58 +6,42 @@ [![Dev Matrix ](https://img.shields.io/static/v1?label=devs&message=%23wgpu&color=blueviolet&logo=matrix)](https://matrix.to/#/#wgpu:matrix.org) [![User Matrix ](https://img.shields.io/static/v1?label=users&message=%23wgpu-users&color=blueviolet&logo=matrix)](https://matrix.to/#/#wgpu-users:matrix.org) [![Build Status](https://github.com/gfx-rs/wgpu/workflows/CI/badge.svg)](https://github.com/gfx-rs/wgpu/actions) -[![codecov.io](https://codecov.io/gh/gfx-rs/wgpu/branch/master/graph/badge.svg?token=84qJTesmeS)](https://codecov.io/gh/gfx-rs/wgpu) +[![codecov.io](https://codecov.io/gh/gfx-rs/wgpu/branch/trunk/graph/badge.svg?token=84qJTesmeS)](https://codecov.io/gh/gfx-rs/wgpu) -`wgpu` is a cross-platform, safe, pure-rust graphics api. It runs natively on Vulkan, Metal, D3D12, D3D11, and OpenGLES; and on top of WebGPU on wasm. +`wgpu` is a cross-platform, safe, pure-rust graphics API. It runs natively on Vulkan, Metal, D3D12, and OpenGL; and on top of WebGL2 and WebGPU on wasm. -The api is based on the [WebGPU standard](https://gpuweb.github.io/gpuweb/). It serves as the core of the WebGPU integration in Firefox, Servo, and Deno. +The API is based on the [WebGPU standard](https://gpuweb.github.io/gpuweb/). It serves as the core of the WebGPU integration in Firefox and Deno. ## Repo Overview The repository hosts the following libraries: - [![Crates.io](https://img.shields.io/crates/v/wgpu.svg?label=wgpu)](https://crates.io/crates/wgpu) [![docs.rs](https://docs.rs/wgpu/badge.svg)](https://docs.rs/wgpu/) - User facing Rust API. -- [![Crates.io](https://img.shields.io/crates/v/wgpu-core.svg?label=wgpu-core)](https://crates.io/crates/wgpu-core) [![docs.rs](https://docs.rs/wgpu-core/badge.svg)](https://docs.rs/wgpu-core/) - Internal WebGPU implementation. +- [![Crates.io](https://img.shields.io/crates/v/wgpu-core.svg?label=wgpu-core)](https://crates.io/crates/wgpu-core) [![docs.rs](https://docs.rs/wgpu-core/badge.svg)](https://docs.rs/wgpu-core/) - Internal safe implementation. - [![Crates.io](https://img.shields.io/crates/v/wgpu-hal.svg?label=wgpu-hal)](https://crates.io/crates/wgpu-hal) [![docs.rs](https://docs.rs/wgpu-hal/badge.svg)](https://docs.rs/wgpu-hal/) - Internal unsafe GPU API abstraction layer. - [![Crates.io](https://img.shields.io/crates/v/wgpu-types.svg?label=wgpu-types)](https://crates.io/crates/wgpu-types) [![docs.rs](https://docs.rs/wgpu-types/badge.svg)](https://docs.rs/wgpu-types/) - Rust types shared between all crates. +- [![Crates.io](https://img.shields.io/crates/v/naga.svg?label=naga)](https://crates.io/crates/naga) [![docs.rs](https://docs.rs/naga/badge.svg)](https://docs.rs/naga/) - Stand-alone shader translation library. +- [![Crates.io](https://img.shields.io/crates/v/d3d12.svg?label=d3d12)](https://crates.io/crates/d3d12) [![docs.rs](https://docs.rs/d3d12/badge.svg)](https://docs.rs/d3d12/) - Collection of thin abstractions over d3d12. - [![Crates.io](https://img.shields.io/crates/v/deno_webgpu.svg?label=deno_webgpu)](https://crates.io/crates/deno_webgpu) - WebGPU implementation for the Deno JavaScript/TypeScript runtime The following binaries: +- [![Crates.io](https://img.shields.io/crates/v/naga-cli.svg?label=naga-cli)](https://crates.io/crates/naga-cli) - Tool for translating shaders between different languages using `naga`. +- [![Crates.io](https://img.shields.io/crates/v/wgpu-info.svg?label=wgpu-info)](https://crates.io/crates/wgpu-info) - Tool for getting information on GPUs in the system. - `cts_runner` - WebGPU Conformance Test Suite runner using `deno_webgpu`. - `player` - standalone application for replaying the API traces. -- `wgpu-info` - program that prints out information about all the adapters on the system or invokes a command for every adapter. For an overview of all the components in the gfx-rs ecosystem, see [the big picture](./etc/big-picture.png). -### MSRV policy - -Minimum Supported Rust Version is **1.65**. -It is enforced on CI (in "/.github/workflows/ci.yml") with `RUST_VERSION` variable. -This version can only be upgraded in breaking releases. - -The `wgpu-core`, `wgpu-hal`, and `wgpu-types` crates should never -require an MSRV ahead of Firefox's MSRV for nightly builds, as -determined by the value of `MINIMUM_RUST_VERSION` in -[`python/mozboot/mozboot/util.py`][util]. However, Firefox uses `cargo -vendor` to extract only those crates it actually uses, so the -workspace's other crates can have more recent MSRVs. - -_Note for Rust 1.64_: The workspace itself can even use a newer MSRV -than Firefox, as long as the vendoring step's `Cargo.toml` rewriting -removes any features Firefox's MSRV couldn't handle. For example, -`wgpu` can use manifest key inheritance, added in Rust 1.64, even -before Firefox reaches that MSRV, because `cargo vendor` copies -inherited values directly into the individual crates' `Cargo.toml` -files, producing 1.63-compatible files. - -[util]: https://searchfox.org/mozilla-central/source/python/mozboot/mozboot/util.py - ## Getting Started ### Rust -Rust examples can be found at `wgpu/examples`. You can run the examples with `cargo run --bin name`. See the [list of examples](examples). For detailed instructions, look at [Running the examples](https://github.com/gfx-rs/wgpu/wiki/Running-the-examples) on the wiki. +Rust examples can be found at [wgpu/examples](examples). You can run the examples on native with `cargo run --bin wgpu-examples `. See the [list of examples](examples). + +To run the examples on WebGPU on wasm, run `cargo xtask run-wasm --bin wgpu-examples`. Then connect to `http://localhost:8000` in your WebGPU-enabled browser, and you can choose an example to run. + +To run the examples on WebGL on wasm, run `cargo xtask run-wasm --bin wgpu-examples --features webgl`. Then connect to `http://localhost:8000` in your WebGL-enabled browser, and you can choose an example to run. If you are looking for a wgpu tutorial, look at the following: @@ -79,8 +63,9 @@ If you want to use wgpu in other languages, there are many bindings to wgpu-nati We have the Matrix space [![Matrix Space](https://img.shields.io/static/v1?label=Space&message=%23Wgpu&color=blue&logo=matrix)](https://matrix.to/#/#Wgpu:matrix.org) with a few different rooms that form the wgpu community: -- [![Dev Matrix](https://img.shields.io/static/v1?label=devs&message=%23wgpu&color=blueviolet&logo=matrix)](https://matrix.to/#/#wgpu:matrix.org) - discussion of the library's development. -- [![User Matrix](https://img.shields.io/static/v1?label=users&message=%23wgpu-users&color=blueviolet&logo=matrix)](https://matrix.to/#/#wgpu-users:matrix.org) - discussion of using the library and the surrounding ecosystem. +- [![Wgpu Matrix](https://img.shields.io/static/v1?label=wgpu-devs&message=%23wgpu&color=blueviolet&logo=matrix)](https://matrix.to/#/#wgpu:matrix.org) - discussion of the wgpu's development. +- [![Naga Matrix](https://img.shields.io/static/v1?label=naga-devs&message=%23naga&color=blueviolet&logo=matrix)](https://matrix.to/#/#naga:matrix.org) - discussion of the naga's development. +- [![User Matrix](https://img.shields.io/static/v1?label=wgpu-users&message=%23wgpu-users&color=blueviolet&logo=matrix)](https://matrix.to/#/#wgpu-users:matrix.org) - discussion of using the library and the surrounding ecosystem. - [![Random Matrix](https://img.shields.io/static/v1?label=random&message=%23wgpu-random&color=blueviolet&logo=matrix)](https://matrix.to/#/#wgpu-random:matrix.org) - discussion of everything else. ## Wiki @@ -89,23 +74,25 @@ We have a [wiki](https://github.com/gfx-rs/wgpu/wiki) that serves as a knowledge ## Supported Platforms -| API | Windows | Linux & Android | macOS & iOS | -| ------ | ------------------------------ | ------------------ | ------------------------- | -| Vulkan | :white_check_mark: | :white_check_mark: | :ok: (vulkan-portability) | -| Metal | | | :white_check_mark: | -| DX12 | :white_check_mark: (W10+ only) | | | -| DX11 | :hammer_and_wrench: | | | -| GLES3 | | :ok: | | -| Angle | :ok: | :ok: | :ok: (macOS only) | +| API | Windows | Linux/Android | macOS/iOS | Web (wasm) | +| ------ | ------------------ | ------------------ | ------------------ | ------------------ | +| Vulkan | :white_check_mark: | :white_check_mark: | :volcano: | | +| Metal | | | :white_check_mark: | | +| DX12 | :white_check_mark: | | | | +| OpenGL | :ok: (GL 3.3+) | :ok: (GL ES 3.0+) | :triangular_ruler: | :ok: (WebGL2) | +| WebGPU | | | | :white_check_mark: | -:white_check_mark: = First Class Support — :ok: = Best Effort Support — :hammer_and_wrench: = Unsupported, but support in progress +:white_check_mark: = First Class Support +:ok: = Downlevel/Best Effort Support +:triangular_ruler: = Requires the [ANGLE](#angle) translation layer (GL ES 3.0 only) +:volcano: = Requires the [MoltenVK](https://vulkan.lunarg.com/sdk/home#mac) translation layer +:hammer_and_wrench: = Unsupported, though open to contributions ### Shader Support wgpu supports shaders in [WGSL](https://gpuweb.github.io/gpuweb/wgsl/), SPIR-V, and GLSL. Both [HLSL](https://github.com/Microsoft/DirectXShaderCompiler) and [GLSL](https://github.com/KhronosGroup/glslang) -have compilers to target SPIR-V. All of these shader languages can be used with any backend, we -will handle all of the conversion. Additionally, support for these shader inputs is not going away. +have compilers to target SPIR-V. All of these shader languages can be used with any backend as we handle all of the conversions. Additionally, support for these shader inputs is not going away. While WebGPU does not support any shading language other than WGSL, we will automatically convert your non-WGSL shaders if you're running on WebGPU. @@ -121,68 +108,76 @@ To enable GLSL shaders, enable the `glsl` feature of wgpu. ### Angle -[Angle](http://angleproject.org) is a translation layer from GLES to other backends, developed by Google. -We support running our GLES3 backend over it in order to reach platforms with GLES2 or DX11 support, which aren't accessible otherwise. -In order to run with Angle, "angle" feature has to be enabled, and Angle libraries placed in a location visible to the application. +[Angle](http://angleproject.org) is a translation layer from GLES to other backends developed by Google. +We support running our GLES3 backend over it in order to reach platforms DX11 support, which aren't accessible otherwise. +In order to run with Angle, the "angle" feature has to be enabled, and Angle libraries placed in a location visible to the application. These binaries can be downloaded from [gfbuild-angle](https://github.com/DileSoft/gfbuild-angle) artifacts, [manual compilation](https://github.com/google/angle/blob/main/doc/DevSetup.md) may be required on Macs with Apple silicon. On Windows, you generally need to copy them into the working directory, in the same directory as the executable, or somewhere in your path. On Linux, you can point to them using `LD_LIBRARY_PATH` environment. +### MSRV policy + +Due to complex dependants, we have two MSRV policies: + - `d3d12`, `naga`, `wgpu-core`, `wgpu-hal`, and `wgpu-types`'s MSRV is **1.70**. + - The rest of the workspace has an MSRV of **1.71**. + +It is enforced on CI (in "/.github/workflows/ci.yml") with the `CORE_MSRV` and `REPO_MSRV` variables. +This version can only be upgraded in breaking releases, though we release a breaking version every three months. + +The `naga`, `wgpu-core`, `wgpu-hal`, and `wgpu-types` crates should never +require an MSRV ahead of Firefox's MSRV for nightly builds, as +determined by the value of `MINIMUM_RUST_VERSION` in +[`python/mozboot/mozboot/util.py`][util]. + +[util]: https://searchfox.org/mozilla-central/source/python/mozboot/mozboot/util.py + ## Environment Variables -All testing and example infrastructure shares the same set of environment variables that determine which Backend/GPU it will run on. +All testing and example infrastructure share the same set of environment variables that determine which Backend/GPU it will run on. - `WGPU_ADAPTER_NAME` with a substring of the name of the adapter you want to use (ex. `1080` will match `NVIDIA GeForce 1080ti`). -- `WGPU_BACKEND` with a comma separated list of the backends you want to use (`vulkan`, `metal`, `dx12`, `dx11`, or `gl`). +- `WGPU_BACKEND` with a comma-separated list of the backends you want to use (`vulkan`, `metal`, `dx12`, or `gl`). - `WGPU_POWER_PREF` with the power preference to choose when a specific adapter name isn't specified (`high`, `low` or `none`) - `WGPU_DX12_COMPILER` with the DX12 shader compiler you wish to use (`dxc` or `fxc`, note that `dxc` requires `dxil.dll` and `dxcompiler.dll` to be in the working directory otherwise it will fall back to `fxc`) - `WGPU_GLES_MINOR_VERSION` with the minor OpenGL ES 3 version number to request (`0`, `1`, `2` or `automatic`). +- `WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER` with a boolean whether non-compliant drivers are enumerated (`0` for false, `1` for true). When running the CTS, use the variables `DENO_WEBGPU_ADAPTER_NAME`, `DENO_WEBGPU_BACKEND`, `DENO_WEBGPU_POWER_PREFERENCE`. ## Testing -We have multiple methods of testing, each of which tests different qualities about wgpu. We automatically run our tests on CI if possible. The current state of CI testing: - -| Backend/Platform | Tests | CTS | Notes | -| ---------------- | ------------------ | ------------------ | ------------------------------------- | -| DX12/Windows 10 | :heavy_check_mark: | :heavy_check_mark: | using WARP | -| DX11/Windows 10 | :construction: | — | using WARP | -| Metal/MacOS | — | — | metal requires GPU | -| Vulkan/Linux | :heavy_check_mark: | :x: | using lavapipe, [cts hangs][cts-hang] | -| GLES/Linux | :heavy_check_mark: | — | using llvmpipe | +We have multiple methods of testing, each of which tests different qualities about wgpu. We automatically run our tests on CI. The current state of CI testing: -[cts-hang]: https://github.com/gfx-rs/wgpu/issues/1974 +| Platform/Backend | Tests | Notes | +| ---------------- | ------------------ | --------------------- | +| Windows/DX12 | :heavy_check_mark: | using WARP | +| Windows/OpenGL | :heavy_check_mark: | using llvmpipe | +| MacOS/Metal | :heavy_check_mark: | using hardware runner | +| Linux/Vulkan | :heavy_check_mark: | using lavapipe | +| Linux/OpenGL ES | :heavy_check_mark: | using llvmpipe | +| Chrome/WebGL | :heavy_check_mark: | using swiftshader | +| Chrome/WebGPU | :x: | not set up | ### Core Test Infrastructure We use a tool called [`cargo nextest`](https://github.com/nextest-rs/nextest) to run our tests. To install it, run `cargo install cargo-nextest`. -To run the test suite on the default device: +To run the test suite: ``` -cargo nextest run --no-fail-fast +cargo xtask test ``` -`wgpu-info` can run the tests once for each adapter on your system. +To run the test suite on WebGL (currently incomplete): ``` -cargo run --bin wgpu-info -- cargo nextest run --no-fail-fast +cd wgpu +wasm-pack test --headless --chrome --no-default-features --features webgl --workspace ``` -Then to run an example's image comparison tests, run: - -``` -cargo nextest run --no-fail-fast -``` - -Or run a part of the integration test suite: - -``` -cargo nextest run -p wgpu -- -``` +This will automatically run the tests using a packaged browser. Remove `--headless` to run the tests with whatever browser you wish at `http://localhost:8000`. If you are a user and want a way to help contribute to wgpu, we always need more help writing test cases. @@ -190,7 +185,7 @@ If you are a user and want a way to help contribute to wgpu, we always need more WebGPU includes a Conformance Test Suite to validate that implementations are working correctly. We can run this CTS against wgpu. -To run the CTS, first you need to check it out: +To run the CTS, first, you need to check it out: ``` git clone https://github.com/gpuweb/cts.git @@ -202,8 +197,8 @@ git checkout $(cat ../cts_runner/revision.txt) To run a given set of tests: ``` -# Must be inside the cts folder we just checked out, else this will fail -cargo run --manifest-path ../cts_runner/Cargo.toml -- ./tools/run_deno --verbose "" +# Must be inside the `cts` folder we just checked out, else this will fail +cargo run --manifest-path ../Cargo.toml --bin cts_runner -- ./tools/run_deno --verbose "" ``` To find the full list of tests, go to the [online cts viewer](https://gpuweb.github.io/cts/standalone/?runnow=0&worker=0&debug=0&q=webgpu:*). @@ -227,7 +222,7 @@ Exactly which WGSL features `wgpu` supports depends on how you are using it: to translate WGSL code into the shading language of your platform's native GPU API. Naga has [a milestone][naga wgsl milestone] for catching up to the WGSL specification, - but in general there is no up-to-date summary + but in general, there is no up-to-date summary of the differences between Naga and the WGSL spec. - When running in a web browser (by compilation to WebAssembly) diff --git a/codecov.yml b/codecov.yml index 959972a69c..ed03de7f7c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1 +1,13 @@ -comment: false \ No newline at end of file +coverage: + status: + project: + default: + informational: true + if_ci_failed: success + patch: + default: + informational: true + if_ci_failed: success +comment: false +github_checks: + annotations: false diff --git a/cts_runner/examples/hello-compute.js b/cts_runner/examples/hello-compute.js index 5bbfa0719e..9c4023ed5e 100644 --- a/cts_runner/examples/hello-compute.js +++ b/cts_runner/examples/hello-compute.js @@ -49,13 +49,14 @@ const size = new Uint32Array(numbers).byteLength; const stagingBuffer = device.createBuffer({ size: size, - usage: 1 | 8, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, }); const storageBuffer = device.createBuffer({ label: "Storage Buffer", size: size, - usage: 0x80 | 8 | 4, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | + GPUBufferUsage.COPY_SRC, mappedAtCreation: true, }); diff --git a/cts_runner/revision.txt b/cts_runner/revision.txt index b4b0529662..4e6b1d36c1 100644 --- a/cts_runner/revision.txt +++ b/cts_runner/revision.txt @@ -1 +1 @@ -a44186d9fa1d5babdb4a198e8ef04cf0d7035ebb \ No newline at end of file +7ea73ba6f44c9f6fedfffc06f14c73ea9f2eaa11 diff --git a/cts_runner/src/bootstrap.js b/cts_runner/src/bootstrap.js index 798a769262..640075e7ee 100644 --- a/cts_runner/src/bootstrap.js +++ b/cts_runner/src/bootstrap.js @@ -42,6 +42,64 @@ import "ext:deno_web/14_compression.js"; let globalThis_; +const { BadResource, Interrupted } = core; + +class NotFound extends Error { + constructor(msg) { + super(msg); + this.name = "NotFound"; + } +} + +class BrokenPipe extends Error { + constructor(msg) { + super(msg); + this.name = "BrokenPipe"; + } +} + +class AlreadyExists extends Error { + constructor(msg) { + super(msg); + this.name = "AlreadyExists"; + } +} + +class InvalidData extends Error { + constructor(msg) { + super(msg); + this.name = "InvalidData"; + } +} + +class TimedOut extends Error { + constructor(msg) { + super(msg); + this.name = "TimedOut"; + } +} + +class WriteZero extends Error { + constructor(msg) { + super(msg); + this.name = "WriteZero"; + } +} + +class UnexpectedEof extends Error { + constructor(msg) { + super(msg); + this.name = "UnexpectedEof"; + } +} + +class NotSupported extends Error { + constructor(msg) { + super(msg); + this.name = "NotSupported"; + } +} + const util = { writable(value) { return { @@ -88,7 +146,7 @@ const NavigatorPrototype = Navigator.prototype; const navigator = webidl.createBranded(Navigator); -ObjectDefineProperties(Navigator.prototype, { +ObjectDefineProperties(NavigatorPrototype, { gpu: { configurable: true, enumerable: true, @@ -108,6 +166,7 @@ const windowOrWorkerGlobalScope = { EventTarget: util.nonEnumerable(event.EventTarget), Navigator: util.nonEnumerable(Navigator), navigator: util.getterOnly(() => navigator), + MessageEvent: util.nonEnumerable(event.MessageEvent), Performance: util.nonEnumerable(performance.Performance), PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry), PerformanceMark: util.nonEnumerable(performance.PerformanceMark), @@ -127,15 +186,17 @@ const windowOrWorkerGlobalScope = { GPU: util.nonEnumerable(webgpu.GPU), GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter), - GPUAdapterLimits: util.nonEnumerable(webgpu.GPUAdapterLimits), + GPUAdapterInfo: util.nonEnumerable(webgpu.GPUAdapterInfo), + GPUSupportedLimits: util.nonEnumerable(webgpu.GPUSupportedLimits), GPUSupportedFeatures: util.nonEnumerable(webgpu.GPUSupportedFeatures), + GPUDeviceLostInfo: util.nonEnumerable(webgpu.GPUDeviceLostInfo), GPUDevice: util.nonEnumerable(webgpu.GPUDevice), GPUQueue: util.nonEnumerable(webgpu.GPUQueue), GPUBuffer: util.nonEnumerable(webgpu.GPUBuffer), GPUBufferUsage: util.nonEnumerable(webgpu.GPUBufferUsage), GPUMapMode: util.nonEnumerable(webgpu.GPUMapMode), - GPUTexture: util.nonEnumerable(webgpu.GPUTexture), GPUTextureUsage: util.nonEnumerable(webgpu.GPUTextureUsage), + GPUTexture: util.nonEnumerable(webgpu.GPUTexture), GPUTextureView: util.nonEnumerable(webgpu.GPUTextureView), GPUSampler: util.nonEnumerable(webgpu.GPUSampler), GPUBindGroupLayout: util.nonEnumerable(webgpu.GPUBindGroupLayout), @@ -153,8 +214,9 @@ const windowOrWorkerGlobalScope = { GPURenderBundleEncoder: util.nonEnumerable(webgpu.GPURenderBundleEncoder), GPURenderBundle: util.nonEnumerable(webgpu.GPURenderBundle), GPUQuerySet: util.nonEnumerable(webgpu.GPUQuerySet), - GPUOutOfMemoryError: util.nonEnumerable(webgpu.GPUOutOfMemoryError), + GPUError: util.nonEnumerable(webgpu.GPUError), GPUValidationError: util.nonEnumerable(webgpu.GPUValidationError), + GPUOutOfMemoryError: util.nonEnumerable(webgpu.GPUOutOfMemoryError), }; windowOrWorkerGlobalScope.console.enumerable = false; @@ -167,27 +229,54 @@ const mainRuntimeGlobalProperties = { const denoNs = { exit(code) { - core.opSync("op_exit", code); + core.ops.op_exit(code); }, readFileSync(path) { - return core.opSync("op_read_file_sync", pathFromURL(path)); + return core.ops.op_read_file_sync(pathFromURL(path)); }, readTextFileSync(path) { - const buf = core.opSync("op_read_file_sync", pathFromURL(path)); + const buf = core.ops.op_read_file_sync(pathFromURL(path)); const decoder = new TextDecoder(); return decoder.decode(buf); }, writeFileSync(path, buf) { - return core.opSync("op_write_file_sync", pathFromURL(path), buf); + return core.ops.op_write_file_sync(pathFromURL(path), buf); }, }; +core.registerErrorClass("NotFound", NotFound); +core.registerErrorClass("AlreadyExists", AlreadyExists); +core.registerErrorClass("InvalidData", InvalidData); +core.registerErrorClass("TimedOut", TimedOut); +core.registerErrorClass("Interrupted", Interrupted); +core.registerErrorClass("WriteZero", WriteZero); +core.registerErrorClass("UnexpectedEof", UnexpectedEof); +core.registerErrorClass("BadResource", BadResource); +core.registerErrorClass("NotSupported", NotSupported); core.registerErrorBuilder( "DOMExceptionOperationError", function DOMExceptionOperationError(msg) { return new DOMException(msg, "OperationError"); }, ); +core.registerErrorBuilder( + "DOMExceptionAbortError", + function DOMExceptionAbortError(msg) { + return new domException.DOMException(msg, "AbortError"); + }, +); +core.registerErrorBuilder( + "DOMExceptionInvalidCharacterError", + function DOMExceptionInvalidCharacterError(msg) { + return new domException.DOMException(msg, "InvalidCharacterError"); + }, +); +core.registerErrorBuilder( + "DOMExceptionDataError", + function DOMExceptionDataError(msg) { + return new domException.DOMException(msg, "DataError"); + }, +); let hasBootstrapped = false; diff --git a/cts_runner/src/main.rs b/cts_runner/src/main.rs index c5a70aabc1..700fe3b3ef 100644 --- a/cts_runner/src/main.rs +++ b/cts_runner/src/main.rs @@ -1,5 +1,6 @@ #[cfg(not(target_arch = "wasm32"))] mod native { + use std::sync::Arc; use std::{ env, fmt, io::{Read, Write}, @@ -8,13 +9,12 @@ mod native { use deno_core::anyhow::anyhow; use deno_core::error::AnyError; - use deno_core::op; + use deno_core::op2; use deno_core::resolve_url_or_path; use deno_core::serde_json::json; use deno_core::v8; use deno_core::JsRuntime; use deno_core::RuntimeOptions; - use deno_core::ZeroCopyBuf; use deno_web::BlobStore; use termcolor::Ansi; use termcolor::Color::Red; @@ -36,7 +36,10 @@ mod native { deno_webidl::deno_webidl::init_ops_and_esm(), deno_console::deno_console::init_ops_and_esm(), deno_url::deno_url::init_ops_and_esm(), - deno_web::deno_web::init_ops_and_esm::(BlobStore::default(), None), + deno_web::deno_web::init_ops_and_esm::( + Arc::new(BlobStore::default()), + None, + ), deno_webgpu::deno_webgpu::init_ops_and_esm(true), cts_runner::init_ops_and_esm(), ], @@ -47,7 +50,7 @@ mod native { let cfg = json!({"args": args, "cwd": env::current_dir().unwrap().to_string_lossy() }); { - let context = isolate.global_context(); + let context = isolate.main_context(); let scope = &mut isolate.handle_scope(); let context_local = v8::Local::new(scope, context); let global_obj = context_local.global(scope); @@ -56,7 +59,6 @@ mod native { let bootstrap_fn = v8::Local::::try_from(bootstrap_fn).unwrap(); let options_v8 = deno_core::serde_v8::to_v8(scope, cfg).unwrap(); - let bootstrap_fn = v8::Local::new(scope, bootstrap_fn); let undefined = v8::undefined(scope); bootstrap_fn .call(scope, undefined.into(), &[options_v8]) @@ -86,28 +88,29 @@ mod native { deps = [deno_webidl, deno_web], ops = [op_exit, op_read_file_sync, op_write_file_sync], esm_entry_point = "ext:cts_runner/bootstrap.js", - esm = ["bootstrap.js"], + esm = ["src/bootstrap.js"], ); - #[op] + #[op2(fast)] fn op_exit(code: i32) -> Result<(), AnyError> { std::process::exit(code) } - #[op] - fn op_read_file_sync(path: String) -> Result { - let path = std::path::Path::new(&path); + #[op2] + #[buffer] + fn op_read_file_sync(#[string] path: &str) -> Result, AnyError> { + let path = std::path::Path::new(path); let mut file = std::fs::File::open(path)?; let mut buf = Vec::new(); file.read_to_end(&mut buf)?; - Ok(ZeroCopyBuf::from(buf)) + Ok(buf) } - #[op] - fn op_write_file_sync(path: String, buf: ZeroCopyBuf) -> Result<(), AnyError> { - let path = std::path::Path::new(&path); + #[op2(fast)] + fn op_write_file_sync(#[string] path: &str, #[buffer] buf: &[u8]) -> Result<(), AnyError> { + let path = std::path::Path::new(path); let mut file = std::fs::File::create(path)?; - file.write_all(&buf)?; + file.write_all(buf)?; Ok(()) } @@ -157,8 +160,6 @@ mod native { fn allow_hrtime(&mut self) -> bool { false } - - fn check_unstable(&self, _state: &deno_core::OpState, _api_name: &'static str) {} } } diff --git a/cts_runner/test.lst b/cts_runner/test.lst index f5ef47ece4..2a410aa5a0 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -1,11 +1,10 @@ unittests:* webgpu:api,operation,command_buffer,basic:* webgpu:api,operation,compute,basic:* -//FAIL: webgpu:api,validation,queue,copyToTexture,ImageBitmap:destination_texture,format:format="stencil8";*' webgpu:api,operation,rendering,basic:clear:* webgpu:api,operation,rendering,basic:fullscreen_quad:* -//HANG: webgpu:api,operation,rendering,basic:large_draw:* +//FAIL: webgpu:api,operation,rendering,basic:large_draw:* webgpu:api,operation,rendering,blending:* webgpu:api,operation,rendering,blending:GPUBlendComponent:* -//FAIL: webgpu:api,operation,rendering,depth:* -//FAIL: webgpu:api,operation,rendering,draw:* +webgpu:api,operation,rendering,depth:* +webgpu:api,operation,rendering,draw:* diff --git a/d3d12/CHANGELOG.md b/d3d12/CHANGELOG.md new file mode 100644 index 0000000000..6af566ae68 --- /dev/null +++ b/d3d12/CHANGELOG.md @@ -0,0 +1,32 @@ +# Change Log + +## v0.6.0 (2023-01-25) + - add helpers for IDXGIFactoryMedia + - add `create_swapchain_for_composition_surface_handle` + +## v0.5.0 (2022-07-01) + - add COM helpers + - enable D3D11 adapter use + +## v0.4.1 (2021-08-18) + - expose all indirect argument types + - expose methods for setting root constants + +## v0.4.0 (2021-04-29) + - update `libloading` to 0.7 + +## v0.3.1 (2020-07-07) + - create shader from IL + - fix default doc target + - debug impl for root descriptors + +## v0.3.0 (2019-11-01) + - resource transitions + - dynamic library loading + +## v0.2.2 (2019-10-04) + - add `D3DHeap` + - add root descriptor + +## v0.1.0 (2018-12-26) + - basic version diff --git a/d3d12/Cargo.toml b/d3d12/Cargo.toml new file mode 100644 index 0000000000..91f3542948 --- /dev/null +++ b/d3d12/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "d3d12" +version = "0.7.0" +authors = ["gfx-rs developers"] +description = "Low level D3D12 API wrapper" +repository = "https://github.com/gfx-rs/wgpu/tree/trunk/d3d12" +keywords = ["windows", "graphics"] +license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/d3d12" +categories = [ + "api-bindings", + "graphics", + "memory-management", + "os::windows-apis", +] +edition = "2018" + +[features] +implicit-link = [] + +[target.'cfg(windows)'.dependencies] +bitflags = "2" +# libloading 0.8 switches from `winapi` to `windows-sys`; permit either +libloading = { version = ">=0.7,<0.9", optional = true } + +[target.'cfg(windows)'.dependencies.winapi] +version = "0.3" +features = [ + "dxgi1_2", + "dxgi1_3", + "dxgi1_4", + "dxgi1_5", + "dxgi1_6", + "dxgidebug", + "d3d12", + "d3d12sdklayers", + "d3dcommon", + "d3dcompiler", + "dxgiformat", + "synchapi", + "winerror", +] + +[package.metadata.docs.rs] +targets = ["x86_64-pc-windows-msvc"] diff --git a/d3d12/README.md b/d3d12/README.md new file mode 100644 index 0000000000..718bf73555 --- /dev/null +++ b/d3d12/README.md @@ -0,0 +1,5 @@ +# d3d12-rs +[![Crates.io](https://img.shields.io/crates/v/d3d12.svg)](https://crates.io/crates/d3d12) +[![Docs.rs](https://docs.rs/d3d12/badge.svg)](https://docs.rs/d3d12) + +Rust wrapper for raw D3D12 access. diff --git a/d3d12/src/com.rs b/d3d12/src/com.rs new file mode 100644 index 0000000000..8f9aad28d8 --- /dev/null +++ b/d3d12/src/com.rs @@ -0,0 +1,263 @@ +use crate::D3DResult; +use std::{ + fmt, + hash::{Hash, Hasher}, + ops::Deref, + ptr, +}; +use winapi::{ctypes::c_void, um::unknwnbase::IUnknown, Interface}; + +#[repr(transparent)] +pub struct ComPtr(*mut T); + +impl ComPtr { + pub fn null() -> Self { + ComPtr(ptr::null_mut()) + } + + pub unsafe fn from_raw(raw: *mut T) -> Self { + if !raw.is_null() { + (*(raw as *mut IUnknown)).AddRef(); + } + ComPtr(raw) + } + + pub fn is_null(&self) -> bool { + self.0.is_null() + } + + pub fn as_ptr(&self) -> *const T { + self.0 + } + + pub fn as_mut_ptr(&self) -> *mut T { + self.0 + } + + pub fn mut_void(&mut self) -> *mut *mut c_void { + &mut self.0 as *mut *mut _ as *mut *mut _ + } + + pub fn mut_self(&mut self) -> *mut *mut T { + &mut self.0 as *mut *mut _ + } +} + +impl ComPtr { + pub unsafe fn as_unknown(&self) -> &IUnknown { + debug_assert!(!self.is_null()); + &*(self.0 as *mut IUnknown) + } + + pub unsafe fn cast(&self) -> D3DResult> + where + U: Interface, + { + debug_assert!(!self.is_null()); + let mut obj = ComPtr::::null(); + let hr = self + .as_unknown() + .QueryInterface(&U::uuidof(), obj.mut_void()); + (obj, hr) + } +} + +impl Clone for ComPtr { + fn clone(&self) -> Self { + debug_assert!(!self.is_null()); + unsafe { + self.as_unknown().AddRef(); + } + ComPtr(self.0) + } +} + +impl Drop for ComPtr { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + self.as_unknown().Release(); + } + } + } +} + +impl Deref for ComPtr { + type Target = T; + fn deref(&self) -> &T { + debug_assert!(!self.is_null()); + unsafe { &*self.0 } + } +} + +impl fmt::Debug for ComPtr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ComPtr( ptr: {:?} )", self.0) + } +} + +impl PartialEq<*mut T> for ComPtr { + fn eq(&self, other: &*mut T) -> bool { + self.0 == *other + } +} + +impl PartialEq for ComPtr { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Hash for ComPtr { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +/// Macro that allows generation of an easy to use enum for dealing with many different possible versions of a COM object. +/// +/// Give the variants so that parents come before children. This often manifests as going up in order (1 -> 2 -> 3). This is vital for safety. +/// +/// Three function names need to be attached to each variant. The examples are given for the MyComObject1 variant below: +/// - the from function (`ComPtr -> Self`) +/// - the as function (`&self -> Option>`) +/// - the unwrap function (`&self -> ComPtr` panicing on failure to cast) +/// +/// ```rust +/// # pub use d3d12::weak_com_inheritance_chain; +/// # mod actual { +/// # pub struct ComObject; impl winapi::Interface for ComObject { fn uuidof() -> winapi::shared::guiddef::GUID { todo!() } } +/// # pub struct ComObject1; impl winapi::Interface for ComObject1 { fn uuidof() -> winapi::shared::guiddef::GUID { todo!() } } +/// # pub struct ComObject2; impl winapi::Interface for ComObject2 { fn uuidof() -> winapi::shared::guiddef::GUID { todo!() } } +/// # } +/// weak_com_inheritance_chain! { +/// pub enum MyComObject { +/// MyComObject(actual::ComObject), from_my_com_object, as_my_com_object, my_com_object; // First variant doesn't use "unwrap" as it can never fail +/// MyComObject1(actual::ComObject1), from_my_com_object1, as_my_com_object1, unwrap_my_com_object1; +/// MyComObject2(actual::ComObject2), from_my_com_object2, as_my_com_object2, unwrap_my_com_object2; +/// } +/// } +/// ``` +#[macro_export] +macro_rules! weak_com_inheritance_chain { + // We first match a human readable enum style, before going into the recursive section. + // + // Internal calls to the macro have either the prefix + // - @recursion_logic for the recursion and termination + // - @render_members for the actual call to fill in the members. + ( + $(#[$meta:meta])* + $vis:vis enum $name:ident { + $first_variant:ident($first_type:ty), $first_from_name:ident, $first_as_name:ident, $first_unwrap_name:ident $(;)? + $($variant:ident($type:ty), $from_name:ident, $as_name:ident, $unwrap_name:ident);* $(;)? + } + ) => { + $(#[$meta])* + $vis enum $name { + $first_variant($crate::ComPtr<$first_type>), + $( + $variant($crate::ComPtr<$type>) + ),+ + } + impl $name { + $crate::weak_com_inheritance_chain! { + @recursion_logic, + $vis, + ; + $first_variant($first_type), $first_from_name, $first_as_name, $first_unwrap_name; + $($variant($type), $from_name, $as_name, $unwrap_name);* + } + } + + impl std::ops::Deref for $name { + type Target = $crate::ComPtr<$first_type>; + fn deref(&self) -> &Self::Target { + self.$first_unwrap_name() + } + } + }; + + // This is the iteration case of the recursion. We instantiate the member functions for the variant we + // are currently at, recursing on ourself for the next variant. Note we only keep track of the previous + // variant name, not the functions names, as those are not needed. + ( + @recursion_logic, + $vis:vis, + $(,)? $($prev_variant:ident),* $(,)?; + $this_variant:ident($this_type:ty), $this_from_name:ident, $this_as_name:ident, $this_unwrap_name:ident $(;)? + $($next_variant:ident($next_type:ty), $next_from_name:ident, $next_as_name:ident, $next_unwrap_name:ident);* + ) => { + // Actually generate the members for this variant. Needs the previous and future variant names. + $crate::weak_com_inheritance_chain! { + @render_members, + $vis, + $this_from_name, $this_as_name, $this_unwrap_name; + $($prev_variant),*; + $this_variant($this_type); + $($next_variant),*; + } + + // Recurse on ourselves. If there is no future variants left, we'll hit the base case as the final expansion returns no tokens. + $crate::weak_com_inheritance_chain! { + @recursion_logic, + $vis, + $($prev_variant),* , $this_variant; + $($next_variant($next_type), $next_from_name, $next_as_name, $next_unwrap_name);* + } + }; + // Base case for recursion. There are no more variants left + ( + @recursion_logic, + $vis:vis, + $($prev_variant:ident),*; + ) => {}; + + + // This is where we generate the members using the given names. + ( + @render_members, + $vis:vis, + $from_name:ident, $as_name:ident, $unwrap_name:ident; + $($prev_variant:ident),*; + $variant:ident($type:ty); + $($next_variant:ident),*; + ) => { + // Construct this enum from weak pointer to this interface. For best usability, always use the highest constructor you can. This doesn't try to upcast. + $vis unsafe fn $from_name(value: $crate::ComPtr<$type>) -> Self { + Self::$variant(value) + } + + // Returns Some if the value implements the interface otherwise returns None. + $vis fn $as_name(&self) -> Option<&$crate::ComPtr<$type>> { + match *self { + $( + Self::$prev_variant(_) => None, + )* + Self::$variant(ref v) => Some(v), + $( + Self::$next_variant(ref v) => { + // v is &ComPtr and se cast to &ComPtr + Some(unsafe { std::mem::transmute(v) }) + } + )* + } + } + + // Returns the interface if the value implements it, otherwise panics. + #[track_caller] + $vis fn $unwrap_name(&self) -> &$crate::ComPtr<$type> { + match *self { + $( + Self::$prev_variant(_) => panic!(concat!("Tried to unwrap a ", stringify!($prev_variant), " as a ", stringify!($variant))), + )* + Self::$variant(ref v) => &*v, + $( + Self::$next_variant(ref v) => { + // v is &ComPtr and se cast to &ComPtr + unsafe { std::mem::transmute(v) } + } + )* + } + } + }; +} diff --git a/d3d12/src/command_allocator.rs b/d3d12/src/command_allocator.rs new file mode 100644 index 0000000000..b50ec00d4a --- /dev/null +++ b/d3d12/src/command_allocator.rs @@ -0,0 +1,14 @@ +//! Command Allocator + +use crate::com::ComPtr; +use winapi::um::d3d12; + +pub type CommandAllocator = ComPtr; + +impl CommandAllocator { + pub fn reset(&self) { + unsafe { + self.Reset(); + } + } +} diff --git a/d3d12/src/command_list.rs b/d3d12/src/command_list.rs new file mode 100644 index 0000000000..1f8c0d53c2 --- /dev/null +++ b/d3d12/src/command_list.rs @@ -0,0 +1,406 @@ +//! Graphics command list + +use crate::{ + com::ComPtr, resource::DiscardRegion, CommandAllocator, CpuDescriptor, DescriptorHeap, Format, + GpuAddress, GpuDescriptor, IndexCount, InstanceCount, PipelineState, Rect, Resource, RootIndex, + RootSignature, Subresource, VertexCount, VertexOffset, WorkGroupCount, HRESULT, +}; +use std::{mem, ptr}; +use winapi::um::d3d12; + +#[repr(u32)] +#[derive(Clone, Copy)] +pub enum CmdListType { + Direct = d3d12::D3D12_COMMAND_LIST_TYPE_DIRECT, + Bundle = d3d12::D3D12_COMMAND_LIST_TYPE_BUNDLE, + Compute = d3d12::D3D12_COMMAND_LIST_TYPE_COMPUTE, + Copy = d3d12::D3D12_COMMAND_LIST_TYPE_COPY, + // VideoDecode = d3d12::D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE, + // VideoProcess = d3d12::D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS, +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct ClearFlags: u32 { + const DEPTH = d3d12::D3D12_CLEAR_FLAG_DEPTH; + const STENCIL = d3d12::D3D12_CLEAR_FLAG_STENCIL; + } +} + +#[repr(transparent)] +pub struct IndirectArgument(d3d12::D3D12_INDIRECT_ARGUMENT_DESC); + +impl IndirectArgument { + pub fn draw() -> Self { + IndirectArgument(d3d12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: d3d12::D3D12_INDIRECT_ARGUMENT_TYPE_DRAW, + ..unsafe { mem::zeroed() } + }) + } + + pub fn draw_indexed() -> Self { + IndirectArgument(d3d12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: d3d12::D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED, + ..unsafe { mem::zeroed() } + }) + } + + pub fn dispatch() -> Self { + IndirectArgument(d3d12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: d3d12::D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH, + ..unsafe { mem::zeroed() } + }) + } + + pub fn vertex_buffer(slot: u32) -> Self { + let mut desc = d3d12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: d3d12::D3D12_INDIRECT_ARGUMENT_TYPE_VERTEX_BUFFER_VIEW, + ..unsafe { mem::zeroed() } + }; + *unsafe { desc.u.VertexBuffer_mut() } = + d3d12::D3D12_INDIRECT_ARGUMENT_DESC_VertexBuffer { Slot: slot }; + IndirectArgument(desc) + } + + pub fn constant(root_index: RootIndex, dest_offset_words: u32, count: u32) -> Self { + let mut desc = d3d12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: d3d12::D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT, + ..unsafe { mem::zeroed() } + }; + *unsafe { desc.u.Constant_mut() } = d3d12::D3D12_INDIRECT_ARGUMENT_DESC_Constant { + RootParameterIndex: root_index, + DestOffsetIn32BitValues: dest_offset_words, + Num32BitValuesToSet: count, + }; + IndirectArgument(desc) + } + + pub fn constant_buffer_view(root_index: RootIndex) -> Self { + let mut desc = d3d12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: d3d12::D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT_BUFFER_VIEW, + ..unsafe { mem::zeroed() } + }; + *unsafe { desc.u.ConstantBufferView_mut() } = + d3d12::D3D12_INDIRECT_ARGUMENT_DESC_ConstantBufferView { + RootParameterIndex: root_index, + }; + IndirectArgument(desc) + } + + pub fn shader_resource_view(root_index: RootIndex) -> Self { + let mut desc = d3d12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: d3d12::D3D12_INDIRECT_ARGUMENT_TYPE_SHADER_RESOURCE_VIEW, + ..unsafe { mem::zeroed() } + }; + *unsafe { desc.u.ShaderResourceView_mut() } = + d3d12::D3D12_INDIRECT_ARGUMENT_DESC_ShaderResourceView { + RootParameterIndex: root_index, + }; + IndirectArgument(desc) + } + + pub fn unordered_access_view(root_index: RootIndex) -> Self { + let mut desc = d3d12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: d3d12::D3D12_INDIRECT_ARGUMENT_TYPE_UNORDERED_ACCESS_VIEW, + ..unsafe { mem::zeroed() } + }; + *unsafe { desc.u.UnorderedAccessView_mut() } = + d3d12::D3D12_INDIRECT_ARGUMENT_DESC_UnorderedAccessView { + RootParameterIndex: root_index, + }; + IndirectArgument(desc) + } +} + +#[repr(transparent)] +pub struct ResourceBarrier(d3d12::D3D12_RESOURCE_BARRIER); + +impl ResourceBarrier { + pub fn transition( + resource: Resource, + subresource: Subresource, + state_before: d3d12::D3D12_RESOURCE_STATES, + state_after: d3d12::D3D12_RESOURCE_STATES, + flags: d3d12::D3D12_RESOURCE_BARRIER_FLAGS, + ) -> Self { + let mut barrier = d3d12::D3D12_RESOURCE_BARRIER { + Type: d3d12::D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + Flags: flags, + ..unsafe { mem::zeroed() } + }; + unsafe { + *barrier.u.Transition_mut() = d3d12::D3D12_RESOURCE_TRANSITION_BARRIER { + pResource: resource.as_mut_ptr(), + Subresource: subresource, + StateBefore: state_before, + StateAfter: state_after, + }; + } + ResourceBarrier(barrier) + } +} + +pub type CommandSignature = ComPtr; +pub type CommandList = ComPtr; +pub type GraphicsCommandList = ComPtr; + +impl GraphicsCommandList { + pub fn as_list(&self) -> CommandList { + unsafe { CommandList::from_raw(self.as_mut_ptr() as *mut _) } + } + + pub fn close(&self) -> HRESULT { + unsafe { self.Close() } + } + + pub fn reset(&self, allocator: &CommandAllocator, initial_pso: PipelineState) -> HRESULT { + unsafe { self.Reset(allocator.as_mut_ptr(), initial_pso.as_mut_ptr()) } + } + + pub fn discard_resource(&self, resource: Resource, region: DiscardRegion) { + debug_assert!(region.subregions.start < region.subregions.end); + unsafe { + self.DiscardResource( + resource.as_mut_ptr(), + &d3d12::D3D12_DISCARD_REGION { + NumRects: region.rects.len() as _, + pRects: region.rects.as_ptr(), + FirstSubresource: region.subregions.start, + NumSubresources: region.subregions.end - region.subregions.start - 1, + }, + ); + } + } + + pub fn clear_depth_stencil_view( + &self, + dsv: CpuDescriptor, + flags: ClearFlags, + depth: f32, + stencil: u8, + rects: &[Rect], + ) { + let num_rects = rects.len() as _; + let rects = if num_rects > 0 { + rects.as_ptr() + } else { + ptr::null() + }; + unsafe { + self.ClearDepthStencilView(dsv, flags.bits(), depth, stencil, num_rects, rects); + } + } + + pub fn clear_render_target_view(&self, rtv: CpuDescriptor, color: [f32; 4], rects: &[Rect]) { + let num_rects = rects.len() as _; + let rects = if num_rects > 0 { + rects.as_ptr() + } else { + ptr::null() + }; + unsafe { + self.ClearRenderTargetView(rtv, &color, num_rects, rects); + } + } + + pub fn dispatch(&self, count: WorkGroupCount) { + unsafe { + self.Dispatch(count[0], count[1], count[2]); + } + } + + pub fn draw( + &self, + num_vertices: VertexCount, + num_instances: InstanceCount, + first_vertex: VertexCount, + first_instance: InstanceCount, + ) { + unsafe { + self.DrawInstanced(num_vertices, num_instances, first_vertex, first_instance); + } + } + + pub fn draw_indexed( + &self, + num_indices: IndexCount, + num_instances: InstanceCount, + first_index: IndexCount, + base_vertex: VertexOffset, + first_instance: InstanceCount, + ) { + unsafe { + self.DrawIndexedInstanced( + num_indices, + num_instances, + first_index, + base_vertex, + first_instance, + ); + } + } + + pub fn set_index_buffer(&self, gpu_address: GpuAddress, size: u32, format: Format) { + let ibv = d3d12::D3D12_INDEX_BUFFER_VIEW { + BufferLocation: gpu_address, + SizeInBytes: size, + Format: format, + }; + unsafe { + self.IASetIndexBuffer(&ibv); + } + } + + pub fn set_blend_factor(&self, factor: [f32; 4]) { + unsafe { + self.OMSetBlendFactor(&factor); + } + } + + pub fn set_stencil_reference(&self, reference: u32) { + unsafe { + self.OMSetStencilRef(reference); + } + } + + pub fn set_pipeline_state(&self, pso: &PipelineState) { + unsafe { + self.SetPipelineState(pso.as_mut_ptr()); + } + } + + pub fn execute_bundle(&self, bundle: GraphicsCommandList) { + unsafe { + self.ExecuteBundle(bundle.as_mut_ptr()); + } + } + + pub fn set_descriptor_heaps(&self, heaps: &[DescriptorHeap]) { + unsafe { + self.SetDescriptorHeaps( + heaps.len() as _, + heaps.as_ptr() as *mut &DescriptorHeap as *mut _, + ); + } + } + + pub fn set_compute_root_signature(&self, signature: &RootSignature) { + unsafe { + self.SetComputeRootSignature(signature.as_mut_ptr()); + } + } + + pub fn set_graphics_root_signature(&self, signature: &RootSignature) { + unsafe { + self.SetGraphicsRootSignature(signature.as_mut_ptr()); + } + } + + pub fn set_compute_root_descriptor_table( + &self, + root_index: RootIndex, + base_descriptor: GpuDescriptor, + ) { + unsafe { + self.SetComputeRootDescriptorTable(root_index, base_descriptor); + } + } + + pub fn set_compute_root_constant_buffer_view( + &self, + root_index: RootIndex, + buffer_location: GpuAddress, + ) { + unsafe { + self.SetComputeRootConstantBufferView(root_index, buffer_location); + } + } + + pub fn set_compute_root_shader_resource_view( + &self, + root_index: RootIndex, + buffer_location: GpuAddress, + ) { + unsafe { + self.SetComputeRootShaderResourceView(root_index, buffer_location); + } + } + + pub fn set_compute_root_unordered_access_view( + &self, + root_index: RootIndex, + buffer_location: GpuAddress, + ) { + unsafe { + self.SetComputeRootUnorderedAccessView(root_index, buffer_location); + } + } + + pub fn set_compute_root_constant( + &self, + root_index: RootIndex, + value: u32, + dest_offset_words: u32, + ) { + unsafe { + self.SetComputeRoot32BitConstant(root_index, value, dest_offset_words); + } + } + + pub fn set_graphics_root_descriptor_table( + &self, + root_index: RootIndex, + base_descriptor: GpuDescriptor, + ) { + unsafe { + self.SetGraphicsRootDescriptorTable(root_index, base_descriptor); + } + } + + pub fn set_graphics_root_constant_buffer_view( + &self, + root_index: RootIndex, + buffer_location: GpuAddress, + ) { + unsafe { + self.SetGraphicsRootConstantBufferView(root_index, buffer_location); + } + } + + pub fn set_graphics_root_shader_resource_view( + &self, + root_index: RootIndex, + buffer_location: GpuAddress, + ) { + unsafe { + self.SetGraphicsRootShaderResourceView(root_index, buffer_location); + } + } + + pub fn set_graphics_root_unordered_access_view( + &self, + root_index: RootIndex, + buffer_location: GpuAddress, + ) { + unsafe { + self.SetGraphicsRootUnorderedAccessView(root_index, buffer_location); + } + } + + pub fn set_graphics_root_constant( + &self, + root_index: RootIndex, + value: u32, + dest_offset_words: u32, + ) { + unsafe { + self.SetGraphicsRoot32BitConstant(root_index, value, dest_offset_words); + } + } + + pub fn resource_barrier(&self, barriers: &[ResourceBarrier]) { + unsafe { + self.ResourceBarrier(barriers.len() as _, barriers.as_ptr() as _) // matches representation + } + } +} diff --git a/d3d12/src/debug.rs b/d3d12/src/debug.rs new file mode 100644 index 0000000000..3a6abc46b7 --- /dev/null +++ b/d3d12/src/debug.rs @@ -0,0 +1,43 @@ +use crate::com::ComPtr; +use winapi::um::d3d12sdklayers; +#[cfg(any(feature = "libloading", feature = "implicit-link"))] +use winapi::Interface as _; + +pub type Debug = ComPtr; + +#[cfg(feature = "libloading")] +impl crate::D3D12Lib { + pub fn get_debug_interface(&self) -> Result, libloading::Error> { + type Fun = extern "system" fn( + winapi::shared::guiddef::REFIID, + *mut *mut winapi::ctypes::c_void, + ) -> crate::HRESULT; + + let mut debug = Debug::null(); + let hr = unsafe { + let func: libloading::Symbol = self.lib.get(b"D3D12GetDebugInterface")?; + func(&d3d12sdklayers::ID3D12Debug::uuidof(), debug.mut_void()) + }; + + Ok((debug, hr)) + } +} + +impl Debug { + #[cfg(feature = "implicit-link")] + pub fn get_interface() -> crate::D3DResult { + let mut debug = Debug::null(); + let hr = unsafe { + winapi::um::d3d12::D3D12GetDebugInterface( + &d3d12sdklayers::ID3D12Debug::uuidof(), + debug.mut_void(), + ) + }; + + (debug, hr) + } + + pub fn enable_layer(&self) { + unsafe { self.EnableDebugLayer() } + } +} diff --git a/d3d12/src/descriptor.rs b/d3d12/src/descriptor.rs new file mode 100644 index 0000000000..b2c3ab23b9 --- /dev/null +++ b/d3d12/src/descriptor.rs @@ -0,0 +1,362 @@ +use crate::{com::ComPtr, Blob, D3DResult, Error, TextureAddressMode}; +use std::{fmt, mem, ops::Range}; +use winapi::{shared::dxgiformat, um::d3d12}; + +pub type CpuDescriptor = d3d12::D3D12_CPU_DESCRIPTOR_HANDLE; +pub type GpuDescriptor = d3d12::D3D12_GPU_DESCRIPTOR_HANDLE; + +#[derive(Clone, Copy, Debug)] +pub struct Binding { + pub space: u32, + pub register: u32, +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug)] +pub enum DescriptorHeapType { + CbvSrvUav = d3d12::D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + Sampler = d3d12::D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, + Rtv = d3d12::D3D12_DESCRIPTOR_HEAP_TYPE_RTV, + Dsv = d3d12::D3D12_DESCRIPTOR_HEAP_TYPE_DSV, +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct DescriptorHeapFlags: u32 { + const SHADER_VISIBLE = d3d12::D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + } +} + +pub type DescriptorHeap = ComPtr; + +impl DescriptorHeap { + pub fn start_cpu_descriptor(&self) -> CpuDescriptor { + unsafe { self.GetCPUDescriptorHandleForHeapStart() } + } + + pub fn start_gpu_descriptor(&self) -> GpuDescriptor { + unsafe { self.GetGPUDescriptorHandleForHeapStart() } + } +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug)] +pub enum ShaderVisibility { + All = d3d12::D3D12_SHADER_VISIBILITY_ALL, + VS = d3d12::D3D12_SHADER_VISIBILITY_VERTEX, + HS = d3d12::D3D12_SHADER_VISIBILITY_HULL, + DS = d3d12::D3D12_SHADER_VISIBILITY_DOMAIN, + GS = d3d12::D3D12_SHADER_VISIBILITY_GEOMETRY, + PS = d3d12::D3D12_SHADER_VISIBILITY_PIXEL, +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug)] +pub enum DescriptorRangeType { + SRV = d3d12::D3D12_DESCRIPTOR_RANGE_TYPE_SRV, + UAV = d3d12::D3D12_DESCRIPTOR_RANGE_TYPE_UAV, + CBV = d3d12::D3D12_DESCRIPTOR_RANGE_TYPE_CBV, + Sampler = d3d12::D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, +} + +#[repr(transparent)] +pub struct DescriptorRange(d3d12::D3D12_DESCRIPTOR_RANGE); +impl DescriptorRange { + pub fn new(ty: DescriptorRangeType, count: u32, base_binding: Binding, offset: u32) -> Self { + DescriptorRange(d3d12::D3D12_DESCRIPTOR_RANGE { + RangeType: ty as _, + NumDescriptors: count, + BaseShaderRegister: base_binding.register, + RegisterSpace: base_binding.space, + OffsetInDescriptorsFromTableStart: offset, + }) + } +} + +impl fmt::Debug for DescriptorRange { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter + .debug_struct("DescriptorRange") + .field("range_type", &self.0.RangeType) + .field("num", &self.0.NumDescriptors) + .field("register_space", &self.0.RegisterSpace) + .field("base_register", &self.0.BaseShaderRegister) + .field("table_offset", &self.0.OffsetInDescriptorsFromTableStart) + .finish() + } +} + +#[repr(transparent)] +pub struct RootParameter(d3d12::D3D12_ROOT_PARAMETER); +impl RootParameter { + // TODO: DescriptorRange must outlive Self + pub fn descriptor_table(visibility: ShaderVisibility, ranges: &[DescriptorRange]) -> Self { + let mut param = d3d12::D3D12_ROOT_PARAMETER { + ParameterType: d3d12::D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE, + ShaderVisibility: visibility as _, + ..unsafe { mem::zeroed() } + }; + + *unsafe { param.u.DescriptorTable_mut() } = d3d12::D3D12_ROOT_DESCRIPTOR_TABLE { + NumDescriptorRanges: ranges.len() as _, + pDescriptorRanges: ranges.as_ptr() as *const _, + }; + + RootParameter(param) + } + + pub fn constants(visibility: ShaderVisibility, binding: Binding, num: u32) -> Self { + let mut param = d3d12::D3D12_ROOT_PARAMETER { + ParameterType: d3d12::D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, + ShaderVisibility: visibility as _, + ..unsafe { mem::zeroed() } + }; + + *unsafe { param.u.Constants_mut() } = d3d12::D3D12_ROOT_CONSTANTS { + ShaderRegister: binding.register, + RegisterSpace: binding.space, + Num32BitValues: num, + }; + + RootParameter(param) + } + + //TODO: should this be unsafe? + pub fn descriptor( + ty: d3d12::D3D12_ROOT_PARAMETER_TYPE, + visibility: ShaderVisibility, + binding: Binding, + ) -> Self { + let mut param = d3d12::D3D12_ROOT_PARAMETER { + ParameterType: ty, + ShaderVisibility: visibility as _, + ..unsafe { mem::zeroed() } + }; + + *unsafe { param.u.Descriptor_mut() } = d3d12::D3D12_ROOT_DESCRIPTOR { + ShaderRegister: binding.register, + RegisterSpace: binding.space, + }; + + RootParameter(param) + } + + pub fn cbv_descriptor(visibility: ShaderVisibility, binding: Binding) -> Self { + Self::descriptor(d3d12::D3D12_ROOT_PARAMETER_TYPE_CBV, visibility, binding) + } + + pub fn srv_descriptor(visibility: ShaderVisibility, binding: Binding) -> Self { + Self::descriptor(d3d12::D3D12_ROOT_PARAMETER_TYPE_SRV, visibility, binding) + } + + pub fn uav_descriptor(visibility: ShaderVisibility, binding: Binding) -> Self { + Self::descriptor(d3d12::D3D12_ROOT_PARAMETER_TYPE_UAV, visibility, binding) + } +} + +impl fmt::Debug for RootParameter { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + #[derive(Debug)] + #[allow(dead_code)] // False-positive + enum Inner<'a> { + Table(&'a [DescriptorRange]), + Constants { binding: Binding, num: u32 }, + SingleCbv(Binding), + SingleSrv(Binding), + SingleUav(Binding), + } + let kind = match self.0.ParameterType { + d3d12::D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE => unsafe { + let raw = self.0.u.DescriptorTable(); + Inner::Table(std::slice::from_raw_parts( + raw.pDescriptorRanges as *const _, + raw.NumDescriptorRanges as usize, + )) + }, + d3d12::D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS => unsafe { + let raw = self.0.u.Constants(); + Inner::Constants { + binding: Binding { + space: raw.RegisterSpace, + register: raw.ShaderRegister, + }, + num: raw.Num32BitValues, + } + }, + _ => unsafe { + let raw = self.0.u.Descriptor(); + let binding = Binding { + space: raw.RegisterSpace, + register: raw.ShaderRegister, + }; + match self.0.ParameterType { + d3d12::D3D12_ROOT_PARAMETER_TYPE_CBV => Inner::SingleCbv(binding), + d3d12::D3D12_ROOT_PARAMETER_TYPE_SRV => Inner::SingleSrv(binding), + d3d12::D3D12_ROOT_PARAMETER_TYPE_UAV => Inner::SingleUav(binding), + other => panic!("Unexpected type {:?}", other), + } + }, + }; + + formatter + .debug_struct("RootParameter") + .field("visibility", &self.0.ShaderVisibility) + .field("kind", &kind) + .finish() + } +} + +#[repr(u32)] +#[derive(Copy, Clone, Debug)] +pub enum StaticBorderColor { + TransparentBlack = d3d12::D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK, + OpaqueBlack = d3d12::D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK, + OpaqueWhite = d3d12::D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE, +} + +#[repr(transparent)] +pub struct StaticSampler(d3d12::D3D12_STATIC_SAMPLER_DESC); +impl StaticSampler { + pub fn new( + visibility: ShaderVisibility, + binding: Binding, + filter: d3d12::D3D12_FILTER, + address_mode: TextureAddressMode, + mip_lod_bias: f32, + max_anisotropy: u32, + comparison_op: d3d12::D3D12_COMPARISON_FUNC, + border_color: StaticBorderColor, + lod: Range, + ) -> Self { + StaticSampler(d3d12::D3D12_STATIC_SAMPLER_DESC { + Filter: filter, + AddressU: address_mode[0], + AddressV: address_mode[1], + AddressW: address_mode[2], + MipLODBias: mip_lod_bias, + MaxAnisotropy: max_anisotropy, + ComparisonFunc: comparison_op, + BorderColor: border_color as _, + MinLOD: lod.start, + MaxLOD: lod.end, + ShaderRegister: binding.register, + RegisterSpace: binding.space, + ShaderVisibility: visibility as _, + }) + } +} + +#[repr(u32)] +#[derive(Copy, Clone, Debug)] +pub enum RootSignatureVersion { + V1_0 = d3d12::D3D_ROOT_SIGNATURE_VERSION_1_0, + V1_1 = d3d12::D3D_ROOT_SIGNATURE_VERSION_1_1, +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct RootSignatureFlags: u32 { + const ALLOW_IA_INPUT_LAYOUT = d3d12::D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + const DENY_VS_ROOT_ACCESS = d3d12::D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS; + const DENY_HS_ROOT_ACCESS = d3d12::D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS; + const DENY_DS_ROOT_ACCESS = d3d12::D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS; + const DENY_GS_ROOT_ACCESS = d3d12::D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS; + const DENY_PS_ROOT_ACCESS = d3d12::D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS; + } +} + +pub type RootSignature = ComPtr; +pub type BlobResult = D3DResult<(Blob, Error)>; + +#[cfg(feature = "libloading")] +impl crate::D3D12Lib { + pub fn serialize_root_signature( + &self, + version: RootSignatureVersion, + parameters: &[RootParameter], + static_samplers: &[StaticSampler], + flags: RootSignatureFlags, + ) -> Result { + use winapi::um::d3dcommon::ID3DBlob; + type Fun = extern "system" fn( + *const d3d12::D3D12_ROOT_SIGNATURE_DESC, + d3d12::D3D_ROOT_SIGNATURE_VERSION, + *mut *mut ID3DBlob, + *mut *mut ID3DBlob, + ) -> crate::HRESULT; + + let desc = d3d12::D3D12_ROOT_SIGNATURE_DESC { + NumParameters: parameters.len() as _, + pParameters: parameters.as_ptr() as *const _, + NumStaticSamplers: static_samplers.len() as _, + pStaticSamplers: static_samplers.as_ptr() as _, + Flags: flags.bits(), + }; + + let mut blob = Blob::null(); + let mut error = Error::null(); + let hr = unsafe { + let func: libloading::Symbol = self.lib.get(b"D3D12SerializeRootSignature")?; + func( + &desc, + version as _, + blob.mut_void() as *mut *mut _, + error.mut_void() as *mut *mut _, + ) + }; + + Ok(((blob, error), hr)) + } +} + +impl RootSignature { + #[cfg(feature = "implicit-link")] + pub fn serialize( + version: RootSignatureVersion, + parameters: &[RootParameter], + static_samplers: &[StaticSampler], + flags: RootSignatureFlags, + ) -> BlobResult { + let mut blob = Blob::null(); + let mut error = Error::null(); + + let desc = d3d12::D3D12_ROOT_SIGNATURE_DESC { + NumParameters: parameters.len() as _, + pParameters: parameters.as_ptr() as *const _, + NumStaticSamplers: static_samplers.len() as _, + pStaticSamplers: static_samplers.as_ptr() as _, + Flags: flags.bits(), + }; + + let hr = unsafe { + d3d12::D3D12SerializeRootSignature( + &desc, + version as _, + blob.mut_void() as *mut *mut _, + error.mut_void() as *mut *mut _, + ) + }; + + ((blob, error), hr) + } +} + +#[repr(transparent)] +pub struct RenderTargetViewDesc(pub(crate) d3d12::D3D12_RENDER_TARGET_VIEW_DESC); + +impl RenderTargetViewDesc { + pub fn texture_2d(format: dxgiformat::DXGI_FORMAT, mip_slice: u32, plane_slice: u32) -> Self { + let mut desc = d3d12::D3D12_RENDER_TARGET_VIEW_DESC { + Format: format, + ViewDimension: d3d12::D3D12_RTV_DIMENSION_TEXTURE2D, + ..unsafe { mem::zeroed() } + }; + + *unsafe { desc.u.Texture2D_mut() } = d3d12::D3D12_TEX2D_RTV { + MipSlice: mip_slice, + PlaneSlice: plane_slice, + }; + + RenderTargetViewDesc(desc) + } +} diff --git a/d3d12/src/device.rs b/d3d12/src/device.rs new file mode 100644 index 0000000000..475fa22b50 --- /dev/null +++ b/d3d12/src/device.rs @@ -0,0 +1,344 @@ +//! Device + +use crate::{ + com::ComPtr, + command_list::{CmdListType, CommandSignature, IndirectArgument}, + descriptor::{CpuDescriptor, DescriptorHeapFlags, DescriptorHeapType, RenderTargetViewDesc}, + heap::{Heap, HeapFlags, HeapProperties}, + pso, query, queue, Blob, CachedPSO, CommandAllocator, CommandQueue, D3DResult, DescriptorHeap, + Fence, GraphicsCommandList, NodeMask, PipelineState, QueryHeap, Resource, RootSignature, + Shader, TextureAddressMode, +}; +use std::ops::Range; +use winapi::{um::d3d12, Interface}; + +pub type Device = ComPtr; + +#[cfg(feature = "libloading")] +impl crate::D3D12Lib { + pub fn create_device( + &self, + adapter: &ComPtr, + feature_level: crate::FeatureLevel, + ) -> Result, libloading::Error> { + type Fun = extern "system" fn( + *mut winapi::um::unknwnbase::IUnknown, + winapi::um::d3dcommon::D3D_FEATURE_LEVEL, + winapi::shared::guiddef::REFGUID, + *mut *mut winapi::ctypes::c_void, + ) -> crate::HRESULT; + + let mut device = Device::null(); + let hr = unsafe { + let func: libloading::Symbol = self.lib.get(b"D3D12CreateDevice")?; + func( + adapter.as_unknown() as *const _ as *mut _, + feature_level as _, + &d3d12::ID3D12Device::uuidof(), + device.mut_void(), + ) + }; + + Ok((device, hr)) + } +} + +impl Device { + #[cfg(feature = "implicit-link")] + pub fn create( + adapter: ComPtr, + feature_level: crate::FeatureLevel, + ) -> D3DResult { + let mut device = Device::null(); + let hr = unsafe { + d3d12::D3D12CreateDevice( + adapter.as_unknown() as *const _ as *mut _, + feature_level as _, + &d3d12::ID3D12Device::uuidof(), + device.mut_void(), + ) + }; + + (device, hr) + } + + pub fn create_heap( + &self, + size_in_bytes: u64, + properties: HeapProperties, + alignment: u64, + flags: HeapFlags, + ) -> D3DResult { + let mut heap = Heap::null(); + + let desc = d3d12::D3D12_HEAP_DESC { + SizeInBytes: size_in_bytes, + Properties: properties.0, + Alignment: alignment, + Flags: flags.bits(), + }; + + let hr = unsafe { self.CreateHeap(&desc, &d3d12::ID3D12Heap::uuidof(), heap.mut_void()) }; + + (heap, hr) + } + + pub fn create_command_allocator(&self, list_type: CmdListType) -> D3DResult { + let mut allocator = CommandAllocator::null(); + let hr = unsafe { + self.CreateCommandAllocator( + list_type as _, + &d3d12::ID3D12CommandAllocator::uuidof(), + allocator.mut_void(), + ) + }; + + (allocator, hr) + } + + pub fn create_command_queue( + &self, + list_type: CmdListType, + priority: queue::Priority, + flags: queue::CommandQueueFlags, + node_mask: NodeMask, + ) -> D3DResult { + let desc = d3d12::D3D12_COMMAND_QUEUE_DESC { + Type: list_type as _, + Priority: priority as _, + Flags: flags.bits(), + NodeMask: node_mask, + }; + + let mut queue = CommandQueue::null(); + let hr = unsafe { + self.CreateCommandQueue( + &desc, + &d3d12::ID3D12CommandQueue::uuidof(), + queue.mut_void(), + ) + }; + + (queue, hr) + } + + pub fn create_descriptor_heap( + &self, + num_descriptors: u32, + heap_type: DescriptorHeapType, + flags: DescriptorHeapFlags, + node_mask: NodeMask, + ) -> D3DResult { + let desc = d3d12::D3D12_DESCRIPTOR_HEAP_DESC { + Type: heap_type as _, + NumDescriptors: num_descriptors, + Flags: flags.bits(), + NodeMask: node_mask, + }; + + let mut heap = DescriptorHeap::null(); + let hr = unsafe { + self.CreateDescriptorHeap( + &desc, + &d3d12::ID3D12DescriptorHeap::uuidof(), + heap.mut_void(), + ) + }; + + (heap, hr) + } + + pub fn get_descriptor_increment_size(&self, heap_type: DescriptorHeapType) -> u32 { + unsafe { self.GetDescriptorHandleIncrementSize(heap_type as _) } + } + + pub fn create_graphics_command_list( + &self, + list_type: CmdListType, + allocator: &CommandAllocator, + initial: PipelineState, + node_mask: NodeMask, + ) -> D3DResult { + let mut command_list = GraphicsCommandList::null(); + let hr = unsafe { + self.CreateCommandList( + node_mask, + list_type as _, + allocator.as_mut_ptr(), + initial.as_mut_ptr(), + &d3d12::ID3D12GraphicsCommandList::uuidof(), + command_list.mut_void(), + ) + }; + + (command_list, hr) + } + + pub fn create_query_heap( + &self, + heap_ty: query::QueryHeapType, + count: u32, + node_mask: NodeMask, + ) -> D3DResult { + let desc = d3d12::D3D12_QUERY_HEAP_DESC { + Type: heap_ty as _, + Count: count, + NodeMask: node_mask, + }; + + let mut query_heap = QueryHeap::null(); + let hr = unsafe { + self.CreateQueryHeap( + &desc, + &d3d12::ID3D12QueryHeap::uuidof(), + query_heap.mut_void(), + ) + }; + + (query_heap, hr) + } + + pub fn create_graphics_pipeline_state( + &self, + _root_signature: RootSignature, + _vs: Shader, + _ps: Shader, + _gs: Shader, + _hs: Shader, + _ds: Shader, + _node_mask: NodeMask, + _cached_pso: CachedPSO, + _flags: pso::PipelineStateFlags, + ) -> D3DResult { + unimplemented!() + } + + pub fn create_compute_pipeline_state( + &self, + root_signature: &RootSignature, + cs: Shader, + node_mask: NodeMask, + cached_pso: CachedPSO, + flags: pso::PipelineStateFlags, + ) -> D3DResult { + let mut pipeline = PipelineState::null(); + let desc = d3d12::D3D12_COMPUTE_PIPELINE_STATE_DESC { + pRootSignature: root_signature.as_mut_ptr(), + CS: *cs, + NodeMask: node_mask, + CachedPSO: *cached_pso, + Flags: flags.bits(), + }; + + let hr = unsafe { + self.CreateComputePipelineState( + &desc, + &d3d12::ID3D12PipelineState::uuidof(), + pipeline.mut_void(), + ) + }; + + (pipeline, hr) + } + + pub fn create_sampler( + &self, + sampler: CpuDescriptor, + filter: d3d12::D3D12_FILTER, + address_mode: TextureAddressMode, + mip_lod_bias: f32, + max_anisotropy: u32, + comparison_op: d3d12::D3D12_COMPARISON_FUNC, + border_color: [f32; 4], + lod: Range, + ) { + let desc = d3d12::D3D12_SAMPLER_DESC { + Filter: filter, + AddressU: address_mode[0], + AddressV: address_mode[1], + AddressW: address_mode[2], + MipLODBias: mip_lod_bias, + MaxAnisotropy: max_anisotropy, + ComparisonFunc: comparison_op, + BorderColor: border_color, + MinLOD: lod.start, + MaxLOD: lod.end, + }; + + unsafe { + self.CreateSampler(&desc, sampler); + } + } + + pub fn create_root_signature( + &self, + blob: Blob, + node_mask: NodeMask, + ) -> D3DResult { + let mut signature = RootSignature::null(); + let hr = unsafe { + self.CreateRootSignature( + node_mask, + blob.GetBufferPointer(), + blob.GetBufferSize(), + &d3d12::ID3D12RootSignature::uuidof(), + signature.mut_void(), + ) + }; + + (signature, hr) + } + + pub fn create_command_signature( + &self, + root_signature: RootSignature, + arguments: &[IndirectArgument], + stride: u32, + node_mask: NodeMask, + ) -> D3DResult { + let mut signature = CommandSignature::null(); + let desc = d3d12::D3D12_COMMAND_SIGNATURE_DESC { + ByteStride: stride, + NumArgumentDescs: arguments.len() as _, + pArgumentDescs: arguments.as_ptr() as *const _, + NodeMask: node_mask, + }; + + let hr = unsafe { + self.CreateCommandSignature( + &desc, + root_signature.as_mut_ptr(), + &d3d12::ID3D12CommandSignature::uuidof(), + signature.mut_void(), + ) + }; + + (signature, hr) + } + + pub fn create_render_target_view( + &self, + resource: Resource, + desc: &RenderTargetViewDesc, + descriptor: CpuDescriptor, + ) { + unsafe { + self.CreateRenderTargetView(resource.as_mut_ptr(), &desc.0 as *const _, descriptor); + } + } + + // TODO: interface not complete + pub fn create_fence(&self, initial: u64) -> D3DResult { + let mut fence = Fence::null(); + let hr = unsafe { + self.CreateFence( + initial, + d3d12::D3D12_FENCE_FLAG_NONE, + &d3d12::ID3D12Fence::uuidof(), + fence.mut_void(), + ) + }; + + (fence, hr) + } +} diff --git a/d3d12/src/dxgi.rs b/d3d12/src/dxgi.rs new file mode 100644 index 0000000000..0cbb5bb63b --- /dev/null +++ b/d3d12/src/dxgi.rs @@ -0,0 +1,377 @@ +use crate::{com::ComPtr, D3DResult, Resource, SampleDesc, HRESULT}; +use std::ptr; +use winapi::{ + shared::{ + dxgi, dxgi1_2, dxgi1_3, dxgi1_4, dxgi1_5, dxgi1_6, dxgiformat, dxgitype, minwindef::TRUE, + windef::HWND, + }, + um::{d3d12, dxgidebug, unknwnbase::IUnknown, winnt::HANDLE}, + Interface, +}; + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct FactoryCreationFlags: u32 { + const DEBUG = dxgi1_3::DXGI_CREATE_FACTORY_DEBUG; + } +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum Scaling { + Stretch = dxgi1_2::DXGI_SCALING_STRETCH, + Identity = dxgi1_2::DXGI_SCALING_NONE, + Aspect = dxgi1_2::DXGI_SCALING_ASPECT_RATIO_STRETCH, +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum SwapEffect { + Discard = dxgi::DXGI_SWAP_EFFECT_DISCARD, + Sequential = dxgi::DXGI_SWAP_EFFECT_SEQUENTIAL, + FlipDiscard = dxgi::DXGI_SWAP_EFFECT_FLIP_DISCARD, + FlipSequential = dxgi::DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum AlphaMode { + Unspecified = dxgi1_2::DXGI_ALPHA_MODE_UNSPECIFIED, + Premultiplied = dxgi1_2::DXGI_ALPHA_MODE_PREMULTIPLIED, + Straight = dxgi1_2::DXGI_ALPHA_MODE_STRAIGHT, + Ignore = dxgi1_2::DXGI_ALPHA_MODE_IGNORE, + ForceDword = dxgi1_2::DXGI_ALPHA_MODE_FORCE_DWORD, +} + +pub type InfoQueue = ComPtr; + +pub type Adapter1 = ComPtr; +pub type Adapter2 = ComPtr; +pub type Adapter3 = ComPtr; +pub type Adapter4 = ComPtr; +crate::weak_com_inheritance_chain! { + #[derive(Debug, Clone, PartialEq, Hash)] + pub enum DxgiAdapter { + Adapter1(dxgi::IDXGIAdapter1), from_adapter1, as_adapter1, adapter1; + Adapter2(dxgi1_2::IDXGIAdapter2), from_adapter2, as_adapter2, unwrap_adapter2; + Adapter3(dxgi1_4::IDXGIAdapter3), from_adapter3, as_adapter3, unwrap_adapter3; + Adapter4(dxgi1_6::IDXGIAdapter4), from_adapter4, as_adapter4, unwrap_adapter4; + } +} + +pub type Factory1 = ComPtr; +pub type Factory2 = ComPtr; +pub type Factory3 = ComPtr; +pub type Factory4 = ComPtr; +pub type Factory5 = ComPtr; +pub type Factory6 = ComPtr; +crate::weak_com_inheritance_chain! { + #[derive(Debug, Clone, PartialEq, Hash)] + pub enum DxgiFactory { + Factory1(dxgi::IDXGIFactory1), from_factory1, as_factory1, factory1; + Factory2(dxgi1_2::IDXGIFactory2), from_factory2, as_factory2, unwrap_factory2; + Factory3(dxgi1_3::IDXGIFactory3), from_factory3, as_factory3, unwrap_factory3; + Factory4(dxgi1_4::IDXGIFactory4), from_factory4, as_factory4, unwrap_factory4; + Factory5(dxgi1_5::IDXGIFactory5), from_factory5, as_factory5, unwrap_factory5; + Factory6(dxgi1_6::IDXGIFactory6), from_factory6, as_factory6, unwrap_factory6; + } +} + +pub type FactoryMedia = ComPtr; + +pub type SwapChain = ComPtr; +pub type SwapChain1 = ComPtr; +pub type SwapChain2 = ComPtr; +pub type SwapChain3 = ComPtr; +crate::weak_com_inheritance_chain! { + #[derive(Debug, Clone, PartialEq, Hash)] + pub enum DxgiSwapchain { + SwapChain(dxgi::IDXGISwapChain), from_swap_chain, as_swap_chain, swap_chain; + SwapChain1(dxgi1_2::IDXGISwapChain1), from_swap_chain1, as_swap_chain1, unwrap_swap_chain1; + SwapChain2(dxgi1_3::IDXGISwapChain2), from_swap_chain2, as_swap_chain2, unwrap_swap_chain2; + SwapChain3(dxgi1_4::IDXGISwapChain3), from_swap_chain3, as_swap_chain3, unwrap_swap_chain3; + } +} + +#[cfg(feature = "libloading")] +#[derive(Debug)] +pub struct DxgiLib { + lib: libloading::Library, +} + +#[cfg(feature = "libloading")] +impl DxgiLib { + pub fn new() -> Result { + unsafe { libloading::Library::new("dxgi.dll").map(|lib| DxgiLib { lib }) } + } + + pub fn create_factory2( + &self, + flags: FactoryCreationFlags, + ) -> Result, libloading::Error> { + type Fun = extern "system" fn( + winapi::shared::minwindef::UINT, + winapi::shared::guiddef::REFIID, + *mut *mut winapi::ctypes::c_void, + ) -> HRESULT; + + let mut factory = Factory4::null(); + let hr = unsafe { + let func: libloading::Symbol = self.lib.get(b"CreateDXGIFactory2")?; + func( + flags.bits(), + &dxgi1_4::IDXGIFactory4::uuidof(), + factory.mut_void(), + ) + }; + + Ok((factory, hr)) + } + + pub fn create_factory1(&self) -> Result, libloading::Error> { + type Fun = extern "system" fn( + winapi::shared::guiddef::REFIID, + *mut *mut winapi::ctypes::c_void, + ) -> HRESULT; + + let mut factory = Factory1::null(); + let hr = unsafe { + let func: libloading::Symbol = self.lib.get(b"CreateDXGIFactory1")?; + func(&dxgi::IDXGIFactory1::uuidof(), factory.mut_void()) + }; + + Ok((factory, hr)) + } + + pub fn create_factory_media(&self) -> Result, libloading::Error> { + type Fun = extern "system" fn( + winapi::shared::guiddef::REFIID, + *mut *mut winapi::ctypes::c_void, + ) -> HRESULT; + + let mut factory = FactoryMedia::null(); + let hr = unsafe { + // https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_3/nn-dxgi1_3-idxgifactorymedia + let func: libloading::Symbol = self.lib.get(b"CreateDXGIFactory1")?; + func(&dxgi1_3::IDXGIFactoryMedia::uuidof(), factory.mut_void()) + }; + + Ok((factory, hr)) + } + + pub fn get_debug_interface1(&self) -> Result, libloading::Error> { + type Fun = extern "system" fn( + winapi::shared::minwindef::UINT, + winapi::shared::guiddef::REFIID, + *mut *mut winapi::ctypes::c_void, + ) -> HRESULT; + + let mut queue = InfoQueue::null(); + let hr = unsafe { + let func: libloading::Symbol = self.lib.get(b"DXGIGetDebugInterface1")?; + func(0, &dxgidebug::IDXGIInfoQueue::uuidof(), queue.mut_void()) + }; + Ok((queue, hr)) + } +} + +// TODO: strong types +pub struct SwapchainDesc { + pub width: u32, + pub height: u32, + pub format: dxgiformat::DXGI_FORMAT, + pub stereo: bool, + pub sample: SampleDesc, + pub buffer_usage: dxgitype::DXGI_USAGE, + pub buffer_count: u32, + pub scaling: Scaling, + pub swap_effect: SwapEffect, + pub alpha_mode: AlphaMode, + pub flags: u32, +} +impl SwapchainDesc { + pub fn to_desc1(&self) -> dxgi1_2::DXGI_SWAP_CHAIN_DESC1 { + dxgi1_2::DXGI_SWAP_CHAIN_DESC1 { + AlphaMode: self.alpha_mode as _, + BufferCount: self.buffer_count, + Width: self.width, + Height: self.height, + Format: self.format, + Flags: self.flags, + BufferUsage: self.buffer_usage, + SampleDesc: dxgitype::DXGI_SAMPLE_DESC { + Count: self.sample.count, + Quality: self.sample.quality, + }, + Scaling: self.scaling as _, + Stereo: self.stereo as _, + SwapEffect: self.swap_effect as _, + } + } +} + +impl Factory1 { + pub fn create_swapchain( + &self, + queue: *mut IUnknown, + hwnd: HWND, + desc: &SwapchainDesc, + ) -> D3DResult { + let mut desc = dxgi::DXGI_SWAP_CHAIN_DESC { + BufferDesc: dxgitype::DXGI_MODE_DESC { + Width: desc.width, + Height: desc.width, + RefreshRate: dxgitype::DXGI_RATIONAL { + Numerator: 1, + Denominator: 60, + }, + Format: desc.format, + ScanlineOrdering: dxgitype::DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, + Scaling: dxgitype::DXGI_MODE_SCALING_UNSPECIFIED, + }, + SampleDesc: dxgitype::DXGI_SAMPLE_DESC { + Count: desc.sample.count, + Quality: desc.sample.quality, + }, + BufferUsage: desc.buffer_usage, + BufferCount: desc.buffer_count, + OutputWindow: hwnd, + Windowed: TRUE, + SwapEffect: desc.swap_effect as _, + Flags: desc.flags, + }; + + let mut swapchain = SwapChain::null(); + let hr = + unsafe { self.CreateSwapChain(queue, &mut desc, swapchain.mut_void() as *mut *mut _) }; + + (swapchain, hr) + } +} + +impl Factory2 { + // TODO: interface not complete + pub fn create_swapchain_for_hwnd( + &self, + queue: *mut IUnknown, + hwnd: HWND, + desc: &SwapchainDesc, + ) -> D3DResult { + let mut swap_chain = SwapChain1::null(); + let hr = unsafe { + self.CreateSwapChainForHwnd( + queue, + hwnd, + &desc.to_desc1(), + ptr::null(), + ptr::null_mut(), + swap_chain.mut_void() as *mut *mut _, + ) + }; + + (swap_chain, hr) + } + + pub fn create_swapchain_for_composition( + &self, + queue: *mut IUnknown, + desc: &SwapchainDesc, + ) -> D3DResult { + let mut swap_chain = SwapChain1::null(); + let hr = unsafe { + self.CreateSwapChainForComposition( + queue, + &desc.to_desc1(), + ptr::null_mut(), + swap_chain.mut_void() as *mut *mut _, + ) + }; + + (swap_chain, hr) + } +} + +impl Factory4 { + #[cfg(feature = "implicit-link")] + pub fn create(flags: FactoryCreationFlags) -> D3DResult { + let mut factory = Factory4::null(); + let hr = unsafe { + dxgi1_3::CreateDXGIFactory2( + flags.bits(), + &dxgi1_4::IDXGIFactory4::uuidof(), + factory.mut_void(), + ) + }; + + (factory, hr) + } + + pub fn enumerate_adapters(&self, id: u32) -> D3DResult { + let mut adapter = Adapter1::null(); + let hr = unsafe { self.EnumAdapters1(id, adapter.mut_void() as *mut *mut _) }; + + (adapter, hr) + } +} + +impl FactoryMedia { + pub fn create_swapchain_for_composition_surface_handle( + &self, + queue: *mut IUnknown, + surface_handle: HANDLE, + desc: &SwapchainDesc, + ) -> D3DResult { + let mut swap_chain = SwapChain1::null(); + let hr = unsafe { + self.CreateSwapChainForCompositionSurfaceHandle( + queue, + surface_handle, + &desc.to_desc1(), + ptr::null_mut(), + swap_chain.mut_void() as *mut *mut _, + ) + }; + + (swap_chain, hr) + } +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct SwapChainPresentFlags: u32 { + const DXGI_PRESENT_DO_NOT_SEQUENCE = dxgi::DXGI_PRESENT_DO_NOT_SEQUENCE; + const DXGI_PRESENT_TEST = dxgi::DXGI_PRESENT_TEST; + const DXGI_PRESENT_RESTART = dxgi::DXGI_PRESENT_RESTART; + const DXGI_PRESENT_DO_NOT_WAIT = dxgi::DXGI_PRESENT_DO_NOT_WAIT; + const DXGI_PRESENT_RESTRICT_TO_OUTPUT = dxgi::DXGI_PRESENT_RESTRICT_TO_OUTPUT; + const DXGI_PRESENT_STEREO_PREFER_RIGHT = dxgi::DXGI_PRESENT_STEREO_PREFER_RIGHT; + const DXGI_PRESENT_STEREO_TEMPORARY_MONO = dxgi::DXGI_PRESENT_STEREO_TEMPORARY_MONO; + const DXGI_PRESENT_USE_DURATION = dxgi::DXGI_PRESENT_USE_DURATION; + const DXGI_PRESENT_ALLOW_TEARING = dxgi::DXGI_PRESENT_ALLOW_TEARING; + } +} + +impl SwapChain { + pub fn get_buffer(&self, id: u32) -> D3DResult { + let mut resource = Resource::null(); + let hr = + unsafe { self.GetBuffer(id, &d3d12::ID3D12Resource::uuidof(), resource.mut_void()) }; + + (resource, hr) + } + + //TODO: replace by present_flags + pub fn present(&self, interval: u32, flags: u32) -> HRESULT { + unsafe { self.Present(interval, flags) } + } + + pub fn present_flags(&self, interval: u32, flags: SwapChainPresentFlags) -> HRESULT { + unsafe { self.Present(interval, flags.bits()) } + } +} + +impl SwapChain3 { + pub fn get_current_back_buffer_index(&self) -> u32 { + unsafe { self.GetCurrentBackBufferIndex() } + } +} diff --git a/d3d12/src/heap.rs b/d3d12/src/heap.rs new file mode 100644 index 0000000000..074de56d77 --- /dev/null +++ b/d3d12/src/heap.rs @@ -0,0 +1,87 @@ +use crate::com::ComPtr; +use winapi::um::d3d12; + +pub type Heap = ComPtr; + +#[repr(u32)] +#[derive(Clone, Copy)] +pub enum HeapType { + Default = d3d12::D3D12_HEAP_TYPE_DEFAULT, + Upload = d3d12::D3D12_HEAP_TYPE_UPLOAD, + Readback = d3d12::D3D12_HEAP_TYPE_READBACK, + Custom = d3d12::D3D12_HEAP_TYPE_CUSTOM, +} + +#[repr(u32)] +#[derive(Clone, Copy)] +pub enum CpuPageProperty { + Unknown = d3d12::D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + NotAvailable = d3d12::D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE, + WriteCombine = d3d12::D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE, + WriteBack = d3d12::D3D12_CPU_PAGE_PROPERTY_WRITE_BACK, +} + +#[repr(u32)] +#[derive(Clone, Copy)] +pub enum MemoryPool { + Unknown = d3d12::D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + L0 = d3d12::D3D12_MEMORY_POOL_L0, + L1 = d3d12::D3D12_MEMORY_POOL_L1, +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct HeapFlags: u32 { + const NONE = d3d12::D3D12_HEAP_FLAG_NONE; + const SHARED = d3d12::D3D12_HEAP_FLAG_SHARED; + const DENY_BUFFERS = d3d12::D3D12_HEAP_FLAG_DENY_BUFFERS; + const ALLOW_DISPLAY = d3d12::D3D12_HEAP_FLAG_ALLOW_DISPLAY; + const SHARED_CROSS_ADAPTER = d3d12::D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER; + const DENT_RT_DS_TEXTURES = d3d12::D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES; + const DENY_NON_RT_DS_TEXTURES = d3d12::D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES; + const HARDWARE_PROTECTED = d3d12::D3D12_HEAP_FLAG_HARDWARE_PROTECTED; + const ALLOW_WRITE_WATCH = d3d12::D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH; + const ALLOW_ALL_BUFFERS_AND_TEXTURES = d3d12::D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES; + const ALLOW_ONLY_BUFFERS = d3d12::D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; + const ALLOW_ONLY_NON_RT_DS_TEXTURES = d3d12::D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES; + const ALLOW_ONLY_RT_DS_TEXTURES = d3d12::D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES; + } +} + +#[repr(transparent)] +pub struct HeapProperties(pub d3d12::D3D12_HEAP_PROPERTIES); +impl HeapProperties { + pub fn new( + heap_type: HeapType, + cpu_page_property: CpuPageProperty, + memory_pool_preference: MemoryPool, + creation_node_mask: u32, + visible_node_mask: u32, + ) -> Self { + HeapProperties(d3d12::D3D12_HEAP_PROPERTIES { + Type: heap_type as _, + CPUPageProperty: cpu_page_property as _, + MemoryPoolPreference: memory_pool_preference as _, + CreationNodeMask: creation_node_mask, + VisibleNodeMask: visible_node_mask, + }) + } +} + +#[repr(transparent)] +pub struct HeapDesc(d3d12::D3D12_HEAP_DESC); +impl HeapDesc { + pub fn new( + size_in_bytes: u64, + properties: HeapProperties, + alignment: u64, + flags: HeapFlags, + ) -> Self { + HeapDesc(d3d12::D3D12_HEAP_DESC { + SizeInBytes: size_in_bytes, + Properties: properties.0, + Alignment: alignment, + Flags: flags.bits(), + }) + } +} diff --git a/d3d12/src/lib.rs b/d3d12/src/lib.rs new file mode 100644 index 0000000000..13f0226891 --- /dev/null +++ b/d3d12/src/lib.rs @@ -0,0 +1,125 @@ +#![cfg(windows)] +#![allow( + clippy::missing_safety_doc, + clippy::too_many_arguments, + clippy::not_unsafe_ptr_arg_deref +)] + +use std::{convert::TryFrom, ffi::CStr}; +use winapi::{ + shared::dxgiformat, + um::{d3d12, d3dcommon}, +}; + +mod com; +mod command_allocator; +mod command_list; +mod debug; +mod descriptor; +mod device; +mod dxgi; +mod heap; +mod pso; +mod query; +mod queue; +mod resource; +mod sync; + +pub use crate::com::*; +pub use crate::command_allocator::*; +pub use crate::command_list::*; +pub use crate::debug::*; +pub use crate::descriptor::*; +pub use crate::device::*; +pub use crate::dxgi::*; +pub use crate::heap::*; +pub use crate::pso::*; +pub use crate::query::*; +pub use crate::queue::*; +pub use crate::resource::*; +pub use crate::sync::*; + +pub use winapi::shared::winerror::HRESULT; + +pub type D3DResult = (T, HRESULT); +pub type GpuAddress = d3d12::D3D12_GPU_VIRTUAL_ADDRESS; +pub type Format = dxgiformat::DXGI_FORMAT; +pub type Rect = d3d12::D3D12_RECT; +pub type NodeMask = u32; + +/// Index into the root signature. +pub type RootIndex = u32; +/// Draw vertex count. +pub type VertexCount = u32; +/// Draw vertex base offset. +pub type VertexOffset = i32; +/// Draw number of indices. +pub type IndexCount = u32; +/// Draw number of instances. +pub type InstanceCount = u32; +/// Number of work groups. +pub type WorkGroupCount = [u32; 3]; + +pub type TextureAddressMode = [d3d12::D3D12_TEXTURE_ADDRESS_MODE; 3]; + +pub struct SampleDesc { + pub count: u32, + pub quality: u32, +} + +#[repr(u32)] +#[non_exhaustive] +pub enum FeatureLevel { + L9_1 = d3dcommon::D3D_FEATURE_LEVEL_9_1, + L9_2 = d3dcommon::D3D_FEATURE_LEVEL_9_2, + L9_3 = d3dcommon::D3D_FEATURE_LEVEL_9_3, + L10_0 = d3dcommon::D3D_FEATURE_LEVEL_10_0, + L10_1 = d3dcommon::D3D_FEATURE_LEVEL_10_1, + L11_0 = d3dcommon::D3D_FEATURE_LEVEL_11_0, + L11_1 = d3dcommon::D3D_FEATURE_LEVEL_11_1, + L12_0 = d3dcommon::D3D_FEATURE_LEVEL_12_0, + L12_1 = d3dcommon::D3D_FEATURE_LEVEL_12_1, +} + +impl TryFrom for FeatureLevel { + type Error = (); + + fn try_from(value: u32) -> Result { + Ok(match value { + d3dcommon::D3D_FEATURE_LEVEL_9_1 => Self::L9_1, + d3dcommon::D3D_FEATURE_LEVEL_9_2 => Self::L9_2, + d3dcommon::D3D_FEATURE_LEVEL_9_3 => Self::L9_3, + d3dcommon::D3D_FEATURE_LEVEL_10_0 => Self::L10_0, + d3dcommon::D3D_FEATURE_LEVEL_10_1 => Self::L10_1, + d3dcommon::D3D_FEATURE_LEVEL_11_0 => Self::L11_0, + d3dcommon::D3D_FEATURE_LEVEL_11_1 => Self::L11_1, + d3dcommon::D3D_FEATURE_LEVEL_12_0 => Self::L12_0, + d3dcommon::D3D_FEATURE_LEVEL_12_1 => Self::L12_1, + _ => return Err(()), + }) + } +} + +pub type Blob = ComPtr; + +pub type Error = ComPtr; +impl Error { + pub unsafe fn as_c_str(&self) -> &CStr { + debug_assert!(!self.is_null()); + let data = self.GetBufferPointer(); + CStr::from_ptr(data as *const _ as *const _) + } +} + +#[cfg(feature = "libloading")] +#[derive(Debug)] +pub struct D3D12Lib { + lib: libloading::Library, +} + +#[cfg(feature = "libloading")] +impl D3D12Lib { + pub fn new() -> Result { + unsafe { libloading::Library::new("d3d12.dll").map(|lib| D3D12Lib { lib }) } + } +} diff --git a/d3d12/src/pso.rs b/d3d12/src/pso.rs new file mode 100644 index 0000000000..83a549621f --- /dev/null +++ b/d3d12/src/pso.rs @@ -0,0 +1,182 @@ +//! Pipeline state + +use crate::{com::ComPtr, Blob, D3DResult, Error}; +use std::{ + ffi::{self, c_void}, + marker::PhantomData, + ops::Deref, + ptr, +}; +use winapi::um::{d3d12, d3dcompiler}; + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct PipelineStateFlags: u32 { + const TOOL_DEBUG = d3d12::D3D12_PIPELINE_STATE_FLAG_TOOL_DEBUG; + } +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct ShaderCompileFlags: u32 { + const DEBUG = d3dcompiler::D3DCOMPILE_DEBUG; + const SKIP_VALIDATION = d3dcompiler::D3DCOMPILE_SKIP_VALIDATION; + const SKIP_OPTIMIZATION = d3dcompiler::D3DCOMPILE_SKIP_OPTIMIZATION; + const PACK_MATRIX_ROW_MAJOR = d3dcompiler::D3DCOMPILE_PACK_MATRIX_ROW_MAJOR; + const PACK_MATRIX_COLUMN_MAJOR = d3dcompiler::D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR; + const PARTIAL_PRECISION = d3dcompiler::D3DCOMPILE_PARTIAL_PRECISION; + // TODO: add missing flags + } +} + +#[derive(Copy, Clone)] +pub struct Shader<'a>(d3d12::D3D12_SHADER_BYTECODE, PhantomData<&'a c_void>); +impl<'a> Shader<'a> { + pub fn null() -> Self { + Shader( + d3d12::D3D12_SHADER_BYTECODE { + BytecodeLength: 0, + pShaderBytecode: ptr::null(), + }, + PhantomData, + ) + } + + pub fn from_raw(data: &'a [u8]) -> Self { + Shader( + d3d12::D3D12_SHADER_BYTECODE { + BytecodeLength: data.len() as _, + pShaderBytecode: data.as_ptr() as _, + }, + PhantomData, + ) + } + + // `blob` may not be null. + pub fn from_blob(blob: &'a Blob) -> Self { + Shader( + d3d12::D3D12_SHADER_BYTECODE { + BytecodeLength: unsafe { blob.GetBufferSize() }, + pShaderBytecode: unsafe { blob.GetBufferPointer() }, + }, + PhantomData, + ) + } + + /// Compile a shader from raw HLSL. + /// + /// * `target`: example format: `ps_5_1`. + pub fn compile( + code: &[u8], + target: &ffi::CStr, + entry: &ffi::CStr, + flags: ShaderCompileFlags, + ) -> D3DResult<(Blob, Error)> { + let mut shader = Blob::null(); + let mut error = Error::null(); + + let hr = unsafe { + d3dcompiler::D3DCompile( + code.as_ptr() as *const _, + code.len(), + ptr::null(), // defines + ptr::null(), // include + ptr::null_mut(), + entry.as_ptr() as *const _, + target.as_ptr() as *const _, + flags.bits(), + 0, + shader.mut_void() as *mut *mut _, + error.mut_void() as *mut *mut _, + ) + }; + + ((shader, error), hr) + } +} + +impl<'a> Deref for Shader<'a> { + type Target = d3d12::D3D12_SHADER_BYTECODE; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Copy, Clone)] +pub struct CachedPSO<'a>(d3d12::D3D12_CACHED_PIPELINE_STATE, PhantomData<&'a c_void>); +impl<'a> CachedPSO<'a> { + pub fn null() -> Self { + CachedPSO( + d3d12::D3D12_CACHED_PIPELINE_STATE { + CachedBlobSizeInBytes: 0, + pCachedBlob: ptr::null(), + }, + PhantomData, + ) + } + + // `blob` may not be null. + pub fn from_blob(blob: &'a Blob) -> Self { + CachedPSO( + d3d12::D3D12_CACHED_PIPELINE_STATE { + CachedBlobSizeInBytes: unsafe { blob.GetBufferSize() }, + pCachedBlob: unsafe { blob.GetBufferPointer() }, + }, + PhantomData, + ) + } +} + +impl<'a> Deref for CachedPSO<'a> { + type Target = d3d12::D3D12_CACHED_PIPELINE_STATE; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub type PipelineState = ComPtr; + +#[repr(u32)] +pub enum Subobject { + RootSignature = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE, + VS = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VS, + PS = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS, + DS = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DS, + HS = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_HS, + GS = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_GS, + CS = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CS, + StreamOutput = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_STREAM_OUTPUT, + Blend = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND, + SampleMask = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_MASK, + Rasterizer = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER, + DepthStencil = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL, + InputLayout = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_INPUT_LAYOUT, + IBStripCut = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_IB_STRIP_CUT_VALUE, + PrimitiveTopology = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PRIMITIVE_TOPOLOGY, + RTFormats = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RENDER_TARGET_FORMATS, + DSFormat = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL_FORMAT, + SampleDesc = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC, + NodeMask = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_NODE_MASK, + CachedPSO = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CACHED_PSO, + Flags = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_FLAGS, + DepthStencil1 = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL1, + // ViewInstancing = d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VIEW_INSTANCING, +} + +/// Subobject of a pipeline stream description +#[repr(C)] +pub struct PipelineStateSubobject { + subobject_align: [usize; 0], // Subobjects must have the same alignment as pointers. + subobject_type: d3d12::D3D12_PIPELINE_STATE_SUBOBJECT_TYPE, + subobject: T, +} + +impl PipelineStateSubobject { + pub fn new(subobject_type: Subobject, subobject: T) -> Self { + PipelineStateSubobject { + subobject_align: [], + subobject_type: subobject_type as _, + subobject, + } + } +} diff --git a/d3d12/src/query.rs b/d3d12/src/query.rs new file mode 100644 index 0000000000..a9dca262bc --- /dev/null +++ b/d3d12/src/query.rs @@ -0,0 +1,15 @@ +use crate::com::ComPtr; +use winapi::um::d3d12; + +#[repr(u32)] +#[derive(Debug, Copy, Clone)] +pub enum QueryHeapType { + Occlusion = d3d12::D3D12_QUERY_HEAP_TYPE_OCCLUSION, + Timestamp = d3d12::D3D12_QUERY_HEAP_TYPE_TIMESTAMP, + PipelineStatistics = d3d12::D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS, + SOStatistics = d3d12::D3D12_QUERY_HEAP_TYPE_SO_STATISTICS, + // VideoDecodeStatistcs = d3d12::D3D12_QUERY_HEAP_TYPE_VIDEO_DECODE_STATISTICS, + // CopyQueueTimestamp = d3d12::D3D12_QUERY_HEAP_TYPE_COPY_QUEUE_TIMESTAMP, +} + +pub type QueryHeap = ComPtr; diff --git a/d3d12/src/queue.rs b/d3d12/src/queue.rs new file mode 100644 index 0000000000..a569344f3f --- /dev/null +++ b/d3d12/src/queue.rs @@ -0,0 +1,32 @@ +use crate::{com::ComPtr, sync::Fence, CommandList, HRESULT}; +use winapi::um::d3d12; + +#[repr(u32)] +pub enum Priority { + Normal = d3d12::D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + High = d3d12::D3D12_COMMAND_QUEUE_PRIORITY_HIGH, + GlobalRealtime = d3d12::D3D12_COMMAND_QUEUE_PRIORITY_GLOBAL_REALTIME, +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct CommandQueueFlags: u32 { + const DISABLE_GPU_TIMEOUT = d3d12::D3D12_COMMAND_QUEUE_FLAG_DISABLE_GPU_TIMEOUT; + } +} + +pub type CommandQueue = ComPtr; + +impl CommandQueue { + pub fn execute_command_lists(&self, command_lists: &[CommandList]) { + let command_lists = command_lists + .iter() + .map(CommandList::as_mut_ptr) + .collect::>(); + unsafe { self.ExecuteCommandLists(command_lists.len() as _, command_lists.as_ptr()) } + } + + pub fn signal(&self, fence: &Fence, value: u64) -> HRESULT { + unsafe { self.Signal(fence.as_mut_ptr(), value) } + } +} diff --git a/d3d12/src/resource.rs b/d3d12/src/resource.rs new file mode 100644 index 0000000000..bdc669dd31 --- /dev/null +++ b/d3d12/src/resource.rs @@ -0,0 +1,53 @@ +//! GPU Resource + +use crate::{com::ComPtr, D3DResult, Rect}; +use std::{ops::Range, ptr}; +use winapi::um::d3d12; + +pub type Subresource = u32; + +pub struct DiscardRegion<'a> { + pub rects: &'a [Rect], + pub subregions: Range, +} + +pub type Resource = ComPtr; + +impl Resource { + /// + pub fn map( + &self, + subresource: Subresource, + read_range: Option>, + ) -> D3DResult<*mut ()> { + let mut ptr = ptr::null_mut(); + let read_range = read_range.map(|r| d3d12::D3D12_RANGE { + Begin: r.start, + End: r.end, + }); + let read = match read_range { + Some(ref r) => r as *const _, + None => ptr::null(), + }; + let hr = unsafe { self.Map(subresource, read, &mut ptr) }; + + (ptr as _, hr) + } + + pub fn unmap(&self, subresource: Subresource, write_range: Option>) { + let write_range = write_range.map(|r| d3d12::D3D12_RANGE { + Begin: r.start, + End: r.end, + }); + let write = match write_range { + Some(ref r) => r as *const _, + None => ptr::null(), + }; + + unsafe { self.Unmap(subresource, write) }; + } + + pub fn gpu_virtual_address(&self) -> u64 { + unsafe { self.GetGPUVirtualAddress() } + } +} diff --git a/d3d12/src/sync.rs b/d3d12/src/sync.rs new file mode 100644 index 0000000000..fa5f090409 --- /dev/null +++ b/d3d12/src/sync.rs @@ -0,0 +1,39 @@ +use crate::{com::ComPtr, HRESULT}; +use std::ptr; +use winapi::um::{d3d12, synchapi, winnt}; + +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct Event(pub winnt::HANDLE); +impl Event { + pub fn create(manual_reset: bool, initial_state: bool) -> Self { + Event(unsafe { + synchapi::CreateEventA( + ptr::null_mut(), + manual_reset as _, + initial_state as _, + ptr::null(), + ) + }) + } + + // TODO: return value + pub fn wait(&self, timeout_ms: u32) -> u32 { + unsafe { synchapi::WaitForSingleObject(self.0, timeout_ms) } + } +} + +pub type Fence = ComPtr; +impl Fence { + pub fn set_event_on_completion(&self, event: Event, value: u64) -> HRESULT { + unsafe { self.SetEventOnCompletion(value, event.0) } + } + + pub fn get_value(&self) -> u64 { + unsafe { self.GetCompletedValue() } + } + + pub fn signal(&self, value: u64) -> HRESULT { + unsafe { self.Signal(value) } + } +} diff --git a/deno_webgpu/01_webgpu.js b/deno_webgpu/01_webgpu.js index 35d5db6b90..3f7b1ed570 100644 --- a/deno_webgpu/01_webgpu.js +++ b/deno_webgpu/01_webgpu.js @@ -12,6 +12,7 @@ const primordials = globalThis.__bootstrap.primordials; import * as webidl from "ext:deno_webidl/00_webidl.js"; import { EventTarget } from "ext:deno_web/02_event.js"; import DOMException from "ext:deno_web/01_dom_exception.js"; +import { createFilteredInspectProxy } from "ext:deno_console/01_console.js"; const { ArrayBuffer, ArrayBufferIsView, @@ -77,10 +78,11 @@ const _count = Symbol("[[count]]"); /** * @param {any} self - * @param {{prefix: string, context: string}} opts + * @param {string} prefix + * @param {string} context * @returns {InnerGPUDevice & {rid: number}} */ -function assertDevice(self, { prefix, context }) { +function assertDevice(self, prefix, context) { const device = self[_device]; const deviceRid = device?.rid; if (deviceRid === undefined) { @@ -103,10 +105,7 @@ function assertDeviceMatch( resource, { prefix, resourceContext, selfContext }, ) { - const resourceDevice = assertDevice(resource, { - prefix, - context: resourceContext, - }); + const resourceDevice = assertDevice(resource, prefix, resourceContext); if (resourceDevice.rid !== self.rid) { throw new DOMException( `${prefix}: ${resourceContext} belongs to a diffent device than ${selfContext}.`, @@ -118,10 +117,11 @@ function assertDeviceMatch( /** * @param {any} self - * @param {{prefix: string, context: string}} opts + * @param {string} prefix + * @param {string} context * @returns {number} */ -function assertResource(self, { prefix, context }) { +function assertResource(self, prefix, context) { const rid = self[_rid]; if (rid === undefined) { throw new DOMException( @@ -140,11 +140,15 @@ function normalizeGPUExtent3D(data) { if (ArrayIsArray(data)) { return { width: data[0], - height: data[1], - depthOrArrayLayers: data[2], + height: data[1] ?? 1, + depthOrArrayLayers: data[2] ?? 1, }; } else { - return data; + return { + width: data.width, + height: data.height ?? 1, + depthOrArrayLayers: data.depthOrArrayLayers ?? 1, + }; } } @@ -203,11 +207,8 @@ class GPUValidationError extends GPUError { /** @param {string} message */ constructor(message) { const prefix = "Failed to construct 'GPUValidationError'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.DOMString(message, { - prefix, - context: "Argument 1", - }); + webidl.requiredArguments(arguments.length, 1, prefix); + message = webidl.converters.DOMString(message, prefix, "Argument 1"); super(illegalConstructorKey); this[webidl.brand] = webidl.brand; this[_message] = message; @@ -219,11 +220,8 @@ class GPUOutOfMemoryError extends GPUError { name = "GPUOutOfMemoryError"; constructor(message) { const prefix = "Failed to construct 'GPUOutOfMemoryError'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.DOMString(message, { - prefix, - context: "Argument 1", - }); + webidl.requiredArguments(arguments.length, 1, prefix); + message = webidl.converters.DOMString(message, prefix, "Argument 1"); super(illegalConstructorKey); this[webidl.brand] = webidl.brand; this[_message] = message; @@ -243,10 +241,11 @@ class GPU { */ async requestAdapter(options = {}) { webidl.assertBranded(this, GPUPrototype); - options = webidl.converters.GPURequestAdapterOptions(options, { - prefix: "Failed to execute 'requestAdapter' on 'GPU'", - context: "Argument 1", - }); + options = webidl.converters.GPURequestAdapterOptions( + options, + "Failed to execute 'requestAdapter' on 'GPU'", + "Argument 1", + ); const { err, ...data } = await core.opAsync( "op_webgpu_request_adapter", @@ -261,8 +260,8 @@ class GPU { } } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return `${this.constructor.name} ${inspect({}, inspectOptions)}`; } } const GPUPrototype = GPU.prototype; @@ -306,6 +305,8 @@ class GPUAdapter { } /** @returns {boolean} */ get isFallbackAdapter() { + webidl.assertBranded(this, GPUAdapterPrototype); + webidl.assertBranded(this, GPUAdapterPrototype); return this[_adapter].isFallbackAdapter; } @@ -320,14 +321,17 @@ class GPUAdapter { async requestDevice(descriptor = {}) { webidl.assertBranded(this, GPUAdapterPrototype); const prefix = "Failed to execute 'requestDevice' on 'GPUAdapter'"; - descriptor = webidl.converters.GPUDeviceDescriptor(descriptor, { + descriptor = webidl.converters.GPUDeviceDescriptor( + descriptor, prefix, - context: "Argument 1", - }); + "Argument 1", + ); const requiredFeatures = descriptor.requiredFeatures ?? []; for (let i = 0; i < requiredFeatures.length; ++i) { const feature = requiredFeatures[i]; - if (!SetPrototypeHas(this[_adapter].features[webidl.setlikeInner], feature)) { + if ( + !SetPrototypeHas(this[_adapter].features[webidl.setlikeInner], feature) + ) { throw new TypeError( `${prefix}: requiredFeatures must be a subset of the adapter features.`, ); @@ -362,10 +366,11 @@ class GPUAdapter { async requestAdapterInfo(unmaskHints = []) { webidl.assertBranded(this, GPUAdapterPrototype); const prefix = "Failed to execute 'requestAdapterInfo' on 'GPUAdapter'"; - unmaskHints = webidl.converters["sequence"](unmaskHints, { + unmaskHints = webidl.converters["sequence"]( + unmaskHints, prefix, - context: "Argument 1", - }); + "Argument 1", + ); const { vendor, @@ -389,13 +394,19 @@ class GPUAdapter { return adapterInfo; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - features: this.features, - limits: this.limits, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUAdapterPrototype, this), + keys: [ + "features", + "limits", + "isFallbackAdapter", + ], + }), + inspectOptions, + ); } } const GPUAdapterPrototype = GPUAdapter.prototype; @@ -433,15 +444,20 @@ class GPUAdapterInfo { return this[_description]; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - vendor: this.vendor, - architecture: this.architecture, - device: this.device, - description: this.description, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUAdapterInfoPrototype, this), + keys: [ + "vendor", + "architecture", + "device", + "description", + ], + }), + inspectOptions, + ); } } const GPUAdapterInfoPrototype = GPUAdapterInfo.prototype; @@ -603,8 +619,44 @@ class GPUSupportedLimits { return this[_limits].maxComputeWorkgroupsPerDimension; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect(this[_limits])}`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUSupportedLimitsPrototype, this), + keys: [ + "maxTextureDimension1D", + "maxTextureDimension2D", + "maxTextureDimension3D", + "maxTextureArrayLayers", + "maxBindGroups", + "maxBindingsPerBindGroup", + "maxBufferSize", + "maxDynamicUniformBuffersPerPipelineLayout", + "maxDynamicStorageBuffersPerPipelineLayout", + "maxSampledTexturesPerShaderStage", + "maxSamplersPerShaderStage", + "maxStorageBuffersPerShaderStage", + "maxStorageTexturesPerShaderStage", + "maxUniformBuffersPerShaderStage", + "maxUniformBufferBindingSize", + "maxStorageBufferBindingSize", + "minUniformBufferOffsetAlignment", + "minStorageBufferOffsetAlignment", + "maxVertexBuffers", + "maxVertexAttributes", + "maxVertexBufferArrayStride", + "maxInterStageShaderComponents", + "maxComputeWorkgroupStorageSize", + "maxComputeInvocationsPerWorkgroup", + "maxComputeWorkgroupSizeX", + "maxComputeWorkgroupSizeY", + "maxComputeWorkgroupSizeZ", + "maxComputeWorkgroupsPerDimension", + ], + }), + inspectOptions, + ); } } const GPUSupportedLimitsPrototype = GPUSupportedLimits.prototype; @@ -625,29 +677,33 @@ class GPUSupportedFeatures { constructor() { webidl.illegalConstructor(); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect([...new SafeArrayIterator(this.values())]) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + if (ObjectPrototypeIsPrototypeOf(GPUSupportedFeaturesPrototype, this)) { + return `${this.constructor.name} ${ + // deno-lint-ignore prefer-primordials + inspect([...this], inspectOptions) + }`; + } else { + return `${this.constructor.name} ${inspect({}, inspectOptions)}`; + } } -} -const GPUSupportedFeaturesPrototype = GPUSupportedFeatures.prototype; + const GPUSupportedFeaturesPrototype = GPUSupportedFeatures.prototype; -/** - * @param {string | undefined} reason - * @param {string} message - * @returns {GPUDeviceLostInfo} - */ -function createGPUDeviceLostInfo(reason, message) { - /** @type {GPUDeviceLostInfo} */ - const deviceLostInfo = webidl.createBranded(GPUDeviceLostInfo); - deviceLostInfo[_reason] = reason ?? "unknown"; - deviceLostInfo[_message] = message; - return deviceLostInfo; -} + /** + * @param {string | undefined} reason + * @param {string} message + * @returns {GPUDeviceLostInfo} + */ + function createGPUDeviceLostInfo(reason, message) { + /** @type {GPUDeviceLostInfo} */ + const deviceLostInfo = webidl.createBranded(GPUDeviceLostInfo); + deviceLostInfo[_reason] = reason ?? "unknown"; + deviceLostInfo[_message] = message; + return deviceLostInfo; + } -class GPUDeviceLostInfo { + class GPUDeviceLostInfo { /** @type {string} */ [_reason]; /** @type {string} */ @@ -666,10 +722,18 @@ class GPUDeviceLostInfo { return this[_message]; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ reason: this[_reason], message: this[_message] }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUDeviceLostInfoPrototype, this), + keys: [ + "reason", + "message", + ], + }), + inspectOptions, + ); } } @@ -694,10 +758,11 @@ function GPUObjectBaseMixin(name, type) { */ set(label) { webidl.assertBranded(this, type.prototype); - label = webidl.converters["UVString?"](label, { - prefix: `Failed to set 'label' on '${name}'`, - context: "Argument 1", - }); + label = webidl.converters["UVString?"]( + label, + `Failed to set 'label' on '${name}'`, + "Argument 1", + ); this[_label] = label; }, }); @@ -904,12 +969,13 @@ class GPUDevice extends EventTarget { createBuffer(descriptor) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createBuffer' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUBufferDescriptor(descriptor, { + webidl.requiredArguments(arguments.length, 1, prefix); + descriptor = webidl.converters.GPUBufferDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); const { rid, err } = ops.op_webgpu_create_buffer( device.rid, descriptor.label, @@ -954,12 +1020,13 @@ class GPUDevice extends EventTarget { createTexture(descriptor) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createTexture' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUTextureDescriptor(descriptor, { + webidl.requiredArguments(arguments.length, 1, prefix); + descriptor = webidl.converters.GPUTextureDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); const { rid, err } = ops.op_webgpu_create_texture({ deviceRid: device.rid, ...descriptor, @@ -983,11 +1050,12 @@ class GPUDevice extends EventTarget { createSampler(descriptor = {}) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createSampler' on 'GPUDevice'"; - descriptor = webidl.converters.GPUSamplerDescriptor(descriptor, { + descriptor = webidl.converters.GPUSamplerDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); const { rid, err } = ops.op_webgpu_create_sampler({ deviceRid: device.rid, ...descriptor, @@ -1010,22 +1078,23 @@ class GPUDevice extends EventTarget { createBindGroupLayout(descriptor) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createBindGroupLayout' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUBindGroupLayoutDescriptor(descriptor, { + webidl.requiredArguments(arguments.length, 1, prefix); + descriptor = webidl.converters.GPUBindGroupLayoutDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); for (let i = 0; i < descriptor.entries.length; ++i) { const entry = descriptor.entries[i]; - let i = 0; - if (entry.buffer) i++; - if (entry.sampler) i++; - if (entry.texture) i++; - if (entry.storageTexture) i++; + let j = 0; + if (entry.buffer) j++; + if (entry.sampler) j++; + if (entry.texture) j++; + if (entry.storageTexture) j++; - if (i !== 1) { + if (j !== 1) { throw new Error(); // TODO(@crowlKats): correct error } } @@ -1053,17 +1122,18 @@ class GPUDevice extends EventTarget { createPipelineLayout(descriptor) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createPipelineLayout' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUPipelineLayoutDescriptor(descriptor, { + webidl.requiredArguments(arguments.length, 1, prefix); + descriptor = webidl.converters.GPUPipelineLayoutDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); const bindGroupLayouts = ArrayPrototypeMap( descriptor.bindGroupLayouts, (layout, i) => { const context = `bind group layout ${i + 1}`; - const rid = assertResource(layout, { prefix, context }); + const rid = assertResource(layout, prefix, context); assertDeviceMatch(device, layout, { prefix, selfContext: "this", @@ -1095,16 +1165,14 @@ class GPUDevice extends EventTarget { createBindGroup(descriptor) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createBindGroup' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUBindGroupDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const layout = assertResource(descriptor.layout, { + webidl.requiredArguments(arguments.length, 1, prefix); + descriptor = webidl.converters.GPUBindGroupDescriptor( + descriptor, prefix, - context: "layout", - }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); + const layout = assertResource(descriptor.layout, prefix, "layout"); assertDeviceMatch(device, descriptor.layout, { prefix, resourceContext: "layout", @@ -1114,10 +1182,7 @@ class GPUDevice extends EventTarget { const context = `entry ${i + 1}`; const resource = entry.resource; if (ObjectPrototypeIsPrototypeOf(GPUSamplerPrototype, resource)) { - const rid = assertResource(resource, { - prefix, - context, - }); + const rid = assertResource(resource, prefix, context); assertDeviceMatch(device, resource, { prefix, resourceContext: context, @@ -1131,14 +1196,8 @@ class GPUDevice extends EventTarget { } else if ( ObjectPrototypeIsPrototypeOf(GPUTextureViewPrototype, resource) ) { - const rid = assertResource(resource, { - prefix, - context, - }); - assertResource(resource[_texture], { - prefix, - context, - }); + const rid = assertResource(resource, prefix, context); + assertResource(resource[_texture], prefix, context); assertDeviceMatch(device, resource[_texture], { prefix, resourceContext: context, @@ -1150,7 +1209,7 @@ class GPUDevice extends EventTarget { resource: rid, }; } else { - const rid = assertResource(resource.buffer, { prefix, context }); + const rid = assertResource(resource.buffer, prefix, context); assertDeviceMatch(device, resource.buffer, { prefix, resourceContext: context, @@ -1189,12 +1248,13 @@ class GPUDevice extends EventTarget { createShaderModule(descriptor) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createShaderModule' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUShaderModuleDescriptor(descriptor, { + webidl.requiredArguments(arguments.length, 1, prefix); + descriptor = webidl.converters.GPUShaderModuleDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); const { rid, err } = ops.op_webgpu_create_shader_module( device.rid, descriptor.label, @@ -1218,26 +1278,28 @@ class GPUDevice extends EventTarget { createComputePipeline(descriptor) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createComputePipeline' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUComputePipelineDescriptor(descriptor, { + webidl.requiredArguments(arguments.length, 1, prefix); + descriptor = webidl.converters.GPUComputePipelineDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); let layout = descriptor.layout; if (typeof descriptor.layout !== "string") { const context = "layout"; - layout = assertResource(descriptor.layout, { prefix, context }); + layout = assertResource(descriptor.layout, prefix, context); assertDeviceMatch(device, descriptor.layout, { prefix, resourceContext: context, selfContext: "this", }); } - const module = assertResource(descriptor.compute.module, { + const module = assertResource( + descriptor.compute.module, prefix, - context: "compute shader module", - }); + "compute shader module", + ); assertDeviceMatch(device, descriptor.compute.module, { prefix, resourceContext: "compute shader module", @@ -1272,26 +1334,28 @@ class GPUDevice extends EventTarget { createRenderPipeline(descriptor) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createRenderPipeline' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPURenderPipelineDescriptor(descriptor, { + webidl.requiredArguments(arguments.length, 1, prefix); + descriptor = webidl.converters.GPURenderPipelineDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); let layout = descriptor.layout; if (typeof descriptor.layout !== "string") { const context = "layout"; - layout = assertResource(descriptor.layout, { prefix, context }); + layout = assertResource(descriptor.layout, prefix, context); assertDeviceMatch(device, descriptor.layout, { prefix, resourceContext: context, selfContext: "this", }); } - const module = assertResource(descriptor.vertex.module, { + const module = assertResource( + descriptor.vertex.module, prefix, - context: "vertex shader module", - }); + "vertex shader module", + ); assertDeviceMatch(device, descriptor.vertex.module, { prefix, resourceContext: "vertex shader module", @@ -1299,10 +1363,11 @@ class GPUDevice extends EventTarget { }); let fragment = undefined; if (descriptor.fragment) { - const module = assertResource(descriptor.fragment.module, { + const module = assertResource( + descriptor.fragment.module, prefix, - context: "fragment shader module", - }); + "fragment shader module", + ); assertDeviceMatch(device, descriptor.fragment.module, { prefix, resourceContext: "fragment shader module", @@ -1357,11 +1422,12 @@ class GPUDevice extends EventTarget { createCommandEncoder(descriptor = {}) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createCommandEncoder' on 'GPUDevice'"; - descriptor = webidl.converters.GPUCommandEncoderDescriptor(descriptor, { + descriptor = webidl.converters.GPUCommandEncoderDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); const { rid, err } = ops.op_webgpu_create_command_encoder( device.rid, descriptor.label, @@ -1388,12 +1454,10 @@ class GPUDevice extends EventTarget { webidl.requiredArguments(arguments.length, 1, { prefix }); descriptor = webidl.converters.GPURenderBundleEncoderDescriptor( descriptor, - { - prefix, - context: "Argument 1", - }, + prefix, + "Argument 1", ); - const device = assertDevice(this, { prefix, context: "this" }); + const device = assertDevice(this, prefix, "this"); const { rid, err } = ops.op_webgpu_create_render_bundle_encoder({ deviceRid: device.rid, ...descriptor, @@ -1416,15 +1480,13 @@ class GPUDevice extends EventTarget { createQuerySet(descriptor) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createQuerySet' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + webidl.requiredArguments(arguments.length, 1, prefix); descriptor = webidl.converters.GPUQuerySetDescriptor( descriptor, - { - prefix, - context: "Argument 1", - }, + prefix, + "Argument 1", ); - const device = assertDevice(this, { prefix, context: "this" }); + const device = assertDevice(this, prefix, "this"); const { rid, err } = ops.op_webgpu_create_query_set({ deviceRid: device.rid, ...descriptor, @@ -1459,12 +1521,9 @@ class GPUDevice extends EventTarget { pushErrorScope(filter) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - filter = webidl.converters.GPUErrorFilter(filter, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); + webidl.requiredArguments(arguments.length, 1, prefix); + filter = webidl.converters.GPUErrorFilter(filter, prefix, "Argument 1"); + const device = assertDevice(this, prefix, "this"); ArrayPrototypePush(device.errorScopeStack, { filter, operations: [] }); } @@ -1475,7 +1534,7 @@ class GPUDevice extends EventTarget { async popErrorScope() { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'popErrorScope' on 'GPUDevice'"; - const device = assertDevice(this, { prefix, context: "this" }); + const device = assertDevice(this, prefix, "this"); if (device.isLost) { throw new DOMException("Device has been lost.", "OperationError"); } @@ -1494,15 +1553,23 @@ class GPUDevice extends EventTarget { ); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - features: this.features, - label: this.label, - limits: this.limits, - queue: this.queue, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUDevicePrototype, this), + keys: [ + "features", + "label", + "limits", + "lost", + "queue", + // TODO(lucacasonato): emit an UncapturedErrorEvent + // "onuncapturederror" + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUDevice", GPUDevice); @@ -1540,14 +1607,15 @@ class GPUQueue { }); commandBuffers = webidl.converters["sequence"]( commandBuffers, - { prefix, context: "Argument 1" }, + prefix, + "Argument 1", ); - const device = assertDevice(this, { prefix, context: "this" }); + const device = assertDevice(this, prefix, "this"); const commandBufferRids = ArrayPrototypeMap( commandBuffers, (buffer, i) => { const context = `command buffer ${i + 1}`; - const rid = assertResource(buffer, { prefix, context }); + const rid = assertResource(buffer, prefix, context); assertDeviceMatch(device, buffer, { prefix, selfContext: "this", @@ -1578,32 +1646,24 @@ class GPUQueue { writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) { webidl.assertBranded(this, GPUQueuePrototype); const prefix = "Failed to execute 'writeBuffer' on 'GPUQueue'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - buffer = webidl.converters["GPUBuffer"](buffer, { - prefix, - context: "Argument 1", - }); - bufferOffset = webidl.converters["GPUSize64"](bufferOffset, { - prefix, - context: "Argument 2", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 3", - }); - dataOffset = webidl.converters["GPUSize64"](dataOffset, { + webidl.requiredArguments(arguments.length, 3, prefix); + buffer = webidl.converters["GPUBuffer"](buffer, prefix, "Argument 1"); + bufferOffset = webidl.converters["GPUSize64"]( + bufferOffset, prefix, - context: "Argument 4", - }); - size = size === undefined ? undefined : webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 5", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(buffer, { + "Argument 2", + ); + data = webidl.converters.BufferSource(data, prefix, "Argument 3"); + dataOffset = webidl.converters["GPUSize64"]( + dataOffset, prefix, - context: "Argument 1", - }); + "Argument 4", + ); + size = size === undefined + ? undefined + : webidl.converters.GPUSize64(size, prefix, "Argument 5"); + const device = assertDevice(this, prefix, "this"); + const bufferRid = assertResource(buffer, prefix, "Argument 1"); assertDeviceMatch(device, buffer, { prefix, selfContext: "this", @@ -1629,28 +1689,21 @@ class GPUQueue { writeTexture(destination, data, dataLayout, size) { webidl.assertBranded(this, GPUQueuePrototype); const prefix = "Failed to execute 'writeTexture' on 'GPUQueue'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - destination = webidl.converters.GPUImageCopyTexture(destination, { - prefix, - context: "Argument 1", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 2", - }); - dataLayout = webidl.converters.GPUImageDataLayout(dataLayout, { - prefix, - context: "Argument 3", - }); - size = webidl.converters.GPUExtent3D(size, { + webidl.requiredArguments(arguments.length, 4, prefix); + destination = webidl.converters.GPUImageCopyTexture( + destination, prefix, - context: "Argument 4", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const textureRid = assertResource(destination.texture, { + "Argument 1", + ); + data = webidl.converters.BufferSource(data, prefix, "Argument 2"); + dataLayout = webidl.converters.GPUImageDataLayout( + dataLayout, prefix, - context: "texture", - }); + "Argument 3", + ); + size = webidl.converters.GPUExtent3D(size, prefix, "Argument 4"); + const device = assertDevice(this, prefix, "this"); + const textureRid = assertResource(destination.texture, prefix, "texture"); assertDeviceMatch(device, destination.texture, { prefix, selfContext: "this", @@ -1673,12 +1726,17 @@ class GPUQueue { device.pushError(err); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUQueuePrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUQueue", GPUQueue); @@ -1784,21 +1842,14 @@ class GPUBuffer { async mapAsync(mode, offset = 0, size) { webidl.assertBranded(this, GPUBufferPrototype); const prefix = "Failed to execute 'mapAsync' on 'GPUBuffer'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - mode = webidl.converters.GPUMapModeFlags(mode, { - prefix, - context: "Argument 1", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 2", - }); - size = size === undefined ? undefined : webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(this, { prefix, context: "this" }); + webidl.requiredArguments(arguments.length, 1, prefix); + mode = webidl.converters.GPUMapModeFlags(mode, prefix, "Argument 1"); + offset = webidl.converters.GPUSize64(offset, prefix, "Argument 2"); + size = size === undefined + ? undefined + : webidl.converters.GPUSize64(size, prefix, "Argument 3"); + const device = assertDevice(this, prefix, "this"); + const bufferRid = assertResource(this, prefix, "this"); /** @type {number} */ let rangeSize; if (size === undefined) { @@ -1882,18 +1933,12 @@ class GPUBuffer { getMappedRange(offset = 0, size) { webidl.assertBranded(this, GPUBufferPrototype); const prefix = "Failed to execute 'getMappedRange' on 'GPUBuffer'"; - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 1", - }); + offset = webidl.converters.GPUSize64(offset, prefix, "Argument 1"); if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 2", - }); + size = webidl.converters.GPUSize64(size, prefix, "Argument 2"); } - assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(this, { prefix, context: "this" }); + assertDevice(this, prefix, "this"); + const bufferRid = assertResource(this, prefix, "this"); /** @type {number} */ let rangeSize; if (size === undefined) { @@ -1937,8 +1982,8 @@ class GPUBuffer { unmap() { webidl.assertBranded(this, GPUBufferPrototype); const prefix = "Failed to execute 'unmap' on 'GPUBuffer'"; - const device = assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(this, { prefix, context: "this" }); + const device = assertDevice(this, prefix, "this"); + const bufferRid = assertResource(this, prefix, "this"); if (this[_state] === "unmapped" || this[_state] === "destroyed") { throw new DOMException( `${prefix}: buffer is not ready to be unmapped.`, @@ -1997,12 +2042,20 @@ class GPUBuffer { this[_cleanup](); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUBufferPrototype, this), + keys: [ + "label", + "mapState", + "size", + "usage", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUBuffer", GPUBuffer); @@ -2133,13 +2186,14 @@ class GPUTexture { createView(descriptor = {}) { webidl.assertBranded(this, GPUTexturePrototype); const prefix = "Failed to execute 'createView' on 'GPUTexture'"; - webidl.requiredArguments(arguments.length, 0, { prefix }); - descriptor = webidl.converters.GPUTextureViewDescriptor(descriptor, { + webidl.requiredArguments(arguments.length, 0, prefix); + descriptor = webidl.converters.GPUTextureViewDescriptor( + descriptor, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const textureRid = assertResource(this, { prefix, context: "this" }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); + const textureRid = assertResource(this, prefix, "this"); const { rid, err } = ops.op_webgpu_create_texture_view({ textureRid, ...descriptor, @@ -2200,12 +2254,25 @@ class GPUTexture { return this[_usage]; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUTexturePrototype, this), + keys: [ + "label", + "width", + "height", + "depthOrArrayLayers", + "mipLevelCount", + "sampleCount", + "dimension", + "format", + "usage", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUTexture", GPUTexture); @@ -2266,12 +2333,17 @@ class GPUTextureView { webidl.illegalConstructor(); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUTextureViewPrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUTextureView", GPUTextureView); @@ -2352,16 +2424,21 @@ class GPUBindGroupLayout { webidl.illegalConstructor(); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUBindGroupLayoutPrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUBindGroupLayout", GPUBindGroupLayout); - +const GPUBindGroupLayoutPrototype = GPUBindGroupLayout.prototype; /** * @param {string | null} label * @param {InnerGPUDevice} device @@ -2395,15 +2472,22 @@ class GPUPipelineLayout { webidl.illegalConstructor(); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUPipelineLayoutPrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUPipelineLayout", GPUPipelineLayout); +const GPUPipelineLayoutPrototype = GPUPipelineLayout.prototype; + /** * @param {string | null} label @@ -2438,16 +2522,21 @@ class GPUBindGroup { webidl.illegalConstructor(); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUBindGroupPrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUBindGroup", GPUBindGroup); - +const GPUBindGroupPrototype = GPUBindGroup.prototype; /** * @param {string | null} label * @param {InnerGPUDevice} device @@ -2481,16 +2570,21 @@ class GPUShaderModule { webidl.illegalConstructor(); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUShaderModulePrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUShaderModule", GPUShaderModule); - +const GPUShaderModulePrototype = GPUShaderModule.prototype; class GPUShaderStage { constructor() { webidl.illegalConstructor(); @@ -2551,17 +2645,11 @@ class GPUComputePipeline { const prefix = "Failed to execute 'getBindGroupLayout' on 'GPUComputePipeline'"; webidl.requiredArguments(arguments.length, 1, { prefix }); - index = webidl.converters["unsigned long"](index, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const computePipelineRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, label, err } = - ops.op_webgpu_compute_pipeline_get_bind_group_layout( + index = webidl.converters["unsigned long"](index, prefix, "Argument 1"); + const device = assertDevice(this, prefix, "this"); + const computePipelineRid = assertResource(this, prefix, "this"); + const { rid, label, err } = ops + .op_webgpu_compute_pipeline_get_bind_group_layout( computePipelineRid, index, ); @@ -2576,12 +2664,17 @@ class GPUComputePipeline { return bindGroupLayout; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUComputePipelinePrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUComputePipeline", GPUComputePipeline); @@ -2627,18 +2720,12 @@ class GPURenderPipeline { webidl.assertBranded(this, GPURenderPipelinePrototype); const prefix = "Failed to execute 'getBindGroupLayout' on 'GPURenderPipeline'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - index = webidl.converters["unsigned long"](index, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderPipelineRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, label, err } = - ops.op_webgpu_render_pipeline_get_bind_group_layout( + webidl.requiredArguments(arguments.length, 1, prefix); + index = webidl.converters["unsigned long"](index, prefix, "Argument 1"); + const device = assertDevice(this, prefix, "this"); + const renderPipelineRid = assertResource(this, prefix, "this"); + const { rid, label, err } = ops + .op_webgpu_render_pipeline_get_bind_group_layout( renderPipelineRid, index, ); @@ -2653,12 +2740,17 @@ class GPURenderPipeline { return bindGroupLayout; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPURenderPipelinePrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPURenderPipeline", GPURenderPipeline); @@ -2736,16 +2828,14 @@ class GPUCommandEncoder { beginRenderPass(descriptor) { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPURenderPassDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { + webidl.requiredArguments(arguments.length, 1, prefix); + descriptor = webidl.converters.GPURenderPassDescriptor( + descriptor, prefix, - context: "this", - }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); if (this[_rid] === undefined) { throw new DOMException( @@ -2756,19 +2846,23 @@ class GPUCommandEncoder { let depthStencilAttachment; if (descriptor.depthStencilAttachment) { - if (descriptor.depthStencilAttachment.depthLoadOp === "clear" && !("depthClearValue" in descriptor.depthStencilAttachment)) { - throw webidl.makeException( + if ( + descriptor.depthStencilAttachment.depthLoadOp === "clear" && + !("depthClearValue" in descriptor.depthStencilAttachment) + ) { + throw webidl.makeException( TypeError, - "`depthClearValue` must be specified when `depthLoadOp` is \"clear\"", + '`depthClearValue` must be specified when `depthLoadOp` is "clear"', prefix, "Argument 1", ); } - const view = assertResource(descriptor.depthStencilAttachment.view, { + const view = assertResource( + descriptor.depthStencilAttachment.view, prefix, - context: "texture view for depth stencil attachment", - }); + "texture view for depth stencil attachment", + ); assertDeviceMatch( device, descriptor.depthStencilAttachment.view[_texture], @@ -2788,14 +2882,16 @@ class GPUCommandEncoder { descriptor.colorAttachments, (colorAttachment, i) => { const context = `color attachment ${i + 1}`; - const view = assertResource(colorAttachment.view, { + const view = assertResource( + colorAttachment.view, prefix, - context: `texture view for ${context}`, - }); - assertResource(colorAttachment.view[_texture], { + `texture view for ${context}`, + ); + assertResource( + colorAttachment.view[_texture], prefix, - context: `texture backing texture view for ${context}`, - }); + `texture backing texture view for ${context}`, + ); assertDeviceMatch( device, colorAttachment.view[_texture], @@ -2809,16 +2905,10 @@ class GPUCommandEncoder { if (colorAttachment.resolveTarget) { resolveTarget = assertResource( colorAttachment.resolveTarget, - { - prefix, - context: `resolve target texture view for ${context}`, - }, - ); - assertResource(colorAttachment.resolveTarget[_texture], { prefix, - context: - `texture backing resolve target texture view for ${context}`, - }); + `resolve target texture view for ${context}`, + ); + assertResource(colorAttachment.resolveTarget[_texture], prefix, `texture backing resolve target texture view for ${context}`); assertDeviceMatch( device, colorAttachment.resolveTarget[_texture], @@ -2839,11 +2929,34 @@ class GPUCommandEncoder { }, ); + let occlusionQuerySet; + + if (descriptor.occlusionQuerySet) { + occlusionQuerySet = assertResource( + descriptor.occlusionQuerySet, + prefix, + "occlusionQuerySet", + ); + } + + let timestampWrites = null; + if (descriptor.timestampWrites) { + const querySet = assertResource(descriptor.timestampWrites.querySet, prefix, "querySet"); + + timestampWrites = { + querySet, + beginningOfPassWriteIndex: descriptor.timestampWrites.beginningOfPassWriteIndex, + endOfPassWriteIndex: descriptor.timestampWrites.endOfPassWriteIndex, + }; + } + const { rid } = ops.op_webgpu_command_encoder_begin_render_pass( commandEncoderRid, descriptor.label, colorAttachments, depthStencilAttachment, + occlusionQuerySet, + timestampWrites, ); const renderPassEncoder = createGPURenderPassEncoder( @@ -2862,20 +2975,30 @@ class GPUCommandEncoder { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'beginComputePass' on 'GPUCommandEncoder'"; - descriptor = webidl.converters.GPUComputePassDescriptor(descriptor, { + descriptor = webidl.converters.GPUComputePassDescriptor( + descriptor, prefix, - context: "Argument 1", - }); + "Argument 1", + ); - assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); + assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); + + let timestampWrites = null; + if (descriptor.timestampWrites) { + const querySet = assertResource(descriptor.timestampWrites.querySet, prefix, "querySet"); + + timestampWrites = { + querySet, + beginningOfPassWriteIndex: descriptor.timestampWrites.beginningOfPassWriteIndex, + endOfPassWriteIndex: descriptor.timestampWrites.endOfPassWriteIndex, + }; + } const { rid } = ops.op_webgpu_command_encoder_begin_compute_pass( commandEncoderRid, descriptor.label, + timestampWrites, ); const computePassEncoder = createGPUComputePassEncoder( @@ -2904,45 +3027,33 @@ class GPUCommandEncoder { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'copyBufferToBuffer' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - source = webidl.converters.GPUBuffer(source, { - prefix, - context: "Argument 1", - }); - sourceOffset = webidl.converters.GPUSize64(sourceOffset, { - prefix, - context: "Argument 2", - }); - destination = webidl.converters.GPUBuffer(destination, { - prefix, - context: "Argument 3", - }); - destinationOffset = webidl.converters.GPUSize64(destinationOffset, { - prefix, - context: "Argument 4", - }); - size = webidl.converters.GPUSize64(size, { + webidl.requiredArguments(arguments.length, 5, prefix); + source = webidl.converters.GPUBuffer(source, prefix, "Argument 1"); + sourceOffset = webidl.converters.GPUSize64( + sourceOffset, prefix, - context: "Argument 5", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { + "Argument 2", + ); + destination = webidl.converters.GPUBuffer( + destination, prefix, - context: "this", - }); - const sourceRid = assertResource(source, { + "Argument 3", + ); + destinationOffset = webidl.converters.GPUSize64( + destinationOffset, prefix, - context: "Argument 1", - }); + "Argument 4", + ); + size = webidl.converters.GPUSize64(size, prefix, "Argument 5"); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); + const sourceRid = assertResource(source, prefix, "Argument 1"); assertDeviceMatch(device, source, { prefix, resourceContext: "Argument 1", selfContext: "this", }); - const destinationRid = assertResource(destination, { - prefix, - context: "Argument 3", - }); + const destinationRid = assertResource(destination, prefix, "Argument 3"); assertDeviceMatch(device, destination, { prefix, resourceContext: "Argument 3", @@ -2969,37 +3080,31 @@ class GPUCommandEncoder { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'copyBufferToTexture' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - source = webidl.converters.GPUImageCopyBuffer(source, { + webidl.requiredArguments(arguments.length, 3, prefix); + source = webidl.converters.GPUImageCopyBuffer(source, prefix, "Argument 1"); + destination = webidl.converters.GPUImageCopyTexture( + destination, prefix, - context: "Argument 1", - }); - destination = webidl.converters.GPUImageCopyTexture(destination, { - prefix, - context: "Argument 2", - }); - copySize = webidl.converters.GPUExtent3D(copySize, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const sourceBufferRid = assertResource(source.buffer, { + "Argument 2", + ); + copySize = webidl.converters.GPUExtent3D(copySize, prefix, "Argument 3"); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); + const sourceBufferRid = assertResource( + source.buffer, prefix, - context: "source in Argument 1", - }); + "source in Argument 1", + ); assertDeviceMatch(device, source.buffer, { prefix, resourceContext: "source in Argument 1", selfContext: "this", }); - const destinationTextureRid = assertResource(destination.texture, { + const destinationTextureRid = assertResource( + destination.texture, prefix, - context: "texture in Argument 2", - }); + "texture in Argument 2", + ); assertDeviceMatch(device, destination.texture, { prefix, resourceContext: "texture in Argument 2", @@ -3034,37 +3139,35 @@ class GPUCommandEncoder { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'copyTextureToBuffer' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - source = webidl.converters.GPUImageCopyTexture(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.GPUImageCopyBuffer(destination, { + webidl.requiredArguments(arguments.length, 3, prefix); + source = webidl.converters.GPUImageCopyTexture( + source, prefix, - context: "Argument 2", - }); - copySize = webidl.converters.GPUExtent3D(copySize, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { + "Argument 1", + ); + destination = webidl.converters.GPUImageCopyBuffer( + destination, prefix, - context: "this", - }); - const sourceTextureRid = assertResource(source.texture, { + "Argument 2", + ); + copySize = webidl.converters.GPUExtent3D(copySize, prefix, "Argument 3"); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); + const sourceTextureRid = assertResource( + source.texture, prefix, - context: "texture in Argument 1", - }); + "texture in Argument 1", + ); assertDeviceMatch(device, source.texture, { prefix, resourceContext: "texture in Argument 1", selfContext: "this", }); - const destinationBufferRid = assertResource(destination.buffer, { + const destinationBufferRid = assertResource( + destination.buffer, prefix, - context: "buffer in Argument 2", - }); + "buffer in Argument 2", + ); assertDeviceMatch(device, destination.buffer, { prefix, resourceContext: "buffer in Argument 2", @@ -3075,9 +3178,7 @@ class GPUCommandEncoder { { texture: sourceTextureRid, mipLevel: source.mipLevel, - origin: source.origin - ? normalizeGPUOrigin3D(source.origin) - : undefined, + origin: source.origin ? normalizeGPUOrigin3D(source.origin) : undefined, aspect: source.aspect, }, { @@ -3098,37 +3199,35 @@ class GPUCommandEncoder { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'copyTextureToTexture' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - source = webidl.converters.GPUImageCopyTexture(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.GPUImageCopyTexture(destination, { - prefix, - context: "Argument 2", - }); - copySize = webidl.converters.GPUExtent3D(copySize, { + webidl.requiredArguments(arguments.length, 3, prefix); + source = webidl.converters.GPUImageCopyTexture( + source, prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { + "Argument 1", + ); + destination = webidl.converters.GPUImageCopyTexture( + destination, prefix, - context: "this", - }); - const sourceTextureRid = assertResource(source.texture, { + "Argument 2", + ); + copySize = webidl.converters.GPUExtent3D(copySize, prefix, "Argument 3"); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); + const sourceTextureRid = assertResource( + source.texture, prefix, - context: "texture in Argument 1", - }); + "texture in Argument 1", + ); assertDeviceMatch(device, source.texture, { prefix, resourceContext: "texture in Argument 1", selfContext: "this", }); - const destinationTextureRid = assertResource(destination.texture, { + const destinationTextureRid = assertResource( + destination.texture, prefix, - context: "texture in Argument 2", - }); + "texture in Argument 2", + ); assertDeviceMatch(device, destination.texture, { prefix, resourceContext: "texture in Argument 2", @@ -3139,9 +3238,7 @@ class GPUCommandEncoder { { texture: sourceTextureRid, mipLevel: source.mipLevel, - origin: source.origin - ? normalizeGPUOrigin3D(source.origin) - : undefined, + origin: source.origin ? normalizeGPUOrigin3D(source.origin) : undefined, aspect: source.aspect, }, { @@ -3165,28 +3262,13 @@ class GPUCommandEncoder { clearBuffer(buffer, offset = 0, size = undefined) { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'clearBuffer' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 1", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 2", - }); - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); + webidl.requiredArguments(arguments.length, 3, prefix); + buffer = webidl.converters.GPUBuffer(buffer, prefix, "Argument 1"); + offset = webidl.converters.GPUSize64(offset, prefix, "Argument 2"); + size = webidl.converters.GPUSize64(size, prefix, "Argument 3"); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); + const bufferRid = assertResource(buffer, prefix, "Argument 1"); const { err } = ops.op_webgpu_command_encoder_clear_buffer( commandEncoderRid, bufferRid, @@ -3202,16 +3284,10 @@ class GPUCommandEncoder { pushDebugGroup(groupLabel) { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'pushDebugGroup' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); + webidl.requiredArguments(arguments.length, 1, prefix); + groupLabel = webidl.converters.USVString(groupLabel, prefix, "Argument 1"); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); const { err } = ops.op_webgpu_command_encoder_push_debug_group( commandEncoderRid, groupLabel, @@ -3222,11 +3298,8 @@ class GPUCommandEncoder { popDebugGroup() { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'popDebugGroup' on 'GPUCommandEncoder'"; - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); const { err } = ops.op_webgpu_command_encoder_pop_debug_group( commandEncoderRid, ); @@ -3240,16 +3313,14 @@ class GPUCommandEncoder { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'insertDebugMarker' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { + webidl.requiredArguments(arguments.length, 1, prefix); + markerLabel = webidl.converters.USVString( + markerLabel, prefix, - context: "this", - }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); const { err } = ops.op_webgpu_command_encoder_insert_debug_marker( commandEncoderRid, markerLabel, @@ -3264,24 +3335,12 @@ class GPUCommandEncoder { writeTimestamp(querySet, queryIndex) { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'writeTimestamp' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); + webidl.requiredArguments(arguments.length, 2, prefix); + querySet = webidl.converters.GPUQuerySet(querySet, prefix, "Argument 1"); + queryIndex = webidl.converters.GPUSize32(queryIndex, prefix, "Argument 2"); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); + const querySetRid = assertResource(querySet, prefix, "Argument 1"); assertDeviceMatch(device, querySet, { prefix, resourceContext: "Argument 1", @@ -3312,44 +3371,28 @@ class GPUCommandEncoder { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'resolveQuerySet' on 'GPUCommandEncoder'"; webidl.requiredArguments(arguments.length, 5, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - firstQuery = webidl.converters.GPUSize32(firstQuery, { - prefix, - context: "Argument 2", - }); - queryCount = webidl.converters.GPUSize32(queryCount, { + querySet = webidl.converters.GPUQuerySet(querySet, prefix, "Argument 1"); + firstQuery = webidl.converters.GPUSize32(firstQuery, prefix, "Argument 2"); + queryCount = webidl.converters.GPUSize32(queryCount, prefix, "Argument 3"); + destination = webidl.converters.GPUBuffer( + destination, prefix, - context: "Argument 3", - }); - destination = webidl.converters.GPUBuffer(destination, { - prefix, - context: "Argument 4", - }); - destinationOffset = webidl.converters.GPUSize64(destinationOffset, { - prefix, - context: "Argument 5", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const querySetRid = assertResource(querySet, { + "Argument 4", + ); + destinationOffset = webidl.converters.GPUSize64( + destinationOffset, prefix, - context: "Argument 1", - }); + "Argument 5", + ); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); + const querySetRid = assertResource(querySet, prefix, "Argument 1"); assertDeviceMatch(device, querySet, { prefix, resourceContext: "Argument 1", selfContext: "this", }); - const destinationRid = assertResource(destination, { - prefix, - context: "Argument 3", - }); + const destinationRid = assertResource(destination, prefix, "Argument 3"); assertDeviceMatch(device, destination, { prefix, resourceContext: "Argument 3", @@ -3373,15 +3416,13 @@ class GPUCommandEncoder { finish(descriptor = {}) { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'finish' on 'GPUCommandEncoder'"; - descriptor = webidl.converters.GPUCommandBufferDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { + descriptor = webidl.converters.GPUCommandBufferDescriptor( + descriptor, prefix, - context: "this", - }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); + const commandEncoderRid = assertResource(this, prefix, "this"); const { rid, err } = ops.op_webgpu_command_encoder_finish( commandEncoderRid, descriptor.label, @@ -3399,12 +3440,17 @@ class GPUCommandEncoder { return commandBuffer; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUCommandEncoderPrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUCommandEncoder", GPUCommandEncoder); @@ -3456,30 +3502,15 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'setViewport' on 'GPUComputePassEncoder'"; webidl.requiredArguments(arguments.length, 6, { prefix }); - x = webidl.converters.float(x, { prefix, context: "Argument 1" }); - y = webidl.converters.float(y, { prefix, context: "Argument 2" }); - width = webidl.converters.float(width, { prefix, context: "Argument 3" }); - height = webidl.converters.float(height, { - prefix, - context: "Argument 4", - }); - minDepth = webidl.converters.float(minDepth, { - prefix, - context: "Argument 5", - }); - maxDepth = webidl.converters.float(maxDepth, { - prefix, - context: "Argument 6", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + x = webidl.converters.float(x, prefix, "Argument 1"); + y = webidl.converters.float(y, prefix, "Argument 2"); + width = webidl.converters.float(width, prefix, "Argument 3"); + height = webidl.converters.float(height, prefix, "Argument 4"); + minDepth = webidl.converters.float(minDepth, prefix, "Argument 5"); + maxDepth = webidl.converters.float(maxDepth, prefix, "Argument 6"); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_pass_set_viewport({ renderPassRid, x, @@ -3501,32 +3532,18 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'setScissorRect' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - x = webidl.converters.GPUIntegerCoordinate(x, { - prefix, - context: "Argument 1", - }); - y = webidl.converters.GPUIntegerCoordinate(y, { - prefix, - context: "Argument 2", - }); - width = webidl.converters.GPUIntegerCoordinate(width, { - prefix, - context: "Argument 3", - }); - height = webidl.converters.GPUIntegerCoordinate(height, { - prefix, - context: "Argument 4", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + webidl.requiredArguments(arguments.length, 4, prefix); + x = webidl.converters.GPUIntegerCoordinate(x, prefix, "Argument 1"); + y = webidl.converters.GPUIntegerCoordinate(y, prefix, "Argument 2"); + width = webidl.converters.GPUIntegerCoordinate(width, prefix, "Argument 3"); + height = webidl.converters.GPUIntegerCoordinate( + height, prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + "Argument 4", + ); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_pass_set_scissor_rect( renderPassRid, x, @@ -3543,20 +3560,11 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'setBlendConstant' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - color = webidl.converters.GPUColor(color, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + webidl.requiredArguments(arguments.length, 1, prefix); + color = webidl.converters.GPUColor(color, prefix, "Argument 1"); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_pass_set_blend_constant( renderPassRid, normalizeGPUColor(color), @@ -3570,20 +3578,15 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'setStencilReference' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - reference = webidl.converters.GPUStencilValue(reference, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + webidl.requiredArguments(arguments.length, 1, prefix); + reference = webidl.converters.GPUStencilValue( + reference, prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + "Argument 1", + ); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_pass_set_stencil_reference( renderPassRid, reference, @@ -3591,103 +3594,31 @@ class GPURenderPassEncoder { } /** - * @param {GPUQuerySet} querySet * @param {number} queryIndex */ - beginPipelineStatisticsQuery(querySet, queryIndex) { + beginOcclusionQuery(queryIndex) { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = - "Failed to execute 'beginPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_begin_pipeline_statistics_query( + "Failed to execute 'beginOcclusionQuery' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, prefix); + queryIndex = webidl.converters.GPUSize32(queryIndex, prefix, "Argument 1"); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); + ops.op_webgpu_render_pass_begin_occlusion_query( renderPassRid, - querySetRid, queryIndex, ); } - endPipelineStatisticsQuery() { + endOcclusionQuery() { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = - "Failed to execute 'endPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_end_pipeline_statistics_query(renderPassRid); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - writeTimestamp(querySet, queryIndex) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'writeTimestamp' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_write_timestamp( - renderPassRid, - querySetRid, - queryIndex, - ); + "Failed to execute 'endOcclusionQuery' on 'GPUComputePassEncoder'"; + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); + ops.op_webgpu_render_pass_end_occlusion_query(renderPassRid); } /** @@ -3697,23 +3628,22 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'executeBundles' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - bundles = webidl.converters["sequence"](bundles, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this[_encoder], { + webidl.requiredArguments(arguments.length, 1, prefix); + bundles = webidl.converters["sequence"]( + bundles, prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + "Argument 1", + ); + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); const bundleRids = ArrayPrototypeMap(bundles, (bundle, i) => { const context = `bundle ${i + 1}`; - const rid = assertResource(bundle, { prefix, context }); + const rid = assertResource(bundle, prefix, context); assertDeviceMatch(device, bundle, { prefix, resourceContext: context, @@ -3727,15 +3657,17 @@ class GPURenderPassEncoder { end() { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'end' on 'GPURenderPassEncoder'"; - const device = assertDevice(this[_encoder], { + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const commandEncoderRid = assertResource(this[_encoder], { + "encoder referenced by this", + ); + const commandEncoderRid = assertResource( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + "encoder referenced by this", + ); + const renderPassRid = assertResource(this, prefix, "this"); const { err } = ops.op_webgpu_render_pass_end( commandEncoderRid, renderPassRid, @@ -3754,19 +3686,14 @@ class GPURenderPassEncoder { ) { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'setBindGroup' on 'GPURenderPassEncoder'"; - const device = assertDevice(this[_encoder], { + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bindGroupRid = assertResource(bindGroup, { - prefix, - context: "Argument 2", - }); + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); + const bindGroupRid = assertResource(bindGroup, prefix, "Argument 2"); assertDeviceMatch(device, bindGroup, { prefix, resourceContext: "Argument 2", @@ -3799,20 +3726,11 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'pushDebugGroup' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + webidl.requiredArguments(arguments.length, 1, prefix); + groupLabel = webidl.converters.USVString(groupLabel, prefix, "Argument 1"); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_pass_push_debug_group(renderPassRid, groupLabel); } @@ -3820,15 +3738,9 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'popDebugGroup' on 'GPURenderPassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_pass_pop_debug_group(renderPassRid); } @@ -3839,20 +3751,15 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'insertDebugMarker' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + webidl.requiredArguments(arguments.length, 1, prefix); + markerLabel = webidl.converters.USVString( + markerLabel, prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + "Argument 1", + ); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_pass_insert_debug_marker(renderPassRid, markerLabel); } @@ -3862,24 +3769,20 @@ class GPURenderPassEncoder { setPipeline(pipeline) { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'setPipeline' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - pipeline = webidl.converters.GPURenderPipeline(pipeline, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + webidl.requiredArguments(arguments.length, 1, prefix); + pipeline = webidl.converters.GPURenderPipeline( + pipeline, prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const pipelineRid = assertResource(pipeline, { + "Argument 1", + ); + const device = assertDevice( + this[_encoder], prefix, - context: "Argument 1", - }); + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); + const pipelineRid = assertResource(pipeline, prefix, "Argument 1"); assertDeviceMatch(device, pipeline, { prefix, resourceContext: "Argument 1", @@ -3898,38 +3801,25 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'setIndexBuffer' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 1", - }); - indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { + webidl.requiredArguments(arguments.length, 2, prefix); + buffer = webidl.converters.GPUBuffer(buffer, prefix, "Argument 1"); + indexFormat = webidl.converters.GPUIndexFormat( + indexFormat, prefix, - context: "Argument 3", - }); + "Argument 2", + ); + offset = webidl.converters.GPUSize64(offset, prefix, "Argument 3"); if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); + size = webidl.converters.GPUSize64(size, prefix, "Argument 4"); } - const device = assertDevice(this[_encoder], { + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); + const bufferRid = assertResource(buffer, prefix, "Argument 1"); assertDeviceMatch(device, buffer, { prefix, resourceContext: "Argument 1", @@ -3954,38 +3844,21 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'setVertexBuffer' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - slot = webidl.converters.GPUSize32(slot, { - prefix, - context: "Argument 2", - }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); + webidl.requiredArguments(arguments.length, 2, prefix); + slot = webidl.converters.GPUSize32(slot, prefix, "Argument 1"); + buffer = webidl.converters.GPUBuffer(buffer, prefix, "Argument 2"); + offset = webidl.converters.GPUSize64(offset, prefix, "Argument 3"); if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); + size = webidl.converters.GPUSize64(size, prefix, "Argument 4"); } - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 2", - }); + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); + const bufferRid = assertResource(buffer, prefix, "Argument 2"); assertDeviceMatch(device, buffer, { prefix, resourceContext: "Argument 2", @@ -4009,32 +3882,30 @@ class GPURenderPassEncoder { draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'draw' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - vertexCount = webidl.converters.GPUSize32(vertexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstVertex = webidl.converters.GPUSize32(firstVertex, { + webidl.requiredArguments(arguments.length, 1, prefix); + vertexCount = webidl.converters.GPUSize32( + vertexCount, prefix, - context: "Argument 3", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { + "Argument 1", + ); + instanceCount = webidl.converters.GPUSize32( + instanceCount, prefix, - context: "Argument 4", - }); - assertDevice(this[_encoder], { + "Argument 2", + ); + firstVertex = webidl.converters.GPUSize32( + firstVertex, prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + "Argument 3", + ); + firstInstance = webidl.converters.GPUSize32( + firstInstance, prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + "Argument 4", + ); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_pass_draw( renderPassRid, vertexCount, @@ -4060,36 +3931,27 @@ class GPURenderPassEncoder { ) { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'drawIndexed' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - indexCount = webidl.converters.GPUSize32(indexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstIndex = webidl.converters.GPUSize32(firstIndex, { - prefix, - context: "Argument 3", - }); - baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { - prefix, - context: "Argument 4", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { + webidl.requiredArguments(arguments.length, 1, prefix); + indexCount = webidl.converters.GPUSize32(indexCount, prefix, "Argument 1"); + instanceCount = webidl.converters.GPUSize32( + instanceCount, prefix, - context: "Argument 5", - }); - assertDevice(this[_encoder], { + "Argument 2", + ); + firstIndex = webidl.converters.GPUSize32(firstIndex, prefix, "Argument 3"); + baseVertex = webidl.converters.GPUSignedOffset32( + baseVertex, prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + "Argument 4", + ); + firstInstance = webidl.converters.GPUSize32( + firstInstance, prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); + "Argument 5", + ); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_pass_draw_indexed( renderPassRid, indexCount, @@ -4107,28 +3969,29 @@ class GPURenderPassEncoder { drawIndirect(indirectBuffer, indirectOffset) { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + webidl.requiredArguments(arguments.length, 2, prefix); + indirectBuffer = webidl.converters.GPUBuffer( + indirectBuffer, prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { + "Argument 1", + ); + indirectOffset = webidl.converters.GPUSize64( + indirectOffset, prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + "Argument 2", + ); + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const indirectBufferRid = assertResource(indirectBuffer, { + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); + const indirectBufferRid = assertResource( + indirectBuffer, prefix, - context: "Argument 1", - }); + "Argument 1", + ); assertDeviceMatch(device, indirectBuffer, { prefix, resourceContext: "Argument 1", @@ -4149,28 +4012,29 @@ class GPURenderPassEncoder { webidl.assertBranded(this, GPURenderPassEncoderPrototype); const prefix = "Failed to execute 'drawIndexedIndirect' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + webidl.requiredArguments(arguments.length, 2, prefix); + indirectBuffer = webidl.converters.GPUBuffer( + indirectBuffer, prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { + "Argument 1", + ); + indirectOffset = webidl.converters.GPUSize64( + indirectOffset, prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + "Argument 2", + ); + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const indirectBufferRid = assertResource(indirectBuffer, { + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const renderPassRid = assertResource(this, prefix, "this"); + const indirectBufferRid = assertResource( + indirectBuffer, prefix, - context: "Argument 1", - }); + "Argument 1", + ); assertDeviceMatch(device, indirectBuffer, { prefix, resourceContext: "Argument 1", @@ -4183,12 +4047,17 @@ class GPURenderPassEncoder { ); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPURenderPassEncoderPrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPURenderPassEncoder", GPURenderPassEncoder); @@ -4235,229 +4104,125 @@ class GPUComputePassEncoder { setPipeline(pipeline) { webidl.assertBranded(this, GPUComputePassEncoderPrototype); const prefix = "Failed to execute 'setPipeline' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - pipeline = webidl.converters.GPUComputePipeline(pipeline, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const pipelineRid = assertResource(pipeline, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, pipeline, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_set_pipeline(computePassRid, pipelineRid); - } - - /** - * @param {number} workgroupCountX - * @param {number} workgroupCountY - * @param {number} workgroupCountZ - */ - dispatchWorkgroups( - workgroupCountX, - workgroupCountY = 1, - workgroupCountZ = 1, - ) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'dispatchWorkgroups' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - workgroupCountX = webidl.converters.GPUSize32(workgroupCountX, { - prefix, - context: "Argument 1", - }); - workgroupCountY = webidl.converters.GPUSize32(workgroupCountY, { - prefix, - context: "Argument 2", - }); - workgroupCountZ = webidl.converters.GPUSize32(workgroupCountZ, { - prefix, - context: "Argument 3", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_dispatch_workgroups( - computePassRid, - workgroupCountX, - workgroupCountY, - workgroupCountZ, - ); - } - - /** - * @param {GPUBuffer} indirectBuffer - * @param {number} indirectOffset - */ - dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'dispatchWorkgroupsIndirect' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const indirectBufferRid = assertResource(indirectBuffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, indirectBuffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_dispatch_workgroups_indirect( - computePassRid, - indirectBufferRid, - indirectOffset, - ); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - beginPipelineStatisticsQuery(querySet, queryIndex) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'beginPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + webidl.requiredArguments(arguments.length, 1, prefix); + pipeline = webidl.converters.GPUComputePipeline( + pipeline, prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { + "Argument 1", + ); + const device = assertDevice( + this[_encoder], prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const computePassRid = assertResource(this, prefix, "this"); + const pipelineRid = assertResource(pipeline, prefix, "Argument 1"); + assertDeviceMatch(device, pipeline, { prefix, resourceContext: "Argument 1", selfContext: "this", }); - ops.op_webgpu_compute_pass_begin_pipeline_statistics_query( - computePassRid, - querySetRid, - queryIndex, - ); + ops.op_webgpu_compute_pass_set_pipeline(computePassRid, pipelineRid); } - endPipelineStatisticsQuery() { + /** + * @param {number} workgroupCountX + * @param {number} workgroupCountY + * @param {number} workgroupCountZ + */ + dispatchWorkgroups( + workgroupCountX, + workgroupCountY = 1, + workgroupCountZ = 1, + ) { webidl.assertBranded(this, GPUComputePassEncoderPrototype); const prefix = - "Failed to execute 'endPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; - assertDevice(this[_encoder], { + "Failed to execute 'dispatchWorkgroups' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, prefix); + workgroupCountX = webidl.converters.GPUSize32( + workgroupCountX, prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + "Argument 1", + ); + workgroupCountY = webidl.converters.GPUSize32( + workgroupCountY, prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_end_pipeline_statistics_query(computePassRid); + "Argument 2", + ); + workgroupCountZ = webidl.converters.GPUSize32( + workgroupCountZ, + prefix, + "Argument 3", + ); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const computePassRid = assertResource(this, prefix, "this"); + ops.op_webgpu_compute_pass_dispatch_workgroups( + computePassRid, + workgroupCountX, + workgroupCountY, + workgroupCountZ, + ); } /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset */ - writeTimestamp(querySet, queryIndex) { + dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset) { webidl.assertBranded(this, GPUComputePassEncoderPrototype); const prefix = - "Failed to execute 'writeTimestamp' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { + "Failed to execute 'dispatchWorkgroupsIndirect' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, prefix); + indirectBuffer = webidl.converters.GPUBuffer( + indirectBuffer, prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { + "Argument 1", + ); + indirectOffset = webidl.converters.GPUSize64( + indirectOffset, prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + "Argument 2", + ); + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const computePassRid = assertResource(this, prefix, "this"); + const indirectBufferRid = assertResource( + indirectBuffer, prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { + "Argument 1", + ); + assertDeviceMatch(device, indirectBuffer, { prefix, resourceContext: "Argument 1", selfContext: "this", }); - ops.op_webgpu_compute_pass_write_timestamp( + ops.op_webgpu_compute_pass_dispatch_workgroups_indirect( computePassRid, - querySetRid, - queryIndex, + indirectBufferRid, + indirectOffset, ); } end() { webidl.assertBranded(this, GPUComputePassEncoderPrototype); const prefix = "Failed to execute 'end' on 'GPUComputePassEncoder'"; - const device = assertDevice(this[_encoder], { + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const commandEncoderRid = assertResource(this[_encoder], { + "encoder referenced by this", + ); + const commandEncoderRid = assertResource( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); + "encoder referenced by this", + ); + const computePassRid = assertResource(this, prefix, "this"); const { err } = ops.op_webgpu_compute_pass_end( commandEncoderRid, computePassRid, @@ -4477,19 +4242,14 @@ class GPUComputePassEncoder { webidl.assertBranded(this, GPUComputePassEncoderPrototype); const prefix = "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + const device = assertDevice( + this[_encoder], prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const bindGroupRid = assertResource(bindGroup, { - prefix, - context: "Argument 2", - }); + "encoder referenced by this", + ); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const computePassRid = assertResource(this, prefix, "this"); + const bindGroupRid = assertResource(bindGroup, prefix, "Argument 2"); assertDeviceMatch(device, bindGroup, { prefix, resourceContext: "Argument 2", @@ -4522,20 +4282,11 @@ class GPUComputePassEncoder { webidl.assertBranded(this, GPUComputePassEncoderPrototype); const prefix = "Failed to execute 'pushDebugGroup' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); + webidl.requiredArguments(arguments.length, 1, prefix); + groupLabel = webidl.converters.USVString(groupLabel, prefix, "Argument 1"); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const computePassRid = assertResource(this, prefix, "this"); ops.op_webgpu_compute_pass_push_debug_group(computePassRid, groupLabel); } @@ -4543,15 +4294,9 @@ class GPUComputePassEncoder { webidl.assertBranded(this, GPUComputePassEncoderPrototype); const prefix = "Failed to execute 'popDebugGroup' on 'GPUComputePassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const computePassRid = assertResource(this, prefix, "this"); ops.op_webgpu_compute_pass_pop_debug_group(computePassRid); } @@ -4562,32 +4307,32 @@ class GPUComputePassEncoder { webidl.assertBranded(this, GPUComputePassEncoderPrototype); const prefix = "Failed to execute 'insertDebugMarker' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { + webidl.requiredArguments(arguments.length, 1, prefix); + markerLabel = webidl.converters.USVString( + markerLabel, prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); + "Argument 1", + ); + assertDevice(this[_encoder], prefix, "encoder referenced by this"); + assertResource(this[_encoder], prefix, "encoder referenced by this"); + const computePassRid = assertResource(this, prefix, "this"); ops.op_webgpu_compute_pass_insert_debug_marker( computePassRid, markerLabel, ); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUComputePassEncoderPrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUComputePassEncoder", GPUComputePassEncoder); @@ -4627,15 +4372,21 @@ class GPUCommandBuffer { webidl.illegalConstructor(); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUCommandBufferPrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUCommandBuffer", GPUCommandBuffer); +const GPUCommandBufferPrototype = GPUCommandBuffer.prototype; /** * @param {string | null} label @@ -4677,15 +4428,13 @@ class GPURenderBundleEncoder { finish(descriptor = {}) { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'finish' on 'GPURenderBundleEncoder'"; - descriptor = webidl.converters.GPURenderBundleDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { + descriptor = webidl.converters.GPURenderBundleDescriptor( + descriptor, prefix, - context: "this", - }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); const { rid, err } = ops.op_webgpu_render_bundle_encoder_finish( renderBundleEncoderRid, descriptor.label, @@ -4713,15 +4462,9 @@ class GPURenderBundleEncoder { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'setBindGroup' on 'GPURenderBundleEncoder'"; - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bindGroupRid = assertResource(bindGroup, { - prefix, - context: "Argument 2", - }); + const device = assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); + const bindGroupRid = assertResource(bindGroup, prefix, "Argument 2"); assertDeviceMatch(device, bindGroup, { prefix, resourceContext: "Argument 2", @@ -4754,16 +4497,10 @@ class GPURenderBundleEncoder { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'pushDebugGroup' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); + webidl.requiredArguments(arguments.length, 1, prefix); + groupLabel = webidl.converters.USVString(groupLabel, prefix, "Argument 1"); + assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_bundle_encoder_push_debug_group( renderBundleEncoderRid, groupLabel, @@ -4774,11 +4511,8 @@ class GPURenderBundleEncoder { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'popDebugGroup' on 'GPURenderBundleEncoder'"; - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); + assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_bundle_encoder_pop_debug_group( renderBundleEncoderRid, ); @@ -4791,16 +4525,14 @@ class GPURenderBundleEncoder { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'insertDebugMarker' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { + webidl.requiredArguments(arguments.length, 1, prefix); + markerLabel = webidl.converters.USVString( + markerLabel, prefix, - context: "this", - }); + "Argument 1", + ); + assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_bundle_encoder_insert_debug_marker( renderBundleEncoderRid, markerLabel, @@ -4814,20 +4546,15 @@ class GPURenderBundleEncoder { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'setPipeline' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - pipeline = webidl.converters.GPURenderPipeline(pipeline, { + webidl.requiredArguments(arguments.length, 1, prefix); + pipeline = webidl.converters.GPURenderPipeline( + pipeline, prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const pipelineRid = assertResource(pipeline, { - prefix, - context: "Argument 1", - }); + "Argument 1", + ); + const device = assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); + const pipelineRid = assertResource(pipeline, prefix, "Argument 1"); assertDeviceMatch(device, pipeline, { prefix, resourceContext: "Argument 1", @@ -4849,32 +4576,18 @@ class GPURenderBundleEncoder { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'setIndexBuffer' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 1", - }); - indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bufferRid = assertResource(buffer, { + webidl.requiredArguments(arguments.length, 2, prefix); + buffer = webidl.converters.GPUBuffer(buffer, prefix, "Argument 1"); + indexFormat = webidl.converters.GPUIndexFormat( + indexFormat, prefix, - context: "Argument 1", - }); + "Argument 2", + ); + offset = webidl.converters.GPUSize64(offset, prefix, "Argument 3"); + size = webidl.converters.GPUSize64(size, prefix, "Argument 4"); + const device = assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); + const bufferRid = assertResource(buffer, prefix, "Argument 1"); assertDeviceMatch(device, buffer, { prefix, resourceContext: "Argument 1", @@ -4899,34 +4612,16 @@ class GPURenderBundleEncoder { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'setVertexBuffer' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - slot = webidl.converters.GPUSize32(slot, { - prefix, - context: "Argument 1", - }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); + webidl.requiredArguments(arguments.length, 2, prefix); + slot = webidl.converters.GPUSize32(slot, prefix, "Argument 1"); + buffer = webidl.converters.GPUBuffer(buffer, prefix, "Argument 2"); + offset = webidl.converters.GPUSize64(offset, prefix, "Argument 3"); if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); + size = webidl.converters.GPUSize64(size, prefix, "Argument 4"); } - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 2", - }); + const device = assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); + const bufferRid = assertResource(buffer, prefix, "Argument 2"); assertDeviceMatch(device, buffer, { prefix, resourceContext: "Argument 2", @@ -4950,28 +4645,29 @@ class GPURenderBundleEncoder { draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'draw' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - vertexCount = webidl.converters.GPUSize32(vertexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { + webidl.requiredArguments(arguments.length, 1, prefix); + vertexCount = webidl.converters.GPUSize32( + vertexCount, prefix, - context: "Argument 2", - }); - firstVertex = webidl.converters.GPUSize32(firstVertex, { + "Argument 1", + ); + instanceCount = webidl.converters.GPUSize32( + instanceCount, prefix, - context: "Argument 3", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { + "Argument 2", + ); + firstVertex = webidl.converters.GPUSize32( + firstVertex, prefix, - context: "Argument 4", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { + "Argument 3", + ); + firstInstance = webidl.converters.GPUSize32( + firstInstance, prefix, - context: "this", - }); + "Argument 4", + ); + assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_bundle_encoder_draw( renderBundleEncoderRid, vertexCount, @@ -4998,32 +4694,26 @@ class GPURenderBundleEncoder { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'drawIndexed' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - indexCount = webidl.converters.GPUSize32(indexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstIndex = webidl.converters.GPUSize32(firstIndex, { - prefix, - context: "Argument 3", - }); - baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { + webidl.requiredArguments(arguments.length, 1, prefix); + indexCount = webidl.converters.GPUSize32(indexCount, prefix, "Argument 1"); + instanceCount = webidl.converters.GPUSize32( + instanceCount, prefix, - context: "Argument 4", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { + "Argument 2", + ); + firstIndex = webidl.converters.GPUSize32(firstIndex, prefix, "Argument 3"); + baseVertex = webidl.converters.GPUSignedOffset32( + baseVertex, prefix, - context: "Argument 5", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { + "Argument 4", + ); + firstInstance = webidl.converters.GPUSize32( + firstInstance, prefix, - context: "this", - }); + "Argument 5", + ); + assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); ops.op_webgpu_render_bundle_encoder_draw_indexed( renderBundleEncoderRid, indexCount, @@ -5042,24 +4732,24 @@ class GPURenderBundleEncoder { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'drawIndirect' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + webidl.requiredArguments(arguments.length, 2, prefix); + indirectBuffer = webidl.converters.GPUBuffer( + indirectBuffer, prefix, - context: "Argument 2", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { + "Argument 1", + ); + indirectOffset = webidl.converters.GPUSize64( + indirectOffset, prefix, - context: "this", - }); - const indirectBufferRid = assertResource(indirectBuffer, { + "Argument 2", + ); + const device = assertDevice(this, prefix, "this"); + const renderBundleEncoderRid = assertResource(this, prefix, "this"); + const indirectBufferRid = assertResource( + indirectBuffer, prefix, - context: "Argument 1", - }); + "Argument 1", + ); assertDeviceMatch(device, indirectBuffer, { prefix, resourceContext: "Argument 1", @@ -5072,12 +4762,17 @@ class GPURenderBundleEncoder { ); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPURenderBundleEncoderPrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPURenderBundleEncoder", GPURenderBundleEncoder); @@ -5117,16 +4812,21 @@ class GPURenderBundle { webidl.illegalConstructor(); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPURenderBundlePrototype, this), + keys: [ + "label", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPURenderBundle", GPURenderBundle); - +const GPURenderBundlePrototype = GPURenderBundle.prototype; /** * @param {string | null} label * @param {InnerGPUDevice} device @@ -5183,29 +4883,31 @@ class GPUQuerySet { this[_count](); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + return inspect( + createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(GPUQuerySetPrototype, this), + keys: [ + "label", + "type", + "count", + ], + }), + inspectOptions, + ); } } GPUObjectBaseMixin("GPUQuerySet", GPUQuerySet); const GPUQuerySetPrototype = GPUQuerySet.prototype; - - - - // Converters - // This needs to be initialized after all of the base classes are implemented, // otherwise their converters might not be available yet. // DICTIONARY: GPUObjectDescriptorBase const dictMembersGPUObjectDescriptorBase = [ - { key: "label", converter: webidl.converters["USVString"] }, + { key: "label", converter: webidl.converters["USVString"], defaultValue: "" }, ]; webidl.converters["GPUObjectDescriptorBase"] = webidl .createDictionaryConverter( @@ -5277,6 +4979,8 @@ webidl.converters["GPUFeatureName"] = webidl.createEnumConverter( "texture-compression-etc2", "texture-compression-astc", "rg11b10ufloat-renderable", + "bgra8unorm-storage", + "float32-filterable", // extended from spec @@ -5285,7 +4989,7 @@ webidl.converters["GPUFeatureName"] = webidl.createEnumConverter( "texture-compression-astc-hdr", "texture-adapter-specific-format-features", // api - "pipeline-statistics-query", + //"pipeline-statistics-query", "timestamp-query-inside-passes", "mappable-primary-buffers", "texture-binding-array", @@ -6172,8 +5876,7 @@ const dictMembersGPUProgrammableStage = [ }, { key: "constants", - converter: - webidl.converters["record"], + converter: webidl.converters["record"], }, ]; webidl.converters["GPUProgrammableStage"] = webidl.createDictionaryConverter( @@ -6862,8 +6565,34 @@ webidl.converters.GPUComputePassEncoder = webidl.createInterfaceConverter( GPUComputePassEncoder.prototype, ); +// DICTIONARY: GPUComputePassTimestampWrites +webidl.converters["GPUComputePassTimestampWrites"] = webidl + .createDictionaryConverter( + "GPUComputePassTimestampWrites", + [ + { + key: "querySet", + converter: webidl.converters["GPUQuerySet"], + required: true, + }, + { + key: "beginningOfPassWriteIndex", + converter: webidl.converters["GPUSize32"], + }, + { + key: "endOfPassWriteIndex", + converter: webidl.converters["GPUSize32"], + }, + ], + ); + // DICTIONARY: GPUComputePassDescriptor -const dictMembersGPUComputePassDescriptor = []; +const dictMembersGPUComputePassDescriptor = [ + { + key: "timestampWrites", + converter: webidl.converters["GPUComputePassTimestampWrites"], + }, +]; webidl.converters["GPUComputePassDescriptor"] = webidl .createDictionaryConverter( "GPUComputePassDescriptor", @@ -7005,6 +6734,27 @@ webidl.converters.GPUQuerySet = webidl.createInterfaceConverter( GPUQuerySet.prototype, ); +// DICTIONARY: GPURenderPassTimestampWrites +webidl.converters["GPURenderPassTimestampWrites"] = webidl + .createDictionaryConverter( + "GPURenderPassTimestampWrites", + [ + { + key: "querySet", + converter: webidl.converters["GPUQuerySet"], + required: true, + }, + { + key: "beginningOfPassWriteIndex", + converter: webidl.converters["GPUSize32"], + }, + { + key: "endOfPassWriteIndex", + converter: webidl.converters["GPUSize32"], + }, + ], + ); + // DICTIONARY: GPURenderPassDescriptor const dictMembersGPURenderPassDescriptor = [ { @@ -7020,6 +6770,14 @@ const dictMembersGPURenderPassDescriptor = [ key: "depthStencilAttachment", converter: webidl.converters["GPURenderPassDepthStencilAttachment"], }, + { + key: "occlusionQuerySet", + converter: webidl.converters["GPUQuerySet"], + }, + { + key: "timestampWrites", + converter: webidl.converters["GPURenderPassTimestampWrites"], + }, ]; webidl.converters["GPURenderPassDescriptor"] = webidl .createDictionaryConverter( @@ -7109,23 +6867,10 @@ webidl.converters["GPUQueryType"] = webidl.createEnumConverter( "GPUQueryType", [ "occlusion", - "pipeline-statistics", "timestamp", ], ); -// ENUM: GPUPipelineStatisticName -webidl.converters["GPUPipelineStatisticName"] = webidl.createEnumConverter( - "GPUPipelineStatisticName", - [ - "vertex-shader-invocations", - "clipper-invocations", - "clipper-primitives-out", - "fragment-shader-invocations", - "compute-shader-invocations", - ], -); - // DICTIONARY: GPUQuerySetDescriptor const dictMembersGPUQuerySetDescriptor = [ { @@ -7216,7 +6961,6 @@ webidl.converters["GPUSignedOffset32"] = (V, opts) => // TYPEDEF: GPUFlagsConstant webidl.converters["GPUFlagsConstant"] = webidl.converters["unsigned long"]; - const gpu = webidl.createBranded(GPU); export { _device, diff --git a/deno_webgpu/Cargo.toml b/deno_webgpu/Cargo.toml index df758a1d9c..b9d281f04d 100644 --- a/deno_webgpu/Cargo.toml +++ b/deno_webgpu/Cargo.toml @@ -34,10 +34,15 @@ features = ["trace", "replay", "serde", "strict_asserts", "wgsl", "gles"] workspace = true features = ["metal"] -# We want the wgpu-core Direct3D backends on Windows. +# We want the wgpu-core Direct3D backend on Windows. [target.'cfg(windows)'.dependencies.wgpu-core] workspace = true -features = ["dx11", "dx12"] +features = ["dx12"] + +[target.'cfg(windows)'.dependencies.wgpu-hal] +version = "0.18.0" +path = "../wgpu-hal" +features = ["windows_rs"] # We want the wgpu-core Vulkan backend on Unix (but not Emscripten) and Windows. [target.'cfg(any(windows, all(unix, not(target_os = "emscripten"))))'.dependencies.wgpu-core] diff --git a/deno_webgpu/README.md b/deno_webgpu/README.md index 2f915dcbbe..1cf031cda2 100644 --- a/deno_webgpu/README.md +++ b/deno_webgpu/README.md @@ -19,7 +19,7 @@ running through our WPT runner. This will be used to validate implementation conformance. GitHub CI doesn't run with GPUs, so testing relies on software like DX WARP & -Vulkan lavapipe. Currently only using DX WARP works, so tests are only run on +Vulkan lavapipe. Currently, only using DX WARP works, so tests are only run on Windows. ## Links diff --git a/deno_webgpu/binding.rs b/deno_webgpu/binding.rs index 9ecb3f5143..5d582e900f 100644 --- a/deno_webgpu/binding.rs +++ b/deno_webgpu/binding.rs @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; @@ -176,12 +176,13 @@ impl From for wgpu_types::BindingType { } } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_bind_group_layout( state: &mut OpState, - device_rid: ResourceId, - label: Option, - entries: Vec, + #[smi] device_rid: ResourceId, + #[string] label: Cow, + #[serde] entries: Vec, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -202,7 +203,7 @@ pub fn op_webgpu_create_bind_group_layout( .collect::>(); let descriptor = wgpu_core::binding_model::BindGroupLayoutDescriptor { - label: label.map(Cow::from), + label: Some(label), entries: Cow::from(entries), }; @@ -213,12 +214,13 @@ pub fn op_webgpu_create_bind_group_layout( ) => state, WebGpuBindGroupLayout) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_pipeline_layout( state: &mut OpState, - device_rid: ResourceId, - label: Option, - bind_group_layouts: Vec, + #[smi] device_rid: ResourceId, + #[string] label: Cow, + #[serde] bind_group_layouts: Vec, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -235,7 +237,7 @@ pub fn op_webgpu_create_pipeline_layout( .collect::, AnyError>>()?; let descriptor = wgpu_core::binding_model::PipelineLayoutDescriptor { - label: label.map(Cow::from), + label: Some(label), bind_group_layouts: Cow::from(bind_group_layouts), push_constant_ranges: Default::default(), }; @@ -257,13 +259,14 @@ pub struct GpuBindGroupEntry { size: Option, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_bind_group( state: &mut OpState, - device_rid: ResourceId, - label: Option, - layout: ResourceId, - entries: Vec, + #[smi] device_rid: ResourceId, + #[string] label: Cow, + #[smi] layout: ResourceId, + #[serde] entries: Vec, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -313,7 +316,7 @@ pub fn op_webgpu_create_bind_group( let bind_group_layout = state.resource_table.get::(layout)?; let descriptor = wgpu_core::binding_model::BindGroupDescriptor { - label: label.map(Cow::from), + label: Some(label), layout: bind_group_layout.1, entries: Cow::from(entries), }; diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index 7c5f9d58c2..171642e1dc 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -3,14 +3,12 @@ use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::futures::channel::oneshot; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; -use deno_core::ZeroCopyBuf; use std::borrow::Cow; use std::cell::RefCell; -use std::convert::TryFrom; use std::rc::Rc; use std::time::Duration; use wgpu_core::resource::BufferAccessResult; @@ -40,12 +38,13 @@ impl Resource for WebGpuBufferMapped { } } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_buffer( state: &mut OpState, - device_rid: ResourceId, - label: Option, - size: u64, + #[smi] device_rid: ResourceId, + #[string] label: Cow, + #[number] size: u64, usage: u32, mapped_at_creation: bool, ) -> Result { @@ -56,7 +55,7 @@ pub fn op_webgpu_create_buffer( let device = device_resource.1; let descriptor = wgpu_core::resource::BufferDescriptor { - label: label.map(Cow::from), + label: Some(label), size, usage: wgpu_types::BufferUsages::from_bits(usage) .ok_or_else(|| type_error("usage is not valid"))?, @@ -70,14 +69,15 @@ pub fn op_webgpu_create_buffer( ) => state, WebGpuBuffer) } -#[op] +#[op2(async)] +#[serde] pub async fn op_webgpu_buffer_get_map_async( state: Rc>, - buffer_rid: ResourceId, - device_rid: ResourceId, + #[smi] buffer_rid: ResourceId, + #[smi] device_rid: ResourceId, mode: u32, - offset: u64, - size: u64, + #[number] offset: u64, + #[number] size: u64, ) -> Result { let (sender, receiver) = oneshot::channel::(); @@ -106,7 +106,7 @@ pub async fn op_webgpu_buffer_get_map_async( 2 => wgpu_core::device::HostMap::Write, _ => unreachable!(), }, - callback: wgpu_core::resource::BufferMapCallback::from_rust(callback), + callback: Some(wgpu_core::resource::BufferMapCallback::from_rust(callback)), } )) .err(); @@ -123,7 +123,7 @@ pub async fn op_webgpu_buffer_get_map_async( { let state = state.borrow(); let instance = state.borrow::(); - gfx_select!(device => instance.device_poll(device, wgpu_types::Maintain::Wait)) + gfx_select!(device => instance.device_poll(device, wgpu_types::Maintain::wait())) .unwrap(); } tokio::time::sleep(Duration::from_millis(10)).await; @@ -143,13 +143,14 @@ pub async fn op_webgpu_buffer_get_map_async( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_buffer_get_mapped_range( state: &mut OpState, - buffer_rid: ResourceId, - offset: u64, - size: Option, - mut buf: ZeroCopyBuf, + #[smi] buffer_rid: ResourceId, + #[number] offset: u64, + #[number] size: Option, + #[buffer] buf: &mut [u8], ) -> Result { let instance = state.borrow::(); let buffer_resource = state.resource_table.get::(buffer_rid)?; @@ -172,12 +173,13 @@ pub fn op_webgpu_buffer_get_mapped_range( Ok(WebGpuResult::rid(rid)) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_buffer_unmap( state: &mut OpState, - buffer_rid: ResourceId, - mapped_rid: ResourceId, - buf: Option, + #[smi] buffer_rid: ResourceId, + #[smi] mapped_rid: ResourceId, + #[buffer] buf: Option<&[u8]>, ) -> Result { let mapped_resource = state .resource_table @@ -188,7 +190,7 @@ pub fn op_webgpu_buffer_unmap( if let Some(buf) = buf { let slice = unsafe { std::slice::from_raw_parts_mut(mapped_resource.0, mapped_resource.1) }; - slice.copy_from_slice(&buf); + slice.copy_from_slice(buf); } gfx_ok!(buffer => instance.buffer_unmap(buffer)) diff --git a/deno_webgpu/bundle.rs b/deno_webgpu/bundle.rs index bcb321f497..b053cf265c 100644 --- a/deno_webgpu/bundle.rs +++ b/deno_webgpu/bundle.rs @@ -2,11 +2,10 @@ use deno_core::error::type_error; use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; -use deno_core::ZeroCopyBuf; use serde::Deserialize; use std::borrow::Cow; use std::cell::RefCell; @@ -40,7 +39,7 @@ impl Resource for WebGpuRenderBundle { #[serde(rename_all = "camelCase")] pub struct CreateRenderBundleEncoderArgs { device_rid: ResourceId, - label: Option, + label: String, color_formats: Vec>, depth_stencil_format: Option, sample_count: u32, @@ -48,10 +47,11 @@ pub struct CreateRenderBundleEncoderArgs { stencil_read_only: bool, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_render_bundle_encoder( state: &mut OpState, - args: CreateRenderBundleEncoderArgs, + #[serde] args: CreateRenderBundleEncoderArgs, ) -> Result { let device_resource = state .resource_table @@ -67,7 +67,7 @@ pub fn op_webgpu_create_render_bundle_encoder( }); let descriptor = wgpu_core::command::RenderBundleEncoderDescriptor { - label: args.label.map(Cow::from), + label: Some(Cow::Owned(args.label)), color_formats: Cow::from(args.color_formats), sample_count: args.sample_count, depth_stencil, @@ -92,11 +92,12 @@ pub fn op_webgpu_create_render_bundle_encoder( Ok(WebGpuResult::rid_err(rid, maybe_err)) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_finish( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, - label: Option, + #[smi] render_bundle_encoder_rid: ResourceId, + #[string] label: Cow, ) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -111,21 +112,22 @@ pub fn op_webgpu_render_bundle_encoder_finish( gfx_put!(render_bundle_encoder.parent() => instance.render_bundle_encoder_finish( render_bundle_encoder, &wgpu_core::command::RenderBundleDescriptor { - label: label.map(Cow::from), + label: Some(label), }, () ) => state, WebGpuRenderBundle) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_set_bind_group( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, + #[smi] render_bundle_encoder_rid: ResourceId, index: u32, - bind_group: ResourceId, - dynamic_offsets_data: ZeroCopyBuf, - dynamic_offsets_data_start: usize, - dynamic_offsets_data_length: usize, + #[smi] bind_group: ResourceId, + #[buffer] dynamic_offsets_data: &[u32], + #[number] dynamic_offsets_data_start: usize, + #[number] dynamic_offsets_data_length: usize, ) -> Result { let bind_group_resource = state .resource_table @@ -134,14 +136,6 @@ pub fn op_webgpu_render_bundle_encoder_set_bind_group( .resource_table .get::(render_bundle_encoder_rid)?; - // Align the data - assert!(dynamic_offsets_data.len() % std::mem::size_of::() == 0); - // SAFETY: A u8 to u32 cast is safe because we asserted that the length is a - // multiple of 4. - let (prefix, dynamic_offsets_data, suffix) = unsafe { dynamic_offsets_data.align_to::() }; - assert!(prefix.is_empty()); - assert!(suffix.is_empty()); - let start = dynamic_offsets_data_start; let len = dynamic_offsets_data_length; @@ -149,7 +143,7 @@ pub fn op_webgpu_render_bundle_encoder_set_bind_group( assert!(start <= dynamic_offsets_data.len()); assert!(len <= dynamic_offsets_data.len() - start); - let dynamic_offsets_data: &[u32] = &dynamic_offsets_data[start..start + len]; + let dynamic_offsets_data = &dynamic_offsets_data[start..start + len]; // SAFETY: the raw pointer and length are of the same slice, and that slice // lives longer than the below function invocation. @@ -166,11 +160,12 @@ pub fn op_webgpu_render_bundle_encoder_set_bind_group( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_push_debug_group( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, - group_label: String, + #[smi] render_bundle_encoder_rid: ResourceId, + #[string] group_label: &str, ) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -189,10 +184,11 @@ pub fn op_webgpu_render_bundle_encoder_push_debug_group( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_pop_debug_group( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, + #[smi] render_bundle_encoder_rid: ResourceId, ) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -205,11 +201,12 @@ pub fn op_webgpu_render_bundle_encoder_pop_debug_group( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_insert_debug_marker( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, - marker_label: String, + #[smi] render_bundle_encoder_rid: ResourceId, + #[string] marker_label: &str, ) -> Result { let render_bundle_encoder_resource = state .resource_table @@ -228,11 +225,12 @@ pub fn op_webgpu_render_bundle_encoder_insert_debug_marker( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_set_pipeline( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, - pipeline: ResourceId, + #[smi] render_bundle_encoder_rid: ResourceId, + #[smi] pipeline: ResourceId, ) -> Result { let render_pipeline_resource = state .resource_table @@ -249,14 +247,15 @@ pub fn op_webgpu_render_bundle_encoder_set_pipeline( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_set_index_buffer( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, - buffer: ResourceId, - index_format: wgpu_types::IndexFormat, - offset: u64, - size: u64, + #[smi] render_bundle_encoder_rid: ResourceId, + #[smi] buffer: ResourceId, + #[serde] index_format: wgpu_types::IndexFormat, + #[number] offset: u64, + #[number] size: u64, ) -> Result { let buffer_resource = state .resource_table @@ -276,14 +275,15 @@ pub fn op_webgpu_render_bundle_encoder_set_index_buffer( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, + #[smi] render_bundle_encoder_rid: ResourceId, slot: u32, - buffer: ResourceId, - offset: u64, - size: Option, + #[smi] buffer: ResourceId, + #[number] offset: u64, + #[number] size: Option, ) -> Result { let buffer_resource = state .resource_table @@ -311,10 +311,11 @@ pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_draw( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, + #[smi] render_bundle_encoder_rid: ResourceId, vertex_count: u32, instance_count: u32, first_vertex: u32, @@ -335,10 +336,11 @@ pub fn op_webgpu_render_bundle_encoder_draw( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_draw_indexed( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, + #[smi] render_bundle_encoder_rid: ResourceId, index_count: u32, instance_count: u32, first_index: u32, @@ -361,12 +363,13 @@ pub fn op_webgpu_render_bundle_encoder_draw_indexed( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_bundle_encoder_draw_indirect( state: &mut OpState, - render_bundle_encoder_rid: ResourceId, - indirect_buffer: ResourceId, - indirect_offset: u64, + #[smi] render_bundle_encoder_rid: ResourceId, + #[smi] indirect_buffer: ResourceId, + #[number] indirect_offset: u64, ) -> Result { let buffer_resource = state .resource_table diff --git a/deno_webgpu/command_encoder.rs b/deno_webgpu/command_encoder.rs index 4857b0a7a7..7079fd96cf 100644 --- a/deno_webgpu/command_encoder.rs +++ b/deno_webgpu/command_encoder.rs @@ -1,7 +1,8 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::WebGpuQuerySet; use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; @@ -44,11 +45,12 @@ impl Resource for WebGpuCommandBuffer { } } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_command_encoder( state: &mut OpState, - device_rid: ResourceId, - label: Option, + #[smi] device_rid: ResourceId, + #[string] label: Cow, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -56,9 +58,7 @@ pub fn op_webgpu_create_command_encoder( .get::(device_rid)?; let device = device_resource.1; - let descriptor = wgpu_types::CommandEncoderDescriptor { - label: label.map(Cow::from), - }; + let descriptor = wgpu_types::CommandEncoderDescriptor { label: Some(label) }; gfx_put!(device => instance.device_create_command_encoder( device, @@ -91,14 +91,24 @@ pub struct GpuRenderPassDepthStencilAttachment { stencil_read_only: bool, } -#[op] +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GPURenderPassTimestampWrites { + query_set: ResourceId, + beginning_of_pass_write_index: Option, + end_of_pass_write_index: Option, +} + +#[op2] +#[serde] pub fn op_webgpu_command_encoder_begin_render_pass( state: &mut OpState, - command_encoder_rid: ResourceId, - label: Option, - color_attachments: Vec>, - depth_stencil_attachment: Option, - occlusion_query_set: Option, + #[smi] command_encoder_rid: ResourceId, + #[string] label: Cow, + #[serde] color_attachments: Vec>, + #[serde] depth_stencil_attachment: Option, + #[smi] occlusion_query_set: Option, + #[serde] timestamp_writes: Option, ) -> Result { let command_encoder_resource = state .resource_table @@ -172,16 +182,31 @@ pub fn op_webgpu_command_encoder_begin_render_pass( }); } + let timestamp_writes = if let Some(timestamp_writes) = timestamp_writes { + let query_set_resource = state + .resource_table + .get::(timestamp_writes.query_set)?; + let query_set = query_set_resource.1; + + Some(wgpu_core::command::RenderPassTimestampWrites { + query_set, + beginning_of_pass_write_index: timestamp_writes.beginning_of_pass_write_index, + end_of_pass_write_index: timestamp_writes.end_of_pass_write_index, + }) + } else { + None + }; + let occlusion_query_set_resource = occlusion_query_set - .map(|rid| state.resource_table.get::(rid)) + .map(|rid| state.resource_table.get::(rid)) .transpose()? .map(|query_set| query_set.1); let descriptor = wgpu_core::command::RenderPassDescriptor { - label: label.map(Cow::from), + label: Some(label), color_attachments: Cow::from(color_attachments), depth_stencil_attachment: processed_depth_stencil_attachment.as_ref(), - timestamp_writes: None, + timestamp_writes: timestamp_writes.as_ref(), occlusion_query_set: occlusion_query_set_resource, }; @@ -196,19 +221,44 @@ pub fn op_webgpu_command_encoder_begin_render_pass( Ok(WebGpuResult::rid(rid)) } -#[op] +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GPUComputePassTimestampWrites { + query_set: ResourceId, + beginning_of_pass_write_index: Option, + end_of_pass_write_index: Option, +} + +#[op2] +#[serde] pub fn op_webgpu_command_encoder_begin_compute_pass( state: &mut OpState, - command_encoder_rid: ResourceId, - label: Option, + #[smi] command_encoder_rid: ResourceId, + #[string] label: Cow, + #[serde] timestamp_writes: Option, ) -> Result { let command_encoder_resource = state .resource_table .get::(command_encoder_rid)?; + let timestamp_writes = if let Some(timestamp_writes) = timestamp_writes { + let query_set_resource = state + .resource_table + .get::(timestamp_writes.query_set)?; + let query_set = query_set_resource.1; + + Some(wgpu_core::command::ComputePassTimestampWrites { + query_set, + beginning_of_pass_write_index: timestamp_writes.beginning_of_pass_write_index, + end_of_pass_write_index: timestamp_writes.end_of_pass_write_index, + }) + } else { + None + }; + let descriptor = wgpu_core::command::ComputePassDescriptor { - label: label.map(Cow::from), - timestamp_writes: None, + label: Some(label), + timestamp_writes: timestamp_writes.as_ref(), }; let compute_pass = @@ -223,15 +273,16 @@ pub fn op_webgpu_command_encoder_begin_compute_pass( Ok(WebGpuResult::rid(rid)) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_copy_buffer_to_buffer( state: &mut OpState, - command_encoder_rid: ResourceId, - source: ResourceId, - source_offset: u64, - destination: ResourceId, - destination_offset: u64, - size: u64, + #[smi] command_encoder_rid: ResourceId, + #[smi] source: ResourceId, + #[number] source_offset: u64, + #[smi] destination: ResourceId, + #[number] destination_offset: u64, + #[number] size: u64, ) -> Result { let instance = state.borrow::(); let command_encoder_resource = state @@ -275,13 +326,14 @@ pub struct GpuImageCopyTexture { pub aspect: wgpu_types::TextureAspect, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_copy_buffer_to_texture( state: &mut OpState, - command_encoder_rid: ResourceId, - source: GpuImageCopyBuffer, - destination: GpuImageCopyTexture, - copy_size: wgpu_types::Extent3d, + #[smi] command_encoder_rid: ResourceId, + #[serde] source: GpuImageCopyBuffer, + #[serde] destination: GpuImageCopyTexture, + #[serde] copy_size: wgpu_types::Extent3d, ) -> Result { let instance = state.borrow::(); let command_encoder_resource = state @@ -317,13 +369,14 @@ pub fn op_webgpu_command_encoder_copy_buffer_to_texture( )) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_copy_texture_to_buffer( state: &mut OpState, - command_encoder_rid: ResourceId, - source: GpuImageCopyTexture, - destination: GpuImageCopyBuffer, - copy_size: wgpu_types::Extent3d, + #[smi] command_encoder_rid: ResourceId, + #[serde] source: GpuImageCopyTexture, + #[serde] destination: GpuImageCopyBuffer, + #[serde] copy_size: wgpu_types::Extent3d, ) -> Result { let instance = state.borrow::(); let command_encoder_resource = state @@ -359,13 +412,14 @@ pub fn op_webgpu_command_encoder_copy_texture_to_buffer( )) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_copy_texture_to_texture( state: &mut OpState, - command_encoder_rid: ResourceId, - source: GpuImageCopyTexture, - destination: GpuImageCopyTexture, - copy_size: wgpu_types::Extent3d, + #[smi] command_encoder_rid: ResourceId, + #[serde] source: GpuImageCopyTexture, + #[serde] destination: GpuImageCopyTexture, + #[serde] copy_size: wgpu_types::Extent3d, ) -> Result { let instance = state.borrow::(); let command_encoder_resource = state @@ -399,13 +453,14 @@ pub fn op_webgpu_command_encoder_copy_texture_to_texture( )) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_clear_buffer( state: &mut OpState, - command_encoder_rid: u32, - buffer_rid: u32, - offset: u64, - size: u64, + #[smi] command_encoder_rid: ResourceId, + #[smi] buffer_rid: ResourceId, + #[number] offset: u64, + #[number] size: u64, ) -> Result { let instance = state.borrow::(); let command_encoder_resource = state @@ -420,15 +475,16 @@ pub fn op_webgpu_command_encoder_clear_buffer( command_encoder, destination_resource.1, offset, - std::num::NonZeroU64::new(size) + Some(size) )) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_push_debug_group( state: &mut OpState, - command_encoder_rid: ResourceId, - group_label: String, + #[smi] command_encoder_rid: ResourceId, + #[string] group_label: &str, ) -> Result { let instance = state.borrow::(); let command_encoder_resource = state @@ -436,13 +492,14 @@ pub fn op_webgpu_command_encoder_push_debug_group( .get::(command_encoder_rid)?; let command_encoder = command_encoder_resource.1; - gfx_ok!(command_encoder => instance.command_encoder_push_debug_group(command_encoder, &group_label)) + gfx_ok!(command_encoder => instance.command_encoder_push_debug_group(command_encoder, group_label)) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_pop_debug_group( state: &mut OpState, - command_encoder_rid: ResourceId, + #[smi] command_encoder_rid: ResourceId, ) -> Result { let instance = state.borrow::(); let command_encoder_resource = state @@ -453,11 +510,12 @@ pub fn op_webgpu_command_encoder_pop_debug_group( gfx_ok!(command_encoder => instance.command_encoder_pop_debug_group(command_encoder)) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_insert_debug_marker( state: &mut OpState, - command_encoder_rid: ResourceId, - marker_label: String, + #[smi] command_encoder_rid: ResourceId, + #[string] marker_label: &str, ) -> Result { let instance = state.borrow::(); let command_encoder_resource = state @@ -467,15 +525,16 @@ pub fn op_webgpu_command_encoder_insert_debug_marker( gfx_ok!(command_encoder => instance.command_encoder_insert_debug_marker( command_encoder, - &marker_label + marker_label )) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_write_timestamp( state: &mut OpState, - command_encoder_rid: ResourceId, - query_set: ResourceId, + #[smi] command_encoder_rid: ResourceId, + #[smi] query_set: ResourceId, query_index: u32, ) -> Result { let instance = state.borrow::(); @@ -494,15 +553,16 @@ pub fn op_webgpu_command_encoder_write_timestamp( )) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_resolve_query_set( state: &mut OpState, - command_encoder_rid: ResourceId, - query_set: ResourceId, + #[smi] command_encoder_rid: ResourceId, + #[smi] query_set: ResourceId, first_query: u32, query_count: u32, - destination: ResourceId, - destination_offset: u64, + #[smi] destination: ResourceId, + #[number] destination_offset: u64, ) -> Result { let instance = state.borrow::(); let command_encoder_resource = state @@ -526,11 +586,12 @@ pub fn op_webgpu_command_encoder_resolve_query_set( )) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_command_encoder_finish( state: &mut OpState, - command_encoder_rid: ResourceId, - label: Option, + #[smi] command_encoder_rid: ResourceId, + #[string] label: Cow, ) -> Result { let command_encoder_resource = state .resource_table @@ -538,9 +599,7 @@ pub fn op_webgpu_command_encoder_finish( let command_encoder = command_encoder_resource.1; let instance = state.borrow::(); - let descriptor = wgpu_types::CommandBufferDescriptor { - label: label.map(Cow::from), - }; + let descriptor = wgpu_types::CommandBufferDescriptor { label: Some(label) }; let (val, maybe_err) = gfx_select!(command_encoder => instance.command_encoder_finish( command_encoder, diff --git a/deno_webgpu/compute_pass.rs b/deno_webgpu/compute_pass.rs index cc70146917..e1c9e29193 100644 --- a/deno_webgpu/compute_pass.rs +++ b/deno_webgpu/compute_pass.rs @@ -1,11 +1,10 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; -use deno_core::ZeroCopyBuf; use std::borrow::Cow; use std::cell::RefCell; @@ -18,11 +17,12 @@ impl Resource for WebGpuComputePass { } } -#[op] +#[op2] +#[serde] pub fn op_webgpu_compute_pass_set_pipeline( state: &mut OpState, - compute_pass_rid: ResourceId, - pipeline: ResourceId, + #[smi] compute_pass_rid: ResourceId, + #[smi] pipeline: ResourceId, ) -> Result { let compute_pipeline_resource = state .resource_table @@ -39,10 +39,11 @@ pub fn op_webgpu_compute_pass_set_pipeline( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_compute_pass_dispatch_workgroups( state: &mut OpState, - compute_pass_rid: ResourceId, + #[smi] compute_pass_rid: ResourceId, x: u32, y: u32, z: u32, @@ -61,12 +62,13 @@ pub fn op_webgpu_compute_pass_dispatch_workgroups( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_compute_pass_dispatch_workgroups_indirect( state: &mut OpState, - compute_pass_rid: ResourceId, - indirect_buffer: ResourceId, - indirect_offset: u64, + #[smi] compute_pass_rid: ResourceId, + #[smi] indirect_buffer: ResourceId, + #[number] indirect_offset: u64, ) -> Result { let buffer_resource = state .resource_table @@ -84,73 +86,12 @@ pub fn op_webgpu_compute_pass_dispatch_workgroups_indirect( Ok(WebGpuResult::empty()) } -#[op] -pub fn op_webgpu_compute_pass_begin_pipeline_statistics_query( - state: &mut OpState, - compute_pass_rid: ResourceId, - query_set: ResourceId, - query_index: u32, -) -> Result { - let compute_pass_resource = state - .resource_table - .get::(compute_pass_rid)?; - let query_set_resource = state - .resource_table - .get::(query_set)?; - - wgpu_core::command::compute_ffi::wgpu_compute_pass_begin_pipeline_statistics_query( - &mut compute_pass_resource.0.borrow_mut(), - query_set_resource.1, - query_index, - ); - - Ok(WebGpuResult::empty()) -} - -#[op] -pub fn op_webgpu_compute_pass_end_pipeline_statistics_query( - state: &mut OpState, - compute_pass_rid: ResourceId, -) -> Result { - let compute_pass_resource = state - .resource_table - .get::(compute_pass_rid)?; - - wgpu_core::command::compute_ffi::wgpu_compute_pass_end_pipeline_statistics_query( - &mut compute_pass_resource.0.borrow_mut(), - ); - - Ok(WebGpuResult::empty()) -} - -#[op] -pub fn op_webgpu_compute_pass_write_timestamp( - state: &mut OpState, - compute_pass_rid: ResourceId, - query_set: ResourceId, - query_index: u32, -) -> Result { - let compute_pass_resource = state - .resource_table - .get::(compute_pass_rid)?; - let query_set_resource = state - .resource_table - .get::(query_set)?; - - wgpu_core::command::compute_ffi::wgpu_compute_pass_write_timestamp( - &mut compute_pass_resource.0.borrow_mut(), - query_set_resource.1, - query_index, - ); - - Ok(WebGpuResult::empty()) -} - -#[op] +#[op2] +#[serde] pub fn op_webgpu_compute_pass_end( state: &mut OpState, - command_encoder_rid: ResourceId, - compute_pass_rid: ResourceId, + #[smi] command_encoder_rid: ResourceId, + #[smi] compute_pass_rid: ResourceId, ) -> Result { let command_encoder_resource = state @@ -169,15 +110,16 @@ pub fn op_webgpu_compute_pass_end( )) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_compute_pass_set_bind_group( state: &mut OpState, - compute_pass_rid: ResourceId, + #[smi] compute_pass_rid: ResourceId, index: u32, - bind_group: ResourceId, - dynamic_offsets_data: ZeroCopyBuf, - dynamic_offsets_data_start: usize, - dynamic_offsets_data_length: usize, + #[smi] bind_group: ResourceId, + #[buffer] dynamic_offsets_data: &[u32], + #[number] dynamic_offsets_data_start: usize, + #[number] dynamic_offsets_data_length: usize, ) -> Result { let bind_group_resource = state .resource_table @@ -186,14 +128,6 @@ pub fn op_webgpu_compute_pass_set_bind_group( .resource_table .get::(compute_pass_rid)?; - // Align the data - assert!(dynamic_offsets_data_start % std::mem::size_of::() == 0); - // SAFETY: A u8 to u32 cast is safe because we asserted that the length is a - // multiple of 4. - let (prefix, dynamic_offsets_data, suffix) = unsafe { dynamic_offsets_data.align_to::() }; - assert!(prefix.is_empty()); - assert!(suffix.is_empty()); - let start = dynamic_offsets_data_start; let len = dynamic_offsets_data_length; @@ -218,11 +152,12 @@ pub fn op_webgpu_compute_pass_set_bind_group( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_compute_pass_push_debug_group( state: &mut OpState, - compute_pass_rid: ResourceId, - group_label: String, + #[smi] compute_pass_rid: ResourceId, + #[string] group_label: &str, ) -> Result { let compute_pass_resource = state .resource_table @@ -242,10 +177,11 @@ pub fn op_webgpu_compute_pass_push_debug_group( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_compute_pass_pop_debug_group( state: &mut OpState, - compute_pass_rid: ResourceId, + #[smi] compute_pass_rid: ResourceId, ) -> Result { let compute_pass_resource = state .resource_table @@ -258,11 +194,12 @@ pub fn op_webgpu_compute_pass_pop_debug_group( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_compute_pass_insert_debug_marker( state: &mut OpState, - compute_pass_rid: ResourceId, - marker_label: String, + #[smi] compute_pass_rid: ResourceId, + #[string] marker_label: &str, ) -> Result { let compute_pass_resource = state .resource_table diff --git a/deno_webgpu/error.rs b/deno_webgpu/error.rs index a68592adfc..6c509a80d3 100644 --- a/deno_webgpu/error.rs +++ b/deno_webgpu/error.rs @@ -104,9 +104,7 @@ impl From for WebGpuError { match err { DeviceError::Lost => WebGpuError::Lost, DeviceError::OutOfMemory => WebGpuError::OutOfMemory, - DeviceError::ResourceCreationFailed | DeviceError::Invalid => { - WebGpuError::Validation(fmt_err(&err)) - } + _ => WebGpuError::Validation(fmt_err(&err)), } } } diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs index 92a6a51334..15151ce952 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -3,7 +3,7 @@ #![warn(unsafe_op_in_unsafe_fn)] use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; @@ -12,7 +12,6 @@ use serde::Serialize; use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashSet; -use std::convert::TryFrom; use std::rc::Rc; pub use wgpu_core; pub use wgpu_types; @@ -34,8 +33,6 @@ mod macros { wgpu_types::Backend::Metal => $global.$method::( $($param),* ), #[cfg(all(not(target_arch = "wasm32"), windows))] wgpu_types::Backend::Dx12 => $global.$method::( $($param),* ), - #[cfg(all(not(target_arch = "wasm32"), windows))] - wgpu_types::Backend::Dx11 => $global.$method::( $($param),* ), #[cfg(any( all(unix, not(target_os = "macos"), not(target_os = "ios")), feature = "angle", @@ -181,9 +178,8 @@ deno_core::extension!( render_pass::op_webgpu_render_pass_set_scissor_rect, render_pass::op_webgpu_render_pass_set_blend_constant, render_pass::op_webgpu_render_pass_set_stencil_reference, - render_pass::op_webgpu_render_pass_begin_pipeline_statistics_query, - render_pass::op_webgpu_render_pass_end_pipeline_statistics_query, - render_pass::op_webgpu_render_pass_write_timestamp, + render_pass::op_webgpu_render_pass_begin_occlusion_query, + render_pass::op_webgpu_render_pass_end_occlusion_query, render_pass::op_webgpu_render_pass_execute_bundles, render_pass::op_webgpu_render_pass_end, render_pass::op_webgpu_render_pass_set_bind_group, @@ -200,9 +196,6 @@ deno_core::extension!( compute_pass::op_webgpu_compute_pass_set_pipeline, compute_pass::op_webgpu_compute_pass_dispatch_workgroups, compute_pass::op_webgpu_compute_pass_dispatch_workgroups_indirect, - compute_pass::op_webgpu_compute_pass_begin_pipeline_statistics_query, - compute_pass::op_webgpu_compute_pass_end_pipeline_statistics_query, - compute_pass::op_webgpu_compute_pass_write_timestamp, compute_pass::op_webgpu_compute_pass_end, compute_pass::op_webgpu_compute_pass_set_bind_group, compute_pass::op_webgpu_compute_pass_push_debug_group, @@ -268,6 +261,12 @@ fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> { if features.contains(wgpu_types::Features::RG11B10UFLOAT_RENDERABLE) { return_features.push("rg11b10ufloat-renderable"); } + if features.contains(wgpu_types::Features::BGRA8UNORM_STORAGE) { + return_features.push("bgra8unorm-storage"); + } + if features.contains(wgpu_types::Features::FLOAT32_FILTERABLE) { + return_features.push("float32-filterable"); + } // extended from spec @@ -365,6 +364,9 @@ fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> { if features.contains(wgpu_types::Features::SHADER_EARLY_DEPTH_TEST) { return_features.push("shader-early-depth-test"); } + if features.contains(wgpu_types::Features::SHADER_UNUSED_VERTEX_OUTPUT) { + return_features.push("shader-unused-vertex-output"); + } return_features } @@ -385,10 +387,11 @@ pub struct GpuAdapterDevice { is_software: bool, } -#[op] +#[op2(async)] +#[serde] pub async fn op_webgpu_request_adapter( state: Rc>, - power_preference: Option, + #[serde] power_preference: Option, force_fallback_adapter: bool, ) -> Result { let mut state = state.borrow_mut(); @@ -405,6 +408,7 @@ pub async fn op_webgpu_request_adapter( wgpu_core::identity::IdentityManagerFactory, wgpu_types::InstanceDescriptor { backends, + flags: wgpu_types::InstanceFlags::from_build_config(), dx12_shader_compiler: wgpu_types::Dx12Compiler::Fxc, gles_minor_version: wgpu_types::Gles3MinorVersion::default(), }, @@ -491,6 +495,14 @@ impl From for wgpu_types::Features { wgpu_types::Features::RG11B10UFLOAT_RENDERABLE, required_features.0.contains("rg11b10ufloat-renderable"), ); + features.set( + wgpu_types::Features::BGRA8UNORM_STORAGE, + required_features.0.contains("bgra8unorm-storage"), + ); + features.set( + wgpu_types::Features::FLOAT32_FILTERABLE, + required_features.0.contains("float32-filterable"), + ); // extended from spec @@ -625,18 +637,23 @@ impl From for wgpu_types::Features { wgpu_types::Features::SHADER_EARLY_DEPTH_TEST, required_features.0.contains("shader-early-depth-test"), ); + features.set( + wgpu_types::Features::SHADER_UNUSED_VERTEX_OUTPUT, + required_features.0.contains("shader-unused-vertex-output"), + ); features } } -#[op] +#[op2(async)] +#[serde] pub async fn op_webgpu_request_device( state: Rc>, - adapter_rid: ResourceId, - label: Option, - required_features: GpuRequiredFeatures, - required_limits: Option, + #[smi] adapter_rid: ResourceId, + #[string] label: String, + #[serde] required_features: GpuRequiredFeatures, + #[serde] required_limits: Option, ) -> Result { let mut state = state.borrow_mut(); let adapter_resource = state.resource_table.get::(adapter_rid)?; @@ -644,15 +661,16 @@ pub async fn op_webgpu_request_device( let instance = state.borrow::(); let descriptor = wgpu_types::DeviceDescriptor { - label: label.map(Cow::from), - features: required_features.into(), - limits: required_limits.unwrap_or_default(), + label: Some(Cow::Owned(label)), + required_features: required_features.into(), + required_limits: required_limits.unwrap_or_default(), }; - let (device, maybe_err) = gfx_select!(adapter => instance.adapter_request_device( + let (device, _queue, maybe_err) = gfx_select!(adapter => instance.adapter_request_device( adapter, &descriptor, std::env::var("DENO_WEBGPU_TRACE").ok().as_ref().map(std::path::Path::new), + (), () )); if let Some(err) = maybe_err { @@ -684,10 +702,11 @@ pub struct GPUAdapterInfo { description: String, } -#[op] +#[op2(async)] +#[serde] pub async fn op_webgpu_request_adapter_info( state: Rc>, - adapter_rid: ResourceId, + #[smi] adapter_rid: ResourceId, ) -> Result { let state = state.borrow_mut(); let adapter_resource = state.resource_table.get::(adapter_rid)?; @@ -708,7 +727,7 @@ pub async fn op_webgpu_request_adapter_info( #[serde(rename_all = "camelCase")] pub struct CreateQuerySetArgs { device_rid: ResourceId, - label: Option, + label: String, #[serde(flatten)] r#type: GpuQueryType, count: u32, @@ -718,10 +737,6 @@ pub struct CreateQuerySetArgs { #[serde(rename_all = "kebab-case", tag = "type")] enum GpuQueryType { Occlusion, - #[serde(rename_all = "camelCase")] - PipelineStatistics { - pipeline_statistics: HashSet, - }, Timestamp, } @@ -729,47 +744,23 @@ impl From for wgpu_types::QueryType { fn from(query_type: GpuQueryType) -> Self { match query_type { GpuQueryType::Occlusion => wgpu_types::QueryType::Occlusion, - GpuQueryType::PipelineStatistics { - pipeline_statistics, - } => { - use wgpu_types::PipelineStatisticsTypes; - - let mut types = PipelineStatisticsTypes::empty(); - - if pipeline_statistics.contains("vertex-shader-invocations") { - types.set(PipelineStatisticsTypes::VERTEX_SHADER_INVOCATIONS, true); - } - if pipeline_statistics.contains("clipper-invocations") { - types.set(PipelineStatisticsTypes::CLIPPER_INVOCATIONS, true); - } - if pipeline_statistics.contains("clipper-primitives-out") { - types.set(PipelineStatisticsTypes::CLIPPER_PRIMITIVES_OUT, true); - } - if pipeline_statistics.contains("fragment-shader-invocations") { - types.set(PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS, true); - } - if pipeline_statistics.contains("compute-shader-invocations") { - types.set(PipelineStatisticsTypes::COMPUTE_SHADER_INVOCATIONS, true); - } - - wgpu_types::QueryType::PipelineStatistics(types) - } GpuQueryType::Timestamp => wgpu_types::QueryType::Timestamp, } } } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_query_set( state: &mut OpState, - args: CreateQuerySetArgs, + #[serde] args: CreateQuerySetArgs, ) -> Result { let device_resource = state.resource_table.get::(args.device_rid)?; let device = device_resource.1; let instance = state.borrow::(); let descriptor = wgpu_types::QuerySetDescriptor { - label: args.label.map(Cow::from), + label: Some(Cow::Owned(args.label)), ty: args.r#type.into(), count: args.count, }; diff --git a/deno_webgpu/pipeline.rs b/deno_webgpu/pipeline.rs index 13589df2da..e0555b9d1e 100644 --- a/deno_webgpu/pipeline.rs +++ b/deno_webgpu/pipeline.rs @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; @@ -81,13 +81,14 @@ pub struct GpuProgrammableStage { // constants: HashMap } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_compute_pipeline( state: &mut OpState, - device_rid: ResourceId, - label: Option, - layout: GPUPipelineLayoutOrGPUAutoLayoutMode, - compute: GpuProgrammableStage, + #[smi] device_rid: ResourceId, + #[string] label: Cow, + #[serde] layout: GPUPipelineLayoutOrGPUAutoLayoutMode, + #[serde] compute: GpuProgrammableStage, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -108,7 +109,7 @@ pub fn op_webgpu_create_compute_pipeline( .get::(compute.module)?; let descriptor = wgpu_core::pipeline::ComputePipelineDescriptor { - label: label.map(Cow::from), + label: Some(label), layout: pipeline_layout, stage: wgpu_core::pipeline::ProgrammableStageDescriptor { module: compute_shader_module_resource.1, @@ -148,10 +149,11 @@ pub struct PipelineLayout { err: Option, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_compute_pipeline_get_bind_group_layout( state: &mut OpState, - compute_pipeline_rid: ResourceId, + #[smi] compute_pipeline_rid: ResourceId, index: u32, ) -> Result { let instance = state.borrow::(); @@ -314,7 +316,7 @@ struct GpuFragmentState { #[serde(rename_all = "camelCase")] pub struct CreateRenderPipelineArgs { device_rid: ResourceId, - label: Option, + label: String, layout: GPUPipelineLayoutOrGPUAutoLayoutMode, vertex: GpuVertexState, primitive: GpuPrimitiveState, @@ -323,10 +325,11 @@ pub struct CreateRenderPipelineArgs { fragment: Option, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_render_pipeline( state: &mut OpState, - args: CreateRenderPipelineArgs, + #[serde] args: CreateRenderPipelineArgs, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -372,7 +375,7 @@ pub fn op_webgpu_create_render_pipeline( .collect(); let descriptor = wgpu_core::pipeline::RenderPipelineDescriptor { - label: args.label.map(Cow::Owned), + label: Some(Cow::Owned(args.label)), layout, vertex: wgpu_core::pipeline::VertexState { stage: wgpu_core::pipeline::ProgrammableStageDescriptor { @@ -412,10 +415,11 @@ pub fn op_webgpu_create_render_pipeline( Ok(WebGpuResult::rid_err(rid, maybe_err)) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pipeline_get_bind_group_layout( state: &mut OpState, - render_pipeline_rid: ResourceId, + #[smi] render_pipeline_rid: ResourceId, index: u32, ) -> Result { let instance = state.borrow::(); diff --git a/deno_webgpu/queue.rs b/deno_webgpu/queue.rs index 2845990776..1f6258935f 100644 --- a/deno_webgpu/queue.rs +++ b/deno_webgpu/queue.rs @@ -1,21 +1,23 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::command_encoder::WebGpuCommandBuffer; use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; +use deno_core::Resource; use deno_core::ResourceId; -use deno_core::ZeroCopyBuf; use serde::Deserialize; use super::error::WebGpuResult; type WebGpuQueue = super::WebGpuDevice; -#[op] +#[op2] +#[serde] pub fn op_webgpu_queue_submit( state: &mut OpState, - queue_rid: ResourceId, - command_buffers: Vec, + #[smi] queue_rid: ResourceId, + #[serde] command_buffers: Vec, ) -> Result { let instance = state.borrow::(); let queue_resource = state.resource_table.get::(queue_rid)?; @@ -24,9 +26,7 @@ pub fn op_webgpu_queue_submit( let ids = command_buffers .iter() .map(|rid| { - let buffer_resource = state - .resource_table - .get::(*rid)?; + let buffer_resource = state.resource_table.get::(*rid)?; let mut id = buffer_resource.1.borrow_mut(); Ok(id.take().unwrap()) }) @@ -35,7 +35,8 @@ pub fn op_webgpu_queue_submit( let maybe_err = gfx_select!(queue => instance.queue_submit(queue, &ids)).err(); for rid in command_buffers { - state.resource_table.close(rid)?; + let resource = state.resource_table.take::(rid)?; + resource.close(); } Ok(WebGpuResult::maybe_err(maybe_err)) @@ -59,15 +60,16 @@ impl From for wgpu_types::ImageDataLayout { } } -#[op] +#[op2] +#[serde] pub fn op_webgpu_write_buffer( state: &mut OpState, - queue_rid: ResourceId, - buffer: ResourceId, - buffer_offset: u64, - data_offset: usize, - size: Option, - buf: ZeroCopyBuf, + #[smi] queue_rid: ResourceId, + #[smi] buffer: ResourceId, + #[number] buffer_offset: u64, + #[number] data_offset: usize, + #[number] size: Option, + #[buffer] buf: &[u8], ) -> Result { let instance = state.borrow::(); let buffer_resource = state @@ -92,14 +94,15 @@ pub fn op_webgpu_write_buffer( Ok(WebGpuResult::maybe_err(maybe_err)) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_write_texture( state: &mut OpState, - queue_rid: ResourceId, - destination: super::command_encoder::GpuImageCopyTexture, - data_layout: GpuImageDataLayout, - size: wgpu_types::Extent3d, - buf: ZeroCopyBuf, + #[smi] queue_rid: ResourceId, + #[serde] destination: super::command_encoder::GpuImageCopyTexture, + #[serde] data_layout: GpuImageDataLayout, + #[serde] size: wgpu_types::Extent3d, + #[buffer] buf: &[u8], ) -> Result { let instance = state.borrow::(); let texture_resource = state @@ -119,7 +122,7 @@ pub fn op_webgpu_write_texture( gfx_ok!(queue => instance.queue_write_texture( queue, &destination, - &*buf, + buf, &data_layout, &size )) diff --git a/deno_webgpu/render_pass.rs b/deno_webgpu/render_pass.rs index 678990ea3d..47b98c91fd 100644 --- a/deno_webgpu/render_pass.rs +++ b/deno_webgpu/render_pass.rs @@ -2,11 +2,10 @@ use deno_core::error::type_error; use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; -use deno_core::ZeroCopyBuf; use serde::Deserialize; use std::borrow::Cow; use std::cell::RefCell; @@ -32,10 +31,11 @@ pub struct RenderPassSetViewportArgs { max_depth: f32, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_set_viewport( state: &mut OpState, - args: RenderPassSetViewportArgs, + #[serde] args: RenderPassSetViewportArgs, ) -> Result { let render_pass_resource = state .resource_table @@ -54,10 +54,11 @@ pub fn op_webgpu_render_pass_set_viewport( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_set_scissor_rect( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, x: u32, y: u32, width: u32, @@ -78,11 +79,12 @@ pub fn op_webgpu_render_pass_set_scissor_rect( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_set_blend_constant( state: &mut OpState, - render_pass_rid: ResourceId, - color: wgpu_types::Color, + #[smi] render_pass_rid: ResourceId, + #[serde] color: wgpu_types::Color, ) -> Result { let render_pass_resource = state .resource_table @@ -96,10 +98,11 @@ pub fn op_webgpu_render_pass_set_blend_constant( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_set_stencil_reference( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, reference: u32, ) -> Result { let render_pass_resource = state @@ -114,10 +117,11 @@ pub fn op_webgpu_render_pass_set_stencil_reference( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_begin_occlusion_query( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, query_index: u32, ) -> Result { let render_pass_resource = state @@ -132,10 +136,11 @@ pub fn op_webgpu_render_pass_begin_occlusion_query( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_end_occlusion_query( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, ) -> Result { let render_pass_resource = state .resource_table @@ -148,73 +153,12 @@ pub fn op_webgpu_render_pass_end_occlusion_query( Ok(WebGpuResult::empty()) } -#[op] -pub fn op_webgpu_render_pass_begin_pipeline_statistics_query( - state: &mut OpState, - render_pass_rid: ResourceId, - query_set: u32, - query_index: u32, -) -> Result { - let render_pass_resource = state - .resource_table - .get::(render_pass_rid)?; - let query_set_resource = state - .resource_table - .get::(query_set)?; - - wgpu_core::command::render_ffi::wgpu_render_pass_begin_pipeline_statistics_query( - &mut render_pass_resource.0.borrow_mut(), - query_set_resource.1, - query_index, - ); - - Ok(WebGpuResult::empty()) -} - -#[op] -pub fn op_webgpu_render_pass_end_pipeline_statistics_query( - state: &mut OpState, - render_pass_rid: ResourceId, -) -> Result { - let render_pass_resource = state - .resource_table - .get::(render_pass_rid)?; - - wgpu_core::command::render_ffi::wgpu_render_pass_end_pipeline_statistics_query( - &mut render_pass_resource.0.borrow_mut(), - ); - - Ok(WebGpuResult::empty()) -} - -#[op] -pub fn op_webgpu_render_pass_write_timestamp( - state: &mut OpState, - render_pass_rid: ResourceId, - query_set: u32, - query_index: u32, -) -> Result { - let render_pass_resource = state - .resource_table - .get::(render_pass_rid)?; - let query_set_resource = state - .resource_table - .get::(query_set)?; - - wgpu_core::command::render_ffi::wgpu_render_pass_write_timestamp( - &mut render_pass_resource.0.borrow_mut(), - query_set_resource.1, - query_index, - ); - - Ok(WebGpuResult::empty()) -} - -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_execute_bundles( state: &mut OpState, - render_pass_rid: ResourceId, - bundles: Vec, + #[smi] render_pass_rid: ResourceId, + #[serde] bundles: Vec, ) -> Result { let bundles = bundles .iter() @@ -243,11 +187,12 @@ pub fn op_webgpu_render_pass_execute_bundles( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_end( state: &mut OpState, - command_encoder_rid: ResourceId, - render_pass_rid: ResourceId, + #[smi] command_encoder_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, ) -> Result { let command_encoder_resource = state @@ -263,15 +208,16 @@ pub fn op_webgpu_render_pass_end( gfx_ok!(command_encoder => instance.command_encoder_run_render_pass(command_encoder, render_pass)) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_set_bind_group( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, index: u32, bind_group: u32, - dynamic_offsets_data: ZeroCopyBuf, - dynamic_offsets_data_start: usize, - dynamic_offsets_data_length: usize, + #[buffer] dynamic_offsets_data: &[u32], + #[number] dynamic_offsets_data_start: usize, + #[number] dynamic_offsets_data_length: usize, ) -> Result { let bind_group_resource = state .resource_table @@ -280,14 +226,6 @@ pub fn op_webgpu_render_pass_set_bind_group( .resource_table .get::(render_pass_rid)?; - // Align the data - assert_eq!(dynamic_offsets_data_start % std::mem::size_of::(), 0); - // SAFETY: A u8 to u32 cast is safe because we asserted that the length is a - // multiple of 4. - let (prefix, dynamic_offsets_data, suffix) = unsafe { dynamic_offsets_data.align_to::() }; - assert!(prefix.is_empty()); - assert!(suffix.is_empty()); - let start = dynamic_offsets_data_start; let len = dynamic_offsets_data_length; @@ -312,11 +250,12 @@ pub fn op_webgpu_render_pass_set_bind_group( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_push_debug_group( state: &mut OpState, - render_pass_rid: ResourceId, - group_label: String, + #[smi] render_pass_rid: ResourceId, + #[string] group_label: &str, ) -> Result { let render_pass_resource = state .resource_table @@ -336,10 +275,11 @@ pub fn op_webgpu_render_pass_push_debug_group( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_pop_debug_group( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, ) -> Result { let render_pass_resource = state .resource_table @@ -352,11 +292,12 @@ pub fn op_webgpu_render_pass_pop_debug_group( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_insert_debug_marker( state: &mut OpState, - render_pass_rid: ResourceId, - marker_label: String, + #[smi] render_pass_rid: ResourceId, + #[string] marker_label: &str, ) -> Result { let render_pass_resource = state .resource_table @@ -376,10 +317,11 @@ pub fn op_webgpu_render_pass_insert_debug_marker( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_set_pipeline( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, pipeline: u32, ) -> Result { let render_pipeline_resource = state @@ -397,14 +339,15 @@ pub fn op_webgpu_render_pass_set_pipeline( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_set_index_buffer( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, buffer: u32, - index_format: wgpu_types::IndexFormat, - offset: u64, - size: Option, + #[serde] index_format: wgpu_types::IndexFormat, + #[number] offset: u64, + #[number] size: Option, ) -> Result { let buffer_resource = state .resource_table @@ -432,14 +375,15 @@ pub fn op_webgpu_render_pass_set_index_buffer( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_set_vertex_buffer( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, slot: u32, buffer: u32, - offset: u64, - size: Option, + #[number] offset: u64, + #[number] size: Option, ) -> Result { let buffer_resource = state .resource_table @@ -468,10 +412,11 @@ pub fn op_webgpu_render_pass_set_vertex_buffer( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_draw( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, vertex_count: u32, instance_count: u32, first_vertex: u32, @@ -492,10 +437,11 @@ pub fn op_webgpu_render_pass_draw( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_draw_indexed( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, index_count: u32, instance_count: u32, first_index: u32, @@ -518,12 +464,13 @@ pub fn op_webgpu_render_pass_draw_indexed( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_draw_indirect( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, indirect_buffer: u32, - indirect_offset: u64, + #[number] indirect_offset: u64, ) -> Result { let buffer_resource = state .resource_table @@ -541,12 +488,13 @@ pub fn op_webgpu_render_pass_draw_indirect( Ok(WebGpuResult::empty()) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_render_pass_draw_indexed_indirect( state: &mut OpState, - render_pass_rid: ResourceId, + #[smi] render_pass_rid: ResourceId, indirect_buffer: u32, - indirect_offset: u64, + #[number] indirect_offset: u64, ) -> Result { let buffer_resource = state .resource_table diff --git a/deno_webgpu/sampler.rs b/deno_webgpu/sampler.rs index d064ba2ebe..6f9b66ad4d 100644 --- a/deno_webgpu/sampler.rs +++ b/deno_webgpu/sampler.rs @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; @@ -30,7 +30,7 @@ impl Resource for WebGpuSampler { #[serde(rename_all = "camelCase")] pub struct CreateSamplerArgs { device_rid: ResourceId, - label: Option, + label: String, address_mode_u: wgpu_types::AddressMode, address_mode_v: wgpu_types::AddressMode, address_mode_w: wgpu_types::AddressMode, @@ -43,10 +43,11 @@ pub struct CreateSamplerArgs { max_anisotropy: u16, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_sampler( state: &mut OpState, - args: CreateSamplerArgs, + #[serde] args: CreateSamplerArgs, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -55,7 +56,7 @@ pub fn op_webgpu_create_sampler( let device = device_resource.1; let descriptor = wgpu_core::resource::SamplerDescriptor { - label: args.label.map(Cow::from), + label: Some(Cow::Owned(args.label)), address_modes: [ args.address_mode_u, args.address_mode_v, diff --git a/deno_webgpu/shader.rs b/deno_webgpu/shader.rs index fb4f316926..f7cce24281 100644 --- a/deno_webgpu/shader.rs +++ b/deno_webgpu/shader.rs @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; @@ -25,12 +25,13 @@ impl Resource for WebGpuShaderModule { } } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_shader_module( state: &mut OpState, - device_rid: ResourceId, - label: Option, - code: String, + #[smi] device_rid: ResourceId, + #[string] label: Cow, + #[string] code: Cow, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -38,10 +39,10 @@ pub fn op_webgpu_create_shader_module( .get::(device_rid)?; let device = device_resource.1; - let source = wgpu_core::pipeline::ShaderModuleSource::Wgsl(Cow::from(code)); + let source = wgpu_core::pipeline::ShaderModuleSource::Wgsl(code); let descriptor = wgpu_core::pipeline::ShaderModuleDescriptor { - label: label.map(Cow::from), + label: Some(label), shader_bound_checks: wgpu_types::ShaderBoundChecks::default(), }; diff --git a/deno_webgpu/surface.rs b/deno_webgpu/surface.rs index 8f797f12a5..1ac9d8704d 100644 --- a/deno_webgpu/surface.rs +++ b/deno_webgpu/surface.rs @@ -2,7 +2,7 @@ use super::WebGpuResult; use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; @@ -51,10 +51,11 @@ pub struct SurfaceConfigureArgs { view_formats: Vec, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_surface_configure( state: &mut OpState, - args: SurfaceConfigureArgs, + #[serde] args: SurfaceConfigureArgs, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -81,11 +82,12 @@ pub fn op_webgpu_surface_configure( Ok(WebGpuResult::maybe_err(err)) } -#[op] +#[op2] +#[serde] pub fn op_webgpu_surface_get_current_texture( state: &mut OpState, - device_rid: ResourceId, - surface_rid: ResourceId, + #[smi] device_rid: ResourceId, + #[smi] surface_rid: ResourceId, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -111,11 +113,11 @@ pub fn op_webgpu_surface_get_current_texture( } } -#[op] +#[op2(fast)] pub fn op_webgpu_surface_present( state: &mut OpState, - device_rid: ResourceId, - surface_rid: ResourceId, + #[smi] device_rid: ResourceId, + #[smi] surface_rid: ResourceId, ) -> Result<(), AnyError> { let instance = state.borrow::(); let device_resource = state diff --git a/deno_webgpu/texture.rs b/deno_webgpu/texture.rs index 92c8457071..5eadd5b3c2 100644 --- a/deno_webgpu/texture.rs +++ b/deno_webgpu/texture.rs @@ -1,7 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use deno_core::error::AnyError; -use deno_core::op; +use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; @@ -48,7 +48,7 @@ impl Resource for WebGpuTextureView { #[serde(rename_all = "camelCase")] pub struct CreateTextureArgs { device_rid: ResourceId, - label: Option, + label: String, size: wgpu_types::Extent3d, mip_level_count: u32, sample_count: u32, @@ -58,10 +58,11 @@ pub struct CreateTextureArgs { view_formats: Vec, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_texture( state: &mut OpState, - args: CreateTextureArgs, + #[serde] args: CreateTextureArgs, ) -> Result { let instance = state.borrow::(); let device_resource = state @@ -70,7 +71,7 @@ pub fn op_webgpu_create_texture( let device = device_resource.1; let descriptor = wgpu_core::resource::TextureDescriptor { - label: args.label.map(Cow::from), + label: Some(Cow::Owned(args.label)), size: args.size, mip_level_count: args.mip_level_count, sample_count: args.sample_count, @@ -99,17 +100,18 @@ pub fn op_webgpu_create_texture( #[serde(rename_all = "camelCase")] pub struct CreateTextureViewArgs { texture_rid: ResourceId, - label: Option, + label: String, format: Option, dimension: Option, #[serde(flatten)] range: wgpu_types::ImageSubresourceRange, } -#[op] +#[op2] +#[serde] pub fn op_webgpu_create_texture_view( state: &mut OpState, - args: CreateTextureViewArgs, + #[serde] args: CreateTextureViewArgs, ) -> Result { let instance = state.borrow::(); let texture_resource = state @@ -118,7 +120,7 @@ pub fn op_webgpu_create_texture_view( let texture = texture_resource.id; let descriptor = wgpu_core::resource::TextureViewDescriptor { - label: args.label.map(Cow::from), + label: Some(Cow::Owned(args.label)), format: args.format, dimension: args.dimension, range: args.range, diff --git a/deno_webgpu/webgpu.idl b/deno_webgpu/webgpu.idl index f2fea59c9f..bf4da0124b 100644 --- a/deno_webgpu/webgpu.idl +++ b/deno_webgpu/webgpu.idl @@ -3,7 +3,7 @@ interface mixin GPUObjectBase { }; dictionary GPUObjectDescriptorBase { - USVString label; + USVString label = ""; }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -102,6 +102,8 @@ enum GPUFeatureName { // shader "shader-f16", "rg11b10ufloat-renderable", + "bgra8unorm-storage", + "float32-filterable", // extended from spec @@ -563,7 +565,8 @@ interface GPUPipelineLayout { }; GPUPipelineLayout includes GPUObjectBase; -dictionary GPUPipelineLayoutDescriptor : GPUObjectDescriptorBase { +dictionary GPUPipelineLayoutDescriptor + : GPUObjectDescriptorBase { required sequence bindGroupLayouts; }; @@ -572,7 +575,8 @@ interface GPUShaderModule { }; GPUShaderModule includes GPUObjectBase; -dictionary GPUShaderModuleDescriptor : GPUObjectDescriptorBase { +dictionary GPUShaderModuleDescriptor + : GPUObjectDescriptorBase { required USVString code; }; @@ -935,11 +939,6 @@ interface GPUComputePassEncoder { undefined dispatchWorkgroups(GPUSize32 workgroupCountX, optional GPUSize32 workgroupCountY = 1, optional GPUSize32 workgroupCountZ = 1); undefined dispatchWorkgroupsIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset); - undefined beginPipelineStatisticsQuery(GPUQuerySet querySet, GPUSize32 queryIndex); - undefined endPipelineStatisticsQuery(); - - undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); - undefined end(); }; GPUComputePassEncoder includes GPUObjectBase; @@ -947,8 +946,15 @@ GPUComputePassEncoder includes GPUCommandsMixin; GPUComputePassEncoder includes GPUDebugCommandsMixin; GPUComputePassEncoder includes GPUBindingCommandsMixin; +dictionary GPUComputePassTimestampWrites { + required GPUQuerySet querySet; + GPUSize32 beginningOfPassWriteIndex; + GPUSize32 endOfPassWriteIndex; +}; + dictionary GPUComputePassDescriptor : GPUObjectDescriptorBase { + GPUComputePassTimestampWrites timestampWrites; }; [Exposed=(Window, DedicatedWorker), SecureContext] @@ -963,10 +969,8 @@ interface GPURenderPassEncoder { undefined setBlendConstant(GPUColor color); undefined setStencilReference(GPUStencilValue reference); - undefined beginPipelineStatisticsQuery(GPUQuerySet querySet, GPUSize32 queryIndex); - undefined endPipelineStatisticsQuery(); - - undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex); + undefined beginOcclusionQuery(GPUSize32 queryIndex); + undefined endOcclusionQuery(); undefined executeBundles(sequence bundles); undefined end(); @@ -977,11 +981,18 @@ GPURenderPassEncoder includes GPUDebugCommandsMixin; GPURenderPassEncoder includes GPUBindingCommandsMixin; GPURenderPassEncoder includes GPURenderCommandsMixin; +dictionary GPURenderPassTimestampWrites { + required GPUQuerySet querySet; + GPUSize32 beginningOfPassWriteIndex; + GPUSize32 endOfPassWriteIndex; +}; + dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase { required sequence colorAttachments; GPURenderPassDepthStencilAttachment depthStencilAttachment; GPUQuerySet occlusionQuerySet; + GPURenderPassTimestampWrites timestampWrites; }; dictionary GPURenderPassColorAttachment { @@ -1100,23 +1111,13 @@ dictionary GPUQuerySetDescriptor : GPUObjectDescriptorBase { required GPUQueryType type; required GPUSize32 count; - sequence pipelineStatistics = []; }; enum GPUQueryType { "occlusion", - "pipeline-statistics", "timestamp", }; -enum GPUPipelineStatisticName { - "vertex-shader-invocations", - "clipper-invocations", - "clipper-primitives-out", - "fragment-shader-invocations", - "compute-shader-invocations", -}; - [Exposed=(Window, DedicatedWorker), SecureContext] interface GPUCanvasContext { readonly attribute (HTMLCanvasElement or OffscreenCanvas) canvas; diff --git a/etc/big-picture.png b/etc/big-picture.png index d52d60189c..86d754da9d 100644 Binary files a/etc/big-picture.png and b/etc/big-picture.png differ diff --git a/etc/big-picture.xml b/etc/big-picture.xml index d43f4631d1..91844f1101 100644 --- a/etc/big-picture.xml +++ b/etc/big-picture.xml @@ -1 +1 @@ -7V1pd5s4F/41/mgfQGz+2DRppzNt35ymnc7kyxxsZJuGgAfjLP31rzDIRgsggxCkk8xyzG7r3vvc5y4SE/D2/ul94m03n2IfhhND858m4HJiGLppGJPsX81/zvc4ppPvWCeBX5x02nET/ITFTq3Yuw98uCNOTOM4TIMtuXMZRxFcpsQ+L0niR/K0VRyST916a8jsuFl6Ibv3e+Cnm3yva2mn/b/BYL3BT9a14sjCW96tk3gfFc+L4gjmR+49fJvi1N3G8+PH0vPA1QS8TeI4zT/dP72FYTaseMTy695VHD1+5QRGqcgFYTRdPF+9e/fz76/B9OMVvN1vbqegENyDF+6LsfgDPhdfOH3G43P4hTC7kT4BF4+bIIU3W2+ZHX1EGoH2bdL7sDgcegsYXhwH5m0cxgk6dBgacLGKo7RQAN0stvEpEwNohz+03wuDdYT2LdHPg+jgxQNM0gCJ7E1xII2z5xZfHx2DT5UDcxpupMEwvodpgn6jVlxgmYWECuU1scQeT6pgzJ2Zle/dlBUBa4hXKOD6ePeTJNCHQhgVgvn9+/r61vv3t6n/cPvpS6qbt/dThxXM7zfTyEuDB6Rg2ne4eH/9LXvwFpkILa4GAfnebnMU5ioIw9L4r1YrY7msHG5GKgdxFk/ObrdLk/gOlm7o2wvbso+SYsTCEV61pFyLkNRx+EuS0m2NlZPTl5hslxFTLprpZywq2RKCum9BR6KE5rYDPEkSMnUBCVlKJaTxJDRdeDs0wCM2JBnmYpPCMGwOsNkcWOtNGNacEcbEsMM0G8vgAX1cZx+/7HfpUSL50UWCD+I96PmlS6RKzl3CGsn1ISjSAXH8D9dm9HlfcjJcZkChjxhTsRkn6SZex5EXXp32Xpz2foyzkTqMzg+Yps+Fv/f2aUyKAT4F6V/os1Z8/jv7jBxtvnX5VDp0+Yw3IvQL/8I3yDZKV2Wbp8sOW/i6HPcwvQN1ktvF+2QJa8YHc1QvWcO07jwzPzEbvFpFSGCY+wuC2coWK3AUiRWLSJsZFiGls2RklDVk5lglJdFrVaS9YIGoYGXL9XDpmyTxnksnbOMgSnelO19nO064ATD9KIADaFQIQJ1vOlbd+ehD/g34VxtzEqZo9MkHt7iI0tTjkIgpLy9UYQlxcvATHIX+mEUgpEo2hhP3ge8f9D2Bu+CntzjcL1OmQgjo5tbFxLqkQhijMeCpcAbHYLZ41OQI+2W9rTHjSm8yRbZiaHOSh+Vb56kjowG6NSMVaAq0GcY4fJt4tdrBzhrA/d1zxfBFeRinycWcwEocq1jIa49ehrBfGgV8OTYJKMB1quFIlhJhcsYy0MysCe2y/93H+MB0d9CWN+gEJJun00HMQB/X2/10GSewRFDzO1Yw1N3G22YfkaC9MIRhvE68+wxvYBKgH5mBFHns+nSgKQWzCp4gTrNxGC4KIF3f5MWCrrEAtqxYkMqrANNiea3J4bV2b+GHqZr/nEdRMX7oE3E63AEtBMECjAIsDEqbdM2q5TrM+UYN15EFLixH4YS3F/DhWTCKHRAjjlEwgxEL1zKtWtUTxwgqQzHnRL4GByGABITwvj18Ti+/v7uKv+jhl3/++SO6+TDF6StVgS9p6Y5g5FtCEpGoikh2tMcL0dgIByVDB0euTQIAaAiOqPCGOr8zYFxtnB/JYnH77ZN5A37sFov5189TwGYn33I1UHFIU1WVEQl1yrpVa2TCMVElfCBD0HUy0sEY3DHUmTpgZrjz0h/5FN0g79hj1CPiU+Rx2Er6OnaHJUBqy1ouieQec+U1Hgwo5biDejBhB6ZrBsmN53PQxI6zrZK+dKS/5suivy4gvZNrUapSm7qzaDI8J79fPgpScnc1Y13CsNlsNibwEGC72RXYigpzkE+AdYfHgDklOhkMmI8f4CXgB4kdRlNg3R4lrJeFElRGzW6IkW06pnbPABWAe1SO6TulmIJJ5AlTPntRFO9fGKzIxxBey0VfQTQ/C6KqzNgi3V6V2+8RQmxBCBlHUt4GVNbW1s/BBCqIBnPq+/UMCjYDCjcweYhHhQm4MajfVjkqLlGaWuOrd3VyVDRq1W0UtR5iUSpwjby19xLqLh50V1zR20sXLlb9hKjAIkMHk8cxTQ7H7K251dQZqSgt5ArXcc/gpu39gy4aiep2Lx6iMVoEuG2kodOjK1vFzxGlq9T5/WRoDTZD6z9hLPs1krS1Nto9STtF7Mq0qWR8vtU1SavrxF2pAKS/nKypKkjuCmAFr8UYNjiCOaPguMac9IiGdU4yDXfNHWHIoPykvEY4LiLpLMnlVAQe9uGdFwmm7X858MJFEwkVJk3TyS7LqSTwshwSEiktkoJe/Pob33sp6u4WLXKfCXmSitw8KOMOovTO7nZIRlO1uVlPoajzTSCXQvH1jS1hIgrFjwF6RiE59WscyUpAF92x5aAJmYt1VDEhnS3ttKpF86L6Q0vlxgtfQmR/VvHZkJT4ZQIiXodlX9Vnrmmo6tBWG6BbvWA9h1iS0tSpsLsid3p2YxT/Mf12XbPVGnRLL5wm7KTLcSfoJJgt3RetKUzNcq1WcctIq3rNmT2PHaydU69RwwA7yXDEMza7y0I18tIJz56rVpyI3ge+bryiY1Z/HBodcWD+i5lWP6m4JtPCqTZlpuUwpuXtNmOyqpWV/cO1qsMfmyvL/3qxtuP2YGViUzkZ0Y1yD5o+07R+MlTZTeS1r4rn3McxHcNwyAqQOVdRvGOtn5MqX4dwB/6rmXJpczGm2kyzAJkql5UpJ5NbNnmD/hLlqvvY1Dj+fvCgyfFje1fm+NmVudZh/Dgmzz8InzbdoT388SecJPMeLu/YJr3G0R2gr86hir/D99XZvBUNaNiK/DfZMpkn91AaR3IFigMTxUtVavnGwUDCILqrG8NGPCmNEG81K7yva/MSKR+LXsusojjfdJ+jGalqZDVEmMv/bqpoC3Oqn6AxTXbjoTlS6ItdZcAtSnGOC8i2kK5TSfFt6JC3v2qczfLdQxENrxta4fs28MlDlIXyesXeo78zznd47CqHgyxEQlERfXAX6ChqgCWBnMB5CQl5R1FGHkP4QDMoGlcKAHTrjqRCnU2v32nWr2BgmbXn91PZw8IpQc4ljKpnXAyGNWr4oa1REzpVYg03iLXVQE1vQazC7qvGGBb0FsPW/cqyNw8ibxtUGtd/PX3NW7q9N2vjZhzZPgfW/E7B2DL0drtgybckcma00c/aQM1Y0N5viy4YhOcRjySCNOnlmAX7u1l/TOUKdDoW7Ykn6Jot1e9zFZ3XhK5E0RsVdgCGyp/9L7JWVkVQqkbTAQWfgiv6sgt50lVEGl4lKbpjm+oV3WATyY9wgdQZ3i/C7Ntz+dULrRHVmrqUEhHQOmZVjnO/5mCmW9rpj0re6Ohheukwfq7ULAx3vFiFaQ2NKlrl+qQAOCElLXQfiALQDdNtKQAzy0cSMtIpCbwsWeX30mvP7wdJeS//ELGLHRqktLp+QU+ZL9nO3ChbzyybydVmfaHGzo5GCzr8hHdBNl6D9oKMi2nTq4WYtHWImhmz1DRtr7Ja5wE5Z09JEwselPKiJhvPR3rDvBznPYxggiQSR+yxWpoy3OsRytnqrbcMovWkahn8WlCRQk5sHIp2JtYUF3Fmdh9FIL6+nFcQ5vIPUX7ZuheuPed4aSBn6RRmaGKraTSjZU8gx3xhq4lLkOdbmgpQNDhKzhS9SZxsrJH/iWDQJ+Bz+DK50khPr0rAtkFTy3RcKRY0pdLw7sztowWQPyK8DtK+wJRXQhEjrOrgVyIZFZ1WOQwZFV3Wo5GMMq9TlITThkPiruyJnHx74CU3GCA9zO38r2IoRgwpjNShFlmV1FFtUH3afaw9ws+msuQ0ewXrR0YtfvkVGimUsFTOvOGLhreUQ9+ubtxTgV3WldVq9Uhdmam1dGW6RispEHJl0tBC9WSwMvOS0bzSTVvkdq9YglMv2foydR/BALZNhxnvxZ9tdODgv4/LQSlanp5tsDw7jUIlnhvfOMlVpGaw4w2zaLcG6EVX6ZfL6jiFJps006+FM898jRwwKR3v2FNZI4p6jv0n4j1IyuN/MY/wIvgcxswociWdolsDgEI6xRWiSBpWpVTqgxtRpJIgKctyqNcIm5xXFtgcWdERuTxZsdnEL7yXS6MfnU44k6fowSwZQLELh7MhXKV1wSxPqCdn1hykluWo1cgxzmotaTb+0gzQEBGro1SsbEST3Qj9d3H491W6XaTL6WxXK12BXltycBsQc8wIyRlstQgpkAfvfYrHiAV0bKgZTEBsYjZ3YdqH+212dbxC/7t6QiMfZblZ7c31h1cE7ISAPJkrRUBsgwMhICXztnM7u0llPjoyabCt8fmLVV+NTVisjj06MsnpAx+RsYkuJSLb2IbmJfhFpuVlXlZPvIUTX83tLHMbmt1z3ow+HnMTrhBKNrfBWSZgk1o/vAdvt0yC7Wu6pJvJDU4nAS/vTIm0uUGfWxeuL42YjdFac4VjsPZONhSk35snWm219cZbSVx1iKsBbMLse5zcZU3ZhnbtpezCqa8mflbEyGnyUGvibeen92ninFf3jNfeOU65rb07zdDRt72zoerX2I9fjV2KPx/c2NmI9esGoh04QtIuggOwB8t0n7DLg3UXd3PzpkSBm3UClyBh3lKPpmtgqZdF6vYm0jYv2JHWDHNOtx4B6rW+QUBuZze41C0FL/iWyg4tLhUiRptJnL3h6gThSOE3n2I/a36++j8= \ No newline at end of file +7V1be5s4E/41vrQfJHHyZU7tdrftl6dpm93c7IONbNMQSDFOnP76T9jIRgeDjMUh3XjbrREgsGbm1TujkTRAFw/r94n3uPgU+zgcQMNfD9DlAELgQJf8k5W8bEvGrrUtmCeBn1+0L7gJfuG80MhLV4GPl8yFaRyHafDIFk7jKMLTlCnzkiR+Zi+bxSH71EdvjoWCm6kXiqW3gZ8utqWuZezL/8DBfEGfDIz8zMSb3s+TeBXlzxtANNt8tqcfPFpXfv1y4fnxc+Gh6GqALpI4TrffHtYXOMzaljbb9r53B87u3jvBUapyQxgNJy9X7979+udrMPx4he9Wi7shgttqnrxwlTfIX/glf+H0hTbS5mfirCIwQOfPiyDFN4/eNDv7TNSClC3ShzA/HXoTHJ7vWuciDuOEnIriiFx/PoujNNcCYObH9BLShMbmQ8q9MJhHpGxKfh4mJ8+fcJIGRG5n+Yk0zp4rtkLeMNnleM03N1FjHD/gNCG/0cjPWmYuoVyDTSqx570+wLEzyvV6UdQGqiZeroXzXe17SZAvuTAOCObP2/n1nffzj6H/dPfpSwrMu4ehIwrmz5th5KXBEybFt3jy/vpb9uBHYie8uCoE5HvLxU6YsyAMC+1P9BdOpwebW5DKRpz5k7PqlmkS3+NChb49sS1bk6QotrxwzV+QFLANUU5OU2KyXUFMW9EMP1NR6ZYQBr6FHY0SGtsO8jRJyAQKErJalZAhk9Bw4i1JA/fYkHSYi80KA9oSYLMlsNaYMKyxIIwBtMM0a8vgiXydZ1+/rJbpTiLbs5OEnqQl5PmFW7RKzp3iEsk1ISi2A5L0P1KbAeOm5ERJXaFBsU9oU34YJ+kinseRF17tS8/3pR/jrKU2rfMDp+lL3t97qzRmxYDXQfo3+W7k3//JvpOOdnt0uS6cunyhBxH5hX/TCrKDwl3Z4f62zRG9b4t7lOOhMskt41UyxSXtQ4mql8xxWnadub0wa7xSRUhwuO0vGHqrW6zIaUmsVETGCFqMlI6SESxqyMixCkoCSlWkvmCRqmB1y3Vz61mSeC+FCx7jIEqXhZqvs4I9biBKP3LgQAbnAnDXm45Vdj35sn0D+d1wzMIUjz7bxs1v4jR11yRqyitzVURCnGz6CYlCf8w8EFYlK92Jh8D3N/qe4GXwy5ts6suUKRcCqdw6H1iXnAsDKx0eURlLbVPoNXaub/5Og7x/ONibDImtQGPM8rDt0XHqKGgAsEasAg2RMaIYR6uJZ7MlPlkDpE00bhm+uB7Gqepi9mCljlUi5NVHL6jcL/UCvhybBRTkOofhSJcSUXImMtDMrBntsn+uYnpiuNxoyxm5gMhmvT9JGejz/HE1nMYJLhDUbY0HGOpy4T1mX4mgvTDEYTxPvIcMb3ASkB+ZgRR77np/oioEMwvWmMbaJAyXOJCub8p8QRdOkK3LF+TiKsi0RF5rSnit3Zj7YbbNf46jqBQ/wECdDp+AFopggXoBFpDTJmBYpVxHuB6WcB1d4CJyFIl7e46fXhS92A4xYucFCxgxcS3TKlU9dYzgIhRjiecLJQiBNCCE9+3pc3p5++4q/gLCL//++1d082FIw1dtOb6spTuKnm8BSVS8KibYUR8vVH0j6pR07Ry5NgsAqMI54twb7vqTAeNq4fxIJpO7b5/MG/RjOZmMv34eIjE6eSHVwJZdmkOjMse6OqVGdrqrQwwBANbToRh8oqszdNAIuuPCh30KgGyNDXo9Kn2KPg57kL72vcNSILVFLddEcnex8pIeDLXKcTvtwZQ7MGBAlhuPx6iKHWdHBX05kf6ar4v+uojtnVyLU5XS0J3Fk+Ex+37bVtASuytp6wKGjUajPoGHAtvN7qBWlJuDfgIMHBkDlgzR6WDAcvxArwE/WOyAVY51fZSwXhdKcBE1u8JHtnmf2j0CVBDNUdmF71rFFEoi95jy2YuiePXKYEU/hshSLppyouVRkLaGGWuE2w/F9huEEFsRQvoRlLcRF7W1wTGYwDnRaMy9X8OgYAugcIOTp7hXmEATg5pNleP8klZDa3L1PhwcVfVagU281o0vyjmukTf3XsO4i4fdmVT09tTFk1kzLiqyWNfBlHFMU8IxG0tuNYEglVYHcpXHcY/gpvX7B6DqiQK7kR6i0ltENG2kItPjVLZKn6NKV7nrm4nQQjFC668plv0eQdpSG9WSjwJNmwvGb49ODdICwNTKOSDNxWTNtpzkUwEs57UUwzpHMKcXHBeO2R4RWscE02jW3A6GINdP6kuEkyISEEmuZETgaRXee5Fi2P63Ay86aKJhhMkwAJtlOdQEXpbDQiKnRc2hFxDDsbXGj2RMfJMGtfDC18DGjxowgpqCNQKJkWVFNTViJB2ObSursl1SbTXS00g6A1aagKPKB+IdRyczyB/TbKakGGElVXrhMBEnSvXbqdZgtnwuo9FiOEVqtT2eoXOC1bbk4fLmxDu4DUcpJQzORz6Ab5aVxZu7tixKxH4z02rG9aoyLepatWZadBrN3rS85aJPVjWzsv+kVrX5iL7R9tOIte2OOxsWMFvOVzJGABZzDgBx8JqZcJpVoi9dST3G0o/0W+iwET8TtRGsFa1fEhqZh3iJ/quREW25t0NjZFiIDY3oioxAplabraDBwIi4msc8jJ/71Ht0wslkK+K0nFIiTtt7j6f34sB+Zet2MBbvcAHj7sfibdksSB75Iv8sW19rDzGFdmRnrW7YDF3eytgebAwkDKL7sjas7KMKLSRbAYOWnTrgycrHUgzoV9WzM6O2kl+gSu/3v5tDXZ9wqZ+QNk2W/ekqtXSB9iEDrjE44LiIHUo6dfoJrYZ3m5rr9GyRM22C+HStsQN93wKvPcLAuV4vL931d/D4Dk9cGamTycucHwvcrjHbaSlphgVyBudPTrosrqGgMnPxoCAr3SWa49RR1mXl7ELED/dpGiiw+TW/zPJZj5ZZen0zIwtUOAXIucTR4SzNzrCmHX5oG9wkkDaxRupHiyM/ZfRwGnrLZTCVx1vY+R2wmRnO1UBYH0lUpz3T2RA94bQmv6hcXVJrcd4L4FcHbAi5gGFrRSKpostSaVpR9EqF7aDPVJ7gb/RK0xEXrVBcl0xcjoiPjfPwqknRHdtsX9GhGNp6xhOizvhhEmZvL+r96418lpq6lsAnMk7083YZrGM0Apax/3DuJCAPA4XT9Lla/UJpe4kKUxsaa/oKvaEA1EXW5kx0RAH4FLK6FEDIVdSEjLyTRBdXOPheoPT6ZpBUtoSxil0sSSOlhyOq/MSfgu2MYdF6Rlk+ap1Z0pXjlZUWtPkJ74KsvTod4ewX0+bnPJq8daiambBgHm+vupIJEZt5bI5bGJqljVKcmrnwfKI3whLf73GEEyKROBLPldKU7hZ5LcbPHr1pEM0HhxbzLAUVLeTEpq7oycSa4yLOyG4iLC3Xl+OGqKT8Q5Vf1s7wqM85XhvIWYDDDENtTmA1WjYEcsILW1Vcgr3eMtoARShRcmEYjsXJylG77wQGfQY+ux+4a9XTA4cisHXQ1DIdV4sFDbkRJXfkNpHYIm8RWV5UU2AqS2tVI6ztwa9GMqo60aQbMqo6ObGSjAqbwmjCaeiwuKt7aovcHmTBDQFIN7Nd/qsYqnM7AsPhlorSlCcIuezDJmZQyqOpIjnNNpL6KKjFb7/ODJ8p2PnQJZRNbm26q2shvnlCV+aKXVmpVve0KzONml0Z4MKXFp0erj8vT96ubU9xKDIvHROKTtMWvROKaLCqSnji+DJXj6nmwNbJeZFtX1RHB9htV1taZFNM+To6jMIFniv3zVHDNun+tSK0tbg8Z+UWWYCG0HSTZn5zC/PIzTDy99KW5VUim3KO/Z3wHiL2/i8v3tJSnlxqAGqRTkmFqBKGbVMq6ptLlyGVBklZlsNthmZKFl6VbUbMe+T6ZCVGE7/ItsgjvzAdSKZz8I1ZMIC8iLqzIZ6lZc6sTKj7zqzaSS3K0SiRY5yNtaRZ+2szQKgiVqdVsYoeTVYR+Xu++fMm3VOkK3FY25WuQq4t27gViNlnhJQ0drsIqRAHbzzpvMcC2iXUdCYgMTCb7wb+4eExuzuekf9drUnLR1ls1ji7/vCGgCchoEzmrSIgtcGOEJCTeSezzexx78gkFFPjt9tDvRmb+sRvu3dkUpIH3iNja2VxA4mxdc1L6HZMxYUnZmvZcmBv5naUuXXN7iX7O/bH3Fqa3SiYW+csE4lBrR/ek7ecJsHjW7jkNJPrnE4iWdyZE2l1gr50XLh8aMSs4631ZKxV4gryu3+ojrbaoLIqjeugSDVADJjdxsl9lpQNjWsvFZcDfDPxozxGyaKB7Zp43fnpTZq42i7bPbF3Sadc196dauho2t5FV/Vr7Mdvxq6lP+/c2EWP9esCkwLqIRnnwQbYg2m6SsQFi04Xd3XypkaBm2UC1yBh2eJzpgup1IsidRsTaZ0tB7QlwxyTrceAemnfUE9uyss2d7C1jqqILz5OZ+HPn9/v/MnqKU6uz5bW+bCtDZJqp7xx7SSRTuOZbMgc1U1ms6qrqp3PRg6TONvUZX85QbTFp9jPstuv/g8= \ No newline at end of file diff --git a/examples/common/Cargo.toml b/examples/Cargo.toml similarity index 60% rename from examples/common/Cargo.toml rename to examples/Cargo.toml index f9dd5d32e2..9d57851086 100644 --- a/examples/common/Cargo.toml +++ b/examples/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wgpu-example" +name = "wgpu-examples" version.workspace = true authors.workspace = true edition.workspace = true @@ -10,21 +10,43 @@ keywords.workspace = true license.workspace = true publish = false +[lib] +path = "src/lib.rs" +harness = false + +[[bin]] +name = "wgpu-examples" +path = "src/main.rs" +test = false + [dependencies] -env_logger.workspace = true +bytemuck.workspace = true +cfg-if.workspace = true +encase = { workspace = true, features = ["glam"] } +flume.workspace = true +getrandom.workspace = true +glam.workspace = true +ktx2.workspace = true log.workspace = true -pollster.workspace = true +nanorand.workspace = true +noise.workspace = true +obj.workspace = true png.workspace = true -winit.workspace = true +pollster.workspace = true +web-time.workspace = true wgpu.workspace = true +winit.workspace = true + +[dev-dependencies] wgpu-test.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -async-executor.workspace = true +env_logger.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook.workspace = true console_log.workspace = true +fern.workspace = true js-sys.workspace = true wasm-bindgen.workspace = true wasm-bindgen-futures.workspace = true @@ -36,7 +58,12 @@ web-sys = { workspace = true, features = [ "RequestInit", "RequestMode", "Request", + "ImageData", "Response", + "HtmlImageElement", "WebGl2RenderingContext", - "CanvasRenderingContext2d" + "CanvasRenderingContext2d", ] } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen-test.workspace = true diff --git a/examples/README.md b/examples/README.md index bf7c9b82a2..8232b863ad 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,54 +6,93 @@ For the simplest examples without using any helping code (see `framework.rs` her - `hello-triangle` for graphics and presentation - `hello-compute` for pure computing -Notably, `capture` example shows rendering without a surface/window. It reads back the contents and saves them to a file. +### Summary of examples -All the examples use [WGSL](https://gpuweb.github.io/gpuweb/wgsl.html) shaders unless specified otherwise. +A summary of the basic examples as split along the graphics and compute "pathways" laid out roughly in order of building on each other. Those further indented, and thus more roughly dependent on more other examples, tend to be more complicated as well as those further down. It should be noted, though, that computing examples, even though they are mentioned further down (because rendering to a window is by far the most common use case), tend to be less complex as they require less surrounding context to create and manage a window to render to. -All framework-based examples render to the window and are reftested against the screenshot in the directory. +The rest of the examples are for demonstrating specific features that you can come back for later when you know what those features are. + +#### General + +- `hello` - Demonstrates the basics of the WGPU library by getting a default Adapter and debugging it to the screen + +#### Graphics + +- `hello-triangle` - Provides an example of a bare-bones WGPU workflow using the Winit crate that simply renders a red triangle on a green background. +- `uniform-values` - Demonstrates the basics of enabling shaders and the GPU, in general, to access app state through uniform variables. `uniform-values` also serves as an example of rudimentary app building as the app stores state and takes window-captured keyboard events. The app displays the Mandelbrot Set in grayscale (similar to `storage-texture`) but allows the user to navigate and explore it using their arrow keys and scroll wheel. +- `cube` - Introduces the user to slightly more advanced models. The example creates a set of triangles to form a cube on the CPU and then uses a vertex and index buffer to send the generated model to the GPU for usage in rendering. It also uses a texture generated on the CPU to shade the sides of the cube and a uniform variable to apply a transformation matrix to the cube in the shader. +- `bunnymark` - Demonstrates many things, but chief among them is performing numerous draw calls with different bind groups in one render pass. The example also uses textures for the icon and uniform buffers to transfer both global and per-particle states. +- `skybox` - Shows off too many concepts to list here. The name comes from game development where a "skybox" acts as a background for rendering, usually to add a sky texture for immersion, although they can also be used for backdrops to give the idea of a world beyond the game scene. This example does so much more than this, though, as it uses a car model loaded from a file and uses the user's mouse to rotate the car model in 3d. `skybox` also makes use of depth textures and similar app patterns to `uniform-values`. +- `shadow` - Likely by far the most complex example (certainly the largest in lines of code) of the official WGPU examples. `shadow` demonstrates basic scene rendering with the main attraction being lighting and shadows (as the name implies). It is recommended that any user looking into lighting be very familiar with the basic concepts of not only rendering with WGPU but also the primary mathematical ideas of computer graphics. +- `render-to-texture` - Renders to an image texture offscreen, demonstrating both off-screen rendering as well as how to add a sort of resolution-agnostic screenshot feature to an engine. This example either outputs an image file of your naming (pass command line arguments after specifying a `--` like `cargo run --bin render-to-texture -- "test.png"`) or adds an `img` element containing the image to the page in WASM. + +#### Compute + +- `hello-compute` - Demonstrates the basic workflow for getting arrays of numbers to the GPU, executing a shader on them, and getting the results back. The operation it performs is finding the Collatz value (how many iterations of the [Collatz equation](https://en.wikipedia.org/wiki/Collatz_conjecture) it takes for the number to either reach 1 or overflow) of a set of numbers and prints the results. +- `repeated-compute` - Mostly for going into detail on subjects `hello-compute` did not. It, too, computes the Collatz conjecture, but this time, it automatically loads large arrays of randomly generated numbers, prints them, runs them, and prints the result. It does this cycle 10 times. +- `hello-workgroups` - Teaches the user about the basics of compute workgroups; what they are and what they can do. +- `hello-synchronization` - Teaches the user about synchronization in WGSL, the ability to force all invocations in a workgroup to synchronize with each other before continuing via a sort of barrier. +- `storage-texture` - Demonstrates the use of storage textures as outputs to compute shaders. The example on the outside seems very similar to `render-to-texture` in that it outputs an image either to the file system or the web page, except displaying a grayscale render of the Mandelbrot Set. However, inside, the example dispatches a grid of compute workgroups, one for each pixel, which calculates the pixel value and stores it to the corresponding pixel of the output storage texture. + +#### Combined + +- `boids` - Demonstrates how to combine compute and render workflows by performing a [boid](https://en.wikipedia.org/wiki/Boids) simulation and rendering the boids to the screen as little triangles. ## Feature matrix -| Feature | boids | bunnymark | cube | mipmap | msaa-line | shadow | skybox | texture-arrays | water | conservative-raster | stencil-triangles | -| ---------------------------- | ------ | --------- | ------ | ------ | --------- | ------ | ------ | -------------- | ------ | ------------------- | ----------------- | -| vertex attributes | :star: | | :star: | | :star: | :star: | :star: | :star: | :star: | | | -| instancing | :star: | | | | | | | | | | | -| lines and points | | | | | :star: | | | | | :star: | | -| dynamic buffer offsets | | :star: | | | | :star: | | | | | | -| implicit layout | | | | :star: | | | | | | | | -| sampled color textures | :star: | :star: | :star: | :star: | | | :star: | :star: | :star: | :star: | | -| storage textures | :star: | | | | | | | | | | | -| comparison samplers | | | | | | :star: | | | | | | -| subresource views | | | | :star: | | :star: | | | | | | -| cubemaps | | | | | | | :star: | | | | | -| multisampling | | | | | :star: | | | | | | | -| off-screen rendering | | | | | | :star: | | | :star: | :star: | | -| stencil testing | | | | | | | | | | | :star: | -| depth testing | | | | | | :star: | :star: | | :star: | | | -| depth biasing | | | | | | :star: | | | | | | -| read-only depth | | | | | | | | | :star: | | | -| blending | | :star: | :star: | | | | | | :star: | | | -| render bundles | | | | | :star: | | | | :star: | | | -| compute passes | :star: | | | | | | | | | | | -| error scopes | | | :star: | | | | | | | | | -| _optional extensions_ | | | | | | | | :star: | | | | -| - SPIR-V shaders | | | | | | | | | | | | -| - binding array | | | | | | | | :star: | | | | -| - push constants | | | | | | | | | | | | -| - depth clamping | | | | | | :star: | | | | | | -| - compressed textures | | | | | | | :star: | | | | | -| - polygon mode | | | :star: | | | | | | | | | -| - queries | | | | :star: | | | | | | | | -| - conservative rasterization | | | | | | | | | | :star: | | -| _integrations_ | | | | | | | | | | | | -| - staging belt | | | | | | | :star: | | | | | -| - typed arena | | | | | | | | | | | | -| - obj loading | | | | | | | :star: | | | | | +| Feature | boids | bunnymark | conservative-raster | cube | hello-synchronization | hello-workgroups | mipmap | msaa-line | render-to-texture | repeated-compute | shadow | skybox | stencil-triangles | storage-texture | texture-arrays | uniform-values | water | +| ---------------------------- | ------ | --------- | ------------------- | ------ | --------------------- | ---------------- | ------ | --------- | ----------------- | ---------------- | ------ | ------ | ----------------- | --------------- | -------------- | -------------- | ------ | +| vertex attributes | :star: | | | :star: | | | | :star: | | | :star: | :star: | | | :star: | | :star: | +| instancing | :star: | | | | | | | | | | | | | | | | | +| lines and points | | | :star: | | | | | :star: | | | | | | | | | | +| dynamic buffer offsets | | :star: | | | | | | | | | :star: | | | | | | | +| implicit layout | | | | | | | :star: | | | | | | | | | | | +| sampled color textures | :star: | :star: | :star: | :star: | | | :star: | | | | | :star: | | | :star: | | :star: | +| storage textures | :star: | | | | | | | | | | | | | :star: | | | | +| comparison samplers | | | | | | | | | | | :star: | | | | | | | +| subresource views | | | | | | | :star: | | | | :star: | | | | | | | +| cubemaps | | | | | | | | | | | | :star: | | | | | | +| multisampling | | | | | | | | :star: | | | | | | | | | | +| off-screen rendering | | | :star: | | | | | | :star: | | :star: | | | | | | :star: | +| stencil testing | | | | | | | | | | | | | :star: | | | | | +| depth testing | | | | | | | | | | | :star: | :star: | | | | | :star: | +| depth biasing | | | | | | | | | | | :star: | | | | | | | +| read-only depth | | | | | | | | | | | | | | | | | :star: | +| blending | | :star: | | :star: | | | | | | | | | | | | | :star: | +| render bundles | | | | | | | | :star: | | | | | | | | | :star: | +| uniform buffers | | | | | | | | | | | | | | | | :star: | | +| compute passes | :star: | | | | :star: | :star: | | | | :star: | | | | :star: | | | | +| buffer mapping | | | | | :star: | :star: | | | | :star: | | | | :star: | | | | +| error scopes | | | | :star: | | | | | | | | | | | | | | +| compute workgroups | | | | | :star: | :star: | | | | | | | | | | | | +| compute synchronization | | | | | :star: | | | | | | | | | | | | | +| _optional extensions_ | | | | | | | | | | | | | | | :star: | | | +| - SPIR-V shaders | | | | | | | | | | | | | | | | | | +| - binding array | | | | | | | | | | | | | | | :star: | | | +| - push constants | | | | | | | | | | | | | | | | | | +| - depth clamping | | | | | | | | | | | :star: | | | | | | | +| - compressed textures | | | | | | | | | | | | :star: | | | | | | +| - polygon mode | | | | :star: | | | | | | | | | | | | | | +| - queries | | | | | | | :star: | | | | | | | | | | | +| - conservative rasterization | | | :star: | | | | | | | | | | | | | | | +| _integrations_ | | | | | | | | | | | | | | | | | | +| - staging belt | | | | | | | | | | | | :star: | | | | | | +| - typed arena | | | | | | | | | | | | | | | | | | +| - obj loading | | | | | | | | | | | | :star: | | | | | | + + +## Additional notes + +Note that the examples regarding computing build off of each other; repeated-compute extends hello-compute, hello-workgroups assumes you know the basic workflow of GPU computation, and hello-synchronization assumes you know what a workgroup is. Also, note that the computing examples cannot be downleveled to WebGL as WebGL does not allow storage textures. Running these in a browser will require that browser to support WebGPU. + +All the examples use [WGSL](https://gpuweb.github.io/gpuweb/wgsl.html) shaders unless specified otherwise. + +All framework-based examples render to the window and are reftested against the screenshot in the directory. ## Hacking -You can record an API trace any of the framework-based examples by starting them as: +You can record an API trace for any of the framework-based examples by starting them as: ```sh -mkdir -p trace && WGPU_TRACE=trace cargo run --features trace --bin +mkdir -p trace && WGPU_TRACE=trace cargo run --features trace --bin wgpu-examples ``` diff --git a/examples/boids/Cargo.toml b/examples/boids/Cargo.toml deleted file mode 100644 index 933acd64eb..0000000000 --- a/examples/boids/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "wgpu-boids-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu boids example" -publish = false - -[[bin]] -name = "boids" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -nanorand.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/bunnymark/Cargo.toml b/examples/bunnymark/Cargo.toml deleted file mode 100644 index 43e0be0d66..0000000000 --- a/examples/bunnymark/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "wgpu-bunnymark-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu bunnymark example" -publish = false - -[[bin]] -name = "bunnymark" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -nanorand.workspace = true -glam.workspace = true -png.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/bunnymark/screenshot.png b/examples/bunnymark/screenshot.png deleted file mode 100644 index f8b8293cac..0000000000 Binary files a/examples/bunnymark/screenshot.png and /dev/null differ diff --git a/examples/capture/Cargo.toml b/examples/capture/Cargo.toml deleted file mode 100644 index 87c9b6c38f..0000000000 --- a/examples/capture/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "wgpu-capture-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu capture example" -publish = false - -[[bin]] -name = "capture" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -env_logger.workspace = true -futures-intrusive.workspace = true -pollster.workspace = true -png.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook.workspace = true -console_log.workspace = true -wasm-bindgen-futures.workspace = true - diff --git a/examples/capture/README.md b/examples/capture/README.md deleted file mode 100644 index 2baea436db..0000000000 --- a/examples/capture/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# capture - -This example shows how to capture an image by rendering it to a texture, copying the texture to -a buffer, and retrieving it from the buffer. - -This could be used for "taking a screenshot," with the added benefit that this method doesn't -require a window to be created. - -## To Run - -``` -cargo run --bin capture -open examples/capture/red.png -``` - -## Screenshots - -![Capture example](./screenshot.png) diff --git a/examples/capture/screenshot.png b/examples/capture/screenshot.png deleted file mode 100644 index 4021a58f6e..0000000000 Binary files a/examples/capture/screenshot.png and /dev/null differ diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs deleted file mode 100644 index b783b3af80..0000000000 --- a/examples/capture/src/main.rs +++ /dev/null @@ -1,265 +0,0 @@ -use std::env; -/// This example shows how to capture an image by rendering it to a texture, copying the texture to -/// a buffer, and retrieving it from the buffer. This could be used for "taking a screenshot," with -/// the added benefit that this method doesn't require a window to be created. -use std::fs::File; -use std::io::Write; -use std::mem::size_of; -use wgpu::{Buffer, Device, SubmissionIndex}; - -async fn run(png_output_path: &str) { - let args: Vec<_> = env::args().collect(); - let (width, height) = match args.len() { - // 0 on wasm, 1 on desktop - 0 | 1 => (100usize, 200usize), - 3 => (args[1].parse().unwrap(), args[2].parse().unwrap()), - _ => { - println!("Incorrect number of arguments, possible usages:"); - println!("* 0 arguments - uses default width and height of (100, 200)"); - println!("* 2 arguments - uses specified width and height values"); - return; - } - }; - let (device, buffer, buffer_dimensions, submission_index) = - create_red_image_with_dimensions(width, height).await; - create_png( - png_output_path, - device, - buffer, - &buffer_dimensions, - submission_index, - ) - .await; -} - -async fn create_red_image_with_dimensions( - width: usize, - height: usize, -) -> (Device, Buffer, BufferDimensions, SubmissionIndex) { - let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends, - dx12_shader_compiler: wgpu::Dx12Compiler::default(), - gles_minor_version: wgpu::Gles3MinorVersion::default(), - }); - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions::default()) - .await - .unwrap(); - - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::downlevel_defaults(), - }, - None, - ) - .await - .unwrap(); - - // It is a WebGPU requirement that ImageCopyBuffer.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 - // So we calculate padded_bytes_per_row by rounding unpadded_bytes_per_row - // up to the next multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. - // https://en.wikipedia.org/wiki/Data_structure_alignment#Computing_padding - let buffer_dimensions = BufferDimensions::new(width, height); - // The output buffer lets us retrieve the data as an array - let output_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let texture_extent = wgpu::Extent3d { - width: buffer_dimensions.width as u32, - height: buffer_dimensions.height as u32, - depth_or_array_layers: 1, - }; - - // The render pipeline renders data into this texture - let texture = device.create_texture(&wgpu::TextureDescriptor { - size: texture_extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, - label: None, - view_formats: &[], - }); - - // Set the background to be red - let command_buffer = { - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &texture.create_view(&wgpu::TextureViewDescriptor::default()), - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::RED), - store: true, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - // Copy the data from the texture to the buffer - encoder.copy_texture_to_buffer( - texture.as_image_copy(), - wgpu::ImageCopyBuffer { - buffer: &output_buffer, - layout: wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(buffer_dimensions.padded_bytes_per_row as u32), - rows_per_image: None, - }, - }, - texture_extent, - ); - - encoder.finish() - }; - - let index = queue.submit(Some(command_buffer)); - (device, output_buffer, buffer_dimensions, index) -} - -async fn create_png( - png_output_path: &str, - device: Device, - output_buffer: Buffer, - buffer_dimensions: &BufferDimensions, - submission_index: SubmissionIndex, -) { - // Note that we're not calling `.await` here. - let buffer_slice = output_buffer.slice(..); - // Sets the buffer up for mapping, sending over the result of the mapping back to us when it is finished. - let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel(); - buffer_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap()); - - // Poll the device in a blocking manner so that our future resolves. - // In an actual application, `device.poll(...)` should - // be called in an event loop or on another thread. - // - // We pass our submission index so we don't need to wait for any other possible submissions. - device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission_index)); - - if let Some(Ok(())) = receiver.receive().await { - // If a file system is available, write the buffer as a PNG - let has_file_system_available = cfg!(not(target_arch = "wasm32")); - if !has_file_system_available { - return; - } - let padded_buffer = buffer_slice.get_mapped_range(); - - let mut png_encoder = png::Encoder::new( - File::create(png_output_path).unwrap(), - buffer_dimensions.width as u32, - buffer_dimensions.height as u32, - ); - png_encoder.set_depth(png::BitDepth::Eight); - png_encoder.set_color(png::ColorType::Rgba); - let mut png_writer = png_encoder - .write_header() - .unwrap() - .into_stream_writer_with_size(buffer_dimensions.unpadded_bytes_per_row) - .unwrap(); - - // from the padded_buffer we write just the unpadded bytes into the image - for chunk in padded_buffer.chunks(buffer_dimensions.padded_bytes_per_row) { - png_writer - .write_all(&chunk[..buffer_dimensions.unpadded_bytes_per_row]) - .unwrap(); - } - png_writer.finish().unwrap(); - - // With the current interface, we have to make sure all mapped views are - // dropped before we unmap the buffer. - drop(padded_buffer); - - output_buffer.unmap(); - } -} - -struct BufferDimensions { - width: usize, - height: usize, - unpadded_bytes_per_row: usize, - padded_bytes_per_row: usize, -} - -impl BufferDimensions { - fn new(width: usize, height: usize) -> Self { - let bytes_per_pixel = size_of::(); - let unpadded_bytes_per_row = width * bytes_per_pixel; - let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; - let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align; - let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding; - Self { - width, - height, - unpadded_bytes_per_row, - padded_bytes_per_row, - } - } -} - -fn main() { - #[cfg(not(target_arch = "wasm32"))] - { - env_logger::init(); - pollster::block_on(run("red.png")); - } - #[cfg(target_arch = "wasm32")] - { - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - console_log::init().expect("could not initialize logger"); - wasm_bindgen_futures::spawn_local(run("red.png")); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use wgpu::BufferView; - - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - - #[test] - // This test never creates a canvas, so will always fail on webgl2. - // #[wasm_bindgen_test::wasm_bindgen_test] - fn ensure_generated_data_matches_expected() { - assert_generated_data_matches_expected(); - } - - fn assert_generated_data_matches_expected() { - let (device, output_buffer, dimensions, submission_index) = - pollster::block_on(create_red_image_with_dimensions(100usize, 200usize)); - let buffer_slice = output_buffer.slice(..); - buffer_slice.map_async(wgpu::MapMode::Read, |_| ()); - device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission_index)); - let padded_buffer = buffer_slice.get_mapped_range(); - let expected_buffer_size = dimensions.padded_bytes_per_row * dimensions.height; - assert_eq!(padded_buffer.len(), expected_buffer_size); - assert_that_content_is_all_red(&dimensions, padded_buffer); - } - - fn assert_that_content_is_all_red(dimensions: &BufferDimensions, padded_buffer: BufferView) { - let red = [0xFFu8, 0, 0, 0xFFu8]; - let single_rgba = 4; - padded_buffer - .chunks(dimensions.padded_bytes_per_row) - .map(|padded_buffer_row| &padded_buffer_row[..dimensions.unpadded_bytes_per_row]) - .for_each(|unpadded_row| { - unpadded_row - .chunks(single_rgba) - .for_each(|chunk| assert_eq!(chunk, &red)) - }); - } -} diff --git a/examples/common/src/framework.rs b/examples/common/src/framework.rs deleted file mode 100644 index 875d8544e7..0000000000 --- a/examples/common/src/framework.rs +++ /dev/null @@ -1,641 +0,0 @@ -use std::future::Future; -#[cfg(target_arch = "wasm32")] -use std::str::FromStr; -#[cfg(not(target_arch = "wasm32"))] -use std::time::Instant; -#[cfg(target_arch = "wasm32")] -use web_sys::{ImageBitmapRenderingContext, OffscreenCanvas}; -use winit::{ - event::{self, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, -}; - -#[allow(dead_code)] -pub fn cast_slice(data: &[T]) -> &[u8] { - use std::{mem::size_of_val, slice::from_raw_parts}; - - unsafe { from_raw_parts(data.as_ptr() as *const u8, size_of_val(data)) } -} - -#[allow(dead_code)] -pub enum ShaderStage { - Vertex, - Fragment, - Compute, -} - -pub trait Example: 'static + Sized { - fn optional_features() -> wgpu::Features { - wgpu::Features::empty() - } - fn required_features() -> wgpu::Features { - wgpu::Features::empty() - } - fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { - wgpu::DownlevelCapabilities { - flags: wgpu::DownlevelFlags::empty(), - shader_model: wgpu::ShaderModel::Sm5, - ..wgpu::DownlevelCapabilities::default() - } - } - fn required_limits() -> wgpu::Limits { - wgpu::Limits::downlevel_webgl2_defaults() // These downlevel limits will allow the code to run on all possible hardware - } - fn init( - config: &wgpu::SurfaceConfiguration, - adapter: &wgpu::Adapter, - device: &wgpu::Device, - queue: &wgpu::Queue, - ) -> Self; - fn resize( - &mut self, - config: &wgpu::SurfaceConfiguration, - device: &wgpu::Device, - queue: &wgpu::Queue, - ); - fn update(&mut self, event: WindowEvent); - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - spawner: &Spawner, - ); -} - -struct Setup { - window: winit::window::Window, - event_loop: EventLoop<()>, - instance: wgpu::Instance, - size: winit::dpi::PhysicalSize, - surface: wgpu::Surface, - adapter: wgpu::Adapter, - device: wgpu::Device, - queue: wgpu::Queue, - #[cfg(target_arch = "wasm32")] - offscreen_canvas_setup: Option, -} - -#[cfg(target_arch = "wasm32")] -struct OffscreenCanvasSetup { - offscreen_canvas: OffscreenCanvas, - bitmap_renderer: ImageBitmapRenderingContext, -} - -async fn setup(title: &str) -> Setup { - #[cfg(not(target_arch = "wasm32"))] - { - env_logger::init(); - }; - - let event_loop = EventLoop::new(); - let mut builder = winit::window::WindowBuilder::new(); - builder = builder.with_title(title); - #[cfg(windows_OFF)] // TODO - { - use winit::platform::windows::WindowBuilderExtWindows; - builder = builder.with_no_redirection_bitmap(true); - } - let window = builder.build(&event_loop).unwrap(); - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - let query_string = web_sys::window().unwrap().location().search().unwrap(); - let level: log::Level = parse_url_query_string(&query_string, "RUST_LOG") - .and_then(|x| x.parse().ok()) - .unwrap_or(log::Level::Error); - console_log::init_with_level(level).expect("could not initialize logger"); - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - // On wasm, append the canvas to the document body - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| doc.body()) - .and_then(|body| { - body.append_child(&web_sys::Element::from(window.canvas())) - .ok() - }) - .expect("couldn't append canvas to document body"); - } - - #[cfg(target_arch = "wasm32")] - let mut offscreen_canvas_setup: Option = None; - #[cfg(target_arch = "wasm32")] - { - use wasm_bindgen::JsCast; - use winit::platform::web::WindowExtWebSys; - - let query_string = web_sys::window().unwrap().location().search().unwrap(); - if let Some(offscreen_canvas_param) = - parse_url_query_string(&query_string, "offscreen_canvas") - { - if FromStr::from_str(offscreen_canvas_param) == Ok(true) { - log::info!("Creating OffscreenCanvasSetup"); - - let offscreen_canvas = - OffscreenCanvas::new(1024, 768).expect("couldn't create OffscreenCanvas"); - - let bitmap_renderer = window - .canvas() - .get_context("bitmaprenderer") - .expect("couldn't create ImageBitmapRenderingContext (Result)") - .expect("couldn't create ImageBitmapRenderingContext (Option)") - .dyn_into::() - .expect("couldn't convert into ImageBitmapRenderingContext"); - - offscreen_canvas_setup = Some(OffscreenCanvasSetup { - offscreen_canvas, - bitmap_renderer, - }) - } - } - }; - - log::info!("Initializing the surface..."); - - let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); - let dx12_shader_compiler = wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(); - let gles_minor_version = wgpu::util::gles_minor_version_from_env().unwrap_or_default(); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends, - dx12_shader_compiler, - gles_minor_version, - }); - let (size, surface) = unsafe { - let size = window.inner_size(); - - #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] - let surface = instance.create_surface(&window).unwrap(); - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - let surface = { - if let Some(offscreen_canvas_setup) = &offscreen_canvas_setup { - log::info!("Creating surface from OffscreenCanvas"); - instance.create_surface_from_offscreen_canvas( - offscreen_canvas_setup.offscreen_canvas.clone(), - ) - } else { - instance.create_surface(&window) - } - } - .unwrap(); - - (size, surface) - }; - let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, Some(&surface)) - .await - .expect("No suitable GPU adapters found on the system!"); - - #[cfg(not(target_arch = "wasm32"))] - { - let adapter_info = adapter.get_info(); - println!("Using {} ({:?})", adapter_info.name, adapter_info.backend); - } - - let optional_features = E::optional_features(); - let required_features = E::required_features(); - let adapter_features = adapter.features(); - assert!( - adapter_features.contains(required_features), - "Adapter does not support required features for this example: {:?}", - required_features - adapter_features - ); - - let required_downlevel_capabilities = E::required_downlevel_capabilities(); - let downlevel_capabilities = adapter.get_downlevel_capabilities(); - assert!( - downlevel_capabilities.shader_model >= required_downlevel_capabilities.shader_model, - "Adapter does not support the minimum shader model required to run this example: {:?}", - required_downlevel_capabilities.shader_model - ); - assert!( - downlevel_capabilities - .flags - .contains(required_downlevel_capabilities.flags), - "Adapter does not support the downlevel capabilities required to run this example: {:?}", - required_downlevel_capabilities.flags - downlevel_capabilities.flags - ); - - // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface. - let needed_limits = E::required_limits().using_resolution(adapter.limits()); - - let trace_dir = std::env::var("WGPU_TRACE"); - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: (optional_features & adapter_features) | required_features, - limits: needed_limits, - }, - trace_dir.ok().as_ref().map(std::path::Path::new), - ) - .await - .expect("Unable to find a suitable GPU adapter!"); - - Setup { - window, - event_loop, - instance, - size, - surface, - adapter, - device, - queue, - #[cfg(target_arch = "wasm32")] - offscreen_canvas_setup, - } -} - -fn start( - #[cfg(not(target_arch = "wasm32"))] Setup { - window, - event_loop, - instance, - size, - surface, - adapter, - device, - queue, - }: Setup, - #[cfg(target_arch = "wasm32")] Setup { - window, - event_loop, - instance, - size, - surface, - adapter, - device, - queue, - offscreen_canvas_setup, - }: Setup, -) { - let spawner = Spawner::new(); - let mut config = surface - .get_default_config(&adapter, size.width, size.height) - .expect("Surface isn't supported by the adapter."); - let surface_view_format = config.format.add_srgb_suffix(); - config.view_formats.push(surface_view_format); - surface.configure(&device, &config); - - log::info!("Initializing the example..."); - let mut example = E::init(&config, &adapter, &device, &queue); - - #[cfg(not(target_arch = "wasm32"))] - let mut last_frame_inst = Instant::now(); - #[cfg(not(target_arch = "wasm32"))] - let (mut frame_count, mut accum_time) = (0, 0.0); - - log::info!("Entering render loop..."); - event_loop.run(move |event, _, control_flow| { - let _ = (&instance, &adapter); // force ownership by the closure - *control_flow = if cfg!(feature = "metal-auto-capture") { - ControlFlow::Exit - } else { - ControlFlow::Poll - }; - match event { - event::Event::RedrawEventsCleared => { - #[cfg(not(target_arch = "wasm32"))] - spawner.run_until_stalled(); - - window.request_redraw(); - } - event::Event::WindowEvent { - event: - WindowEvent::Resized(size) - | WindowEvent::ScaleFactorChanged { - new_inner_size: &mut size, - .. - }, - .. - } => { - // Once winit is fixed, the detection conditions here can be removed. - // https://github.com/rust-windowing/winit/issues/2876 - let max_dimension = adapter.limits().max_texture_dimension_2d; - if size.width > max_dimension || size.height > max_dimension { - log::warn!( - "The resizing size {:?} exceeds the limit of {}.", - size, - max_dimension - ); - } else { - log::info!("Resizing to {:?}", size); - config.width = size.width.max(1); - config.height = size.height.max(1); - example.resize(&config, &device, &queue); - surface.configure(&device, &config); - } - } - event::Event::WindowEvent { event, .. } => match event { - WindowEvent::KeyboardInput { - input: - event::KeyboardInput { - virtual_keycode: Some(event::VirtualKeyCode::Escape), - state: event::ElementState::Pressed, - .. - }, - .. - } - | WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - #[cfg(not(target_arch = "wasm32"))] - WindowEvent::KeyboardInput { - input: - event::KeyboardInput { - virtual_keycode: Some(event::VirtualKeyCode::R), - state: event::ElementState::Pressed, - .. - }, - .. - } => { - println!("{:#?}", instance.generate_report()); - } - _ => { - example.update(event); - } - }, - event::Event::RedrawRequested(_) => { - #[cfg(not(target_arch = "wasm32"))] - { - accum_time += last_frame_inst.elapsed().as_secs_f32(); - last_frame_inst = Instant::now(); - frame_count += 1; - if frame_count == 100 { - println!( - "Avg frame time {}ms", - accum_time * 1000.0 / frame_count as f32 - ); - accum_time = 0.0; - frame_count = 0; - } - } - - let frame = match surface.get_current_texture() { - Ok(frame) => frame, - Err(_) => { - surface.configure(&device, &config); - surface - .get_current_texture() - .expect("Failed to acquire next surface texture!") - } - }; - let view = frame.texture.create_view(&wgpu::TextureViewDescriptor { - format: Some(surface_view_format), - ..wgpu::TextureViewDescriptor::default() - }); - - example.render(&view, &device, &queue, &spawner); - - frame.present(); - - #[cfg(target_arch = "wasm32")] - { - if let Some(offscreen_canvas_setup) = &offscreen_canvas_setup { - let image_bitmap = offscreen_canvas_setup - .offscreen_canvas - .transfer_to_image_bitmap() - .expect("couldn't transfer offscreen canvas to image bitmap."); - offscreen_canvas_setup - .bitmap_renderer - .transfer_from_image_bitmap(&image_bitmap); - - log::info!("Transferring OffscreenCanvas to ImageBitmapRenderer"); - } - } - } - _ => {} - } - }); -} - -#[cfg(not(target_arch = "wasm32"))] -pub struct Spawner<'a> { - executor: async_executor::LocalExecutor<'a>, -} - -#[cfg(not(target_arch = "wasm32"))] -impl<'a> Spawner<'a> { - fn new() -> Self { - Self { - executor: async_executor::LocalExecutor::new(), - } - } - - #[allow(dead_code)] - pub fn spawn_local(&self, future: impl Future + 'a) { - self.executor.spawn(future).detach(); - } - - fn run_until_stalled(&self) { - while self.executor.try_tick() {} - } -} - -#[cfg(target_arch = "wasm32")] -pub struct Spawner {} - -#[cfg(target_arch = "wasm32")] -impl Spawner { - fn new() -> Self { - Self {} - } - - #[allow(dead_code)] - pub fn spawn_local(&self, future: impl Future + 'static) { - wasm_bindgen_futures::spawn_local(future); - } -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn run(title: &str) { - let setup = pollster::block_on(setup::(title)); - start::(setup); -} - -#[cfg(target_arch = "wasm32")] -pub fn run(title: &str) { - use wasm_bindgen::prelude::*; - - let title = title.to_owned(); - wasm_bindgen_futures::spawn_local(async move { - let setup = setup::(&title).await; - let start_closure = Closure::once_into_js(move || start::(setup)); - - // make sure to handle JS exceptions thrown inside start. - // Otherwise wasm_bindgen_futures Queue would break and never handle any tasks again. - // This is required, because winit uses JS exception for control flow to escape from `run`. - if let Err(error) = call_catch(&start_closure) { - let is_control_flow_exception = error.dyn_ref::().map_or(false, |e| { - e.message().includes("Using exceptions for control flow", 0) - }); - - if !is_control_flow_exception { - web_sys::console::error_1(&error); - } - } - - #[wasm_bindgen] - extern "C" { - #[wasm_bindgen(catch, js_namespace = Function, js_name = "prototype.call.call")] - fn call_catch(this: &JsValue) -> Result<(), JsValue>; - } - }); -} - -#[cfg(target_arch = "wasm32")] -/// Parse the query string as returned by `web_sys::window()?.location().search()?` and get a -/// specific key out of it. -pub fn parse_url_query_string<'a>(query: &'a str, search_key: &str) -> Option<&'a str> { - let query_string = query.strip_prefix('?')?; - - for pair in query_string.split('&') { - let mut pair = pair.split('='); - let key = pair.next()?; - let value = pair.next()?; - - if key == search_key { - return Some(value); - } - } - - None -} - -pub use wgpu_test::image::ComparisonType; - -pub struct FrameworkRefTest { - // Path to the reference image, relative to the root of the repo. - pub image_path: &'static str, - pub width: u32, - pub height: u32, - pub optional_features: wgpu::Features, - pub base_test_parameters: wgpu_test::TestParameters, - /// Comparisons against FLIP statistics that determine if the test passes or fails. - pub comparisons: &'static [ComparisonType], -} - -#[allow(dead_code)] -pub fn test(mut params: FrameworkRefTest) { - use std::mem; - - assert_eq!(params.width % 64, 0, "width needs to be aligned 64"); - - let features = E::required_features() | params.optional_features; - - wgpu_test::initialize_test( - mem::take(&mut params.base_test_parameters).features(features), - |ctx| { - let spawner = Spawner::new(); - - let dst_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { - label: Some("destination"), - size: wgpu::Extent3d { - width: params.width, - height: params.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - - let dst_view = dst_texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let dst_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: Some("image map buffer"), - size: params.width as u64 * params.height as u64 * 4, - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, - mapped_at_creation: false, - }); - - let mut example = E::init( - &wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - width: params.width, - height: params.height, - present_mode: wgpu::PresentMode::Fifo, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![wgpu::TextureFormat::Rgba8UnormSrgb], - }, - &ctx.adapter, - &ctx.device, - &ctx.queue, - ); - - example.render(&dst_view, &ctx.device, &ctx.queue, &spawner); - - // Handle specific case for bunnymark - #[allow(deprecated)] - if params.image_path == "/examples/bunnymark/screenshot.png" { - // Press spacebar to spawn bunnies - example.update(winit::event::WindowEvent::KeyboardInput { - input: winit::event::KeyboardInput { - scancode: 0, - state: winit::event::ElementState::Pressed, - virtual_keycode: Some(winit::event::VirtualKeyCode::Space), - modifiers: winit::event::ModifiersState::empty(), - }, - device_id: unsafe { winit::event::DeviceId::dummy() }, - is_synthetic: false, - }); - - // Step 3 extra frames - for _ in 0..3 { - example.render(&dst_view, &ctx.device, &ctx.queue, &spawner); - } - } - - let mut cmd_buf = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - - cmd_buf.copy_texture_to_buffer( - wgpu::ImageCopyTexture { - texture: &dst_texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - wgpu::ImageCopyBuffer { - buffer: &dst_buffer, - layout: wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(params.width * 4), - rows_per_image: None, - }, - }, - wgpu::Extent3d { - width: params.width, - height: params.height, - depth_or_array_layers: 1, - }, - ); - - ctx.queue.submit(Some(cmd_buf.finish())); - - let dst_buffer_slice = dst_buffer.slice(..); - dst_buffer_slice.map_async(wgpu::MapMode::Read, |_| ()); - ctx.device.poll(wgpu::Maintain::Wait); - let bytes = dst_buffer_slice.get_mapped_range().to_vec(); - - wgpu_test::image::compare_image_output( - env!("CARGO_MANIFEST_DIR").to_string() + "/../../" + params.image_path, - &ctx.adapter_info, - params.width, - params.height, - &bytes, - params.comparisons, - ); - }, - ); -} - -// This allows treating the framework as a standalone example, -// thus avoiding listing the example names in `Cargo.toml`. -#[allow(dead_code)] -fn main() {} diff --git a/examples/common/src/lib.rs b/examples/common/src/lib.rs deleted file mode 100644 index 0c717499e5..0000000000 --- a/examples/common/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod framework; diff --git a/examples/conservative-raster/Cargo.toml b/examples/conservative-raster/Cargo.toml deleted file mode 100644 index 1b4de48395..0000000000 --- a/examples/conservative-raster/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "wgpu-conservative-raster-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu conservative raster example" -publish = false - -[[bin]] -name = "conservative-raster" -path = "src/main.rs" - -[dependencies] -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/cube/Cargo.toml b/examples/cube/Cargo.toml deleted file mode 100644 index 697aa240e6..0000000000 --- a/examples/cube/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "wgpu-cube-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu cube example" -publish = false - -[[bin]] -name = "cube" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -glam.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/hello-compute/Cargo.toml b/examples/hello-compute/Cargo.toml deleted file mode 100644 index af5b29d735..0000000000 --- a/examples/hello-compute/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "wgpu-hello-compute-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu hello compute example" -publish = false - -[[bin]] -name = "hello-compute" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -env_logger.workspace = true -futures-intrusive.workspace = true -pollster.workspace = true -wgpu.workspace = true -winit.workspace = true - -[target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook.workspace = true -console_log.workspace = true -log.workspace = true -wasm-bindgen-futures.workspace = true - -[dev-dependencies] -wasm-bindgen-test.workspace = true -wgpu-test.workspace = true - diff --git a/examples/hello-compute/src/tests.rs b/examples/hello-compute/src/tests.rs deleted file mode 100644 index 7f8649f72f..0000000000 --- a/examples/hello-compute/src/tests.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::sync::Arc; - -use super::*; -use wgpu_test::{initialize_test, FailureCase, TestParameters}; - -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn test_compute_1() { - initialize_test( - TestParameters::default() - .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) - .limits(wgpu::Limits::downlevel_defaults()) - .features(wgpu::Features::TIMESTAMP_QUERY) - .skip(FailureCase::adapter("V3D")), - |ctx| { - let input = &[1, 2, 3, 4]; - - pollster::block_on(assert_execute_gpu( - &ctx.device, - &ctx.queue, - input, - &[0, 1, 7, 2], - )); - }, - ); -} - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn test_compute_2() { - initialize_test( - TestParameters::default() - .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) - .limits(wgpu::Limits::downlevel_defaults()) - .features(wgpu::Features::TIMESTAMP_QUERY) - .skip(FailureCase::adapter("V3D")), - |ctx| { - let input = &[5, 23, 10, 9]; - - pollster::block_on(assert_execute_gpu( - &ctx.device, - &ctx.queue, - input, - &[5, 15, 6, 19], - )); - }, - ); -} - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn test_compute_overflow() { - initialize_test( - TestParameters::default() - .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) - .limits(wgpu::Limits::downlevel_defaults()) - .features(wgpu::Features::TIMESTAMP_QUERY) - .skip(FailureCase::adapter("V3D")), - |ctx| { - let input = &[77031, 837799, 8400511, 63728127]; - pollster::block_on(assert_execute_gpu( - &ctx.device, - &ctx.queue, - input, - &[350, 524, OVERFLOW, OVERFLOW], - )); - }, - ); -} - -#[test] -// Wasm doesn't support threads -fn test_multithreaded_compute() { - initialize_test( - TestParameters::default() - .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) - .limits(wgpu::Limits::downlevel_defaults()) - .features(wgpu::Features::TIMESTAMP_QUERY) - .skip(FailureCase::adapter("V3D")) - // https://github.com/gfx-rs/wgpu/issues/3944 - .skip(FailureCase::backend_adapter( - wgpu::Backends::VULKAN, - "swiftshader", - )) - // https://github.com/gfx-rs/wgpu/issues/3250 - .skip(FailureCase::backend_adapter(wgpu::Backends::GL, "llvmpipe")) - .skip(FailureCase::molten_vk()), - |ctx| { - use std::{sync::mpsc, thread, time::Duration}; - - let ctx = Arc::new(ctx); - - let thread_count = 8; - - let (tx, rx) = mpsc::channel(); - for _ in 0..thread_count { - let tx = tx.clone(); - let ctx = Arc::clone(&ctx); - thread::spawn(move || { - let input = &[100, 100, 100]; - pollster::block_on(assert_execute_gpu( - &ctx.device, - &ctx.queue, - input, - &[25, 25, 25], - )); - tx.send(true).unwrap(); - }); - } - - for _ in 0..thread_count { - rx.recv_timeout(Duration::from_secs(10)) - .expect("A thread never completed."); - } - }, - ); -} - -async fn assert_execute_gpu( - device: &wgpu::Device, - queue: &wgpu::Queue, - input: &[u32], - expected: &[u32], -) { - if let Some(produced) = execute_gpu_inner(device, queue, input).await { - assert_eq!(produced, expected); - } -} diff --git a/examples/hello-triangle/Cargo.toml b/examples/hello-triangle/Cargo.toml deleted file mode 100644 index 1c0d6b9a3e..0000000000 --- a/examples/hello-triangle/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "wgpu-hello-triangle-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu hello triangle example" -publish = false - -[[bin]] -name = "hello-triangle" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -env_logger.workspace = true -pollster.workspace = true -wgpu.workspace = true -winit.workspace = true - -[target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook.workspace = true -console_log.workspace = true -wasm-bindgen-futures.workspace = true -web-sys.workspace = true diff --git a/examples/hello-triangle/src/main.rs b/examples/hello-triangle/src/main.rs deleted file mode 100644 index c5432acd07..0000000000 --- a/examples/hello-triangle/src/main.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::borrow::Cow; -use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::Window, -}; - -async fn run(event_loop: EventLoop<()>, window: Window) { - let size = window.inner_size(); - - let instance = wgpu::Instance::default(); - - let surface = unsafe { instance.create_surface(&window) }.unwrap(); - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - force_fallback_adapter: false, - // Request an adapter which can render to our surface - compatible_surface: Some(&surface), - }) - .await - .expect("Failed to find an appropriate adapter"); - - // Create the logical device and command queue - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. - limits: wgpu::Limits::downlevel_webgl2_defaults() - .using_resolution(adapter.limits()), - }, - None, - ) - .await - .expect("Failed to create device"); - - // Load the shaders from disk - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: None, - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let swapchain_capabilities = surface.get_capabilities(&adapter); - let swapchain_format = swapchain_capabilities.formats[0]; - - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(swapchain_format.into())], - }), - primitive: wgpu::PrimitiveState::default(), - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - multiview: None, - }); - - let mut config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: swapchain_format, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Fifo, - alpha_mode: swapchain_capabilities.alpha_modes[0], - view_formats: vec![], - }; - - surface.configure(&device, &config); - - event_loop.run(move |event, _, control_flow| { - // Have the closure take ownership of the resources. - // `event_loop.run` never returns, therefore we must do this to ensure - // the resources are properly cleaned up. - let _ = (&instance, &adapter, &shader, &pipeline_layout); - - *control_flow = ControlFlow::Wait; - match event { - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - // Reconfigure the surface with the new size - config.width = size.width; - config.height = size.height; - surface.configure(&device, &config); - // On macos the window needs to be redrawn manually after resizing - window.request_redraw(); - } - Event::RedrawRequested(_) => { - let frame = surface - .get_current_texture() - .expect("Failed to acquire next swap chain texture"); - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - { - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), - store: true, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - rpass.set_pipeline(&render_pipeline); - rpass.draw(0..3, 0..1); - } - - queue.submit(Some(encoder.finish())); - frame.present(); - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - _ => {} - } - }); -} - -fn main() { - let event_loop = EventLoop::new(); - let window = winit::window::Window::new(&event_loop).unwrap(); - #[cfg(not(target_arch = "wasm32"))] - { - env_logger::init(); - pollster::block_on(run(event_loop, window)); - } - #[cfg(target_arch = "wasm32")] - { - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - console_log::init().expect("could not initialize logger"); - use winit::platform::web::WindowExtWebSys; - // On wasm, append the canvas to the document body - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| doc.body()) - .and_then(|body| { - body.append_child(&web_sys::Element::from(window.canvas())) - .ok() - }) - .expect("couldn't append canvas to document body"); - wasm_bindgen_futures::spawn_local(run(event_loop, window)); - } -} diff --git a/examples/hello-windows/Cargo.toml b/examples/hello-windows/Cargo.toml deleted file mode 100644 index 966197f5b0..0000000000 --- a/examples/hello-windows/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "wgpu-hello-windows-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu hello windows example" -publish = false - -[[bin]] -name = "hello-windows" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -env_logger.workspace = true -pollster.workspace = true -wgpu.workspace = true -winit.workspace = true - -[target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook.workspace = true diff --git a/examples/hello/Cargo.toml b/examples/hello/Cargo.toml deleted file mode 100644 index 54b145c264..0000000000 --- a/examples/hello/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "wgpu-hello-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu hello example" -publish = false - -[[bin]] -name = "hello" -path = "src/main.rs" - -[dependencies] -env_logger.workspace = true -glam.workspace = true -log.workspace = true -pollster.workspace = true -wgpu.workspace = true - -[target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook.workspace = true -console_log.workspace = true -wasm-bindgen-futures.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true - diff --git a/examples/mipmap/Cargo.toml b/examples/mipmap/Cargo.toml deleted file mode 100644 index ab117e409b..0000000000 --- a/examples/mipmap/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "wgpu-mipmap-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu mipmap example" -publish = false - -[[bin]] -name = "mipmap" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -glam.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/msaa-line/Cargo.toml b/examples/msaa-line/Cargo.toml deleted file mode 100644 index c84d2676ce..0000000000 --- a/examples/msaa-line/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "wgpu-msaa-line-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu msaa line example" -publish = false - -[[bin]] -name = "msaa-line" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -glam.workspace = true -log.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true - diff --git a/examples/ray-cube-compute/Cargo.toml b/examples/ray-cube-compute/Cargo.toml deleted file mode 100644 index adeffa765b..0000000000 --- a/examples/ray-cube-compute/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "ray-cube-compute" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "todo" -publish = false - -[[bin]] -name = "ray-cube-compute" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -glam.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/ray-cube-fragment/Cargo.toml b/examples/ray-cube-fragment/Cargo.toml deleted file mode 100644 index a81f7ab97d..0000000000 --- a/examples/ray-cube-fragment/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "ray-cube-fragment" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "todo" -publish = false - -[[bin]] -name = "ray-cube-fragment" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -glam.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/ray-scene/Cargo.toml b/examples/ray-scene/Cargo.toml deleted file mode 100644 index 9fbeffab9f..0000000000 --- a/examples/ray-scene/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "ray-scene" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "todo" -publish = false - -[[bin]] -name = "ray-scene" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -glam.workspace = true -obj.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/shadow/Cargo.toml b/examples/shadow/Cargo.toml deleted file mode 100644 index 0f7847a888..0000000000 --- a/examples/shadow/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "wgpu-shadow-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu shadow example" -publish = false - -[[bin]] -name = "shadow" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -glam.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true - diff --git a/examples/skybox/Cargo.toml b/examples/skybox/Cargo.toml deleted file mode 100644 index f70e78e9b1..0000000000 --- a/examples/skybox/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "wgpu-skybox-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu skybox example" -publish = false - -[[bin]] -name = "skybox" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -ddsfile.workspace = true -glam.workspace = true -obj.workspace = true -log.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true \ No newline at end of file diff --git a/examples/skybox/images/astc.dds b/examples/skybox/images/astc.dds deleted file mode 100644 index b1bee88fa7..0000000000 Binary files a/examples/skybox/images/astc.dds and /dev/null differ diff --git a/examples/skybox/images/bc1.dds b/examples/skybox/images/bc1.dds deleted file mode 100644 index 4a9ec28371..0000000000 Binary files a/examples/skybox/images/bc1.dds and /dev/null differ diff --git a/examples/skybox/images/bgra.dds b/examples/skybox/images/bgra.dds deleted file mode 100644 index de3135c85c..0000000000 Binary files a/examples/skybox/images/bgra.dds and /dev/null differ diff --git a/examples/skybox/images/etc2.dds b/examples/skybox/images/etc2.dds deleted file mode 100644 index 77a3fa0da9..0000000000 Binary files a/examples/skybox/images/etc2.dds and /dev/null differ diff --git a/examples/skybox/screenshot-astc.png b/examples/skybox/screenshot-astc.png deleted file mode 100644 index 38515fada4..0000000000 Binary files a/examples/skybox/screenshot-astc.png and /dev/null differ diff --git a/examples/skybox/screenshot-bc1.png b/examples/skybox/screenshot-bc1.png deleted file mode 100644 index efba0b5c8c..0000000000 Binary files a/examples/skybox/screenshot-bc1.png and /dev/null differ diff --git a/examples/skybox/screenshot-etc2.png b/examples/skybox/screenshot-etc2.png deleted file mode 100644 index 46ae8d3198..0000000000 Binary files a/examples/skybox/screenshot-etc2.png and /dev/null differ diff --git a/examples/skybox/screenshot.png b/examples/skybox/screenshot.png deleted file mode 100644 index d90debe148..0000000000 Binary files a/examples/skybox/screenshot.png and /dev/null differ diff --git a/examples/boids/README.md b/examples/src/boids/README.md similarity index 78% rename from examples/boids/README.md rename to examples/src/boids/README.md index 7c75ee9848..5cd6bc2024 100644 --- a/examples/boids/README.md +++ b/examples/src/boids/README.md @@ -5,7 +5,7 @@ Flocking boids example with gpu compute update pass ## To Run ``` -cargo run --bin boids +cargo run --bin wgpu-examples boids ``` ## Screenshots diff --git a/examples/boids/src/compute.wgsl b/examples/src/boids/compute.wgsl similarity index 100% rename from examples/boids/src/compute.wgsl rename to examples/src/boids/compute.wgsl diff --git a/examples/boids/src/draw.wgsl b/examples/src/boids/draw.wgsl similarity index 100% rename from examples/boids/src/draw.wgsl rename to examples/src/boids/draw.wgsl diff --git a/examples/boids/src/main.rs b/examples/src/boids/mod.rs similarity index 91% rename from examples/boids/src/main.rs rename to examples/src/boids/mod.rs index e8aa2f71fd..b608394134 100644 --- a/examples/boids/src/main.rs +++ b/examples/src/boids/mod.rs @@ -24,7 +24,7 @@ struct Example { frame_num: usize, } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn required_limits() -> wgpu::Limits { wgpu::Limits::downlevel_defaults() } @@ -261,13 +261,7 @@ impl wgpu_example::framework::Example for Example { /// render is called each frame, dispatching compute groups proportional /// a TriangleList draw call for all NUM_PARTICLES at 3 vertices each - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { // create render pass descriptor and its color attachments let color_attachments = [Some(wgpu::RenderPassColorAttachment { view, @@ -276,7 +270,7 @@ impl wgpu_example::framework::Example for Example { // Not clearing here in order to test wgpu's zero texture initialization on a surface texture. // Users should avoid loading uninitialized memory since this can cause additional overhead. load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })]; let render_pass_descriptor = wgpu::RenderPassDescriptor { @@ -326,26 +320,24 @@ impl wgpu_example::framework::Example for Example { } /// run example -fn main() { - wgpu_example::framework::run::("boids"); +pub fn main() { + crate::framework::run::("boids"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn boids() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - // Generated on 1080ti on Vk/Windows - image_path: "examples/boids/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default() - .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) - .limits(wgpu::Limits::downlevel_defaults()) - // Lots of validation errors, maybe related to https://github.com/gfx-rs/wgpu/issues/3160 - .expect_fail(wgpu_test::FailureCase::molten_vk()), - comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "boids", + // Generated on 1080ti on Vk/Windows + image_path: "/examples/src/boids/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default() + .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) + .limits(wgpu::Limits::downlevel_defaults()) + // Lots of validation errors, maybe related to https://github.com/gfx-rs/wgpu/issues/3160 + .expect_fail(wgpu_test::FailureCase::molten_vk()), + comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/boids/screenshot.png b/examples/src/boids/screenshot.png similarity index 100% rename from examples/boids/screenshot.png rename to examples/src/boids/screenshot.png diff --git a/examples/src/bunnymark/README.md b/examples/src/bunnymark/README.md new file mode 100644 index 0000000000..95083cd19d --- /dev/null +++ b/examples/src/bunnymark/README.md @@ -0,0 +1,12 @@ +# bunnymark + + +## To Run + +``` +cargo run --bin wgpu-examples bunnymark +``` + +## Example output + +![Example output](./screenshot.png) diff --git a/examples/bunnymark/src/main.rs b/examples/src/bunnymark/mod.rs similarity index 80% rename from examples/bunnymark/src/main.rs rename to examples/src/bunnymark/mod.rs index 256083eebb..c29da351ee 100644 --- a/examples/bunnymark/src/main.rs +++ b/examples/src/bunnymark/mod.rs @@ -2,6 +2,10 @@ use bytemuck::{Pod, Zeroable}; use nanorand::{Rng, WyRand}; use std::{borrow::Cow, mem}; use wgpu::util::DeviceExt; +use winit::{ + event::{ElementState, KeyEvent}, + keyboard::{Key, NamedKey}, +}; const MAX_BUNNIES: usize = 1 << 20; const BUNNY_SIZE: f32 = 0.15 * 256.0; @@ -18,25 +22,116 @@ struct Globals { #[repr(C, align(256))] #[derive(Clone, Copy, Zeroable)] -struct Locals { +struct Bunny { position: [f32; 2], velocity: [f32; 2], color: u32, _pad: u32, } +impl Bunny { + fn update_data(&mut self, delta: f32, extent: &[u32; 2]) { + self.position[0] += self.velocity[0] * delta; + self.position[1] += self.velocity[1] * delta; + self.velocity[1] += GRAVITY * delta; + if (self.velocity[0] > 0.0 && self.position[0] + 0.5 * BUNNY_SIZE > extent[0] as f32) + || (self.velocity[0] < 0.0 && self.position[0] - 0.5 * BUNNY_SIZE < 0.0) + { + self.velocity[0] *= -1.0; + } + if self.velocity[1] < 0.0 && self.position[1] < 0.5 * BUNNY_SIZE { + self.velocity[1] *= -1.0; + } + } +} + /// Example struct holds references to wgpu resources and frame persistent data struct Example { global_group: wgpu::BindGroup, local_group: wgpu::BindGroup, pipeline: wgpu::RenderPipeline, - bunnies: Vec, + bunnies: Vec, local_buffer: wgpu::Buffer, extent: [u32; 2], rng: WyRand, } -impl wgpu_example::framework::Example for Example { +impl Example { + fn spawn_bunnies(&mut self) { + let spawn_count = 64; + let color = self.rng.generate::(); + println!( + "Spawning {} bunnies, total at {}", + spawn_count, + self.bunnies.len() + spawn_count + ); + for _ in 0..spawn_count { + let speed = self.rng.generate::() * MAX_VELOCITY - (MAX_VELOCITY * 0.5); + self.bunnies.push(Bunny { + position: [0.0, 0.5 * (self.extent[1] as f32)], + velocity: [speed, 0.0], + color, + _pad: 0, + }); + } + } + + fn render_inner( + &mut self, + view: &wgpu::TextureView, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) { + let delta = 0.01; + for bunny in self.bunnies.iter_mut() { + bunny.update_data(delta, &self.extent); + } + + let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment; + queue.write_buffer(&self.local_buffer, 0, unsafe { + std::slice::from_raw_parts( + self.bunnies.as_ptr() as *const u8, + self.bunnies.len() * uniform_alignment as usize, + ) + }); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + { + let clear_color = wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }; + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(clear_color), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, &self.global_group, &[]); + for i in 0..self.bunnies.len() { + let offset = + (i as wgpu::DynamicOffset) * (uniform_alignment as wgpu::DynamicOffset); + rpass.set_bind_group(1, &self.local_group, &[offset]); + rpass.draw(0..4, 0..1); + } + } + + queue.submit(Some(encoder.finish())); + } +} + +impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, @@ -90,7 +185,7 @@ impl wgpu_example::framework::Example for Example { ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: true, - min_binding_size: wgpu::BufferSize::new(mem::size_of::() as _), + min_binding_size: wgpu::BufferSize::new(mem::size_of::() as _), }, count: None, }], @@ -228,7 +323,7 @@ impl wgpu_example::framework::Example for Example { resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &local_buffer, offset: 0, - size: wgpu::BufferSize::new(mem::size_of::() as _), + size: wgpu::BufferSize::new(mem::size_of::() as _), }), }], label: None, @@ -236,7 +331,7 @@ impl wgpu_example::framework::Example for Example { let rng = WyRand::new_seed(42); - Example { + let mut ex = Example { pipeline, global_group, local_group, @@ -244,36 +339,25 @@ impl wgpu_example::framework::Example for Example { local_buffer, extent: [config.width, config.height], rng, - } + }; + + ex.spawn_bunnies(); + + ex } fn update(&mut self, event: winit::event::WindowEvent) { if let winit::event::WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(winit::event::VirtualKeyCode::Space), - state: winit::event::ElementState::Pressed, + event: + KeyEvent { + logical_key: Key::Named(NamedKey::Space), + state: ElementState::Pressed, .. }, .. } = event { - let spawn_count = 64 + self.bunnies.len() / 2; - let color = self.rng.generate::(); - println!( - "Spawning {} bunnies, total at {}", - spawn_count, - self.bunnies.len() + spawn_count - ); - for _ in 0..spawn_count { - let speed = self.rng.generate::() * MAX_VELOCITY - (MAX_VELOCITY * 0.5); - self.bunnies.push(Locals { - position: [0.0, 0.5 * (self.extent[1] as f32)], - velocity: [speed, 0.0], - color, - _pad: 0, - }); - } + self.spawn_bunnies(); } } @@ -286,95 +370,31 @@ impl wgpu_example::framework::Example for Example { //empty } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { - let delta = 0.01; - for bunny in self.bunnies.iter_mut() { - bunny.position[0] += bunny.velocity[0] * delta; - bunny.position[1] += bunny.velocity[1] * delta; - bunny.velocity[1] += GRAVITY * delta; - if (bunny.velocity[0] > 0.0 - && bunny.position[0] + 0.5 * BUNNY_SIZE > self.extent[0] as f32) - || (bunny.velocity[0] < 0.0 && bunny.position[0] - 0.5 * BUNNY_SIZE < 0.0) - { - bunny.velocity[0] *= -1.0; - } - if bunny.velocity[1] < 0.0 && bunny.position[1] < 0.5 * BUNNY_SIZE { - bunny.velocity[1] *= -1.0; - } - } - - let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment; - queue.write_buffer(&self.local_buffer, 0, unsafe { - std::slice::from_raw_parts( - self.bunnies.as_ptr() as *const u8, - self.bunnies.len() * uniform_alignment as usize, - ) - }); - - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - { - let clear_color = wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.3, - a: 1.0, - }; - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(clear_color), - store: true, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - rpass.set_pipeline(&self.pipeline); - rpass.set_bind_group(0, &self.global_group, &[]); - for i in 0..self.bunnies.len() { - let offset = - (i as wgpu::DynamicOffset) * (uniform_alignment as wgpu::DynamicOffset); - rpass.set_bind_group(1, &self.local_group, &[offset]); - rpass.draw(0..4, 0..1); - } - } - - queue.submit(Some(encoder.finish())); + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + self.render_inner(view, device, queue); } } -fn main() { - wgpu_example::framework::run::("bunnymark"); +pub fn main() { + crate::framework::run::("bunnymark"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn bunnymark() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/bunnymark/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default(), - // We're looking for very small differences, so look in the high percentiles. - comparisons: &[ - wgpu_test::ComparisonType::Mean(0.05), - wgpu_test::ComparisonType::Percentile { - percentile: 0.95, - threshold: 0.05, - }, - ], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "bunnymark", + image_path: "/examples/src/bunnymark/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + // We're looking for very small differences, so look in the high percentiles. + comparisons: &[ + wgpu_test::ComparisonType::Mean(0.05), + wgpu_test::ComparisonType::Percentile { + percentile: 0.99, + threshold: 0.37, + }, + ], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/src/bunnymark/screenshot.png b/examples/src/bunnymark/screenshot.png new file mode 100644 index 0000000000..132a1f79bb Binary files /dev/null and b/examples/src/bunnymark/screenshot.png differ diff --git a/examples/conservative-raster/README.md b/examples/src/conservative_raster/README.md similarity index 90% rename from examples/conservative-raster/README.md rename to examples/src/conservative_raster/README.md index bf79a9a168..9100a8bae4 100644 --- a/examples/conservative-raster/README.md +++ b/examples/src/conservative_raster/README.md @@ -1,4 +1,4 @@ -# conservative-raster +# conservative_raster This example shows how to render with conservative rasterization (native extension with limited support). @@ -12,7 +12,7 @@ Pixels only drawn with conservative rasterization enabled are depicted red. ## To Run ``` -cargo run --bin conservative-raster +cargo run --bin wgpu-examples conservative_raster ``` ## Screenshots diff --git a/examples/conservative-raster/src/main.rs b/examples/src/conservative_raster/mod.rs similarity index 92% rename from examples/conservative-raster/src/main.rs rename to examples/src/conservative_raster/mod.rs index e5cfb4d775..ce2054caa0 100644 --- a/examples/conservative-raster/src/main.rs +++ b/examples/src/conservative_raster/mod.rs @@ -63,7 +63,7 @@ impl Example { } } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::CONSERVATIVE_RASTERIZATION } @@ -250,13 +250,7 @@ impl wgpu_example::framework::Example for Example { fn update(&mut self, _event: winit::event::WindowEvent) {} - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("primary"), }); @@ -269,7 +263,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -290,7 +284,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -312,21 +306,19 @@ impl wgpu_example::framework::Example for Example { } } -fn main() { - wgpu_example::framework::run::("conservative-raster"); +pub fn main() { + crate::framework::run::("conservative-raster"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn conservative_raster() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/conservative-raster/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default(), - comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "conservative-raster", + image_path: "/examples/src/conservative_raster/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/conservative-raster/screenshot.png b/examples/src/conservative_raster/screenshot.png similarity index 100% rename from examples/conservative-raster/screenshot.png rename to examples/src/conservative_raster/screenshot.png diff --git a/examples/conservative-raster/src/triangle_and_lines.wgsl b/examples/src/conservative_raster/triangle_and_lines.wgsl similarity index 100% rename from examples/conservative-raster/src/triangle_and_lines.wgsl rename to examples/src/conservative_raster/triangle_and_lines.wgsl diff --git a/examples/conservative-raster/src/upscale.wgsl b/examples/src/conservative_raster/upscale.wgsl similarity index 100% rename from examples/conservative-raster/src/upscale.wgsl rename to examples/src/conservative_raster/upscale.wgsl diff --git a/examples/cube/README.md b/examples/src/cube/README.md similarity index 76% rename from examples/cube/README.md rename to examples/src/cube/README.md index 3c1324958d..39af75ae78 100644 --- a/examples/cube/README.md +++ b/examples/src/cube/README.md @@ -5,7 +5,7 @@ This example renders a textured cube. ## To Run ``` -cargo run --bin cube +cargo run --bin wgpu-examples cube ``` ## Screenshots diff --git a/examples/cube/src/main.rs b/examples/src/cube/mod.rs similarity index 82% rename from examples/cube/src/main.rs rename to examples/src/cube/mod.rs index a10dfd0fd0..d21aafe5de 100644 --- a/examples/cube/src/main.rs +++ b/examples/src/cube/mod.rs @@ -1,5 +1,5 @@ use bytemuck::{Pod, Zeroable}; -use std::{borrow::Cow, f32::consts, future::Future, mem, pin::Pin, task}; +use std::{borrow::Cow, f32::consts, mem}; use wgpu::util::DeviceExt; #[repr(C)] @@ -80,28 +80,6 @@ fn create_texels(size: usize) -> Vec { .collect() } -/// A wrapper for `pop_error_scope` futures that panics if an error occurs. -/// -/// Given a future `inner` of an `Option` for some error type `E`, -/// wait for the future to be ready, and panic if its value is `Some`. -/// -/// This can be done simpler with `FutureExt`, but we don't want to add -/// a dependency just for this small case. -struct ErrorFuture { - inner: F, -} -impl>> Future for ErrorFuture { - type Output = (); - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<()> { - let inner = unsafe { self.map_unchecked_mut(|me| &mut me.inner) }; - inner.poll(cx).map(|error| { - if let Some(e) = error { - panic!("Rendering {e}"); - } - }) - } -} - struct Example { vertex_buf: wgpu::Buffer, index_buf: wgpu::Buffer, @@ -124,7 +102,7 @@ impl Example { } } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::POLYGON_MODE_LINE } @@ -352,14 +330,7 @@ impl wgpu_example::framework::Example for Example { queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref)); } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - spawner: &wgpu_example::framework::Spawner, - ) { - device.push_error_scope(wgpu::ErrorFilter::Validation); + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { @@ -375,7 +346,7 @@ impl wgpu_example::framework::Example for Example { b: 0.3, a: 1.0, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -397,53 +368,46 @@ impl wgpu_example::framework::Example for Example { } queue.submit(Some(encoder.finish())); - - // If an error occurs, report it and panic. - spawner.spawn_local(ErrorFuture { - inner: device.pop_error_scope(), - }); } } -fn main() { - wgpu_example::framework::run::("cube"); +pub fn main() { + crate::framework::run::("cube"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "cube", + // Generated on 1080ti on Vk/Windows + image_path: "/examples/src/cube/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[ + wgpu_test::ComparisonType::Mean(0.04), // Bounded by Intel 630 on Vk/Windows + ], + _phantom: std::marker::PhantomData::, +}; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn cube() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - // Generated on 1080ti on Vk/Windows - image_path: "/examples/cube/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default(), - comparisons: &[ - wgpu_test::ComparisonType::Mean(0.04), // Bounded by Intel 630 on Vk/Windows - ], - }); -} - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn cube_lines() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - // Generated on 1080ti on Vk/Windows - image_path: "/examples/cube/screenshot-lines.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::POLYGON_MODE_LINE, - base_test_parameters: wgpu_test::TestParameters::default(), - // We're looking for tiny changes here, so we focus on a spike in the 95th percentile. - comparisons: &[ - wgpu_test::ComparisonType::Mean(0.05), // Bounded by Intel 630 on Vk/Windows - wgpu_test::ComparisonType::Percentile { - percentile: 0.95, - threshold: 0.36, - }, // Bounded by 1080ti on DX12 - ], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_LINES: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "cube-lines", + // Generated on 1080ti on Vk/Windows + image_path: "/examples/src/cube/screenshot-lines.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::POLYGON_MODE_LINE, + base_test_parameters: wgpu_test::TestParameters::default(), + // We're looking for tiny changes here, so we focus on a spike in the 95th percentile. + comparisons: &[ + wgpu_test::ComparisonType::Mean(0.05), // Bounded by Intel 630 on Vk/Windows + wgpu_test::ComparisonType::Percentile { + percentile: 0.95, + threshold: 0.36, + }, // Bounded by 1080ti on DX12 + ], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/cube/screenshot-lines.png b/examples/src/cube/screenshot-lines.png similarity index 100% rename from examples/cube/screenshot-lines.png rename to examples/src/cube/screenshot-lines.png diff --git a/examples/cube/screenshot.png b/examples/src/cube/screenshot.png similarity index 100% rename from examples/cube/screenshot.png rename to examples/src/cube/screenshot.png diff --git a/examples/cube/src/shader.wgsl b/examples/src/cube/shader.wgsl similarity index 100% rename from examples/cube/src/shader.wgsl rename to examples/src/cube/shader.wgsl diff --git a/examples/src/framework.rs b/examples/src/framework.rs new file mode 100644 index 0000000000..dd2c2ee6d1 --- /dev/null +++ b/examples/src/framework.rs @@ -0,0 +1,631 @@ +use std::sync::Arc; + +use wgpu::{Instance, Surface}; +use winit::{ + dpi::PhysicalSize, + event::{Event, KeyEvent, StartCause, WindowEvent}, + event_loop::{EventLoop, EventLoopWindowTarget}, + keyboard::{Key, NamedKey}, + window::Window, +}; + +pub trait Example: 'static + Sized { + const SRGB: bool = true; + + fn optional_features() -> wgpu::Features { + wgpu::Features::empty() + } + + fn required_features() -> wgpu::Features { + wgpu::Features::empty() + } + + fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities { + wgpu::DownlevelCapabilities { + flags: wgpu::DownlevelFlags::empty(), + shader_model: wgpu::ShaderModel::Sm5, + ..wgpu::DownlevelCapabilities::default() + } + } + + fn required_limits() -> wgpu::Limits { + wgpu::Limits::downlevel_webgl2_defaults() // These downlevel limits will allow the code to run on all possible hardware + } + + fn init( + config: &wgpu::SurfaceConfiguration, + adapter: &wgpu::Adapter, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> Self; + + fn resize( + &mut self, + config: &wgpu::SurfaceConfiguration, + device: &wgpu::Device, + queue: &wgpu::Queue, + ); + + fn update(&mut self, event: WindowEvent); + + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue); +} + +// Initialize logging in platform dependant ways. +fn init_logger() { + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + // As we don't have an environment to pull logging level from, we use the query string. + let query_string = web_sys::window().unwrap().location().search().unwrap(); + let query_level: Option = parse_url_query_string(&query_string, "RUST_LOG") + .and_then(|x| x.parse().ok()); + + // We keep wgpu at Error level, as it's very noisy. + let base_level = query_level.unwrap_or(log::LevelFilter::Info); + let wgpu_level = query_level.unwrap_or(log::LevelFilter::Error); + + // On web, we use fern, as console_log doesn't have filtering on a per-module level. + fern::Dispatch::new() + .level(base_level) + .level_for("wgpu_core", wgpu_level) + .level_for("wgpu_hal", wgpu_level) + .level_for("naga", wgpu_level) + .chain(fern::Output::call(console_log::log)) + .apply() + .unwrap(); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + } else { + // parse_default_env will read the RUST_LOG environment variable and apply it on top + // of these default filters. + env_logger::builder() + .filter_level(log::LevelFilter::Info) + // We keep wgpu at Error level, as it's very noisy. + .filter_module("wgpu_core", log::LevelFilter::Info) + .filter_module("wgpu_hal", log::LevelFilter::Error) + .filter_module("naga", log::LevelFilter::Error) + .parse_default_env() + .init(); + } + } +} + +struct EventLoopWrapper { + event_loop: EventLoop<()>, + window: Arc, +} + +impl EventLoopWrapper { + pub fn new(title: &str) -> Self { + let event_loop = EventLoop::new().unwrap(); + let mut builder = winit::window::WindowBuilder::new(); + #[cfg(target_arch = "wasm32")] + { + use wasm_bindgen::JsCast; + use winit::platform::web::WindowBuilderExtWebSys; + let canvas = web_sys::window() + .unwrap() + .document() + .unwrap() + .get_element_by_id("canvas") + .unwrap() + .dyn_into::() + .unwrap(); + builder = builder.with_canvas(Some(canvas)); + } + builder = builder.with_title(title); + let window = Arc::new(builder.build(&event_loop).unwrap()); + + Self { event_loop, window } + } +} + +/// Wrapper type which manages the surface and surface configuration. +/// +/// As surface usage varies per platform, wrapping this up cleans up the event loop code. +struct SurfaceWrapper { + surface: Option>, + config: Option, +} + +impl SurfaceWrapper { + /// Create a new surface wrapper with no surface or configuration. + fn new() -> Self { + Self { + surface: None, + config: None, + } + } + + /// Called after the instance is created, but before we request an adapter. + /// + /// On wasm, we need to create the surface here, as the WebGL backend needs + /// a surface (and hence a canvas) to be present to create the adapter. + /// + /// We cannot unconditionally create a surface here, as Android requires + /// us to wait until we recieve the `Resumed` event to do so. + fn pre_adapter(&mut self, instance: &Instance, window: Arc) { + if cfg!(target_arch = "wasm32") { + self.surface = Some(instance.create_surface(window).unwrap()); + } + } + + /// Check if the event is the start condition for the surface. + fn start_condition(e: &Event<()>) -> bool { + match e { + // On all other platforms, we can create the surface immediately. + Event::NewEvents(StartCause::Init) => !cfg!(target_os = "android"), + // On android we need to wait for a resumed event to create the surface. + Event::Resumed => cfg!(target_os = "android"), + _ => false, + } + } + + /// Called when an event which matches [`Self::start_condition`] is recieved. + /// + /// On all native platforms, this is where we create the surface. + /// + /// Additionally, we configure the surface based on the (now valid) window size. + fn resume(&mut self, context: &ExampleContext, window: Arc, srgb: bool) { + // Window size is only actually valid after we enter the event loop. + let window_size = window.inner_size(); + let width = window_size.width.max(1); + let height = window_size.height.max(1); + + log::info!("Surface resume {window_size:?}"); + + // We didn't create the surface in pre_adapter, so we need to do so now. + if !cfg!(target_arch = "wasm32") { + self.surface = Some(context.instance.create_surface(window).unwrap()); + } + + // From here on, self.surface should be Some. + + let surface = self.surface.as_ref().unwrap(); + + // Get the default configuration, + let mut config = surface + .get_default_config(&context.adapter, width, height) + .expect("Surface isn't supported by the adapter."); + if srgb { + // Not all platforms (WebGPU) support sRGB swapchains, so we need to use view formats + let view_format = config.format.add_srgb_suffix(); + config.view_formats.push(view_format); + } else { + // All platforms support non-sRGB swapchains, so we can just use the format directly. + let format = config.format.remove_srgb_suffix(); + config.format = format; + config.view_formats.push(format); + }; + + surface.configure(&context.device, &config); + self.config = Some(config); + } + + /// Resize the surface, making sure to not resize to zero. + fn resize(&mut self, context: &ExampleContext, size: PhysicalSize) { + log::info!("Surface resize {size:?}"); + + let config = self.config.as_mut().unwrap(); + config.width = size.width.max(1); + config.height = size.height.max(1); + let surface = self.surface.as_ref().unwrap(); + surface.configure(&context.device, config); + } + + /// Acquire the next surface texture. + fn acquire(&mut self, context: &ExampleContext) -> wgpu::SurfaceTexture { + let surface = self.surface.as_ref().unwrap(); + + match surface.get_current_texture() { + Ok(frame) => frame, + // If we timed out, just try again + Err(wgpu::SurfaceError::Timeout) => surface + .get_current_texture() + .expect("Failed to acquire next surface texture!"), + Err( + // If the surface is outdated, or was lost, reconfigure it. + wgpu::SurfaceError::Outdated + | wgpu::SurfaceError::Lost + // If OutOfMemory happens, reconfiguring may not help, but we might as well try + | wgpu::SurfaceError::OutOfMemory, + ) => { + surface.configure(&context.device, self.config()); + surface + .get_current_texture() + .expect("Failed to acquire next surface texture!") + } + } + } + + /// On suspend on android, we drop the surface, as it's no longer valid. + /// + /// A suspend event is always followed by at least one resume event. + fn suspend(&mut self) { + if cfg!(target_os = "android") { + self.surface = None; + } + } + + fn get(&self) -> Option<&Surface> { + self.surface.as_ref() + } + + fn config(&self) -> &wgpu::SurfaceConfiguration { + self.config.as_ref().unwrap() + } +} + +/// Context containing global wgpu resources. +struct ExampleContext { + instance: wgpu::Instance, + adapter: wgpu::Adapter, + device: wgpu::Device, + queue: wgpu::Queue, +} +impl ExampleContext { + /// Initializes the example context. + async fn init_async(surface: &mut SurfaceWrapper, window: Arc) -> Self { + log::info!("Initializing wgpu..."); + + let backends = wgpu::util::backend_bits_from_env().unwrap_or_default(); + let dx12_shader_compiler = wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(); + let gles_minor_version = wgpu::util::gles_minor_version_from_env().unwrap_or_default(); + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, + flags: wgpu::InstanceFlags::from_build_config().with_env(), + dx12_shader_compiler, + gles_minor_version, + }); + surface.pre_adapter(&instance, window); + let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, surface.get()) + .await + .expect("No suitable GPU adapters found on the system!"); + + let adapter_info = adapter.get_info(); + log::info!("Using {} ({:?})", adapter_info.name, adapter_info.backend); + + let optional_features = E::optional_features(); + let required_features = E::required_features(); + let adapter_features = adapter.features(); + assert!( + adapter_features.contains(required_features), + "Adapter does not support required features for this example: {:?}", + required_features - adapter_features + ); + + let required_downlevel_capabilities = E::required_downlevel_capabilities(); + let downlevel_capabilities = adapter.get_downlevel_capabilities(); + assert!( + downlevel_capabilities.shader_model >= required_downlevel_capabilities.shader_model, + "Adapter does not support the minimum shader model required to run this example: {:?}", + required_downlevel_capabilities.shader_model + ); + assert!( + downlevel_capabilities + .flags + .contains(required_downlevel_capabilities.flags), + "Adapter does not support the downlevel capabilities required to run this example: {:?}", + required_downlevel_capabilities.flags - downlevel_capabilities.flags + ); + + // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface. + let needed_limits = E::required_limits().using_resolution(adapter.limits()); + + let trace_dir = std::env::var("WGPU_TRACE"); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: (optional_features & adapter_features) | required_features, + required_limits: needed_limits, + }, + trace_dir.ok().as_ref().map(std::path::Path::new), + ) + .await + .expect("Unable to find a suitable GPU adapter!"); + + Self { + instance, + adapter, + device, + queue, + } + } +} + +struct FrameCounter { + // Instant of the last time we printed the frame time. + last_printed_instant: web_time::Instant, + // Number of frames since the last time we printed the frame time. + frame_count: u32, +} + +impl FrameCounter { + fn new() -> Self { + Self { + last_printed_instant: web_time::Instant::now(), + frame_count: 0, + } + } + + fn update(&mut self) { + self.frame_count += 1; + let new_instant = web_time::Instant::now(); + let elapsed_secs = (new_instant - self.last_printed_instant).as_secs_f32(); + if elapsed_secs > 1.0 { + let elapsed_ms = elapsed_secs * 1000.0; + let frame_time = elapsed_ms / self.frame_count as f32; + let fps = self.frame_count as f32 / elapsed_secs; + log::info!("Frame time {:.2}ms ({:.1} FPS)", frame_time, fps); + + self.last_printed_instant = new_instant; + self.frame_count = 0; + } + } +} + +async fn start(title: &str) { + init_logger(); + let window_loop = EventLoopWrapper::new(title); + let mut surface = SurfaceWrapper::new(); + let context = ExampleContext::init_async::(&mut surface, window_loop.window.clone()).await; + let mut frame_counter = FrameCounter::new(); + + // We wait to create the example until we have a valid surface. + let mut example = None; + + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + use winit::platform::web::EventLoopExtWebSys; + let event_loop_function = EventLoop::spawn; + } else { + let event_loop_function = EventLoop::run; + } + } + + log::info!("Entering event loop..."); + // On native this is a result, but on wasm it's a unit type. + #[allow(clippy::let_unit_value)] + let _ = (event_loop_function)( + window_loop.event_loop, + move |event: Event<()>, target: &EventLoopWindowTarget<()>| { + match event { + ref e if SurfaceWrapper::start_condition(e) => { + surface.resume(&context, window_loop.window.clone(), E::SRGB); + + // If we haven't created the example yet, do so now. + if example.is_none() { + example = Some(E::init( + surface.config(), + &context.adapter, + &context.device, + &context.queue, + )); + } + } + Event::Suspended => { + surface.suspend(); + } + Event::WindowEvent { event, .. } => match event { + WindowEvent::Resized(size) => { + surface.resize(&context, size); + example.as_mut().unwrap().resize( + surface.config(), + &context.device, + &context.queue, + ); + + window_loop.window.request_redraw(); + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Named(NamedKey::Escape), + .. + }, + .. + } + | WindowEvent::CloseRequested => { + target.exit(); + } + #[cfg(not(target_arch = "wasm32"))] + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character(s), + .. + }, + .. + } if s == "r" => { + println!("{:#?}", context.instance.generate_report()); + } + WindowEvent::RedrawRequested => { + // On MacOS, currently redraw requested comes in _before_ Init does. + // If this happens, just drop the requested redraw on the floor. + // + // See https://github.com/rust-windowing/winit/issues/3235 for some discussion + if example.is_none() { + return; + } + + frame_counter.update(); + + let frame = surface.acquire(&context); + let view = frame.texture.create_view(&wgpu::TextureViewDescriptor { + format: Some(surface.config().view_formats[0]), + ..wgpu::TextureViewDescriptor::default() + }); + + example + .as_mut() + .unwrap() + .render(&view, &context.device, &context.queue); + + frame.present(); + + window_loop.window.request_redraw(); + } + _ => example.as_mut().unwrap().update(event), + }, + _ => {} + } + }, + ); +} + +pub fn run(title: &'static str) { + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + wasm_bindgen_futures::spawn_local(async move { start::(title).await }) + } else { + pollster::block_on(start::(title)); + } + } +} + +#[cfg(target_arch = "wasm32")] +/// Parse the query string as returned by `web_sys::window()?.location().search()?` and get a +/// specific key out of it. +pub fn parse_url_query_string<'a>(query: &'a str, search_key: &str) -> Option<&'a str> { + let query_string = query.strip_prefix('?')?; + + for pair in query_string.split('&') { + let mut pair = pair.split('='); + let key = pair.next()?; + let value = pair.next()?; + + if key == search_key { + return Some(value); + } + } + + None +} + +#[cfg(test)] +pub use wgpu_test::image::ComparisonType; + +#[cfg(test)] +#[derive(Clone)] +pub struct ExampleTestParams { + pub name: &'static str, + // Path to the reference image, relative to the root of the repo. + pub image_path: &'static str, + pub width: u32, + pub height: u32, + pub optional_features: wgpu::Features, + pub base_test_parameters: wgpu_test::TestParameters, + /// Comparisons against FLIP statistics that determine if the test passes or fails. + pub comparisons: &'static [ComparisonType], + pub _phantom: std::marker::PhantomData, +} + +#[cfg(test)] +impl From> + for wgpu_test::GpuTestConfiguration +{ + fn from(params: ExampleTestParams) -> Self { + wgpu_test::GpuTestConfiguration::new() + .name(params.name) + .parameters({ + assert_eq!(params.width % 64, 0, "width needs to be aligned 64"); + + let features = E::required_features() | params.optional_features; + + params.base_test_parameters.clone().features(features) + }) + .run_async(move |ctx| async move { + let format = if E::SRGB { + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Rgba8Unorm + }; + let dst_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: Some("destination"), + size: wgpu::Extent3d { + width: params.width, + height: params.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let dst_view = dst_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let dst_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("image map buffer"), + size: params.width as u64 * params.height as u64 * 4, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let mut example = E::init( + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width: params.width, + height: params.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![format], + }, + &ctx.adapter, + &ctx.device, + &ctx.queue, + ); + + example.render(&dst_view, &ctx.device, &ctx.queue); + + let mut cmd_buf = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + cmd_buf.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: &dst_texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &dst_buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(params.width * 4), + rows_per_image: None, + }, + }, + wgpu::Extent3d { + width: params.width, + height: params.height, + depth_or_array_layers: 1, + }, + ); + + ctx.queue.submit(Some(cmd_buf.finish())); + + let dst_buffer_slice = dst_buffer.slice(..); + dst_buffer_slice.map_async(wgpu::MapMode::Read, |_| ()); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + let bytes = dst_buffer_slice.get_mapped_range().to_vec(); + + wgpu_test::image::compare_image_output( + dbg!(env!("CARGO_MANIFEST_DIR").to_string() + "/../" + params.image_path), + &ctx.adapter_info, + params.width, + params.height, + &bytes, + params.comparisons, + ) + .await; + }) + } +} diff --git a/examples/hello/README.md b/examples/src/hello/README.md similarity index 95% rename from examples/hello/README.md rename to examples/src/hello/README.md index 170f53538b..1d51a6b83b 100644 --- a/examples/hello/README.md +++ b/examples/src/hello/README.md @@ -5,7 +5,7 @@ This example prints output describing the adapter in use. ## To Run ``` -cargo run --bin hello +cargo run --bin wgpu-examples hello ``` ## Example output diff --git a/examples/hello/src/main.rs b/examples/src/hello/mod.rs similarity index 98% rename from examples/hello/src/main.rs rename to examples/src/hello/mod.rs index 71d947cdc6..12239e43f7 100644 --- a/examples/hello/src/main.rs +++ b/examples/src/hello/mod.rs @@ -19,7 +19,7 @@ async fn run() { log::info!("Selected adapter: {:?}", adapter.get_info()) } -fn main() { +pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::init(); diff --git a/examples/hello-compute/README.md b/examples/src/hello_compute/README.md similarity index 82% rename from examples/hello-compute/README.md rename to examples/src/hello_compute/README.md index ee34a94838..8b3f3e111d 100644 --- a/examples/hello-compute/README.md +++ b/examples/src/hello_compute/README.md @@ -12,7 +12,7 @@ that it will take to finish and reach the number `1`. ``` # Pass in any 4 numbers as arguments -RUST_LOG=hello_compute cargo run --bin hello-compute 1 4 3 295 +RUST_LOG=hello_compute cargo run --bin wgpu-examples hello_compute 1 4 3 295 ``` ## Example Output diff --git a/examples/hello-compute/src/main.rs b/examples/src/hello_compute/mod.rs similarity index 92% rename from examples/hello-compute/src/main.rs rename to examples/src/hello_compute/mod.rs index 3b102f4e0e..ef452bf023 100644 --- a/examples/hello-compute/src/main.rs +++ b/examples/src/hello_compute/mod.rs @@ -4,14 +4,15 @@ use wgpu::util::DeviceExt; // Indicates a u32 overflow in an intermediate Collatz value const OVERFLOW: u32 = 0xffffffff; +#[cfg_attr(test, allow(dead_code))] async fn run() { - let numbers = if std::env::args().len() <= 1 { + let numbers = if std::env::args().len() <= 2 { let default = vec![1, 2, 3, 4]; println!("No numbers were provided, defaulting to {default:?}"); default } else { std::env::args() - .skip(1) + .skip(2) .map(|s| u32::from_str(&s).expect("You must pass a list of positive integers!")) .collect() }; @@ -31,6 +32,7 @@ async fn run() { log::info!("Steps: [{}]", disp_steps.join(", ")); } +#[cfg_attr(test, allow(dead_code))] async fn execute_gpu(numbers: &[u32]) -> Option> { // Instantiates instance of WebGPU let instance = wgpu::Instance::default(); @@ -46,20 +48,14 @@ async fn execute_gpu(numbers: &[u32]) -> Option> { .request_device( &wgpu::DeviceDescriptor { label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::downlevel_defaults(), + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_defaults(), }, None, ) .await .unwrap(); - let info = adapter.get_info(); - // skip this on LavaPipe temporarily - if info.vendor == 0x10005 { - return None; - } - execute_gpu_inner(&device, &queue, numbers).await } @@ -150,16 +146,16 @@ async fn execute_gpu_inner( // Note that we're not calling `.await` here. let buffer_slice = staging_buffer.slice(..); // Sets the buffer up for mapping, sending over the result of the mapping back to us when it is finished. - let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel(); + let (sender, receiver) = flume::bounded(1); buffer_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap()); // Poll the device in a blocking manner so that our future resolves. // In an actual application, `device.poll(...)` should // be called in an event loop or on another thread. - device.poll(wgpu::Maintain::Wait); + device.poll(wgpu::Maintain::wait()).panic_on_timeout(); // Awaits until `buffer_future` can be read from - if let Some(Ok(())) = receiver.receive().await { + if let Ok(Ok(())) = receiver.recv_async().await { // Gets contents of buffer let data = buffer_slice.get_mapped_range(); // Since contents are got in bytes, this converts these bytes back to u32 @@ -181,7 +177,7 @@ async fn execute_gpu_inner( } } -fn main() { +pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::init(); @@ -195,5 +191,5 @@ fn main() { } } -#[cfg(all(test, not(target_arch = "wasm32")))] +#[cfg(test)] mod tests; diff --git a/examples/hello-compute/src/shader.wgsl b/examples/src/hello_compute/shader.wgsl similarity index 100% rename from examples/hello-compute/src/shader.wgsl rename to examples/src/hello_compute/shader.wgsl diff --git a/examples/src/hello_compute/tests.rs b/examples/src/hello_compute/tests.rs new file mode 100644 index 0000000000..8021e191ed --- /dev/null +++ b/examples/src/hello_compute/tests.rs @@ -0,0 +1,102 @@ +use super::*; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; + +#[gpu_test] +static COMPUTE_1: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) + .limits(wgpu::Limits::downlevel_defaults()) + .skip(FailureCase::adapter("V3D")), + ) + .run_async(|ctx| { + let input = &[1, 2, 3, 4]; + + async move { assert_execute_gpu(&ctx.device, &ctx.queue, input, &[0, 1, 7, 2]).await } + }); + +#[gpu_test] +static COMPUTE_2: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) + .limits(wgpu::Limits::downlevel_defaults()) + .skip(FailureCase::adapter("V3D")), + ) + .run_async(|ctx| { + let input = &[5, 23, 10, 9]; + + async move { assert_execute_gpu(&ctx.device, &ctx.queue, input, &[5, 15, 6, 19]).await } + }); + +#[gpu_test] +static COMPUTE_OVERFLOW: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) + .limits(wgpu::Limits::downlevel_defaults()) + .skip(FailureCase::adapter("V3D")), + ) + .run_async(|ctx| { + let input = &[77031, 837799, 8400511, 63728127]; + async move { + assert_execute_gpu( + &ctx.device, + &ctx.queue, + input, + &[350, 524, OVERFLOW, OVERFLOW], + ) + .await + } + }); + +#[cfg(not(target_arch = "wasm32"))] +#[gpu_test] +static MULTITHREADED_COMPUTE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) + .limits(wgpu::Limits::downlevel_defaults()) + .skip(FailureCase::adapter("V3D")) + // Segfaults on linux CI only https://github.com/gfx-rs/wgpu/issues/4285 + .skip(FailureCase::backend_adapter(wgpu::Backends::GL, "llvmpipe")), + ) + .run_sync(|ctx| { + use std::{sync::mpsc, sync::Arc, thread, time::Duration}; + + let ctx = Arc::new(ctx); + + let thread_count = 8; + + let (tx, rx) = mpsc::channel(); + for _ in 0..thread_count { + let tx = tx.clone(); + let ctx = Arc::clone(&ctx); + thread::spawn(move || { + let input = &[100, 100, 100]; + pollster::block_on(assert_execute_gpu( + &ctx.device, + &ctx.queue, + input, + &[25, 25, 25], + )); + tx.send(true).unwrap(); + }); + } + + for _ in 0..thread_count { + rx.recv_timeout(Duration::from_secs(10)) + .expect("A thread never completed."); + } + }); + +async fn assert_execute_gpu( + device: &wgpu::Device, + queue: &wgpu::Queue, + input: &[u32], + expected: &[u32], +) { + if let Some(produced) = execute_gpu_inner(device, queue, input).await { + assert_eq!(produced, expected); + } +} diff --git a/examples/src/hello_synchronization/README.md b/examples/src/hello_synchronization/README.md new file mode 100644 index 0000000000..5750801f14 --- /dev/null +++ b/examples/src/hello_synchronization/README.md @@ -0,0 +1,25 @@ +# hello_synchronization + +This example is +1. A small demonstration of the importance of synchronization. +2. How basic synchronization you can understand from the CPU is preformed on the GPU. + +## To Run + +``` +cargo run --bin wgpu-examples hello_synchronization +``` + +## A Primer on WGSL Synchronization Functions + +The official documentation is a little scattered and sparse. The meat of the subject is found [here](https://www.w3.org/TR/2023/WD-WGSL-20230629/#sync-builtin-functions) but there's also a bit on control barriers [here](https://www.w3.org/TR/2023/WD-WGSL-20230629/#control-barrier). The most important part comes from that first link though, where the spec says "the affected memory and atomic operations program-ordered before the synchronization function must be visible to all other threads in the workgroup before any affected memory or atomic operation program-ordered after the synchronization function is executed by a member of the workgroup." And at the second, we also get "a control barrier is executed by all invocations in the same workgroup as if it were executed concurrently." + +That's rather vague (and it is by design) so let's break it down and make a comparison that should make that sentence come a bit more into focus. [Barriers in Rust](https://doc.rust-lang.org/std/sync/struct.Barrier.html#) fit both bills rather nicely. Firstly, Rust barriers are executed as if they were executed concurrently because they are - at least as long as you define the execution by when it finishes, when [`Barrier::wait`](https://doc.rust-lang.org/std/sync/struct.Barrier.html#method.wait) finally unblocks the thread and execution continues concurrently from there. Rust barriers also fit the first bill; because all affected threads must execute `Barrier::wait` in order for execution to continue, we can guarantee that _all (synchronous)_ operations ordered before the wait call are executed before any operations ordered after the wait call begin execution. Applying this to WGSL barriers, we can think of a barrier in WGSL as a checkpoint all invocations within each workgroup must reach before the entire workgroup continues with the program together. + +There are two key differences though and one is that although Rust barriers don't enforce that atomic operations called before the barrier are visible after the barrier, WGSL barriers do. This is incredibly useful and important though and is demonstrated in this example. + +Another is that WGSL's synchronous functions only affect memory and atomic operations in a certain address space. This applies to the whole 'all atomic operations called before the function are visible after the function' thing. There are currently three different synchronization functions: +- `storageBarrier` which works in the storage address space and is a simple barrier. +- `workgroupBarrier` which works in the workgroup address space and is a simple barrier. +- `workgroupUniformLoad` which also works in the workgroup address space and is more than just a barrier. +Read up on all three [here](https://www.w3.org/TR/2023/WD-WGSL-20230629/#sync-builtin-functions). \ No newline at end of file diff --git a/examples/src/hello_synchronization/mod.rs b/examples/src/hello_synchronization/mod.rs new file mode 100644 index 0000000000..c2a6fe8b26 --- /dev/null +++ b/examples/src/hello_synchronization/mod.rs @@ -0,0 +1,213 @@ +const ARR_SIZE: usize = 128; + +struct ExecuteResults { + patient_workgroup_results: Vec, + #[cfg_attr(test, allow(unused))] + hasty_workgroup_results: Vec, +} + +#[cfg_attr(test, allow(unused))] +async fn run() { + let instance = wgpu::Instance::default(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions::default()) + .await + .unwrap(); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_defaults(), + }, + None, + ) + .await + .unwrap(); + + let ExecuteResults { + patient_workgroup_results, + hasty_workgroup_results, + } = execute(&device, &queue, ARR_SIZE).await; + + // Print data + log::info!("Patient results: {:?}", patient_workgroup_results); + if !patient_workgroup_results.iter().any(|e| *e != 16) { + log::info!("patient_main was patient."); + } else { + log::error!("patient_main was not patient!"); + } + log::info!("Hasty results: {:?}", hasty_workgroup_results); + if hasty_workgroup_results.iter().any(|e| *e != 16) { + log::info!("hasty_main was not patient."); + } else { + log::info!("hasty_main got lucky."); + } +} + +async fn execute( + device: &wgpu::Device, + queue: &wgpu::Queue, + result_vec_size: usize, +) -> ExecuteResults { + let mut local_patient_workgroup_results = vec![0u32; result_vec_size]; + let mut local_hasty_workgroup_results = local_patient_workgroup_results.clone(); + + let shaders_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shaders.wgsl"))), + }); + + let storage_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: std::mem::size_of_val(local_patient_workgroup_results.as_slice()) as u64, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: std::mem::size_of_val(local_patient_workgroup_results.as_slice()) as u64, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: storage_buffer.as_entire_binding(), + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let patient_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &shaders_module, + entry_point: "patient_main", + }); + let hasty_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &shaders_module, + entry_point: "hasty_main", + }); + + //---------------------------------------------------------- + + let mut command_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + compute_pass.set_pipeline(&patient_pipeline); + compute_pass.set_bind_group(0, &bind_group, &[]); + compute_pass.dispatch_workgroups(local_patient_workgroup_results.len() as u32, 1, 1); + } + queue.submit(Some(command_encoder.finish())); + + get_data( + local_patient_workgroup_results.as_mut_slice(), + &storage_buffer, + &output_staging_buffer, + device, + queue, + ) + .await; + + let mut command_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + compute_pass.set_pipeline(&hasty_pipeline); + compute_pass.set_bind_group(0, &bind_group, &[]); + compute_pass.dispatch_workgroups(local_patient_workgroup_results.len() as u32, 1, 1); + } + queue.submit(Some(command_encoder.finish())); + + get_data( + local_hasty_workgroup_results.as_mut_slice(), + &storage_buffer, + &output_staging_buffer, + device, + queue, + ) + .await; + + ExecuteResults { + patient_workgroup_results: local_patient_workgroup_results, + hasty_workgroup_results: local_hasty_workgroup_results, + } +} + +async fn get_data( + output: &mut [T], + storage_buffer: &wgpu::Buffer, + staging_buffer: &wgpu::Buffer, + device: &wgpu::Device, + queue: &wgpu::Queue, +) { + let mut command_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + command_encoder.copy_buffer_to_buffer( + storage_buffer, + 0, + staging_buffer, + 0, + std::mem::size_of_val(output) as u64, + ); + queue.submit(Some(command_encoder.finish())); + let buffer_slice = staging_buffer.slice(..); + let (sender, receiver) = flume::bounded(1); + buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); + device.poll(wgpu::Maintain::wait()).panic_on_timeout(); + receiver.recv_async().await.unwrap().unwrap(); + output.copy_from_slice(bytemuck::cast_slice(&buffer_slice.get_mapped_range()[..])); + staging_buffer.unmap(); +} + +pub fn main() { + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + pollster::block_on(run()); + } + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); + + crate::utils::add_web_nothing_to_see_msg(); + + wasm_bindgen_futures::spawn_local(run()); + } +} + +#[cfg(test)] +mod tests; diff --git a/examples/src/hello_synchronization/shaders.wgsl b/examples/src/hello_synchronization/shaders.wgsl new file mode 100644 index 0000000000..dd3b2dd38f --- /dev/null +++ b/examples/src/hello_synchronization/shaders.wgsl @@ -0,0 +1,30 @@ +@group(0) +@binding(0) +var output: array; + +var count: atomic; + +@compute +@workgroup_size(16) +fn patient_main( + @builtin(local_invocation_id) local_id: vec3, + @builtin(workgroup_id) workgroup_id: vec3 +) { + atomicAdd(&count, 1u); + workgroupBarrier(); + if (local_id.x == 0u) { + output[workgroup_id.x] = atomicLoad(&count); + } +} + +@compute +@workgroup_size(16) +fn hasty_main( + @builtin(local_invocation_id) local_id: vec3, + @builtin(workgroup_id) workgroup_id: vec3 +) { + atomicAdd(&count, 1u); + if (local_id.x == 0u) { + output[workgroup_id.x] = atomicLoad(&count); + } +} \ No newline at end of file diff --git a/examples/src/hello_synchronization/tests.rs b/examples/src/hello_synchronization/tests.rs new file mode 100644 index 0000000000..756289a363 --- /dev/null +++ b/examples/src/hello_synchronization/tests.rs @@ -0,0 +1,18 @@ +use super::*; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; + +#[gpu_test] +static SYNC: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + // Taken from hello-compute tests. + TestParameters::default() + .downlevel_flags(wgpu::DownlevelFlags::COMPUTE_SHADERS) + .limits(wgpu::Limits::downlevel_defaults()), + ) + .run_async(|ctx| async move { + let ExecuteResults { + patient_workgroup_results, + hasty_workgroup_results: _, + } = execute(&ctx.device, &ctx.queue, ARR_SIZE).await; + assert_eq!(patient_workgroup_results, [16_u32; ARR_SIZE]); + }); diff --git a/examples/hello-triangle/README.md b/examples/src/hello_triangle/README.md similarity index 65% rename from examples/hello-triangle/README.md rename to examples/src/hello_triangle/README.md index 31425c0679..52daa8164e 100644 --- a/examples/hello-triangle/README.md +++ b/examples/src/hello_triangle/README.md @@ -1,11 +1,11 @@ -# hello-triangle +# hello_triangle This example renders a triangle to a window. ## To Run ``` -cargo run --bin hello-triangle +cargo run --bin wgpu-examples hello_triangle ``` ## Screenshots diff --git a/examples/src/hello_triangle/mod.rs b/examples/src/hello_triangle/mod.rs new file mode 100644 index 0000000000..40cb805c28 --- /dev/null +++ b/examples/src/hello_triangle/mod.rs @@ -0,0 +1,182 @@ +use std::borrow::Cow; +use winit::{ + event::{Event, WindowEvent}, + event_loop::EventLoop, + window::Window, +}; + +async fn run(event_loop: EventLoop<()>, window: Window) { + let mut size = window.inner_size(); + size.width = size.width.max(1); + size.height = size.height.max(1); + + let instance = wgpu::Instance::default(); + + let surface = instance.create_surface(&window).unwrap(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + force_fallback_adapter: false, + // Request an adapter which can render to our surface + compatible_surface: Some(&surface), + }) + .await + .expect("Failed to find an appropriate adapter"); + + // Create the logical device and command queue + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. + required_limits: wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()), + }, + None, + ) + .await + .expect("Failed to create device"); + + // Load the shaders from disk + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let swapchain_capabilities = surface.get_capabilities(&adapter); + let swapchain_format = swapchain_capabilities.formats[0]; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(swapchain_format.into())], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + let mut config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: swapchain_capabilities.alpha_modes[0], + view_formats: vec![], + }; + + surface.configure(&device, &config); + + let window = &window; + event_loop + .run(move |event, target| { + // Have the closure take ownership of the resources. + // `event_loop.run` never returns, therefore we must do this to ensure + // the resources are properly cleaned up. + let _ = (&instance, &adapter, &shader, &pipeline_layout); + + if let Event::WindowEvent { + window_id: _, + event, + } = event + { + match event { + WindowEvent::Resized(new_size) => { + // Reconfigure the surface with the new size + config.width = new_size.width.max(1); + config.height = new_size.height.max(1); + surface.configure(&device, &config); + // On macos the window needs to be redrawn manually after resizing + window.request_redraw(); + } + WindowEvent::RedrawRequested => { + let frame = surface + .get_current_texture() + .expect("Failed to acquire next swap chain texture"); + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: None, + }); + { + let mut rpass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_pipeline(&render_pipeline); + rpass.draw(0..3, 0..1); + } + + queue.submit(Some(encoder.finish())); + frame.present(); + } + WindowEvent::CloseRequested => target.exit(), + _ => {} + }; + } + }) + .unwrap(); +} + +pub fn main() { + let event_loop = EventLoop::new().unwrap(); + #[allow(unused_mut)] + let mut builder = winit::window::WindowBuilder::new(); + #[cfg(target_arch = "wasm32")] + { + use wasm_bindgen::JsCast; + use winit::platform::web::WindowBuilderExtWebSys; + let canvas = web_sys::window() + .unwrap() + .document() + .unwrap() + .get_element_by_id("canvas") + .unwrap() + .dyn_into::() + .unwrap(); + builder = builder.with_canvas(Some(canvas)); + } + let window = builder.build(&event_loop).unwrap(); + + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::init(); + pollster::block_on(run(event_loop, window)); + } + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init().expect("could not initialize logger"); + wasm_bindgen_futures::spawn_local(run(event_loop, window)); + } +} diff --git a/examples/hello-triangle/screenshot.png b/examples/src/hello_triangle/screenshot.png similarity index 100% rename from examples/hello-triangle/screenshot.png rename to examples/src/hello_triangle/screenshot.png diff --git a/examples/hello-triangle/src/shader.wgsl b/examples/src/hello_triangle/shader.wgsl similarity index 100% rename from examples/hello-triangle/src/shader.wgsl rename to examples/src/hello_triangle/shader.wgsl diff --git a/examples/hello-windows/README.md b/examples/src/hello_windows/README.md similarity index 71% rename from examples/hello-windows/README.md rename to examples/src/hello_windows/README.md index c483b79acb..6e1abd5819 100644 --- a/examples/hello-windows/README.md +++ b/examples/src/hello_windows/README.md @@ -1,11 +1,11 @@ -# hello-windows +# hello_windows This example renders a set of 16 windows, with a differently colored background ## To Run ``` -cargo run --bin hello-windows +cargo run --bin wgpu-examples hello_windows ``` ## Screenshots diff --git a/examples/hello-windows/src/main.rs b/examples/src/hello_windows/mod.rs similarity index 52% rename from examples/hello-windows/src/main.rs rename to examples/src/hello_windows/mod.rs index f368804c36..9a42b9afbd 100644 --- a/examples/hello-windows/src/main.rs +++ b/examples/src/hello_windows/mod.rs @@ -1,16 +1,16 @@ #![cfg_attr(target_arch = "wasm32", allow(dead_code))] -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use winit::{ event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + event_loop::EventLoop, window::{Window, WindowId}, }; struct ViewportDesc { - window: Window, + window: Arc, background: wgpu::Color, - surface: wgpu::Surface, + surface: wgpu::Surface<'static>, } struct Viewport { @@ -19,8 +19,8 @@ struct Viewport { } impl ViewportDesc { - fn new(window: Window, background: wgpu::Color, instance: &wgpu::Instance) -> Self { - let surface = unsafe { instance.create_surface(&window) }.unwrap(); + fn new(window: Arc, background: wgpu::Color, instance: &wgpu::Instance) -> Self { + let surface = instance.create_surface(window.clone()).unwrap(); Self { window, background, @@ -62,7 +62,7 @@ impl Viewport { } } -async fn run(event_loop: EventLoop<()>, viewports: Vec<(Window, wgpu::Color)>) { +async fn run(event_loop: EventLoop<()>, viewports: Vec<(Arc, wgpu::Color)>) { let instance = wgpu::Instance::default(); let viewports: Vec<_> = viewports .into_iter() @@ -82,8 +82,8 @@ async fn run(event_loop: EventLoop<()>, viewports: Vec<(Window, wgpu::Color)>) { .request_device( &wgpu::DeviceDescriptor { label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::downlevel_defaults(), + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_defaults(), }, None, ) @@ -95,71 +95,73 @@ async fn run(event_loop: EventLoop<()>, viewports: Vec<(Window, wgpu::Color)>) { .map(|desc| (desc.window.id(), desc.build(&adapter, &device))) .collect(); - event_loop.run(move |event, _, control_flow| { - // Have the closure take ownership of the resources. - // `event_loop.run` never returns, therefore we must do this to ensure - // the resources are properly cleaned up. - let _ = (&instance, &adapter); - - *control_flow = ControlFlow::Wait; - match event { - Event::WindowEvent { - window_id, - event: WindowEvent::Resized(size), - .. - } => { - // Recreate the swap chain with the new size - if let Some(viewport) = viewports.get_mut(&window_id) { - viewport.resize(&device, size); - // On macos the window needs to be redrawn manually after resizing - viewport.desc.window.request_redraw(); - } - } - Event::RedrawRequested(window_id) => { - if let Some(viewport) = viewports.get_mut(&window_id) { - let frame = viewport.get_current_texture(); - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - { - let _rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(viewport.desc.background), - store: true, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); + event_loop + .run(move |event, target| { + // Have the closure take ownership of the resources. + // `event_loop.run` never returns, therefore we must do this to ensure + // the resources are properly cleaned up. + let _ = (&instance, &adapter); + + if let Event::WindowEvent { window_id, event } = event { + match event { + WindowEvent::Resized(new_size) => { + // Recreate the swap chain with the new size + if let Some(viewport) = viewports.get_mut(&window_id) { + viewport.resize(&device, new_size); + // On macos the window needs to be redrawn manually after resizing + viewport.desc.window.request_redraw(); + } } - - queue.submit(Some(encoder.finish())); - frame.present(); - } - } - Event::WindowEvent { - window_id, - event: WindowEvent::CloseRequested, - .. - } => { - viewports.remove(&window_id); - if viewports.is_empty() { - *control_flow = ControlFlow::Exit + WindowEvent::RedrawRequested => { + if let Some(viewport) = viewports.get_mut(&window_id) { + let frame = viewport.get_current_texture(); + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: None, + }); + { + let _rpass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear( + viewport.desc.background, + ), + store: wgpu::StoreOp::Store, + }, + }, + )], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + } + + queue.submit(Some(encoder.finish())); + frame.present(); + } + } + WindowEvent::CloseRequested => { + viewports.remove(&window_id); + if viewports.is_empty() { + target.exit(); + } + } + _ => {} } } - _ => {} - } - }); + }) + .unwrap(); } -fn main() { +pub fn main() { #[cfg(not(target_arch = "wasm32"))] { const WINDOW_SIZE: u32 = 128; @@ -169,7 +171,7 @@ fn main() { const ROWS: u32 = 4; const COLUMNS: u32 = 4; - let event_loop = EventLoop::new(); + let event_loop = EventLoop::new().unwrap(); let mut viewports = Vec::with_capacity((ROWS * COLUMNS) as usize); for row in 0..ROWS { for column in 0..COLUMNS { @@ -178,6 +180,7 @@ fn main() { .with_inner_size(winit::dpi::PhysicalSize::new(WINDOW_SIZE, WINDOW_SIZE)) .build(&event_loop) .unwrap(); + let window = Arc::new(window); window.set_outer_position(winit::dpi::PhysicalPosition::new( WINDOW_PADDING + column * WINDOW_OFFSET, WINDOW_PADDING + row * (WINDOW_OFFSET + WINDOW_TITLEBAR), diff --git a/examples/hello-windows/screenshot.png b/examples/src/hello_windows/screenshot.png similarity index 100% rename from examples/hello-windows/screenshot.png rename to examples/src/hello_windows/screenshot.png diff --git a/examples/src/hello_workgroups/README.md b/examples/src/hello_workgroups/README.md new file mode 100644 index 0000000000..e7bcad2834 --- /dev/null +++ b/examples/src/hello_workgroups/README.md @@ -0,0 +1,74 @@ +# hello_workgroups + +Now you finally know what that silly little `@workgroup_size(1)` means! + +This example is an extremely bare-bones and arguably somewhat unreasonable demonstration of what workgroup sizes mean in an attempt to explain workgroups in general. + +The example starts with two arrays of numbers. One where `a[i] = i` and the other where `b[i] = 2i`. Both are bound to the shader. The program dispatches a workgroup for each index, each workgroup representing both elements at that index in both arrays. Each invocation in each workgroup works on its respective array and adds 1 to the element there. + +## To Run + +``` +cargo run --bin wgpu-examples hello_workgroups +``` + +## What are Workgroups? + +### TLDR / Key Takeaways + +- Workgroups fit in a 3d grid of workgroups executed in a single dispatch. +- All invocations in a workgroup are guaranteed to execute concurrently. +- Workgroups carry no other guarantees for concurrency outside of those individual workgroups, meaning... + - No two workgroups can be guaranteed to be executed in parallel. + - No two workgroups can be guaranteed NOT to be executed in parallel. + - No set of workgroups can be guaranteed to execute in any predictable or reliable order in relation to each other. +- Ths size of a workgroup is defined with the `@workgroup_size` attribute on a compute shader main function. +- The location of an invocation within its workgroup grid can be got with `@builtin(local_invocation_id)`. +- The location of an invocation within the entire compute shader grid can be gotten with `@builtin(global_invocation_id)`. +- The location of an invocation's workgroup within the dispatch grid can be gotten with `@builtin(workgroup_id)`. +- Workgroups share memory within the `workgroup` address space. Workgroup memory is similar to private memory but it is shared within a workgroup. Invocations within a workgroup will see the same memory but invocations across workgroups will be accessing different memory. + +### Introduction + +When you call `ComputePass::dispatch_workgroups`, the function dispatches multiple workgroups in a 3d grid defined by the `x`, `y`, and `z` parameters you pass to the function. For example, `dispatch_workgroups(5, 2, 1)` would create a dispatch grid like +|||||| +|---|---|---|---|---| +| W | W | W | W | W | +| W | W | W | W | W | + +Where each W is a workgroup. If you want your shader to consider what workgroup within the dispatch the current invocation is in, add a function argument with type `vec3` and with the attribute `@builtin(workgroup_id)`. + +Note here that in this example, the term "dispatch grid" is used throughout to mean the grid of workgroups within the dispatch but is not a proper term within WGSL. Other terms to know though that are proper are "workgroup grid" which refers to the invocations in a single _workgroup_ and "compute shader grid" which refers to the grid of _all_ the invocations in the _entire dispatch_. + +### Within the Workgroup + +Although with hello-compute and repeated-compute, we used a workgroup size of `(1)`, or rather, (1, 1, 1), and then each workgroup called from `dispatch_workgroups` made _an_ invocation, this isn't always the case. Each workgroup represents its own little grid of individual invocations tied together. This could be just one or practically any number in a 3d grid of invocations. The grid size of each workgroup and thus the number of invocations called per workgroup is determined by the `@workgroup_size` attribute you've seen in other compute shaders. To get the current invocation's location within a workgroup, add a `vec3` argument to the main function with the attribute `@builtin(local_invocation_id)`. We'll look at the compute shader grid of a dispatch of size (2, 2, 1) with workgroup sizes of (2, 2, 1) as well. Let `w` be the `workgroup_id` and `i` be the `local_invocation_id`. + +||||| +|------------------------|------------------------|------------------------|------------------------| +| w(0, 0, 0), i(0, 0, 0) | w(0, 0, 0), i(1, 0, 0) | w(1, 0, 0), i(0, 0, 0) | w(1, 0, 0), i(1, 0, 0) | +| w(0, 0, 0), i(0, 1, 0) | w(0, 0, 0), i(1, 1, 0) | w(1, 0, 0), i(0, 1, 0) | w(1, 0, 0), i(1, 1, 0) | +| w(0, 1, 0), i(0, 0, 0) | w(0, 1, 0), i(1, 0, 0) | w(1, 1, 0), i(0, 0, 0) | w(1, 1, 0), i(1, 0, 0) | +| w(0, 1, 0), i(0, 1, 0) | w(0, 1, 0), i(1, 1, 0) | w(1, 1, 0), i(0, 1, 0) | w(1, 1, 0), i(1, 1, 0) | + +### Execution of Workgroups + +As stated before, workgroups are groups of invocations. The invocations within a workgroup are always guaranteed to execute in parallel. That said, the guarantees basically stop there. You cannot get any guarantee as to when any given workgroup will execute, including in relation to other workgroups. You can't guarantee that any two workgroups will execute together nor can you guarantee that they will _not_ execute together. Of the workgroups that don't execute together, you additionally cannot guarantee that they will execute in any particular order. When your function runs in an invocation, you know that it will be working together with its workgroup buddies and that's basically it. + +See [the WGSL spec on compute shader execution](https://www.w3.org/TR/2023/WD-WGSL-20230629/#compute-shader-workgroups) for more details. + +### Workgroups and their Invocations in a Global Scope + +As mentioned above, invocations exist both within the context of a workgroup grid as well as a compute shader grid which is a grid, divided into workgroup sections, of invocations that represents the whole of the dispatch. Similar to how `@builtin(local_invocation_id)` gets you the place of the invocation within the workgroup grid, `@builtin(global_invocation_id)` gets you the place of the invocation within the entire compute shader grid. Slight trivia: you might imagine that this is computed from `local_invocation_id` and `workgroup_id` but it's actually the opposite. Everything operates on the compute shader grid, the workgroups are imagined sectors within the compute shader grid, and `local_invocation_id` and `workgroup_id` are calculated based on global id and known workgroup size. Yes, we live in a matrix... of compute shader invocations. This isn't super useful information but it can help fit things into a larger picture. + +## Barriers and Workgroups + +Arguably, workgroups are at their most useful when being used alongside barriers. Since barriers are already explained more thoroughly in the hello-synchronization example, this section will be short. Despite affecting different memory address spaces, all synchronization functions affect invocations on a workgroup level, synchronizing the workgroup. See [hello-synchronization/README.md](../hello-synchronization/README.md) for more. + +## Links to Technical Resources + +For a rather long explainer, this README may still leave the more technically minded person with questions. The specifications for both WebGPU and WGSL ("WebGPU Shading Language") are long and it's rather unintuitive that by far the vast majority of specification on how workgroups and compute shaders more generally work, is all in the WGSL spec. Below are some links into the specifications at a couple interesting points: + +- [Here](https://www.w3.org/TR/WGSL/#compute-shader-workgroups) is the main section on workgroups and outlines important terminology in technical terms. It is recommended that everyone looking for something in this section of this README start by reading this. +- [Here](https://www.w3.org/TR/webgpu/#computing-operations) is a section on compute shaders from a WebGPU perspective (instead of WGSL). It's still a stub but hopefully it will grow in the future. +- Don't forget your [`@builtin()`'s](https://www.w3.org/TR/WGSL/#builtin-inputs-outputs)! \ No newline at end of file diff --git a/examples/src/hello_workgroups/mod.rs b/examples/src/hello_workgroups/mod.rs new file mode 100644 index 0000000000..3e5795048f --- /dev/null +++ b/examples/src/hello_workgroups/mod.rs @@ -0,0 +1,199 @@ +//! This example assumes that you've seen hello-compute and or repeated-compute +//! and thus have a general understanding of what's going on here. +//! +//! There's an explainer on what this example does exactly and what workgroups +//! are and the meaning of `@workgroup(size_x, size_y, size_z)` in the +//! README. Also see commenting in shader.wgsl as well. +//! +//! Only parts specific to this example will be commented. + +use wgpu::util::DeviceExt; + +async fn run() { + let mut local_a = [0i32; 100]; + for (i, e) in local_a.iter_mut().enumerate() { + *e = i as i32; + } + log::info!("Input a: {local_a:?}"); + let mut local_b = [0i32; 100]; + for (i, e) in local_b.iter_mut().enumerate() { + *e = i as i32 * 2; + } + log::info!("Input b: {local_b:?}"); + + let instance = wgpu::Instance::default(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions::default()) + .await + .unwrap(); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_defaults(), + }, + None, + ) + .await + .unwrap(); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let storage_buffer_a = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&local_a[..]), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + }); + let storage_buffer_b = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&local_b[..]), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + }); + let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: std::mem::size_of_val(&local_a) as u64, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: storage_buffer_a.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: storage_buffer_b.as_entire_binding(), + }, + ], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &shader, + entry_point: "main", + }); + + //---------------------------------------------------------- + + let mut command_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + compute_pass.set_pipeline(&pipeline); + compute_pass.set_bind_group(0, &bind_group, &[]); + /* Note that since each workgroup will cover both arrays, we only need to + cover the length of one array. */ + compute_pass.dispatch_workgroups(local_a.len() as u32, 1, 1); + } + queue.submit(Some(command_encoder.finish())); + + //---------------------------------------------------------- + + get_data( + &mut local_a[..], + &storage_buffer_a, + &output_staging_buffer, + &device, + &queue, + ) + .await; + get_data( + &mut local_b[..], + &storage_buffer_b, + &output_staging_buffer, + &device, + &queue, + ) + .await; + + log::info!("Output in A: {local_a:?}"); + log::info!("Output in B: {local_b:?}"); +} + +async fn get_data( + output: &mut [T], + storage_buffer: &wgpu::Buffer, + staging_buffer: &wgpu::Buffer, + device: &wgpu::Device, + queue: &wgpu::Queue, +) { + let mut command_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + command_encoder.copy_buffer_to_buffer( + storage_buffer, + 0, + staging_buffer, + 0, + std::mem::size_of_val(output) as u64, + ); + queue.submit(Some(command_encoder.finish())); + let buffer_slice = staging_buffer.slice(..); + let (sender, receiver) = flume::bounded(1); + buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); + device.poll(wgpu::Maintain::wait()).panic_on_timeout(); + receiver.recv_async().await.unwrap().unwrap(); + output.copy_from_slice(bytemuck::cast_slice(&buffer_slice.get_mapped_range()[..])); + staging_buffer.unmap(); +} + +pub fn main() { + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + pollster::block_on(run()); + } + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); + + crate::utils::add_web_nothing_to_see_msg(); + + wasm_bindgen_futures::spawn_local(run()); + } +} diff --git a/examples/src/hello_workgroups/shader.wgsl b/examples/src/hello_workgroups/shader.wgsl new file mode 100644 index 0000000000..c1676663ab --- /dev/null +++ b/examples/src/hello_workgroups/shader.wgsl @@ -0,0 +1,23 @@ +// This is useful because we can't use, say, vec2> because +// of array being unsized. Normally we would interweave them or use +// and array of structs but this is just for the sake of demonstration. + +@group(0) +@binding(0) +var a: array; + +@group(0) +@binding(1) +var b: array; + +@compute +@workgroup_size(2, 1, 1) +fn main(@builtin(local_invocation_id) lid: vec3, @builtin(workgroup_id) wid: vec3) { + if lid.x == 0u { + // Do computation (use your imagionation) + a[wid.x] += 1; + } else if lid.x == 1u { + // Do computation + b[wid.x] += 1; + } +} \ No newline at end of file diff --git a/examples/src/lib.rs b/examples/src/lib.rs new file mode 100644 index 0000000000..d6a18c0f11 --- /dev/null +++ b/examples/src/lib.rs @@ -0,0 +1,32 @@ +pub mod framework; +pub mod utils; + +pub mod boids; +pub mod bunnymark; +pub mod conservative_raster; +pub mod cube; +pub mod hello; +pub mod hello_compute; +pub mod hello_synchronization; +pub mod hello_triangle; +pub mod hello_windows; +pub mod hello_workgroups; +pub mod mipmap; +pub mod msaa_line; +pub mod ray_cube_compute; +pub mod ray_cube_fragment; +pub mod ray_scene; +pub mod render_to_texture; +pub mod repeated_compute; +pub mod shadow; +pub mod skybox; +pub mod srgb_blend; +pub mod stencil_triangles; +pub mod storage_texture; +pub mod texture_arrays; +pub mod timestamp_queries; +pub mod uniform_values; +pub mod water; + +#[cfg(test)] +wgpu_test::gpu_test_main!(); diff --git a/examples/src/main.rs b/examples/src/main.rs new file mode 100644 index 0000000000..d5db62e0cf --- /dev/null +++ b/examples/src/main.rs @@ -0,0 +1,248 @@ +struct ExampleDesc { + name: &'static str, + function: fn(), + #[allow(dead_code)] // isn't used on native + webgl: bool, + #[allow(dead_code)] // isn't used on native + webgpu: bool, +} + +const EXAMPLES: &[ExampleDesc] = &[ + ExampleDesc { + name: "boids", + function: wgpu_examples::boids::main, + webgl: false, // No compute + webgpu: true, + }, + ExampleDesc { + name: "bunnymark", + function: wgpu_examples::bunnymark::main, + webgl: true, + webgpu: true, + }, + ExampleDesc { + name: "conservative_raster", + function: wgpu_examples::conservative_raster::main, + webgl: false, // No conservative raster + webgpu: false, // No conservative raster + }, + ExampleDesc { + name: "cube", + function: wgpu_examples::cube::main, + webgl: true, + webgpu: true, + }, + ExampleDesc { + name: "hello", + function: wgpu_examples::hello::main, + webgl: false, // No canvas for WebGL + webgpu: true, + }, + ExampleDesc { + name: "hello_compute", + function: wgpu_examples::hello_compute::main, + webgl: false, // No compute + webgpu: true, + }, + ExampleDesc { + name: "hello_synchronization", + function: wgpu_examples::hello_synchronization::main, + webgl: false, // No canvas for WebGL + webgpu: true, + }, + ExampleDesc { + name: "hello_triangle", + function: wgpu_examples::hello_triangle::main, + webgl: true, + webgpu: true, + }, + ExampleDesc { + name: "hello_windows", + function: wgpu_examples::hello_windows::main, + webgl: false, // Native only example + webgpu: false, // Native only example + }, + ExampleDesc { + name: "hello_workgroups", + function: wgpu_examples::hello_workgroups::main, + webgl: false, + webgpu: true, + }, + ExampleDesc { + name: "mipmap", + function: wgpu_examples::mipmap::main, + webgl: true, + webgpu: true, + }, + ExampleDesc { + name: "msaa_line", + function: wgpu_examples::msaa_line::main, + webgl: true, + webgpu: true, + }, + ExampleDesc { + name: "render_to_texture", + function: wgpu_examples::render_to_texture::main, + webgl: false, // No canvas for WebGL + webgpu: true, + }, + ExampleDesc { + name: "repeated_compute", + function: wgpu_examples::repeated_compute::main, + webgl: false, // No compute + webgpu: true, + }, + ExampleDesc { + name: "shadow", + function: wgpu_examples::shadow::main, + webgl: true, + webgpu: true, + }, + ExampleDesc { + name: "skybox", + function: wgpu_examples::skybox::main, + webgl: true, + webgpu: true, + }, + ExampleDesc { + name: "srgb_blend", + function: wgpu_examples::srgb_blend::main, + webgl: true, + webgpu: true, + }, + ExampleDesc { + name: "stencil_triangles", + function: wgpu_examples::stencil_triangles::main, + webgl: true, + webgpu: true, + }, + ExampleDesc { + name: "storage_texture", + function: wgpu_examples::storage_texture::main, + webgl: false, // No storage textures + webgpu: true, + }, + ExampleDesc { + name: "texture_arrays", + function: wgpu_examples::texture_arrays::main, + webgl: false, // No texture arrays + webgpu: false, // No texture arrays + }, + ExampleDesc { + name: "timestamp_queries", + function: wgpu_examples::timestamp_queries::main, + webgl: false, // No canvas for WebGL + webgpu: false, // No timestamp queries + }, + ExampleDesc { + name: "uniform_values", + function: wgpu_examples::uniform_values::main, + webgl: false, // No compute + webgpu: true, + }, + ExampleDesc { + name: "water", + function: wgpu_examples::water::main, + webgl: false, // No RODS + webgpu: true, + }, + ExampleDesc { + name: "ray_cube_compute", + function: wgpu_examples::ray_cube_compute::main, + webgl: false, // No Ray-tracing extensions + webgpu: false, // No Ray-tracing extensions (yet) + }, + ExampleDesc { + name: "ray_cube_fragment", + function: wgpu_examples::ray_cube_fragment::main, + webgl: false, // No Ray-tracing extensions + webgpu: false, // No Ray-tracing extensions (yet) + }, + ExampleDesc { + name: "ray_scene", + function: wgpu_examples::ray_scene::main, + webgl: false, // No Ray-tracing extensions + webgpu: false, // No Ray-tracing extensions (yet) + }, +]; + +fn get_example_name() -> Option { + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + let query_string = web_sys::window()?.location().search().ok()?; + + wgpu_examples::framework::parse_url_query_string(&query_string, "example").map(String::from) + } else { + std::env::args().nth(1) + } + } +} + +#[cfg(target_arch = "wasm32")] +fn print_examples() { + // Get the document, header, and body elements. + let document = web_sys::window().unwrap().document().unwrap(); + + for backend in ["webgl2", "webgpu"] { + let ul = document + .get_element_by_id(&format!("{backend}-list")) + .unwrap(); + + for example in EXAMPLES { + if backend == "webgl2" && !example.webgl { + continue; + } + if backend == "webgpu" && !example.webgpu { + continue; + } + + let link = document.create_element("a").unwrap(); + link.set_text_content(Some(example.name)); + link.set_attribute( + "href", + &format!("?backend={backend}&example={}", example.name), + ) + .unwrap(); + link.set_class_name("example-link"); + + let item = document.create_element("div").unwrap(); + item.append_child(&link).unwrap(); + item.set_class_name("example-item"); + ul.append_child(&item).unwrap(); + } + } +} + +#[cfg(target_arch = "wasm32")] +fn print_unknown_example(_result: Option) {} + +#[cfg(not(target_arch = "wasm32"))] +fn print_unknown_example(result: Option) { + if let Some(example) = result { + println!("Unknown example: {}", example); + } else { + println!("Please specify an example as the first argument!"); + } + + println!("\nAvailable Examples:"); + for examples in EXAMPLES { + println!("\t{}", examples.name); + } +} + +fn main() { + #[cfg(target_arch = "wasm32")] + print_examples(); + + let Some(example) = get_example_name() else { + print_unknown_example(None); + return; + }; + + let Some(found) = EXAMPLES.iter().find(|e| e.name == example) else { + print_unknown_example(Some(example)); + return; + }; + + (found.function)(); +} diff --git a/examples/mipmap/README.md b/examples/src/mipmap/README.md similarity index 78% rename from examples/mipmap/README.md rename to examples/src/mipmap/README.md index 547df30c5d..d77009bc64 100644 --- a/examples/mipmap/README.md +++ b/examples/src/mipmap/README.md @@ -5,7 +5,7 @@ This example shows how to generate and make use of mipmaps. ## To Run ``` -cargo run --bin mipmap +cargo run --bin wgpu-examples mipmap ``` ## Screenshots diff --git a/examples/mipmap/src/blit.wgsl b/examples/src/mipmap/blit.wgsl similarity index 100% rename from examples/mipmap/src/blit.wgsl rename to examples/src/mipmap/blit.wgsl diff --git a/examples/mipmap/src/draw.wgsl b/examples/src/mipmap/draw.wgsl similarity index 100% rename from examples/mipmap/src/draw.wgsl rename to examples/src/mipmap/draw.wgsl diff --git a/examples/mipmap/src/main.rs b/examples/src/mipmap/mod.rs similarity index 92% rename from examples/mipmap/src/main.rs rename to examples/src/mipmap/mod.rs index a85110ff14..a4600753e9 100644 --- a/examples/mipmap/src/main.rs +++ b/examples/src/mipmap/mod.rs @@ -163,7 +163,7 @@ impl Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -204,7 +204,7 @@ impl Example { } } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { QUERY_FEATURES } @@ -410,7 +410,7 @@ impl wgpu_example::framework::Example for Example { .slice(..) .map_async(wgpu::MapMode::Read, |_| ()); // Wait for device to be done rendering mipmaps - device.poll(wgpu::Maintain::Wait); + device.poll(wgpu::Maintain::wait()).panic_on_timeout(); // This is guaranteed to be ready. let timestamp_view = query_sets .mapping_buffer @@ -467,13 +467,7 @@ impl wgpu_example::framework::Example for Example { queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref)); } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { @@ -490,7 +484,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(clear_color), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -506,36 +500,32 @@ impl wgpu_example::framework::Example for Example { } } -fn main() { - wgpu_example::framework::run::("mipmap"); +pub fn main() { + crate::framework::run::("mipmap"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn mipmap() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/mipmap/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default() - .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::GL)), - comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "mipmap", + image_path: "/examples/src/mipmap/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn mipmap_query() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/mipmap/screenshot-query.png", - width: 1024, - height: 768, - optional_features: QUERY_FEATURES, - base_test_parameters: wgpu_test::TestParameters::default() - .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::GL)), - comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_QUERY: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "mipmap-query", + image_path: "/examples/src/mipmap/screenshot_query.png", + width: 1024, + height: 768, + optional_features: QUERY_FEATURES, + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.025)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/mipmap/screenshot-query.png b/examples/src/mipmap/screenshot-query.png similarity index 100% rename from examples/mipmap/screenshot-query.png rename to examples/src/mipmap/screenshot-query.png diff --git a/examples/mipmap/screenshot.png b/examples/src/mipmap/screenshot.png similarity index 100% rename from examples/mipmap/screenshot.png rename to examples/src/mipmap/screenshot.png diff --git a/examples/src/mipmap/screenshot_query.png b/examples/src/mipmap/screenshot_query.png new file mode 100644 index 0000000000..2313cb5375 Binary files /dev/null and b/examples/src/mipmap/screenshot_query.png differ diff --git a/examples/msaa-line/README.md b/examples/src/msaa_line/README.md similarity index 69% rename from examples/msaa-line/README.md rename to examples/src/msaa_line/README.md index f0c63314f0..24aae95ee1 100644 --- a/examples/msaa-line/README.md +++ b/examples/src/msaa_line/README.md @@ -1,11 +1,11 @@ -# msaa-line +# msaa_line This example shows how to render lines using MSAA. ## To Run ``` -cargo run --bin msaa-line +cargo run --bin wgpu-examples msaa_line ``` ## Screenshots diff --git a/examples/msaa-line/src/main.rs b/examples/src/msaa_line/mod.rs similarity index 79% rename from examples/msaa-line/src/main.rs rename to examples/src/msaa_line/mod.rs index aa7a277418..595bcbf17a 100644 --- a/examples/msaa-line/src/main.rs +++ b/examples/src/msaa_line/mod.rs @@ -12,8 +12,10 @@ use std::{borrow::Cow, iter}; use bytemuck::{Pod, Zeroable}; use wgpu::util::DeviceExt; -#[cfg(test)] -use wgpu_test::FailureCase; +use winit::{ + event::{ElementState, KeyEvent, WindowEvent}, + keyboard::{Key, NamedKey}, +}; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] @@ -118,7 +120,7 @@ impl Example { } } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES } @@ -215,27 +217,31 @@ impl wgpu_example::framework::Example for Example { #[allow(clippy::single_match)] fn update(&mut self, event: winit::event::WindowEvent) { match event { - winit::event::WindowEvent::KeyboardInput { input, .. } => { - if let winit::event::ElementState::Pressed = input.state { - match input.virtual_keycode { - // TODO: Switch back to full scans of possible options when we expose - // supported sample counts to the user. - Some(winit::event::VirtualKeyCode::Left) => { - if self.sample_count == self.max_sample_count { - self.sample_count = 1; - self.rebuild_bundle = true; - } - } - Some(winit::event::VirtualKeyCode::Right) => { - if self.sample_count == 1 { - self.sample_count = self.max_sample_count; - self.rebuild_bundle = true; - } - } - _ => {} + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key, + state: ElementState::Pressed, + .. + }, + .. + } => match logical_key { + // TODO: Switch back to full scans of possible options when we expose + // supported sample counts to the user. + Key::Named(NamedKey::ArrowLeft) => { + if self.sample_count == self.max_sample_count { + self.sample_count = 1; + self.rebuild_bundle = true; } } - } + Key::Named(NamedKey::ArrowRight) => { + if self.sample_count == 1 { + self.sample_count = self.max_sample_count; + self.rebuild_bundle = true; + } + } + _ => {} + }, _ => {} } } @@ -251,13 +257,7 @@ impl wgpu_example::framework::Example for Example { Example::create_multisampled_framebuffer(device, config, self.sample_count); } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { if self.rebuild_bundle { self.bundle = Example::create_bundle( device, @@ -282,7 +282,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: true, + store: wgpu::StoreOp::Store, }, } } else { @@ -293,7 +293,7 @@ impl wgpu_example::framework::Example for Example { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), // Storing pre-resolve MSAA data is unnecessary if it isn't used later. // On tile-based GPU, avoid store can reduce your app's memory footprint. - store: false, + store: wgpu::StoreOp::Discard, }, } }; @@ -313,35 +313,27 @@ impl wgpu_example::framework::Example for Example { } } -fn main() { - wgpu_example::framework::run::("msaa-line"); +pub fn main() { + crate::framework::run::("msaa-line"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn msaa_line() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/msaa-line/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, - base_test_parameters: wgpu_test::TestParameters::default() - // AMD seems to render nothing on DX12 https://github.com/gfx-rs/wgpu/issues/3838 - .expect_fail(FailureCase { - backends: Some(wgpu::Backends::DX12), - vendor: Some(0x1002), - ..FailureCase::default() - }), - // There's a lot of natural variance so we check the weighted median too to differentiate - // real failures from variance. - comparisons: &[ - wgpu_test::ComparisonType::Mean(0.065), - wgpu_test::ComparisonType::Percentile { - percentile: 0.5, - threshold: 0.29, - }, - ], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "msaa-line", + image_path: "/examples/src/msaa_line/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + base_test_parameters: wgpu_test::TestParameters::default(), + // There's a lot of natural variance so we check the weighted median too to differentiate + // real failures from variance. + comparisons: &[ + wgpu_test::ComparisonType::Mean(0.065), + wgpu_test::ComparisonType::Percentile { + percentile: 0.5, + threshold: 0.29, + }, + ], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/msaa-line/screenshot.png b/examples/src/msaa_line/screenshot.png similarity index 100% rename from examples/msaa-line/screenshot.png rename to examples/src/msaa_line/screenshot.png diff --git a/examples/msaa-line/src/shader.wgsl b/examples/src/msaa_line/shader.wgsl similarity index 100% rename from examples/msaa-line/src/shader.wgsl rename to examples/src/msaa_line/shader.wgsl diff --git a/examples/ray-cube-compute/README.md b/examples/src/ray_cube_compute/README.md similarity index 86% rename from examples/ray-cube-compute/README.md rename to examples/src/ray_cube_compute/README.md index 7020f6a37c..9110787e38 100644 --- a/examples/ray-cube-compute/README.md +++ b/examples/src/ray_cube_compute/README.md @@ -11,4 +11,4 @@ cargo run --example ray-cube-compute ## Screenshots -![Cube example](./screenshot.png) +![Cube example](screenshot.png) diff --git a/examples/ray-cube-compute/src/blit.wgsl b/examples/src/ray_cube_compute/blit.wgsl similarity index 100% rename from examples/ray-cube-compute/src/blit.wgsl rename to examples/src/ray_cube_compute/blit.wgsl diff --git a/examples/ray-cube-compute/src/main.rs b/examples/src/ray_cube_compute/mod.rs similarity index 93% rename from examples/ray-cube-compute/src/main.rs rename to examples/src/ray_cube_compute/mod.rs index 44289df80c..97c68255ae 100644 --- a/examples/ray-cube-compute/src/main.rs +++ b/examples/src/ray_cube_compute/mod.rs @@ -5,7 +5,7 @@ use glam::{Affine3A, Mat4, Quat, Vec3}; use wgpu::util::DeviceExt; use rt::traits::*; -use wgpu::ray_tracing as rt; +use wgpu::{ray_tracing as rt, StoreOp}; // from cube #[repr(C)] @@ -236,7 +236,7 @@ impl>> Future for ErrorFuture { let inner = unsafe { self.map_unchecked_mut(|me| &mut me.inner) }; inner.poll(cx).map(|error| { if let Some(e) = error { - panic!("Rendering {e}"); + panic!("Rendering {}", e); } }) } @@ -259,7 +259,7 @@ struct Example { start_inst: Instant, } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY @@ -538,13 +538,7 @@ impl wgpu_example::framework::Example for Example { ) { } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { device.push_error_scope(wgpu::ErrorFilter::Validation); let anim_time = self.start_inst.elapsed().as_secs_f64() as f32; @@ -592,7 +586,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), - store: true, + store: StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -606,32 +600,29 @@ impl wgpu_example::framework::Example for Example { } queue.submit(Some(encoder.finish())); - - // If an error occurs, report it and panic. - spawner.spawn_local(ErrorFuture { - inner: device.pop_error_scope(), - }); } } -fn main() { - wgpu_example::framework::run::("ray-cube"); +pub fn main() { + crate::framework::run::("ray-cube"); } -#[test] -fn ray_cube_compute() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/ray-cube-compute/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters { - required_features: ::required_features(), - required_downlevel_properties: - ::required_downlevel_capabilities(), - required_limits: ::required_limits(), - failures: Vec::new(), - }, - comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "ray_cube_compute", + image_path: "/examples/ray_cube_compute/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters { + required_features: ::required_features(), + required_limits: ::required_limits(), + skips: vec![], + failures: Vec::new(), + required_downlevel_caps: + ::required_downlevel_capabilities(), + }, + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/ray-cube-compute/screenshot.png b/examples/src/ray_cube_compute/screenshot.png similarity index 100% rename from examples/ray-cube-compute/screenshot.png rename to examples/src/ray_cube_compute/screenshot.png diff --git a/examples/ray-cube-compute/src/shader.wgsl b/examples/src/ray_cube_compute/shader.wgsl similarity index 100% rename from examples/ray-cube-compute/src/shader.wgsl rename to examples/src/ray_cube_compute/shader.wgsl diff --git a/examples/ray-cube-fragment/README.md b/examples/src/ray_cube_fragment/README.md similarity index 81% rename from examples/ray-cube-fragment/README.md rename to examples/src/ray_cube_fragment/README.md index f101fb5eaa..fba22bc6d6 100644 --- a/examples/ray-cube-fragment/README.md +++ b/examples/src/ray_cube_fragment/README.md @@ -10,4 +10,4 @@ cargo run --example ray-cube-fragment ## Screenshots -![Cube example](./screenshot.png) +![Cube example](screenshot.png) diff --git a/examples/ray-cube-fragment/src/main.rs b/examples/src/ray_cube_fragment/mod.rs similarity index 90% rename from examples/ray-cube-fragment/src/main.rs rename to examples/src/ray_cube_fragment/mod.rs index a642207fd3..8aa81a1810 100644 --- a/examples/ray-cube-fragment/src/main.rs +++ b/examples/src/ray_cube_fragment/mod.rs @@ -91,7 +91,7 @@ impl>> Future for ErrorFuture { let inner = unsafe { self.map_unchecked_mut(|me| &mut me.inner) }; inner.poll(cx).map(|error| { if let Some(e) = error { - panic!("Rendering {e}"); + panic!("Rendering {}", e); } }) } @@ -110,7 +110,7 @@ struct Example { start_inst: Instant, } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::RAY_QUERY | wgpu::Features::RAY_TRACING_ACCELERATION_STRUCTURE } @@ -295,13 +295,7 @@ impl wgpu_example::framework::Example for Example { queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(&[self.uniforms])); } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { device.push_error_scope(wgpu::ErrorFilter::Validation); // scene update @@ -359,7 +353,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -373,32 +367,29 @@ impl wgpu_example::framework::Example for Example { } queue.submit(Some(encoder.finish())); - - // If an error occurs, report it and panic. - spawner.spawn_local(ErrorFuture { - inner: device.pop_error_scope(), - }); } } -fn main() { - wgpu_example::framework::run::("ray-cube"); +pub fn main() { + crate::framework::run::("ray-cube"); } -#[test] -fn ray_cube_fragment() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/ray-cube-fragment/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters { - required_features: ::required_features(), - required_downlevel_properties: - ::required_downlevel_capabilities(), - required_limits: ::required_limits(), - failures: Vec::new(), - }, - comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "ray_cube_fragment", + image_path: "/examples/ray_cube_fragment/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters { + required_features: ::required_features(), + required_limits: ::required_limits(), + skips: vec![], + failures: Vec::new(), + required_downlevel_caps: + ::required_downlevel_capabilities(), + }, + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/ray-cube-fragment/screenshot.png b/examples/src/ray_cube_fragment/screenshot.png similarity index 100% rename from examples/ray-cube-fragment/screenshot.png rename to examples/src/ray_cube_fragment/screenshot.png diff --git a/examples/ray-cube-fragment/src/shader.wgsl b/examples/src/ray_cube_fragment/shader.wgsl similarity index 100% rename from examples/ray-cube-fragment/src/shader.wgsl rename to examples/src/ray_cube_fragment/shader.wgsl diff --git a/examples/ray-scene/cube.mtl b/examples/src/ray_scene/cube.mtl similarity index 100% rename from examples/ray-scene/cube.mtl rename to examples/src/ray_scene/cube.mtl diff --git a/examples/ray-scene/cube.obj b/examples/src/ray_scene/cube.obj similarity index 100% rename from examples/ray-scene/cube.obj rename to examples/src/ray_scene/cube.obj diff --git a/examples/ray-scene/src/main.rs b/examples/src/ray_scene/mod.rs similarity index 90% rename from examples/ray-scene/src/main.rs rename to examples/src/ray_scene/mod.rs index 2175b88976..da8877f437 100644 --- a/examples/ray-scene/src/main.rs +++ b/examples/src/ray_scene/mod.rs @@ -42,7 +42,7 @@ impl>> Future for ErrorFuture { let inner = unsafe { self.map_unchecked_mut(|me| &mut me.inner) }; inner.poll(cx).map(|error| { if let Some(e) = error { - panic!("Rendering {e}"); + panic!("Rendering {}", e); } }) } @@ -94,7 +94,8 @@ struct Material { } fn load_model(scene: &mut RawSceneComponents, path: &str) { - let path = env!("CARGO_MANIFEST_DIR").to_string() + "/../../" + path; + let path = env!("CARGO_MANIFEST_DIR").to_string() + "/src" + path; + println!("{}", path); let mut object = obj::Obj::load(path).unwrap(); object.load_mtls().unwrap(); @@ -226,7 +227,6 @@ fn upload_scene_components( .map(|(vertex_range, geometry_range)| { let size_desc: Vec = (*geometry_range) .clone() - .into_iter() .map(|i| rt::BlasTriangleGeometrySizeDescriptor { vertex_format: wgpu::VertexFormat::Float32x3, vertex_count: vertex_range.end as u32 - vertex_range.start as u32, @@ -260,7 +260,7 @@ fn upload_scene_components( .map(|(((vertex_range, geometry_range), size_desc), blas)| { let triangle_geometries: Vec<_> = size_desc .iter() - .zip(geometry_range.clone().into_iter()) + .zip(geometry_range.clone()) .map(|(size, i)| rt::BlasTriangleGeometry { size, vertex_buffer: &vertices, @@ -298,9 +298,8 @@ fn upload_scene_components( fn load_scene(device: &wgpu::Device, queue: &wgpu::Queue) -> SceneComponents { let mut scene = RawSceneComponents::default(); - load_model(&mut scene, "/examples/skybox/models/teslacyberv3.0.obj"); - load_model(&mut scene, "/examples/ray-scene/cube.obj"); + load_model(&mut scene, "/ray_scene/cube.obj"); upload_scene_components(device, queue, &scene) } @@ -316,7 +315,7 @@ struct Example { scene_components: SceneComponents, } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn required_features() -> wgpu::Features { wgpu::Features::RAY_QUERY | wgpu::Features::RAY_TRACING_ACCELERATION_STRUCTURE } @@ -461,13 +460,7 @@ impl wgpu_example::framework::Example for Example { queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(&[self.uniforms])); } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { device.push_error_scope(wgpu::ErrorFilter::Validation); // scene update @@ -482,10 +475,14 @@ impl wgpu_example::framework::Example for Example { for y in 0..side_count { let instance = self .tlas_package - .get_mut_single((x + y * side_count) as usize) + .get_mut_single((x + y) * side_count) .unwrap(); - let blas_index = (x + y) % 2; + let blas_index = (x + y) + % self + .scene_components + .bottom_level_acceleration_structures + .len(); let x = x as f32 / (side_count - 1) as f32; let y = y as f32 / (side_count - 1) as f32; @@ -508,7 +505,6 @@ impl wgpu_example::framework::Example for Example { let transform = transform.transpose().to_cols_array()[..12] .try_into() .unwrap(); - *instance = Some(rt::TlasInstance::new( &self.scene_components.bottom_level_acceleration_structures[blas_index], transform, @@ -532,7 +528,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -546,32 +542,29 @@ impl wgpu_example::framework::Example for Example { } queue.submit(Some(encoder.finish())); - - // If an error occurs, report it and panic. - spawner.spawn_local(ErrorFuture { - inner: device.pop_error_scope(), - }); } } -fn main() { - wgpu_example::framework::run::("ray-scene"); +pub fn main() { + crate::framework::run::("ray_scene"); } -#[test] -fn ray_cube_fragment() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/ray-cube-fragment/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters { - required_features: ::required_features(), - required_downlevel_properties: - ::required_downlevel_capabilities(), - required_limits: ::required_limits(), - failures: Vec::new(), - }, - comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "ray_cube_fragment", + image_path: "/examples/ray_cube_fragment/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters { + required_features: ::required_features(), + required_limits: ::required_limits(), + skips: vec![], + failures: Vec::new(), + required_downlevel_caps: + ::required_downlevel_capabilities(), + }, + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/ray-scene/src/shader.wgsl b/examples/src/ray_scene/shader.wgsl similarity index 100% rename from examples/ray-scene/src/shader.wgsl rename to examples/src/ray_scene/shader.wgsl diff --git a/examples/src/render_to_texture/README.md b/examples/src/render_to_texture/README.md new file mode 100644 index 0000000000..04bb3fff44 --- /dev/null +++ b/examples/src/render_to_texture/README.md @@ -0,0 +1,11 @@ +# render_to_texture + +Similar to hello-triangle but instead of rendering to a window or canvas, renders to a texture that is then output as an image like the storage-texture example. + +If all goes well, the end result should look familiarly like hello-triangle with its red triangle on a green background. + +## To Run + +``` +cargo run --bin wgpu-examples render_to_texture +``` diff --git a/examples/src/render_to_texture/mod.rs b/examples/src/render_to_texture/mod.rs new file mode 100644 index 0000000000..75d22dfe87 --- /dev/null +++ b/examples/src/render_to_texture/mod.rs @@ -0,0 +1,170 @@ +#[cfg(not(target_arch = "wasm32"))] +use crate::utils::output_image_native; +#[cfg(target_arch = "wasm32")] +use crate::utils::output_image_wasm; + +const TEXTURE_DIMS: (usize, usize) = (512, 512); + +async fn run(_path: Option) { + // This will later store the raw pixel value data locally. We'll create it now as + // a convenient size reference. + let mut texture_data = Vec::::with_capacity(TEXTURE_DIMS.0 * TEXTURE_DIMS.1 * 4); + + let instance = wgpu::Instance::default(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions::default()) + .await + .unwrap(); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_defaults(), + }, + None, + ) + .await + .unwrap(); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let render_target = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: TEXTURE_DIMS.0 as u32, + height: TEXTURE_DIMS.1 as u32, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], + }); + let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: texture_data.capacity() as u64, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::TextureFormat::Rgba8UnormSrgb.into())], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + log::info!("Wgpu context set up."); + + //----------------------------------------------- + + let texture_view = render_target.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut command_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + { + let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &texture_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + render_pass.set_pipeline(&pipeline); + render_pass.draw(0..3, 0..1); + } + // The texture now contains our rendered image + command_encoder.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: &render_target, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &output_staging_buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + // This needs to be a multiple of 256. Normally we would need to pad + // it but we here know it will work out anyways. + bytes_per_row: Some((TEXTURE_DIMS.0 * 4) as u32), + rows_per_image: Some(TEXTURE_DIMS.1 as u32), + }, + }, + wgpu::Extent3d { + width: TEXTURE_DIMS.0 as u32, + height: TEXTURE_DIMS.1 as u32, + depth_or_array_layers: 1, + }, + ); + queue.submit(Some(command_encoder.finish())); + log::info!("Commands submitted."); + + //----------------------------------------------- + + // Time to get our image. + let buffer_slice = output_staging_buffer.slice(..); + let (sender, receiver) = flume::bounded(1); + buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); + device.poll(wgpu::Maintain::wait()).panic_on_timeout(); + receiver.recv_async().await.unwrap().unwrap(); + log::info!("Output buffer mapped."); + { + let view = buffer_slice.get_mapped_range(); + texture_data.extend_from_slice(&view[..]); + } + log::info!("Image data copied to local."); + output_staging_buffer.unmap(); + + #[cfg(not(target_arch = "wasm32"))] + output_image_native(texture_data.to_vec(), TEXTURE_DIMS, _path.unwrap()); + #[cfg(target_arch = "wasm32")] + output_image_wasm(texture_data.to_vec(), TEXTURE_DIMS); + log::info!("Done."); +} + +pub fn main() { + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let path = std::env::args() + .nth(1) + .unwrap_or_else(|| "please_don't_git_push_me.png".to_string()); + pollster::block_on(run(Some(path))); + } + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); + wasm_bindgen_futures::spawn_local(run(None)); + } +} diff --git a/examples/src/render_to_texture/shader.wgsl b/examples/src/render_to_texture/shader.wgsl new file mode 100644 index 0000000000..f7131a1be1 --- /dev/null +++ b/examples/src/render_to_texture/shader.wgsl @@ -0,0 +1,14 @@ +@vertex +fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + var vertices = array, 3>( + vec4(0.0, 1.0, 0.0, 1.0), + vec4(-1.0, -1.0, 0.0, 1.0), + vec4(1.0, -1.0, 0.0, 1.0) + ); + return vertices[in_vertex_index]; +} + +@fragment +fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/examples/src/repeated_compute/README.md b/examples/src/repeated_compute/README.md new file mode 100644 index 0000000000..874903760d --- /dev/null +++ b/examples/src/repeated_compute/README.md @@ -0,0 +1,21 @@ +# repeated_compute + +Repeatedly performs the Collatz calculation used in `hello-compute` on sets of random numbers. + +## To Run + +``` +cargo run --bin wgpu-examples repeated_compute +``` + +## Sample + +Randomly generated input: +``` +[61917, 53957, 5717, 40520, 41020, 5120, 44281, 19584, 2975, 5310, 4162, 38159, 25343, 16551, 40532, 31464, 64505, 55815, 34793, 24285, 62190, 10530, 49321, 57494, 18473, 18291, 9067, 2665, 53877, 6754, 37616, 51136, 54990, 31159, 38648, 24127, 49640, 12095, 4529, 56275, 18200, 24423, 14065, 17512, 31421, 19612, 63709, 47666, 21805, 13608, 63529, 17809, 6737, 55362, 24647, 30348, 44906, 46325, 503, 52776, 63112, 20785, 63338, 28904, 55772, 56851, 53870, 65503, 30290, 57374, 61244, 39866, 625, 2353, 54901, 25511, 64046, 47882, 22723, 54917, 19563, 24130, 54374, 41964, 3999, 2805, 918, 32932, 6717, 46311, 4818, 28843, 37972, 50981, 31555, 39064, 42814, 37957, 17963, 22678, 3048, 18823, 7293, 63312, 29086, 45580, 5347, 1761, 19090, 41520, 35919, 38705, 51378, 29090, 31100, 55324, 26807, 26017, 24295, 62389, 51934, 27026, 1795, 14965, 51274, 10875, 21396, 22828, 37077, 49922, 46486, 55817, 58928, 64455, 47269, 53484, 6602, 52270, 24417, 6525, 60485, 6389, 10336, 62651, 15721, 8793, 37174, 11962, 768, 21426, 9919, 14295, 55401, 33099, 2221, 9021, 793, 27731, 58923, 28847, 56634, 20447, 33108, 11355, 32437, 15594, 26951, 62607, 28151, 46173, 53140, 48397, 64164, 12279, 54591, 36440, 42712, 3495, 28316, 4674, 35028, 50809, 17289, 3355, 6840, 38134, 29806, 53215, 12076, 55685, 31314, 33548, 51846, 29484, 36845, 12242, 11836, 5449, 11549, 12626, 23699, 52777, 350, 19344, 6380, 63964, 49649, 42487, 26543, 60198, 43868, 38280, 12917, 33574, 44104, 24176, 1348, 47752, 34890, 1471, 34329, 59348, 25115, 148, 62147, 12340, 23654, 26821, 3695, 41075, 15125, 56593, 44273, 34180, 35209, 26294, 48642, 19007, 40617, 46831, 9988, 522, 36478, 64700, 31220, 41376, 43870, 6053, 56665, 56475, 475, 60238, 38170, 53613, 23654, 26273] +``` + +Resulting output: +``` +["148", "78", "36", "75", "150", "15", "163", "43", "48", "54", "64", "80", "201", "120", "36", "147", "192", "65", "129", "157", "60", "42", "189", "73", "92", "66", "47", "53", "91", "36", "62", "78", "215", "54", "124", "144", "158", "94", "64", "83", "22", "100", "58", "35", "85", "105", "254", "101", "56", "63", "78", "97", "181", "228", "219", "72", "132", "57", "66", "34", "104", "149", "148", "121", "60", "104", "91", "130", "165", "78", "86", "106", "25", "32", "122", "113", "47", "96", "82", "60", "79", "51", "184", "88", "188", "84", "129", "147", "88", "114", "121", "165", "80", "83", "103", "75", "194", "155", "48", "131", "110", "61", "163", "55", "165", "70", "116", "104", "79", "106", "93", "75", "52", "134", "54", "91", "108", "126", "188", "148", "109", "38", "68", "133", "127", "117", "48", "30", "36", "52", "114", "184", "135", "161", "83", "52", "137", "109", "69", "137", "86", "124", "104", "179", "84", "127", "62", "50", "15", "30", "148", "102", "78", "160", "32", "140", "77", "90", "135", "165", "104", "180", "129", "161", "160", "146", "183", "148", "108", "145", "109", "70", "104", "125", "78", "62", "49", "56", "103", "59", "36", "202", "110", "92", "57", "54", "165", "171", "68", "109", "85", "67", "171", "46", "124", "174", "99", "160", "130", "156", "100", "83", "81", "61", "75", "55", "158", "101", "77", "91", "119", "75", "76", "129", "101", "95", "114", "96", "142", "171", "111", "122", "64", "23", "179", "37", "82", "46", "206", "150", "40", "104", "101", "129", "155", "64", "65", "154", "212", "132", "91", "30", "67", "148", "178", "106", "163", "67", "60", "135", "27", "117", "106", "109", "82", "201"] +``` \ No newline at end of file diff --git a/examples/src/repeated_compute/mod.rs b/examples/src/repeated_compute/mod.rs new file mode 100644 index 0000000000..596de7297d --- /dev/null +++ b/examples/src/repeated_compute/mod.rs @@ -0,0 +1,259 @@ +//! See hello-compute example main.rs for more details +//! as similar items here are not explained. +//! +//! This example does elaborate on some things though that the +//! hello-compute example does not such as mapping buffers +//! and why use the async channels. + +use std::mem::size_of_val; + +const OVERFLOW: u32 = 0xffffffff; + +async fn run() { + let mut numbers = [0u32; 256]; + let context = WgpuContext::new(size_of_val(&numbers)).await; + + for _ in 0..10 { + for p in numbers.iter_mut() { + *p = generate_rand() as u32; + } + + compute(&mut numbers, &context).await; + + let printed_numbers = numbers + .iter() + .map(|n| match n { + &OVERFLOW => "(overflow)".to_string(), + n => n.to_string(), + }) + .collect::>(); + log::info!("Results: {printed_numbers:?}"); + } +} + +fn generate_rand() -> u16 { + let mut bytes = [0u8; 2]; + getrandom::getrandom(&mut bytes[..]).unwrap(); + u16::from_le_bytes(bytes) +} + +async fn compute(local_buffer: &mut [u32], context: &WgpuContext) { + log::info!("Beginning GPU compute on data {local_buffer:?}."); + // Local buffer contents -> GPU storage buffer + // Adds a write buffer command to the queue. This command is more complicated + // than it appears. + context.queue.write_buffer( + &context.storage_buffer, + 0, + bytemuck::cast_slice(local_buffer), + ); + log::info!("Wrote to buffer."); + + let mut command_encoder = context + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + { + let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + compute_pass.set_pipeline(&context.pipeline); + compute_pass.set_bind_group(0, &context.bind_group, &[]); + compute_pass.dispatch_workgroups(local_buffer.len() as u32, 1, 1); + } + // We finish the compute pass by dropping it. + + // Entire storage buffer -> staging buffer. + command_encoder.copy_buffer_to_buffer( + &context.storage_buffer, + 0, + &context.output_staging_buffer, + 0, + context.storage_buffer.size(), + ); + + // Finalize the command encoder, add the contained commands to the queue and flush. + context.queue.submit(Some(command_encoder.finish())); + log::info!("Submitted commands."); + + // Finally time to get our results. + // First we get a buffer slice which represents a chunk of the buffer (which we + // can't access yet). + // We want the whole thing so use unbounded range. + let buffer_slice = context.output_staging_buffer.slice(..); + // Now things get complicated. WebGPU, for safety reasons, only allows either the GPU + // or CPU to access a buffer's contents at a time. We need to "map" the buffer which means + // flipping ownership of the buffer over to the CPU and making access legal. We do this + // with `BufferSlice::map_async`. + // + // The problem is that map_async is not an async function so we can't await it. What + // we need to do instead is pass in a closure that will be executed when the slice is + // either mapped or the mapping has failed. + // + // The problem with this is that we don't have a reliable way to wait in the main + // code for the buffer to be mapped and even worse, calling get_mapped_range or + // get_mapped_range_mut prematurely will cause a panic, not return an error. + // + // Using channels solves this as awaiting the receiving of a message from + // the passed closure will force the outside code to wait. It also doesn't hurt + // if the closure finishes before the outside code catches up as the message is + // buffered and receiving will just pick that up. + // + // It may also be worth noting that although on native, the usage of asynchronous + // channels is wholely unnecessary, for the sake of portability to WASM (std channels + // don't work on WASM,) we'll use async channels that work on both native and WASM. + let (sender, receiver) = flume::bounded(1); + buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); + // In order for the mapping to be completed, one of three things must happen. + // One of those can be calling `Device::poll`. This isn't necessary on the web as devices + // are polled automatically but natively, we need to make sure this happens manually. + // `Maintain::Wait` will cause the thread to wait on native but not on WebGpu. + context + .device + .poll(wgpu::Maintain::wait()) + .panic_on_timeout(); + log::info!("Device polled."); + // Now we await the receiving and panic if anything went wrong because we're lazy. + receiver.recv_async().await.unwrap().unwrap(); + log::info!("Result received."); + // NOW we can call get_mapped_range. + { + let view = buffer_slice.get_mapped_range(); + local_buffer.copy_from_slice(bytemuck::cast_slice(&view)); + } + log::info!("Results written to local buffer."); + // We need to make sure all `BufferView`'s are dropped before we do what we're about + // to do. + // Unmap so that we can copy to the staging buffer in the next iteration. + context.output_staging_buffer.unmap(); +} + +pub fn main() { + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + pollster::block_on(run()); + } + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); + + crate::utils::add_web_nothing_to_see_msg(); + + wasm_bindgen_futures::spawn_local(run()); + } +} + +/// A convenient way to hold together all the useful wgpu stuff together. +struct WgpuContext { + device: wgpu::Device, + queue: wgpu::Queue, + pipeline: wgpu::ComputePipeline, + bind_group: wgpu::BindGroup, + storage_buffer: wgpu::Buffer, + output_staging_buffer: wgpu::Buffer, +} + +impl WgpuContext { + async fn new(buffer_size: usize) -> WgpuContext { + let instance = wgpu::Instance::default(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions::default()) + .await + .unwrap(); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_defaults(), + }, + None, + ) + .await + .unwrap(); + + // Our shader, kindly compiled with Naga. + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!( + "shader.wgsl" + ))), + }); + + // This is where the GPU will read from and write to. + let storage_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: buffer_size as wgpu::BufferAddress, + usage: wgpu::BufferUsages::STORAGE + | wgpu::BufferUsages::COPY_DST + | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + // For portability reasons, WebGPU draws a distinction between memory that is + // accessible by the CPU and memory that is accessible by the GPU. Only + // buffers accessible by the CPU can be mapped and accessed by the CPU and + // only buffers visible to the GPU can be used in shaders. In order to get + // data from the GPU, we need to use CommandEncoder::copy_buffer_to_buffer + // (which we will later) to copy the buffer modified by the GPU into a + // mappable, CPU-accessible buffer which we'll create here. + let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: buffer_size as wgpu::BufferAddress, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + // This can be though of as the function signature for our CPU-GPU function. + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + // Going to have this be None just to be safe. + min_binding_size: None, + }, + count: None, + }], + }); + // This ties actual resources stored in the GPU to our metaphorical function + // through the binding slots we defined above. + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: storage_buffer.as_entire_binding(), + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &shader, + entry_point: "main", + }); + + WgpuContext { + device, + queue, + pipeline, + bind_group, + storage_buffer, + output_staging_buffer, + } + } +} diff --git a/examples/src/repeated_compute/shader.wgsl b/examples/src/repeated_compute/shader.wgsl new file mode 100644 index 0000000000..41af4363a2 --- /dev/null +++ b/examples/src/repeated_compute/shader.wgsl @@ -0,0 +1,38 @@ +@group(0) +@binding(0) +var v_indices: array; // this is used as both input and output for convenience + +// The Collatz Conjecture states that for any integer n: +// If n is even, n = n/2 +// If n is odd, n = 3n+1 +// And repeat this process for each new n, you will always eventually reach 1. +// Though the conjecture has not been proven, no counterexample has ever been found. +// This function returns how many times this recurrence needs to be applied to reach 1. +fn collatz_iterations(n_base: u32) -> u32{ + var n: u32 = n_base; + var i: u32 = 0u; + loop { + if (n <= 1u) { + break; + } + if (n % 2u == 0u) { + n = n / 2u; + } + else { + // Overflow? (i.e. 3*n + 1 > 0xffffffffu?) + if (n >= 1431655765u) { // 0x55555555u + return 4294967295u; // 0xffffffffu + } + + n = 3u * n + 1u; + } + i = i + 1u; + } + return i; +} + +@compute +@workgroup_size(1) +fn main(@builtin(global_invocation_id) global_id: vec3) { + v_indices[global_id.x] = collatz_iterations(v_indices[global_id.x]); +} diff --git a/examples/shadow/README.md b/examples/src/shadow/README.md similarity index 78% rename from examples/shadow/README.md rename to examples/src/shadow/README.md index 3298975b8d..86435a7289 100644 --- a/examples/shadow/README.md +++ b/examples/src/shadow/README.md @@ -5,7 +5,7 @@ This animated example demonstrates shadow mapping. ## To Run ``` -cargo run --bin shadow +cargo run --bin wgpu-examples shadow ``` ## Screenshots diff --git a/examples/shadow/src/main.rs b/examples/src/shadow/mod.rs similarity index 93% rename from examples/shadow/src/main.rs rename to examples/src/shadow/mod.rs index 3f963d0c53..485d0d78d6 100644 --- a/examples/shadow/src/main.rs +++ b/examples/src/shadow/mod.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, f32::consts, iter, mem, ops::Range, rc::Rc}; +use std::{borrow::Cow, f32::consts, iter, mem, ops::Range, sync::Arc}; use bytemuck::{Pod, Zeroable}; use wgpu::util::{align_to, DeviceExt}; @@ -80,8 +80,8 @@ struct Entity { mx_world: glam::Mat4, rotation_speed: f32, color: wgpu::Color, - vertex_buf: Rc, - index_buf: Rc, + vertex_buf: Arc, + index_buf: Arc, index_format: wgpu::IndexFormat, index_count: usize, uniform_offset: wgpu::DynamicOffset, @@ -201,7 +201,7 @@ impl Example { } } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::DEPTH_CLIP_CONTROL } @@ -221,7 +221,7 @@ impl wgpu_example::framework::Example for Example { // Create the vertex and index buffers let vertex_size = mem::size_of::(); let (cube_vertex_data, cube_index_data) = create_cube(); - let cube_vertex_buf = Rc::new(device.create_buffer_init( + let cube_vertex_buf = Arc::new(device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Cubes Vertex Buffer"), contents: bytemuck::cast_slice(&cube_vertex_data), @@ -229,7 +229,7 @@ impl wgpu_example::framework::Example for Example { }, )); - let cube_index_buf = Rc::new(device.create_buffer_init( + let cube_index_buf = Arc::new(device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Cubes Index Buffer"), contents: bytemuck::cast_slice(&cube_index_data), @@ -306,8 +306,8 @@ impl wgpu_example::framework::Example for Example { mx_world: glam::Mat4::IDENTITY, rotation_speed: 0.0, color: wgpu::Color::WHITE, - vertex_buf: Rc::new(plane_vertex_buf), - index_buf: Rc::new(plane_index_buf), + vertex_buf: Arc::new(plane_vertex_buf), + index_buf: Arc::new(plane_index_buf), index_format, index_count: plane_index_data.len(), uniform_offset: 0, @@ -327,8 +327,8 @@ impl wgpu_example::framework::Example for Example { mx_world, rotation_speed: cube.rotation, color: wgpu::Color::GREEN, - vertex_buf: Rc::clone(&cube_vertex_buf), - index_buf: Rc::clone(&cube_index_buf), + vertex_buf: Arc::clone(&cube_vertex_buf), + index_buf: Arc::clone(&cube_index_buf), index_format, index_count: cube_index_data.len(), uniform_offset: ((i + 1) * uniform_alignment as usize) as _, @@ -703,13 +703,7 @@ impl wgpu_example::framework::Example for Example { self.forward_depth = Self::create_depth_texture(config, device); } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { // update uniforms for entity in self.entities.iter_mut() { if entity.rotation_speed != 0.0 { @@ -773,7 +767,7 @@ impl wgpu_example::framework::Example for Example { view: &light.target_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: true, + store: wgpu::StoreOp::Store, }), stencil_ops: None, }), @@ -810,14 +804,14 @@ impl wgpu_example::framework::Example for Example { b: 0.3, a: 1.0, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.forward_depth, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: false, + store: wgpu::StoreOp::Discard, }), stencil_ops: None, }), @@ -840,32 +834,25 @@ impl wgpu_example::framework::Example for Example { } } -fn main() { - wgpu_example::framework::run::("shadow"); +pub fn main() { + crate::framework::run::("shadow"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn shadow() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/shadow/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default() - .downlevel_flags(wgpu::DownlevelFlags::COMPARISON_SAMPLERS) - // rpi4 on VK doesn't work: https://gitlab.freedesktop.org/mesa/mesa/-/issues/3916 - .expect_fail(wgpu_test::FailureCase::backend_adapter( - wgpu::Backends::VULKAN, - "V3D", - )) - // llvmpipe versions in CI are flaky: https://github.com/gfx-rs/wgpu/issues/2594 - .skip(wgpu_test::FailureCase::backend_adapter( - wgpu::Backends::VULKAN, - "llvmpipe", - )), - comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "shadow", + image_path: "/examples/src/shadow/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default() + .downlevel_flags(wgpu::DownlevelFlags::COMPARISON_SAMPLERS) + // rpi4 on VK doesn't work: https://gitlab.freedesktop.org/mesa/mesa/-/issues/3916 + .expect_fail(wgpu_test::FailureCase::backend_adapter( + wgpu::Backends::VULKAN, + "V3D", + )), + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/shadow/screenshot.png b/examples/src/shadow/screenshot.png similarity index 100% rename from examples/shadow/screenshot.png rename to examples/src/shadow/screenshot.png diff --git a/examples/shadow/src/shader.wgsl b/examples/src/shadow/shader.wgsl similarity index 100% rename from examples/shadow/src/shader.wgsl rename to examples/src/shadow/shader.wgsl diff --git a/examples/skybox/README.md b/examples/src/skybox/README.md similarity index 88% rename from examples/skybox/README.md rename to examples/src/skybox/README.md index 7fe9de1df6..2e7e8d3174 100644 --- a/examples/skybox/README.md +++ b/examples/src/skybox/README.md @@ -6,7 +6,7 @@ It hooks up `winit` mouse controls for camera rotation around the model at the c ## To Run ``` -cargo run --bin skybox +cargo run --bin wgpu-examples skybox ``` ## Screenshots diff --git a/examples/src/skybox/images/astc.ktx2 b/examples/src/skybox/images/astc.ktx2 new file mode 100644 index 0000000000..a4bbea0d05 Binary files /dev/null and b/examples/src/skybox/images/astc.ktx2 differ diff --git a/examples/src/skybox/images/bc7.ktx2 b/examples/src/skybox/images/bc7.ktx2 new file mode 100644 index 0000000000..c0d9e5b52d Binary files /dev/null and b/examples/src/skybox/images/bc7.ktx2 differ diff --git a/examples/src/skybox/images/etc2.ktx2 b/examples/src/skybox/images/etc2.ktx2 new file mode 100644 index 0000000000..6c417a1462 Binary files /dev/null and b/examples/src/skybox/images/etc2.ktx2 differ diff --git a/examples/src/skybox/images/generation.bash b/examples/src/skybox/images/generation.bash new file mode 100644 index 0000000000..baf54680e1 --- /dev/null +++ b/examples/src/skybox/images/generation.bash @@ -0,0 +1,45 @@ +# Needs montage from ImageMagick in PATH +# Needs compressonatorcli.exe from https://github.com/GPUOpen-Tools/compressonator in PATH +# Needs PVRTexToolCLI.exe from https://developer.imaginationtech.com/pvrtextool/ in PATH + +# Generate a skybox image from 6 jpeg in the folder in first argument. +# The images must be named right.jpg, left.jpg, top.jpg, bottom.jpg, back.jpg, front.jpg +# +# Must be called from the root of the project. +# +# bash examples/src/skybox/images/generation.bash ./path/to/images/folder + +SCRIPT_DIRECTORY=examples/src/skybox/images +CHUNK_SIZE="256x256" + +set -e + +# ensure the script is called from the root of the project +if [ ! -f "$SCRIPT_DIRECTORY/generation.bash" ]; then + echo "The script must be called from the root of the project!" + exit 1 +fi + +# ensure an argument is passed +if [ $# -eq 0 ]; then + echo "No arguments supplied!" + echo + echo "Usage: bash examples/src/skybox/images/generation.bash ./path/to/images/folder" + exit 1 +fi + +TEMP=examples/src/skybox/images/tmp + +mkdir -p $TEMP +# resize images to 256x256 +magick mogrify -path $TEMP -resize 256x256 -format png $1/*.jpg +# create an uncompressed ktx2 cubemap file +PVRTexToolCLI.exe -i $TEMP/right.png,$TEMP/left.png,$TEMP/top.png,$TEMP/bottom.png,$TEMP/front.png,$TEMP/back.png -ics SRGB -cube -m -f r8g8b8a8,UBN,SRGB -o $SCRIPT_DIRECTORY/rgba8.ktx2 +# create the bc7 compressed ktx2 cubemap files using compressonator +compressonatorcli.exe -fd BC7 $SCRIPT_DIRECTORY/rgba8.ktx2 $SCRIPT_DIRECTORY/bc7.ktx2 +# create the etc2 and astc compressed ktx2 cubemap file using PVRTexTool +# +# compressonator has support for etc2, but the result looks terrible. +PVRTexToolCLI.exe -i $SCRIPT_DIRECTORY/rgba8.ktx2 -ics srgb -m -f ETC2_RGB_A1,UBN,SRGB -q etcslow -o $SCRIPT_DIRECTORY/etc2.ktx2 +PVRTexToolCLI.exe -i $SCRIPT_DIRECTORY/rgba8.ktx2 -ics srgb -m -f ASTC_4X4,UBN,SRGB -q astcexhaustive -o $SCRIPT_DIRECTORY/astc.ktx2 +rm -r $TEMP diff --git a/examples/src/skybox/images/rgba8.ktx2 b/examples/src/skybox/images/rgba8.ktx2 new file mode 100644 index 0000000000..0babc622d7 Binary files /dev/null and b/examples/src/skybox/images/rgba8.ktx2 differ diff --git a/examples/skybox/src/main.rs b/examples/src/skybox/mod.rs similarity index 80% rename from examples/skybox/src/main.rs rename to examples/src/skybox/mod.rs index d09622f53c..bdb5e66142 100644 --- a/examples/skybox/src/main.rs +++ b/examples/src/skybox/mod.rs @@ -2,7 +2,7 @@ use bytemuck::{Pod, Zeroable}; use std::{borrow::Cow, f32::consts}; use wgpu::{util::DeviceExt, AstcBlock, AstcChannel}; -const IMAGE_SIZE: u32 = 128; +const IMAGE_SIZE: u32 = 256; #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] @@ -52,7 +52,7 @@ impl Camera { } } -pub struct Skybox { +pub struct Example { camera: Camera, sky_pipeline: wgpu::RenderPipeline, entity_pipeline: wgpu::RenderPipeline, @@ -63,7 +63,7 @@ pub struct Skybox { staging_belt: wgpu::util::StagingBelt, } -impl Skybox { +impl Example { const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24Plus; fn create_depth_texture( @@ -89,7 +89,7 @@ impl Skybox { } } -impl wgpu_example::framework::Example for Skybox { +impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::TEXTURE_COMPRESSION_ASTC | wgpu::Features::TEXTURE_COMPRESSION_ETC2 @@ -104,7 +104,7 @@ impl wgpu_example::framework::Example for Skybox { ) -> Self { let mut entities = Vec::new(); { - let source = include_bytes!("../models/teslacyberv3.0.obj"); + let source = include_bytes!("models/teslacyberv3.0.obj"); let data = obj::ObjData::load_buf(&source[..]).unwrap(); let mut vertices = Vec::new(); for object in data.objects { @@ -266,20 +266,20 @@ impl wgpu_example::framework::Example for Skybox { let device_features = device.features(); let skybox_format = if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) { - log::info!("Using ASTC"); + log::info!("Using astc"); wgpu::TextureFormat::Astc { block: AstcBlock::B4x4, channel: AstcChannel::UnormSrgb, } } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) { - log::info!("Using ETC2"); - wgpu::TextureFormat::Etc2Rgb8UnormSrgb + log::info!("Using etc2"); + wgpu::TextureFormat::Etc2Rgb8A1UnormSrgb } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) { - log::info!("Using BC"); - wgpu::TextureFormat::Bc1RgbaUnormSrgb + log::info!("Using bc7"); + wgpu::TextureFormat::Bc7RgbaUnormSrgb } else { - log::info!("Using plain"); - wgpu::TextureFormat::Bgra8UnormSrgb + log::info!("Using rgba8"); + wgpu::TextureFormat::Rgba8UnormSrgb }; let size = wgpu::Extent3d { @@ -306,20 +306,26 @@ impl wgpu_example::framework::Example for Skybox { wgpu::TextureFormat::Astc { block: AstcBlock::B4x4, channel: AstcChannel::UnormSrgb, - } => &include_bytes!("../images/astc.dds")[..], - wgpu::TextureFormat::Etc2Rgb8UnormSrgb => &include_bytes!("../images/etc2.dds")[..], - wgpu::TextureFormat::Bc1RgbaUnormSrgb => &include_bytes!("../images/bc1.dds")[..], - wgpu::TextureFormat::Bgra8UnormSrgb => &include_bytes!("../images/bgra.dds")[..], + } => &include_bytes!("images/astc.ktx2")[..], + wgpu::TextureFormat::Etc2Rgb8A1UnormSrgb => &include_bytes!("images/etc2.ktx2")[..], + wgpu::TextureFormat::Bc7RgbaUnormSrgb => &include_bytes!("images/bc7.ktx2")[..], + wgpu::TextureFormat::Rgba8UnormSrgb => &include_bytes!("images/rgba8.ktx2")[..], _ => unreachable!(), }; - let image = ddsfile::Dds::read(&mut std::io::Cursor::new(&bytes)).unwrap(); + let reader = ktx2::Reader::new(bytes).unwrap(); + let header = reader.header(); + + let mut image = Vec::with_capacity(reader.data().len()); + for level in reader.levels() { + image.extend_from_slice(level); + } let texture = device.create_texture_with_data( queue, &wgpu::TextureDescriptor { size, - mip_level_count: max_mips, + mip_level_count: header.level_count, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: skybox_format, @@ -327,7 +333,9 @@ impl wgpu_example::framework::Example for Skybox { label: None, view_formats: &[], }, - &image.data, + // KTX2 stores mip levels in mip major order. + wgpu::util::TextureDataOrder::MipMajor, + &image, ); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { @@ -356,7 +364,7 @@ impl wgpu_example::framework::Example for Skybox { let depth_view = Self::create_depth_texture(config, device); - Skybox { + Example { camera, sky_pipeline, entity_pipeline, @@ -391,13 +399,7 @@ impl wgpu_example::framework::Example for Skybox { self.camera.screen_size = (config.width, config.height); } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); @@ -428,14 +430,14 @@ impl wgpu_example::framework::Example for Skybox { b: 0.3, a: 1.0, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.depth_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: false, + store: wgpu::StoreOp::Discard, }), stencil_ops: None, }), @@ -461,62 +463,60 @@ impl wgpu_example::framework::Example for Skybox { } } -fn main() { - wgpu_example::framework::run::("skybox"); +pub fn main() { + crate::framework::run::("skybox"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn skybox() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/skybox/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default().expect_fail( - wgpu_test::FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE"), - ), - comparisons: &[wgpu_test::ComparisonType::Mean(0.015)], - }); -} - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn skybox_bc1() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/skybox/screenshot-bc1.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::TEXTURE_COMPRESSION_BC, - base_test_parameters: wgpu_test::TestParameters::default(), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 - comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], - }); -} - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn skybox_etc2() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/skybox/screenshot-etc2.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::TEXTURE_COMPRESSION_ETC2, - base_test_parameters: wgpu_test::TestParameters::default(), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 - comparisons: &[wgpu_test::ComparisonType::Mean(0.015)], - }); -} - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn skybox_astc() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/skybox/screenshot-astc.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::TEXTURE_COMPRESSION_ASTC, - base_test_parameters: wgpu_test::TestParameters::default(), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 - comparisons: &[wgpu_test::ComparisonType::Mean(0.016)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "skybox", + image_path: "/examples/src/skybox/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default().expect_fail( + wgpu_test::FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE"), + ), + comparisons: &[wgpu_test::ComparisonType::Mean(0.015)], + _phantom: std::marker::PhantomData::, +}; + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_BCN: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "skybox-bc7", + image_path: "/examples/src/skybox/screenshot_bc7.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::TEXTURE_COMPRESSION_BC, + base_test_parameters: wgpu_test::TestParameters::default(), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 + comparisons: &[wgpu_test::ComparisonType::Mean(0.02)], + _phantom: std::marker::PhantomData::, +}; + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_ETC2: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "skybox-etc2", + image_path: "/examples/src/skybox/screenshot_etc2.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::TEXTURE_COMPRESSION_ETC2, + base_test_parameters: wgpu_test::TestParameters::default(), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 + comparisons: &[wgpu_test::ComparisonType::Mean(0.015)], + _phantom: std::marker::PhantomData::, +}; + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_ASTC: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "skybox-astc", + image_path: "/examples/src/skybox/screenshot_astc.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::TEXTURE_COMPRESSION_ASTC, + base_test_parameters: wgpu_test::TestParameters::default(), // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 + comparisons: &[wgpu_test::ComparisonType::Mean(0.016)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/skybox/models/teslacyberv3.0.mtl b/examples/src/skybox/models/teslacyberv3.0.mtl similarity index 100% rename from examples/skybox/models/teslacyberv3.0.mtl rename to examples/src/skybox/models/teslacyberv3.0.mtl diff --git a/examples/skybox/models/teslacyberv3.0.obj b/examples/src/skybox/models/teslacyberv3.0.obj similarity index 100% rename from examples/skybox/models/teslacyberv3.0.obj rename to examples/src/skybox/models/teslacyberv3.0.obj diff --git a/examples/src/skybox/screenshot.png b/examples/src/skybox/screenshot.png new file mode 100644 index 0000000000..b5e9d554af Binary files /dev/null and b/examples/src/skybox/screenshot.png differ diff --git a/examples/src/skybox/screenshot_astc.png b/examples/src/skybox/screenshot_astc.png new file mode 100644 index 0000000000..93dd704bf5 Binary files /dev/null and b/examples/src/skybox/screenshot_astc.png differ diff --git a/examples/src/skybox/screenshot_bc7.png b/examples/src/skybox/screenshot_bc7.png new file mode 100644 index 0000000000..9a6a278efe Binary files /dev/null and b/examples/src/skybox/screenshot_bc7.png differ diff --git a/examples/src/skybox/screenshot_etc2.png b/examples/src/skybox/screenshot_etc2.png new file mode 100644 index 0000000000..a7bf7537b9 Binary files /dev/null and b/examples/src/skybox/screenshot_etc2.png differ diff --git a/examples/skybox/src/shader.wgsl b/examples/src/skybox/shader.wgsl similarity index 100% rename from examples/skybox/src/shader.wgsl rename to examples/src/skybox/shader.wgsl diff --git a/examples/src/srgb_blend/README.md b/examples/src/srgb_blend/README.md new file mode 100644 index 0000000000..ebb3ee8150 --- /dev/null +++ b/examples/src/srgb_blend/README.md @@ -0,0 +1,23 @@ +# srgb_blend + +This example shows blending in sRGB or linear space. + +## To Run + +``` +cargo run --bin wgpu-examples srgb_blend linear +``` + +``` +cargo run --bin wgpu-examples srgb_blend +``` + +## Screenshots + +Blending in linear space: + +![sRGB blend example](./screenshot-linear.png) + +Blending in sRGB space: + +![sRGB blend example](./screenshot-srgb.png) diff --git a/examples/src/srgb_blend/mod.rs b/examples/src/srgb_blend/mod.rs new file mode 100644 index 0000000000..d4021e6c5f --- /dev/null +++ b/examples/src/srgb_blend/mod.rs @@ -0,0 +1,250 @@ +use bytemuck::{Pod, Zeroable}; +use std::{borrow::Cow, mem}; +use wgpu::util::DeviceExt; + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Vertex { + _pos: [f32; 4], + _color: [f32; 4], +} + +fn vertex(pos: [i8; 2], _color: [f32; 4], offset: f32) -> Vertex { + let scale = 0.5; + Vertex { + _pos: [ + (pos[0] as f32 + offset) * scale, + (pos[1] as f32 + offset) * scale, + 0.0, + 1.0, + ], + _color, + } +} + +fn quad(vertices: &mut Vec, indices: &mut Vec, color: [f32; 4], offset: f32) { + let base = vertices.len() as u16; + + vertices.extend_from_slice(&[ + vertex([-1, -1], color, offset), + vertex([1, -1], color, offset), + vertex([1, 1], color, offset), + vertex([-1, 1], color, offset), + ]); + + indices.extend([0, 1, 2, 2, 3, 0].iter().map(|i| base + *i)); +} + +fn create_vertices() -> (Vec, Vec) { + let mut vertices = Vec::new(); + let mut indices = Vec::new(); + + let red = [1.0, 0.0, 0.0, 0.5]; + let blue = [0.0, 0.0, 1.0, 0.5]; + + quad(&mut vertices, &mut indices, red, 0.5); + quad(&mut vertices, &mut indices, blue, -0.5); + + (vertices, indices) +} + +struct Example { + vertex_buf: wgpu::Buffer, + index_buf: wgpu::Buffer, + index_count: usize, + bind_group: wgpu::BindGroup, + pipeline: wgpu::RenderPipeline, +} + +impl crate::framework::Example for Example { + const SRGB: bool = SRGB; + + fn optional_features() -> wgpu::Features { + wgpu::Features::POLYGON_MODE_LINE + } + + fn init( + config: &wgpu::SurfaceConfiguration, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + _queue: &wgpu::Queue, + ) -> Self { + // Create the vertex and index buffers + let vertex_size = mem::size_of::(); + let (vertex_data, index_data) = create_vertices(); + + let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertex_data), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&index_data), + usage: wgpu::BufferUsages::INDEX, + }); + + // Create pipeline layout + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[], + }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + // Create bind group + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[], + label: None, + }); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let vertex_buffers = [wgpu::VertexBufferLayout { + array_stride: vertex_size as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x4, + offset: 0, + shader_location: 0, + }, + wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x4, + offset: 4 * 4, + shader_location: 1, + }, + ], + }]; + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &vertex_buffers, + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.view_formats[0], + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + cull_mode: Some(wgpu::Face::Back), + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + // Done + Example { + vertex_buf, + index_buf, + index_count: index_data.len(), + bind_group, + pipeline, + } + } + + fn update(&mut self, _event: winit::event::WindowEvent) { + //empty + } + + fn resize( + &mut self, + _config: &wgpu::SurfaceConfiguration, + _device: &wgpu::Device, + _queue: &wgpu::Queue, + ) { + } + + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + device.push_error_scope(wgpu::ErrorFilter::Validation); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.push_debug_group("Prepare data for draw."); + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, &self.bind_group, &[]); + rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint16); + rpass.set_vertex_buffer(0, self.vertex_buf.slice(..)); + rpass.pop_debug_group(); + rpass.insert_debug_marker("Draw!"); + rpass.draw_indexed(0..self.index_count as u32, 0, 0..1); + } + + queue.submit(Some(encoder.finish())); + } +} + +pub fn main() { + let mut args = std::env::args(); + args.next(); + if Some("linear") == args.nth(1).as_deref() { + crate::framework::run::>("srgb-blend-linear"); + } else { + crate::framework::run::>("srgb-blend-srg"); + } +} + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_SRGB: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "srgb-blend-srg", + // Generated on WARP/Windows + image_path: "/examples/src/srgb_blend/screenshot-srgb.png", + width: 192, + height: 192, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.04)], + _phantom: std::marker::PhantomData::>, +}; + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_LINEAR: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "srgb-blend-linear", + // Generated on WARP/Windows + image_path: "/examples/src/srgb_blend/screenshot-linear.png", + width: 192, + height: 192, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.04)], + _phantom: std::marker::PhantomData::>, +}; diff --git a/examples/src/srgb_blend/screenshot-linear.png b/examples/src/srgb_blend/screenshot-linear.png new file mode 100644 index 0000000000..34eafd05f5 Binary files /dev/null and b/examples/src/srgb_blend/screenshot-linear.png differ diff --git a/examples/src/srgb_blend/screenshot-srgb.png b/examples/src/srgb_blend/screenshot-srgb.png new file mode 100644 index 0000000000..ed64cf09ab Binary files /dev/null and b/examples/src/srgb_blend/screenshot-srgb.png differ diff --git a/examples/src/srgb_blend/shader.wgsl b/examples/src/srgb_blend/shader.wgsl new file mode 100644 index 0000000000..766b229e7b --- /dev/null +++ b/examples/src/srgb_blend/shader.wgsl @@ -0,0 +1,24 @@ +struct VertexOutput { + @location(0) color: vec4, + @builtin(position) position: vec4, +}; + +@vertex +fn vs_main( + @location(0) position: vec4, + @location(1) color: vec4, +) -> VertexOutput { + var result: VertexOutput; + result.color = color; + result.position = position; + return result; +} + +@group(0) +@binding(1) +var color: vec4; + +@fragment +fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { + return vertex.color; +} diff --git a/examples/stencil-triangles/README.md b/examples/src/stencil_triangles/README.md similarity index 86% rename from examples/stencil-triangles/README.md rename to examples/src/stencil_triangles/README.md index 4b6842e091..e7c8c87a3d 100644 --- a/examples/stencil-triangles/README.md +++ b/examples/src/stencil_triangles/README.md @@ -1,4 +1,4 @@ -# hello-triangle +# stencil_triangles This example renders two different sized triangles to display three same sized triangles, by demonstrating the use of stencil buffers. @@ -10,7 +10,7 @@ Then, it draws a larger "outer" triangle which only touches pixels where the ste ## To Run ``` -cargo run --bin stencil-triangles +cargo run --bin wgpu-examples stencil_triangles ``` ## Screenshots diff --git a/examples/stencil-triangles/src/main.rs b/examples/src/stencil_triangles/mod.rs similarity index 84% rename from examples/stencil-triangles/src/main.rs rename to examples/src/stencil_triangles/mod.rs index 55aad9c9ba..bf645d3a34 100644 --- a/examples/stencil-triangles/src/main.rs +++ b/examples/src/stencil_triangles/mod.rs @@ -15,7 +15,7 @@ fn vertex(x: f32, y: f32) -> Vertex { } } -struct Triangles { +struct Example { outer_vertex_buffer: wgpu::Buffer, mask_vertex_buffer: wgpu::Buffer, outer_pipeline: wgpu::RenderPipeline, @@ -23,7 +23,7 @@ struct Triangles { stencil_buffer: wgpu::Texture, } -impl wgpu_example::framework::Example for Triangles { +impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, @@ -155,7 +155,7 @@ impl wgpu_example::framework::Example for Triangles { }); // Done - Triangles { + Example { outer_vertex_buffer, mask_vertex_buffer, outer_pipeline, @@ -170,20 +170,27 @@ impl wgpu_example::framework::Example for Triangles { fn resize( &mut self, - _config: &wgpu::SurfaceConfiguration, - _device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + device: &wgpu::Device, _queue: &wgpu::Queue, ) { - // empty + self.stencil_buffer = device.create_texture(&wgpu::TextureDescriptor { + label: Some("Stencil buffer"), + size: wgpu::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Stencil8, + view_formats: &[], + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + }); } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { @@ -200,7 +207,7 @@ impl wgpu_example::framework::Example for Triangles { b: 0.3, a: 1.0, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { @@ -208,7 +215,7 @@ impl wgpu_example::framework::Example for Triangles { depth_ops: None, stencil_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(0), - store: true, + store: wgpu::StoreOp::Store, }), }), timestamp_writes: None, @@ -230,21 +237,19 @@ impl wgpu_example::framework::Example for Triangles { } } -fn main() { - wgpu_example::framework::run::("stencil-triangles"); +pub fn main() { + crate::framework::run::("stencil-triangles"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn stencil_triangles() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/stencil-triangles/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default(), - comparisons: &[wgpu_test::ComparisonType::Mean(0.03)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "stencil-triangles", + image_path: "/examples/src/stencil_triangles/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.03)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/stencil-triangles/screenshot.png b/examples/src/stencil_triangles/screenshot.png similarity index 100% rename from examples/stencil-triangles/screenshot.png rename to examples/src/stencil_triangles/screenshot.png diff --git a/examples/stencil-triangles/src/shader.wgsl b/examples/src/stencil_triangles/shader.wgsl similarity index 100% rename from examples/stencil-triangles/src/shader.wgsl rename to examples/src/stencil_triangles/shader.wgsl diff --git a/examples/src/storage_texture/README.md b/examples/src/storage_texture/README.md new file mode 100644 index 0000000000..092fb69418 --- /dev/null +++ b/examples/src/storage_texture/README.md @@ -0,0 +1,14 @@ +# storage_texture + +A simple example that uses a storage texture to compute an image of the Mandelbrot set (https://en.wikipedia.org/wiki/Mandelbrot_set) and either saves it as an image or presents it to the browser screen in such a way that it can be saved as an image. + +## To Run + +``` +cargo run --bin wgpu-examples storage_texture +``` + + +## Example Output + +![Example output](./example.png) \ No newline at end of file diff --git a/examples/src/storage_texture/example.png b/examples/src/storage_texture/example.png new file mode 100644 index 0000000000..f90a220c69 Binary files /dev/null and b/examples/src/storage_texture/example.png differ diff --git a/examples/src/storage_texture/mod.rs b/examples/src/storage_texture/mod.rs new file mode 100644 index 0000000000..d389ce139e --- /dev/null +++ b/examples/src/storage_texture/mod.rs @@ -0,0 +1,182 @@ +//! This example demonstrates the basic usage of storage textures for the purpose of +//! creating a digital image of the Mandelbrot set +//! (). +//! +//! Storage textures work like normal textures but they operate similar to storage buffers +//! in that they can be written to. The issue is that as it stands, write-only is the +//! only valid access mode for storage textures in WGSL and although there is a WGPU feature +//! to allow for read-write access, this is unfortunately a native-only feature and thus +//! we won't be using it here. If we needed a reference texture, we would need to add a +//! second texture to act as a reference and attach that as well. Luckily, we don't need +//! to read anything in our shader except the dimensions of our texture, which we can +//! easily get via `textureDimensions`. +//! +//! A lot of things aren't explained here via comments. See hello-compute and +//! repeated-compute for code that is more thoroughly commented. + +#[cfg(not(target_arch = "wasm32"))] +use crate::utils::output_image_native; +#[cfg(target_arch = "wasm32")] +use crate::utils::output_image_wasm; + +const TEXTURE_DIMS: (usize, usize) = (512, 512); + +async fn run(_path: Option) { + let mut texture_data = vec![0u8; TEXTURE_DIMS.0 * TEXTURE_DIMS.1 * 4]; + + let instance = wgpu::Instance::default(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions::default()) + .await + .unwrap(); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_defaults(), + }, + None, + ) + .await + .unwrap(); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + let storage_texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: TEXTURE_DIMS.0 as u32, + height: TEXTURE_DIMS.1 as u32, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + let storage_texture_view = storage_texture.create_view(&wgpu::TextureViewDescriptor::default()); + let output_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: std::mem::size_of_val(&texture_data[..]) as u64, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::WriteOnly, + format: wgpu::TextureFormat::Rgba8Unorm, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }], + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&storage_texture_view), + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &shader, + entry_point: "main", + }); + + log::info!("Wgpu context set up."); + //---------------------------------------- + + let mut command_encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut compute_pass = command_encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + compute_pass.set_bind_group(0, &bind_group, &[]); + compute_pass.set_pipeline(&pipeline); + compute_pass.dispatch_workgroups(TEXTURE_DIMS.0 as u32, TEXTURE_DIMS.1 as u32, 1); + } + command_encoder.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: &storage_texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &output_staging_buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + // This needs to be padded to 256. + bytes_per_row: Some((TEXTURE_DIMS.0 * 4) as u32), + rows_per_image: Some(TEXTURE_DIMS.1 as u32), + }, + }, + wgpu::Extent3d { + width: TEXTURE_DIMS.0 as u32, + height: TEXTURE_DIMS.1 as u32, + depth_or_array_layers: 1, + }, + ); + queue.submit(Some(command_encoder.finish())); + + let buffer_slice = output_staging_buffer.slice(..); + let (sender, receiver) = flume::bounded(1); + buffer_slice.map_async(wgpu::MapMode::Read, move |r| sender.send(r).unwrap()); + device.poll(wgpu::Maintain::wait()).panic_on_timeout(); + receiver.recv_async().await.unwrap().unwrap(); + log::info!("Output buffer mapped"); + { + let view = buffer_slice.get_mapped_range(); + texture_data.copy_from_slice(&view[..]); + } + log::info!("GPU data copied to local."); + output_staging_buffer.unmap(); + + #[cfg(not(target_arch = "wasm32"))] + output_image_native(texture_data.to_vec(), TEXTURE_DIMS, _path.unwrap()); + #[cfg(target_arch = "wasm32")] + output_image_wasm(texture_data.to_vec(), TEXTURE_DIMS); + log::info!("Done.") +} + +pub fn main() { + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let path = std::env::args() + .nth(1) + .unwrap_or_else(|| "please_don't_git_push_me.png".to_string()); + pollster::block_on(run(Some(path))); + } + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init_with_level(log::Level::Info).expect("could not initialize logger"); + wasm_bindgen_futures::spawn_local(run(None)); + } +} diff --git a/examples/src/storage_texture/shader.wgsl b/examples/src/storage_texture/shader.wgsl new file mode 100644 index 0000000000..0dd48b3194 --- /dev/null +++ b/examples/src/storage_texture/shader.wgsl @@ -0,0 +1,29 @@ +const MAX_ITERATIONS: u32 = 50u; + +@group(0) +@binding(0) +var texture: texture_storage_2d; + +@compute +@workgroup_size(1) +fn main(@builtin(global_invocation_id) id: vec3) { + var final_iteration = MAX_ITERATIONS; + var c = vec2( + // Translated to put everything nicely in frame. + (f32(id.x) / f32(textureDimensions(texture).x)) * 3.0 - 2.25, + (f32(id.y) / f32(textureDimensions(texture).y)) * 3.0 - 1.5 + ); + var current_z = c; + var next_z: vec2; + for (var i = 0u; i < MAX_ITERATIONS; i++) { + next_z.x = (current_z.x * current_z.x - current_z.y * current_z.y) + c.x; + next_z.y = (2.0 * current_z.x * current_z.y) + c.y; + current_z = next_z; + if length(current_z) > 4.0 { + final_iteration = i; + break; + } + } + let value = f32(final_iteration) / f32(MAX_ITERATIONS); + textureStore(texture, vec2(i32(id.x), i32(id.y)), vec4(value, value, value, 1.0)); +} \ No newline at end of file diff --git a/examples/src/texture_arrays/README.md b/examples/src/texture_arrays/README.md new file mode 100644 index 0000000000..ddc63a78b2 --- /dev/null +++ b/examples/src/texture_arrays/README.md @@ -0,0 +1,11 @@ +# texture_arrays + +## To Run + +``` +cargo run --bin wgpu-examples texture_arrays +``` + +## Example Output + +![Example output](./screenshot.png) \ No newline at end of file diff --git a/examples/texture-arrays/src/indexing.wgsl b/examples/src/texture_arrays/indexing.wgsl similarity index 100% rename from examples/texture-arrays/src/indexing.wgsl rename to examples/src/texture_arrays/indexing.wgsl diff --git a/examples/texture-arrays/src/main.rs b/examples/src/texture_arrays/mod.rs similarity index 90% rename from examples/texture-arrays/src/main.rs rename to examples/src/texture_arrays/mod.rs index 373c2396ae..d4fed29efc 100644 --- a/examples/texture-arrays/src/main.rs +++ b/examples/src/texture_arrays/mod.rs @@ -70,7 +70,7 @@ struct Example { uniform_workaround: bool, } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn optional_features() -> wgpu::Features { wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING } @@ -361,13 +361,7 @@ impl wgpu_example::framework::Example for Example { fn update(&mut self, _event: winit::event::WindowEvent) { // noop } - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("primary"), }); @@ -379,7 +373,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -406,35 +400,47 @@ impl wgpu_example::framework::Example for Example { } } -fn main() { - wgpu_example::framework::run::("texture-arrays"); +pub fn main() { + crate::framework::run::("texture-arrays"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "texture-arrays", + image_path: "/examples/src/texture_arrays/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::empty(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], + _phantom: std::marker::PhantomData::, +}; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn texture_arrays_uniform() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/texture-arrays/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::empty(), - base_test_parameters: wgpu_test::TestParameters::default(), - comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_UNIFORM: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "texture-arrays-uniform", + image_path: "/examples/src/texture_arrays/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::empty(), + base_test_parameters: wgpu_test::TestParameters::default(), + comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], + _phantom: std::marker::PhantomData::, +}; -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn texture_arrays_non_uniform() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/texture-arrays/screenshot.png", +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST_NON_UNIFORM: crate::framework::ExampleTestParams = + crate::framework::ExampleTestParams { + name: "texture-arrays-non-uniform", + image_path: "/examples/src/texture_arrays/screenshot.png", width: 1024, height: 768, optional_features: wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, base_test_parameters: wgpu_test::TestParameters::default(), comparisons: &[wgpu_test::ComparisonType::Mean(0.0)], - }); -} + _phantom: std::marker::PhantomData::, + }; diff --git a/examples/texture-arrays/src/non_uniform_indexing.wgsl b/examples/src/texture_arrays/non_uniform_indexing.wgsl similarity index 100% rename from examples/texture-arrays/src/non_uniform_indexing.wgsl rename to examples/src/texture_arrays/non_uniform_indexing.wgsl diff --git a/examples/texture-arrays/screenshot.png b/examples/src/texture_arrays/screenshot.png similarity index 100% rename from examples/texture-arrays/screenshot.png rename to examples/src/texture_arrays/screenshot.png diff --git a/examples/timestamp-queries/README.md b/examples/src/timestamp_queries/README.md similarity index 55% rename from examples/timestamp-queries/README.md rename to examples/src/timestamp_queries/README.md index 1c95ff9f11..18cf292538 100644 --- a/examples/timestamp-queries/README.md +++ b/examples/src/timestamp_queries/README.md @@ -1,9 +1,9 @@ -# timestamp-queries +# timestamp_queries This example shows various ways of querying time when supported. ## To Run ``` -cargo run --bin timestamp-queries +cargo run --bin wgpu-examples timestamp_queries ``` diff --git a/examples/timestamp-queries/src/main.rs b/examples/src/timestamp_queries/mod.rs similarity index 92% rename from examples/timestamp-queries/src/main.rs rename to examples/src/timestamp_queries/mod.rs index d4f0e53361..beccac73b2 100644 --- a/examples/timestamp-queries/src/main.rs +++ b/examples/src/timestamp_queries/mod.rs @@ -47,6 +47,7 @@ impl QueryResults { // * compute end const NUM_QUERIES: u64 = 8; + #[allow(clippy::redundant_closure)] // False positive fn from_raw_results(timestamps: Vec, timestamps_inside_passes: bool) -> Self { assert_eq!(timestamps.len(), Self::NUM_QUERIES as usize); @@ -60,9 +61,9 @@ impl QueryResults { let mut encoder_timestamps = [0, 0]; encoder_timestamps[0] = get_next_slot(); let render_start_end_timestamps = [get_next_slot(), get_next_slot()]; - let render_inside_timestamp = timestamps_inside_passes.then_some(get_next_slot()); + let render_inside_timestamp = timestamps_inside_passes.then(|| get_next_slot()); let compute_start_end_timestamps = [get_next_slot(), get_next_slot()]; - let compute_inside_timestamp = timestamps_inside_passes.then_some(get_next_slot()); + let compute_inside_timestamp = timestamps_inside_passes.then(|| get_next_slot()); encoder_timestamps[1] = get_next_slot(); QueryResults { @@ -74,13 +75,14 @@ impl QueryResults { } } + #[cfg_attr(test, allow(unused))] fn print(&self, queue: &wgpu::Queue) { let period = queue.get_timestamp_period(); let elapsed_us = |start, end: u64| end.wrapping_sub(start) as f64 * period as f64 / 1000.0; println!( - "Elapsed time render + compute: {:.2} μs", - elapsed_us(self.encoder_timestamps[0], self.encoder_timestamps[1]) + "Elapsed time before render until after compute: {:.2} μs", + elapsed_us(self.encoder_timestamps[0], self.encoder_timestamps[1]), ); println!( "Elapsed time render pass: {:.2} μs", @@ -157,7 +159,7 @@ impl Queries { self.destination_buffer .slice(..) .map_async(wgpu::MapMode::Read, |_| ()); - device.poll(wgpu::Maintain::Wait); + device.poll(wgpu::Maintain::wait()).panic_on_timeout(); let timestamps = { let timestamp_view = self @@ -173,11 +175,13 @@ impl Queries { } } +#[cfg_attr(test, allow(unused))] async fn run() { // Instantiates instance of wgpu - let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); + let backends = wgpu::util::backend_bits_from_env().unwrap_or_default(); let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends, + flags: wgpu::InstanceFlags::from_build_config().with_env(), dx12_shader_compiler: wgpu::Dx12Compiler::default(), gles_minor_version: wgpu::Gles3MinorVersion::default(), }); @@ -210,8 +214,8 @@ async fn run() { .request_device( &wgpu::DeviceDescriptor { label: None, - features, - limits: wgpu::Limits::downlevel_defaults(), + required_features: features, + required_limits: wgpu::Limits::downlevel_defaults(), }, None, ) @@ -374,7 +378,7 @@ fn render_pass( resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -401,7 +405,7 @@ fn render_pass( rpass.draw(0..3, 0..1); } -fn main() { +pub fn main() { #[cfg(not(target_arch = "wasm32"))] { env_logger::init(); @@ -417,35 +421,29 @@ fn main() { #[cfg(test)] mod tests { - use crate::{submit_render_and_compute_pass_with_queries, QueryResults}; + use wgpu_test::{gpu_test, GpuTestConfiguration}; - #[test] - #[wasm_bindgen_test::wasm_bindgen_test] - fn test_timestamps_encoder() { - wgpu_test::initialize_test( + use super::{submit_render_and_compute_pass_with_queries, QueryResults}; + + #[gpu_test] + static TIMESTAMPS_ENCODER: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( wgpu_test::TestParameters::default() .limits(wgpu::Limits::downlevel_defaults()) .features(wgpu::Features::TIMESTAMP_QUERY), - |ctx| { - test_timestamps(ctx, false); - }, - ); - } + ) + .run_sync(|ctx| test_timestamps(ctx, false)); - #[test] - #[wasm_bindgen_test::wasm_bindgen_test] - fn test_timestamps_passes() { - wgpu_test::initialize_test( + #[gpu_test] + static TIMESTAMPS_PASSES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( wgpu_test::TestParameters::default() .limits(wgpu::Limits::downlevel_defaults()) .features( wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES, ), - |ctx| { - test_timestamps(ctx, true); - }, - ); - } + ) + .run_sync(|ctx| test_timestamps(ctx, true)); fn test_timestamps(ctx: wgpu_test::TestingContext, timestamps_inside_passes: bool) { let queries = submit_render_and_compute_pass_with_queries(&ctx.device, &ctx.queue); @@ -464,13 +462,10 @@ mod tests { render_start_end_timestamps[1].wrapping_sub(render_start_end_timestamps[0]); let compute_delta = compute_start_end_timestamps[1].wrapping_sub(compute_start_end_timestamps[0]); + let encoder_delta = encoder_timestamps[1].wrapping_sub(encoder_timestamps[0]); - // TODO: Metal encoder timestamps aren't implemented yet. - if ctx.adapter.get_info().backend != wgpu::Backend::Metal { - let encoder_delta = encoder_timestamps[1].wrapping_sub(encoder_timestamps[0]); - assert!(encoder_delta > 0); - assert!(encoder_delta >= render_delta + compute_delta); - } + assert!(encoder_delta > 0); + assert!(encoder_delta >= render_delta + compute_delta); if let Some(render_inside_timestamp) = render_inside_timestamp { assert!(render_inside_timestamp >= render_start_end_timestamps[0]); diff --git a/examples/timestamp-queries/src/shader.wgsl b/examples/src/timestamp_queries/shader.wgsl similarity index 100% rename from examples/timestamp-queries/src/shader.wgsl rename to examples/src/timestamp_queries/shader.wgsl diff --git a/examples/src/uniform_values/README.md b/examples/src/uniform_values/README.md new file mode 100644 index 0000000000..cbf34ae4df --- /dev/null +++ b/examples/src/uniform_values/README.md @@ -0,0 +1,22 @@ +# uniform_values + +Creates a window which displays a grayscale render of the [Mandelbrot set](https://en.wikipedia.org/wiki/Mandelbrot_set). Pressing the arrow keys will translate the set and scrolling the mouse wheel will zoom in and out. If the image appears too 'bright', it may be because you are using too few iterations or 'samples'. Use U and D to increase or decrease respectively the max number of iterations used. Make sure to play around with this too to get an optimally photogenic screen cap. The window can be resized and pressing ESC will close the window. Explore the Mandelbrot set using the power of uniform variables to transfer state from the main program to the shader! + +## To Run + +``` +cargo run --bin wgpu-examples uniform_values +``` + +## Usage of Uniform Buffers / Variables + +Since the codebase of this example is so large (because why not demonstrate with a sort-of game) and the points of interest in terms of the actual point of the example so small, there is a module doc comment at the top of main.rs that points out the important points of the usage of uniform values. + +## Limitations +At some point in exploring the fractal, you may discover there is actually a resolution; if you zoom to deep, things become weirdly pixilated. Unfortunately, the relatively basic shader is currently limited by the faults of 32-bit floating point precision. As much as I'd like to upgrade to 64-bit floats, the support in WGSL for f64's is limited and you can't even cast to one as of time of writing. Still pretty cool though. + +## Screenshots + +![On load](screenshot1.png) +![Zoomed in](screenshot2.png) +![A different part zoomed in](screenshot3.png) \ No newline at end of file diff --git a/examples/src/uniform_values/mod.rs b/examples/src/uniform_values/mod.rs new file mode 100644 index 0000000000..de71ce5067 --- /dev/null +++ b/examples/src/uniform_values/mod.rs @@ -0,0 +1,400 @@ +//! Points of interest for seeing uniforms in action: +//! +//! 1. the struct for the data stored in the uniform buffer is defined. +//! 2. the uniform buffer itself is created. +//! 3. the bind group that will bind the uniform buffer and it's layout are created. +//! 4. the bind group layout is attached to the pipeline layout. +//! 5. the uniform buffer and the bind group are stored alongside the pipeline. +//! 6. an instance of `AppState` is created. This variable will be modified +//! to change parameters in the shader and modified by app events to preform and save +//! those changes. +//! 7. (7a and 7b) the `state` variable created at (6) is modified by commands such +//! as pressing the arrow keys or zooming in or out. +//! 8. the contents of the `AppState` are loaded into the uniform buffer in preparation. +//! 9. the bind group with the uniform buffer is attached to the render pass. +//! +//! The usage of the uniform buffer within the shader itself is pretty self-explanatory given +//! some understanding of WGSL. + +use std::sync::Arc; +// We won't bring StorageBuffer into scope as that might be too easy to confuse +// with actual GPU-allocated WGPU storage buffers. +use encase::ShaderType; +use winit::{ + event::{Event, KeyEvent, WindowEvent}, + event_loop::EventLoop, + keyboard::{Key, NamedKey}, + window::Window, +}; + +const ZOOM_INCREMENT_FACTOR: f32 = 1.1; +const CAMERA_POS_INCREMENT_FACTOR: f32 = 0.1; + +// (1) +#[derive(Debug, ShaderType)] +struct AppState { + pub cursor_pos: glam::Vec2, + pub zoom: f32, + pub max_iterations: u32, +} + +impl AppState { + // Translating Rust structures to WGSL is always tricky and can prove + // incredibly difficult to remember all the rules by which WGSL + // lays out and formats structs in memory. It is also often extremely + // frustrating to debug when things don't go right. + // + // You may sometimes see structs translated to bytes through + // using `#[repr(C)]` on the struct so that the struct has a defined, + // guaranteed internal layout and then implementing bytemuck's POD + // trait so that one can preform a bitwise cast. There are issues with + // this approach though as C's struct layouts aren't always compatible + // with WGSL, such as when special WGSL types like vec's and mat's + // get involved that have special alignment rules and especially + // when the target buffer is going to be used in the uniform memory + // space. + // + // Here though, we use the encase crate which makes translating potentially + // complex Rust structs easy through combined use of the [`ShaderType`] trait + // / derive macro and the buffer structs which hold data formatted for WGSL + // in either the storage or uniform spaces. + fn as_wgsl_bytes(&self) -> encase::internal::Result> { + let mut buffer = encase::UniformBuffer::new(Vec::new()); + buffer.write(self)?; + Ok(buffer.into_inner()) + } + + fn translate_view(&mut self, increments: i32, axis: usize) { + self.cursor_pos[axis] += CAMERA_POS_INCREMENT_FACTOR * increments as f32 / self.zoom; + } + + fn zoom(&mut self, amount: f32) { + self.zoom += ZOOM_INCREMENT_FACTOR * amount * self.zoom.powf(1.02); + self.zoom = self.zoom.max(1.1); + } +} + +impl Default for AppState { + fn default() -> Self { + AppState { + cursor_pos: glam::Vec2::ZERO, + zoom: 1.0, + max_iterations: 50, + } + } +} + +struct WgpuContext { + pub window: Arc, + pub surface: wgpu::Surface<'static>, + pub surface_config: wgpu::SurfaceConfiguration, + pub device: wgpu::Device, + pub queue: wgpu::Queue, + pub pipeline: wgpu::RenderPipeline, + pub bind_group: wgpu::BindGroup, + pub uniform_buffer: wgpu::Buffer, +} + +impl WgpuContext { + async fn new(window: Arc) -> WgpuContext { + let size = window.inner_size(); + + let instance = wgpu::Instance::default(); + let surface = instance.create_surface(window.clone()).unwrap(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_defaults(), + }, + None, + ) + .await + .unwrap(); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!( + "shader.wgsl" + ))), + }); + + // (2) + let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + // (3) + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &uniform_buffer, + offset: 0, + size: None, + }), + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + // (4) + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let swapchain_capabilities = surface.get_capabilities(&adapter); + let swapchain_format = swapchain_capabilities.formats[0]; + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(swapchain_format.into())], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: swapchain_capabilities.alpha_modes[0], + view_formats: vec![], + }; + surface.configure(&device, &surface_config); + + // (5) + WgpuContext { + window, + surface, + surface_config, + device, + queue, + pipeline, + bind_group, + uniform_buffer, + } + } + + fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + self.surface_config.width = new_size.width; + self.surface_config.height = new_size.height; + self.surface.configure(&self.device, &self.surface_config); + self.window.request_redraw(); + } +} + +async fn run(event_loop: EventLoop<()>, window: Arc) { + let mut wgpu_context = Some(WgpuContext::new(window).await); + // (6) + let mut state = Some(AppState::default()); + let main_window_id = wgpu_context.as_ref().unwrap().window.id(); + event_loop + .run(move |event, target| { + match event { + Event::LoopExiting => { + wgpu_context = None; + state = None; + } + Event::WindowEvent { window_id, event } if window_id == main_window_id => { + match event { + WindowEvent::CloseRequested => { + target.exit(); + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key, text, .. + }, + .. + } => { + let state_mut = state.as_mut().unwrap(); + let wgpu_context_ref = wgpu_context.as_ref().unwrap(); + + if let Key::Named(key) = logical_key { + match key { + NamedKey::Escape => target.exit(), + NamedKey::ArrowUp => state_mut.translate_view(1, 1), + NamedKey::ArrowDown => state_mut.translate_view(-1, 1), + NamedKey::ArrowLeft => state_mut.translate_view(-1, 0), + NamedKey::ArrowRight => state_mut.translate_view(1, 0), + _ => {} + } + } + + if let Some(text) = text { + if text == "u" { + state_mut.max_iterations += 3; + } else if text == "d" { + state_mut.max_iterations -= 3; + } + }; + + wgpu_context_ref.window.request_redraw(); + } + WindowEvent::MouseWheel { delta, .. } => { + let change = match delta { + winit::event::MouseScrollDelta::LineDelta(_, vertical) => vertical, + winit::event::MouseScrollDelta::PixelDelta(pos) => { + pos.y as f32 / 20.0 + } + }; + let state_mut = state.as_mut().unwrap(); + let wgpu_context_ref = wgpu_context.as_ref().unwrap(); + // (7b) + state_mut.zoom(change); + wgpu_context_ref.window.request_redraw(); + } + WindowEvent::Resized(new_size) => { + let wgpu_context_mut = wgpu_context.as_mut().unwrap(); + wgpu_context_mut.resize(new_size); + wgpu_context_mut.window.request_redraw(); + } + WindowEvent::RedrawRequested => { + let wgpu_context_ref = wgpu_context.as_ref().unwrap(); + let state_ref = state.as_ref().unwrap(); + let frame = wgpu_context_ref.surface.get_current_texture().unwrap(); + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + // (8) + wgpu_context_ref.queue.write_buffer( + &wgpu_context_ref.uniform_buffer, + 0, + &state_ref.as_wgsl_bytes().expect( + "Error in encase translating AppState \ + struct to WGSL bytes.", + ), + ); + let mut encoder = wgpu_context_ref.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { label: None }, + ); + { + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: wgpu::StoreOp::Store, + }, + }, + )], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + render_pass.set_pipeline(&wgpu_context_ref.pipeline); + // (9) + render_pass.set_bind_group(0, &wgpu_context_ref.bind_group, &[]); + render_pass.draw(0..3, 0..1); + } + wgpu_context_ref.queue.submit(Some(encoder.finish())); + frame.present(); + } + _ => {} + } + } + _ => {} + } + }) + .unwrap(); +} + +pub fn main() { + let event_loop = EventLoop::new().unwrap(); + #[allow(unused_mut)] + let mut builder = winit::window::WindowBuilder::new() + .with_title("Remember: Use U/D to change sample count!") + .with_inner_size(winit::dpi::LogicalSize::new(900, 900)); + + #[cfg(target_arch = "wasm32")] + { + use wasm_bindgen::JsCast; + use winit::platform::web::WindowBuilderExtWebSys; + let canvas = web_sys::window() + .unwrap() + .document() + .unwrap() + .get_element_by_id("canvas") + .unwrap() + .dyn_into::() + .unwrap(); + builder = builder.with_canvas(Some(canvas)); + } + let window = builder.build(&event_loop).unwrap(); + + let window = Arc::new(window); + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::builder().format_timestamp_nanos().init(); + pollster::block_on(run(event_loop, window)); + } + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init().expect("could not initialize logger"); + + let document = web_sys::window() + .and_then(|win| win.document()) + .expect("Failed to get document."); + let body = document.body().unwrap(); + let controls_text = document + .create_element("p") + .expect("Failed to create controls text as element."); + controls_text.set_inner_html( + "Controls:
+Up, Down, Left, Right: Move view,
+Scroll: Zoom,
+U, D: Increase / decrease sample count.", + ); + body.append_child(&controls_text) + .expect("Failed to append controls text to body."); + + wasm_bindgen_futures::spawn_local(run(event_loop, window)); + } +} diff --git a/examples/src/uniform_values/screenshot1.png b/examples/src/uniform_values/screenshot1.png new file mode 100644 index 0000000000..33205a7d6d Binary files /dev/null and b/examples/src/uniform_values/screenshot1.png differ diff --git a/examples/src/uniform_values/screenshot2.png b/examples/src/uniform_values/screenshot2.png new file mode 100644 index 0000000000..3ccb8d6a13 Binary files /dev/null and b/examples/src/uniform_values/screenshot2.png differ diff --git a/examples/src/uniform_values/screenshot3.png b/examples/src/uniform_values/screenshot3.png new file mode 100644 index 0000000000..a8f78a5699 Binary files /dev/null and b/examples/src/uniform_values/screenshot3.png differ diff --git a/examples/src/uniform_values/shader.wgsl b/examples/src/uniform_values/shader.wgsl new file mode 100644 index 0000000000..1fdbbc44d6 --- /dev/null +++ b/examples/src/uniform_values/shader.wgsl @@ -0,0 +1,61 @@ +// Some credit to https://github.com/paulgb/wgsl-playground/tree/main. + +// We use seperate the x and y instead of using a vec2 to avoid wgsl padding. +struct AppState { + pos_x: f32, + pos_y: f32, + zoom: f32, + max_iterations: u32, +} + +struct VertexInput { + @builtin(vertex_index) vertex_index: u32, +}; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) coord: vec2, +}; + +@group(0) +@binding(0) +var app_state: AppState; + +@vertex +fn vs_main(in: VertexInput) -> VertexOutput { + var vertices = array, 3>( + vec2(-1., 1.), + vec2(3.0, 1.), + vec2(-1., -3.0), + ); + var out: VertexOutput; + out.coord = vertices[in.vertex_index]; + out.position = vec4(out.coord, 0.0, 1.0); + + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let max_iterations = app_state.max_iterations; + var final_iteration = max_iterations; + let c = vec2( + // Translated to put everything nicely in frame. + (in.coord.x) * 3.0 / app_state.zoom + app_state.pos_x, + (in.coord.y) * 3.0 / app_state.zoom + app_state.pos_y + ); + var current_z = c; + var next_z: vec2; + for (var i = 0u; i < max_iterations; i++) { + next_z.x = (current_z.x * current_z.x - current_z.y * current_z.y) + c.x; + next_z.y = (2.0 * current_z.x * current_z.y) + c.y; + current_z = next_z; + if length(current_z) > 4.0 { + final_iteration = i; + break; + } + } + let value = f32(final_iteration) / f32(max_iterations); + + return vec4(value, value, value, 1.0); +} \ No newline at end of file diff --git a/examples/src/utils.rs b/examples/src/utils.rs new file mode 100644 index 0000000000..7b663e2bc3 --- /dev/null +++ b/examples/src/utils.rs @@ -0,0 +1,158 @@ +#[cfg(not(target_arch = "wasm32"))] +use std::io::Write; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +/// Replaces the site body with a message telling the user to open the console and use that. +#[cfg(target_arch = "wasm32")] +pub fn add_web_nothing_to_see_msg() { + web_sys::window() + .and_then(|window| window.document()) + .and_then(|document| document.body()) + .expect("Could not get document / body.") + .set_inner_html("

Nothing to see here! Open the console!

"); +} + +/// Outputs a vector of RGBA bytes as a png image with the given dimensions on the given path. +#[cfg(not(target_arch = "wasm32"))] +pub fn output_image_native(image_data: Vec, texture_dims: (usize, usize), path: String) { + let mut png_data = Vec::::with_capacity(image_data.len()); + let mut encoder = png::Encoder::new( + std::io::Cursor::new(&mut png_data), + texture_dims.0 as u32, + texture_dims.1 as u32, + ); + encoder.set_color(png::ColorType::Rgba); + let mut png_writer = encoder.write_header().unwrap(); + png_writer.write_image_data(&image_data[..]).unwrap(); + png_writer.finish().unwrap(); + log::info!("PNG file encoded in memory."); + + let mut file = std::fs::File::create(&path).unwrap(); + file.write_all(&png_data[..]).unwrap(); + log::info!("PNG file written to disc as \"{}\".", path); +} + +/// Effectively a version of `output_image_native` but meant for web browser contexts. +/// +/// This is achieved via in `img` element on the page. If the target image element does +/// not exist, this function creates one. If it does, the image data is overridden. +/// +/// This function makes use of a hidden staging canvas which the data is copied to in +/// order to create a data URL. +#[cfg(target_arch = "wasm32")] +pub fn output_image_wasm(image_data: Vec, texture_dims: (usize, usize)) { + let document = web_sys::window().unwrap().document().unwrap(); + let body = document.body().unwrap(); + + let canvas = if let Some(found_canvas) = document.get_element_by_id("staging-canvas") { + match found_canvas.dyn_into::() { + Ok(canvas_as_canvas) => canvas_as_canvas, + Err(e) => { + log::error!( + "In searching for a staging canvas for outputting an image \ + (element with id \"staging-canvas\"), found non-canvas element: {:?}. + Replacing with standard staging canvas.", + e + ); + e.remove(); + create_staging_canvas(&document) + } + } + } else { + log::info!("Output image staging canvas element not found; creating."); + create_staging_canvas(&document) + }; + // Having the size attributes the right size is so important, we should always do it + // just to be safe. Also, what if we might want the image size to be able to change? + let image_dimension_strings = (texture_dims.0.to_string(), texture_dims.1.to_string()); + canvas + .set_attribute("width", image_dimension_strings.0.as_str()) + .unwrap(); + canvas + .set_attribute("height", image_dimension_strings.1.as_str()) + .unwrap(); + + let context = canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap(); + let image_data = web_sys::ImageData::new_with_u8_clamped_array( + wasm_bindgen::Clamped(&image_data), + texture_dims.0 as u32, + ) + .unwrap(); + context.put_image_data(&image_data, 0.0, 0.0).unwrap(); + + // Get the img element that will act as our target for rendering from the canvas. + let image_element = if let Some(found_image_element) = + document.get_element_by_id("output-image-target") + { + match found_image_element.dyn_into::() { + Ok(e) => e, + Err(e) => { + log::error!( + "Found an element with the id \"output-image-target\" but it was not an image: {:?}. + Replacing with default image output element.", + e + ); + e.remove(); + create_output_image_element(&document) + } + } + } else { + log::info!("Output image element not found; creating."); + create_output_image_element(&document) + }; + // The canvas is currently the image we ultimately want. We can create a data url from it now. + let data_url = canvas.to_data_url().unwrap(); + image_element.set_src(&data_url); + log::info!("Copied image from staging canvas to image element."); + + if document.get_element_by_id("image-for-you-text").is_none() { + log::info!("\"Image for you\" text not found; creating."); + let p = document + .create_element("p") + .expect("Failed to create p element for \"image for you text\"."); + p.set_text_content(Some( + "The above image is for you! + You can drag it to your desktop to download.", + )); + p.set_id("image-for-you-text"); + body.append_child(&p) + .expect("Failed to append \"image for you text\" to document body."); + } +} + +#[cfg(target_arch = "wasm32")] +fn create_staging_canvas(document: &web_sys::Document) -> web_sys::HtmlCanvasElement { + let body = document.body().expect("Failed to get document body."); + let new_canvas = document + .create_element("canvas") + .expect("Failed to create staging canvas.") + .dyn_into::() + .unwrap(); + // We don't want to show the canvas, we just want it to exist in the background. + new_canvas.set_attribute("hidden", "true").unwrap(); + new_canvas.set_attribute("background-color", "red").unwrap(); + body.append_child(&new_canvas).unwrap(); + log::info!("Created new staging canvas: {:?}", &new_canvas); + new_canvas +} + +#[cfg(target_arch = "wasm32")] +fn create_output_image_element(document: &web_sys::Document) -> web_sys::HtmlImageElement { + let body = document.body().expect("Failed to get document body."); + let new_image = document + .create_element("img") + .expect("Failed to create output image element.") + .dyn_into::() + .unwrap(); + new_image.set_id("output-image-target"); + body.append_child(&new_image) + .expect("Failed to append output image target to document body."); + log::info!("Created new output target image: {:?}", &new_image); + new_image +} diff --git a/examples/water/README.md b/examples/src/water/README.md similarity index 95% rename from examples/water/README.md rename to examples/src/water/README.md index 79934430a8..9ad0501f27 100644 --- a/examples/water/README.md +++ b/examples/src/water/README.md @@ -19,7 +19,7 @@ water ## To run ``` -cargo run --bin water +cargo run --bin wgpu-examples water ``` ## Screenshot diff --git a/examples/water/src/main.rs b/examples/src/water/mod.rs similarity index 96% rename from examples/water/src/main.rs rename to examples/src/water/mod.rs index 5d5daa1f59..e1a80b1e4f 100644 --- a/examples/water/src/main.rs +++ b/examples/src/water/mod.rs @@ -265,7 +265,7 @@ impl Example { } } -impl wgpu_example::framework::Example for Example { +impl crate::framework::Example for Example { fn init( config: &wgpu::SurfaceConfiguration, _adapter: &wgpu::Adapter, @@ -691,13 +691,7 @@ impl wgpu_example::framework::Example for Example { } #[allow(clippy::eq_op)] - fn render( - &mut self, - view: &wgpu::TextureView, - device: &wgpu::Device, - queue: &wgpu::Queue, - _spawner: &wgpu_example::framework::Spawner, - ) { + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { // Increment frame count regardless of if we draw. self.current_frame += 1; let back_color = wgpu::Color { @@ -739,7 +733,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(back_color), - store: true, + store: wgpu::StoreOp::Store, }, })], // We still need to use the depth buffer here @@ -748,7 +742,7 @@ impl wgpu_example::framework::Example for Example { view: &self.depth_buffer, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: true, + store: wgpu::StoreOp::Store, }), stencil_ops: None, }), @@ -768,14 +762,14 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(back_color), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.depth_buffer, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: true, + store: wgpu::StoreOp::Store, }), stencil_ops: None, }), @@ -797,7 +791,7 @@ impl wgpu_example::framework::Example for Example { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { @@ -819,22 +813,20 @@ impl wgpu_example::framework::Example for Example { } } -fn main() { - wgpu_example::framework::run::("water"); +pub fn main() { + crate::framework::run::("water"); } -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - -#[test] -#[wasm_bindgen_test::wasm_bindgen_test] -fn water() { - wgpu_example::framework::test::(wgpu_example::framework::FrameworkRefTest { - image_path: "/examples/water/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default() - .downlevel_flags(wgpu::DownlevelFlags::READ_ONLY_DEPTH_STENCIL), - comparisons: &[wgpu_test::ComparisonType::Mean(0.01)], - }); -} +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "water", + image_path: "/examples/src/water/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default() + .downlevel_flags(wgpu::DownlevelFlags::READ_ONLY_DEPTH_STENCIL), + comparisons: &[wgpu_test::ComparisonType::Mean(0.01)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/water/src/point_gen.rs b/examples/src/water/point_gen.rs similarity index 100% rename from examples/water/src/point_gen.rs rename to examples/src/water/point_gen.rs diff --git a/examples/water/screenshot.png b/examples/src/water/screenshot.png similarity index 100% rename from examples/water/screenshot.png rename to examples/src/water/screenshot.png diff --git a/examples/water/src/terrain.wgsl b/examples/src/water/terrain.wgsl similarity index 100% rename from examples/water/src/terrain.wgsl rename to examples/src/water/terrain.wgsl diff --git a/examples/water/src/water.wgsl b/examples/src/water/water.wgsl similarity index 100% rename from examples/water/src/water.wgsl rename to examples/src/water/water.wgsl diff --git a/examples/static/index.html b/examples/static/index.html new file mode 100644 index 0000000000..135c04d810 --- /dev/null +++ b/examples/static/index.html @@ -0,0 +1,189 @@ + + + + + + + + + +
+ + + + +
+ + + + \ No newline at end of file diff --git a/examples/stencil-triangles/Cargo.toml b/examples/stencil-triangles/Cargo.toml deleted file mode 100644 index cd8e42676c..0000000000 --- a/examples/stencil-triangles/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "wgpu-stencil-triangle-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu stencil triangles example" -publish = false - -[[bin]] -name = "stencil-triangles" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/texture-arrays/Cargo.toml b/examples/texture-arrays/Cargo.toml deleted file mode 100644 index af30e93054..0000000000 --- a/examples/texture-arrays/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "wgpu-texture-arrays-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu texture arrays example" -publish = false - -[[bin]] -name = "texture-arrays" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/examples/timestamp-queries/Cargo.toml b/examples/timestamp-queries/Cargo.toml deleted file mode 100644 index f2d7de3f1e..0000000000 --- a/examples/timestamp-queries/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "wgpu-timestamp-queries-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu timestamp query example" -publish = false - -[[bin]] -name = "timestamp-queries" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -env_logger.workspace = true -futures-intrusive.workspace = true -pollster.workspace = true -wgpu.workspace = true -winit.workspace = true - -[target.'cfg(target_arch = "wasm32")'.dependencies] -console_error_panic_hook.workspace = true -console_log.workspace = true -log.workspace = true -wasm-bindgen-futures.workspace = true - -[dev-dependencies] -wasm-bindgen-test.workspace = true -wgpu-test.workspace = true - diff --git a/examples/water/Cargo.toml b/examples/water/Cargo.toml deleted file mode 100644 index 6310f83f8f..0000000000 --- a/examples/water/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "wgpu-water-example" -version.workspace = true -license.workspace = true -edition.workspace = true -description = "wgpu water example" -publish = false - -[[bin]] -name = "water" -path = "src/main.rs" - -[dependencies] -bytemuck.workspace = true -nanorand.workspace = true -glam.workspace = true -noise.workspace = true -wasm-bindgen-test.workspace = true -wgpu-example.workspace = true -wgpu.workspace = true -winit.workspace = true - -[dev-dependencies] -wgpu-test.workspace = true diff --git a/logo.png b/logo.png index 63f222a432..1b18a0b111 100644 Binary files a/logo.png and b/logo.png differ diff --git a/naga-cli/Cargo.toml b/naga-cli/Cargo.toml new file mode 100644 index 0000000000..7b8c3024f1 --- /dev/null +++ b/naga-cli/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "naga-cli" +version = "0.14.0" +authors = ["gfx-rs developers"] +edition = "2021" +description = "Shader translation command line tool" +repository = "https://github.com/gfx-rs/wgpu/tree/trunk/naga-cli" +keywords = ["shader", "SPIR-V", "GLSL", "MSL"] +license = "MIT OR Apache-2.0" + +[[bin]] +name = "naga" +path = "src/bin/naga.rs" +# This _must_ be false, as this conflicts with `naga`'s docs. +# +# See https://github.com/gfx-rs/wgpu/issues/4997 +doc = false +test = false + +[dependencies] +bincode = "1" +log = "0.4" +codespan-reporting = "0.11" +env_logger = "0.10" +argh = "0.1.5" + +[dependencies.naga] +version = "0.14" +path = "../naga" +features = [ + "compact", + "wgsl-in", + "wgsl-out", + "glsl-in", + "glsl-out", + "spv-in", + "spv-out", + "msl-out", + "hlsl-out", + "dot-out", + "serialize", + "deserialize", +] diff --git a/naga-cli/src/bin/naga.rs b/naga-cli/src/bin/naga.rs new file mode 100644 index 0000000000..8960866b34 --- /dev/null +++ b/naga-cli/src/bin/naga.rs @@ -0,0 +1,753 @@ +#![allow(clippy::manual_strip)] +#[allow(unused_imports)] +use std::fs; +use std::{error::Error, fmt, io::Read, path::Path, str::FromStr}; + +/// Translate shaders to different formats. +#[derive(argh::FromArgs, Debug, Clone)] +struct Args { + /// bitmask of the ValidationFlags to be used, use 0 to disable validation + #[argh(option)] + validate: Option, + + /// what policy to use for index bounds checking for arrays, vectors, and + /// matrices. + /// + /// May be `Restrict` (force all indices in-bounds), `ReadZeroSkipWrite` + /// (out-of-bounds indices read zeros, and don't write at all), or + /// `Unchecked` (generate the simplest code, and whatever happens, happens) + /// + /// `Unchecked` is the default. + #[argh(option)] + index_bounds_check_policy: Option, + + /// what policy to use for index bounds checking for arrays, vectors, and + /// matrices, when they are stored in globals in the `storage` or `uniform` + /// storage classes. + /// + /// Possible values are the same as for `index-bounds-check-policy`. If + /// omitted, defaults to the index bounds check policy. + #[argh(option)] + buffer_bounds_check_policy: Option, + + /// what policy to use for texture loads bounds checking. + /// + /// Possible values are the same as for `index-bounds-check-policy`. If + /// omitted, defaults to the index bounds check policy. + #[argh(option)] + image_load_bounds_check_policy: Option, + + /// what policy to use for texture stores bounds checking. + /// + /// Possible values are the same as for `index-bounds-check-policy`. If + /// omitted, defaults to the index bounds check policy. + #[argh(option)] + image_store_bounds_check_policy: Option, + + /// directory to dump the SPIR-V block context dump to + #[argh(option)] + block_ctx_dir: Option, + + /// the shader entrypoint to use when compiling to GLSL + #[argh(option)] + entry_point: Option, + + /// the shader profile to use, for example `es`, `core`, `es330`, if translating to GLSL + #[argh(option)] + profile: Option, + + /// the shader model to use if targeting HLSL + /// + /// May be `50`, 51`, or `60` + #[argh(option)] + shader_model: Option, + + /// if the selected frontends/backends support coordinate space conversions, + /// disable them + #[argh(switch)] + keep_coordinate_space: bool, + + /// in dot output, include only the control flow graph + #[argh(switch)] + dot_cfg_only: bool, + + /// specify file path to process STDIN as + #[argh(option)] + stdin_file_path: Option, + + /// generate debug symbols, only works for spv-out for now + #[argh(switch, short = 'g')] + generate_debug_symbols: bool, + + /// compact the module's IR and revalidate. + /// + /// Output files will reflect the compacted IR. If you want to see the IR as + /// it was before compaction, use the `--before-compaction` option. + #[argh(switch)] + compact: bool, + + /// write the module's IR before compaction to the given file. + /// + /// This implies `--compact`. Like any other output file, the filename + /// extension determines the form in which the module is written. + #[argh(option)] + before_compaction: Option, + + /// bulk validation mode: all filenames are inputs to read and validate. + #[argh(switch)] + bulk_validate: bool, + + /// show version + #[argh(switch)] + version: bool, + + /// the input and output files. + /// + /// First positional argument is the input file. If not specified, the + /// input will be read from stdin. In the case, --stdin-file-path must also + /// be specified. + /// + /// The rest arguments are the output files. If not specified, only + /// validation will be performed. + /// + /// In bulk validation mode, these are all input files to be validated. + #[argh(positional)] + files: Vec, +} + +/// Newtype so we can implement [`FromStr`] for `BoundsCheckPolicy`. +#[derive(Debug, Clone, Copy)] +struct BoundsCheckPolicyArg(naga::proc::BoundsCheckPolicy); + +impl FromStr for BoundsCheckPolicyArg { + type Err = String; + + fn from_str(s: &str) -> Result { + use naga::proc::BoundsCheckPolicy; + Ok(Self(match s.to_lowercase().as_str() { + "restrict" => BoundsCheckPolicy::Restrict, + "readzeroskipwrite" => BoundsCheckPolicy::ReadZeroSkipWrite, + "unchecked" => BoundsCheckPolicy::Unchecked, + _ => { + return Err(format!( + "Invalid value for --index-bounds-check-policy: {s}" + )) + } + })) + } +} + +/// Newtype so we can implement [`FromStr`] for `ShaderModel`. +#[derive(Debug, Clone)] +struct ShaderModelArg(naga::back::hlsl::ShaderModel); + +impl FromStr for ShaderModelArg { + type Err = String; + + fn from_str(s: &str) -> Result { + use naga::back::hlsl::ShaderModel; + Ok(Self(match s.to_lowercase().as_str() { + "50" => ShaderModel::V5_0, + "51" => ShaderModel::V5_1, + "60" => ShaderModel::V6_0, + _ => return Err(format!("Invalid value for --shader-model: {s}")), + })) + } +} + +/// Newtype so we can implement [`FromStr`] for [`naga::back::glsl::Version`]. +#[derive(Clone, Debug)] +struct GlslProfileArg(naga::back::glsl::Version); + +impl FromStr for GlslProfileArg { + type Err = String; + + fn from_str(s: &str) -> Result { + use naga::back::glsl::Version; + Ok(Self(if s.starts_with("core") { + Version::Desktop(s[4..].parse().unwrap_or(330)) + } else if s.starts_with("es") { + Version::new_gles(s[2..].parse().unwrap_or(310)) + } else { + return Err(format!("Unknown profile: {s}")); + })) + } +} + +#[derive(Default)] +struct Parameters<'a> { + validation_flags: naga::valid::ValidationFlags, + bounds_check_policies: naga::proc::BoundsCheckPolicies, + entry_point: Option, + keep_coordinate_space: bool, + spv_in: naga::front::spv::Options, + spv_out: naga::back::spv::Options<'a>, + dot: naga::back::dot::Options, + msl: naga::back::msl::Options, + glsl: naga::back::glsl::Options, + hlsl: naga::back::hlsl::Options, +} + +trait PrettyResult { + type Target; + fn unwrap_pretty(self) -> Self::Target; +} + +fn print_err(error: &dyn Error) { + eprint!("{error}"); + + let mut e = error.source(); + if e.is_some() { + eprintln!(": "); + } else { + eprintln!(); + } + + while let Some(source) = e { + eprintln!("\t{source}"); + e = source.source(); + } +} + +impl PrettyResult for Result { + type Target = T; + fn unwrap_pretty(self) -> T { + match self { + Result::Ok(value) => value, + Result::Err(error) => { + print_err(&error); + std::process::exit(1); + } + } + } +} + +fn main() { + if let Err(e) = run() { + print_err(e.as_ref()); + std::process::exit(1); + } +} + +/// Error type for the CLI +#[derive(Debug, Clone)] +struct CliError(&'static str); +impl fmt::Display for CliError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} +impl std::error::Error for CliError {} + +fn run() -> Result<(), Box> { + env_logger::init(); + + // Parse commandline arguments + let args: Args = argh::from_env(); + if args.version { + println!("{}", env!("CARGO_PKG_VERSION")); + return Ok(()); + } + + // Initialize default parameters + //TODO: read the parameters from RON? + let mut params = Parameters::default(); + + // Update parameters from commandline arguments + if let Some(bits) = args.validate { + params.validation_flags = naga::valid::ValidationFlags::from_bits(bits) + .ok_or(CliError("Invalid validation flags"))?; + } + if let Some(policy) = args.index_bounds_check_policy { + params.bounds_check_policies.index = policy.0; + } + params.bounds_check_policies.buffer = match args.buffer_bounds_check_policy { + Some(arg) => arg.0, + None => params.bounds_check_policies.index, + }; + params.bounds_check_policies.image_load = match args.image_load_bounds_check_policy { + Some(arg) => arg.0, + None => params.bounds_check_policies.index, + }; + params.bounds_check_policies.image_store = match args.image_store_bounds_check_policy { + Some(arg) => arg.0, + None => params.bounds_check_policies.index, + }; + + params.spv_in = naga::front::spv::Options { + adjust_coordinate_space: !args.keep_coordinate_space, + strict_capabilities: false, + block_ctx_dump_prefix: args.block_ctx_dir.clone().map(std::path::PathBuf::from), + }; + + params.entry_point = args.entry_point.clone(); + if let Some(ref version) = args.profile { + params.glsl.version = version.0; + } + if let Some(ref model) = args.shader_model { + params.hlsl.shader_model = model.0; + } + params.keep_coordinate_space = args.keep_coordinate_space; + + params.dot.cfg_only = args.dot_cfg_only; + + params.spv_out.bounds_check_policies = params.bounds_check_policies; + params.spv_out.flags.set( + naga::back::spv::WriterFlags::ADJUST_COORDINATE_SPACE, + !params.keep_coordinate_space, + ); + + if args.bulk_validate { + return bulk_validate(args, ¶ms); + } + + let (input_path, input) = if let Some(path) = args.files.first() { + let path = Path::new(path); + (path, fs::read(path)?) + } else if let Some(path) = &args.stdin_file_path { + let mut input = vec![]; + std::io::stdin().lock().read_to_end(&mut input)?; + (Path::new(path), input) + } else { + return Err(CliError("Input file path is not specified").into()); + }; + + let Parsed { + mut module, + input_text, + } = parse_input(input_path, input, ¶ms)?; + + // Include debugging information if requested. + if args.generate_debug_symbols { + if let Some(ref input_text) = input_text { + params + .spv_out + .flags + .set(naga::back::spv::WriterFlags::DEBUG, true); + params.spv_out.debug_info = Some(naga::back::spv::DebugInfo { + source_code: input_text, + file_name: input_path, + }) + } else { + eprintln!( + "warning: `--generate-debug-symbols` was passed, \ + but input is not human-readable: {}", + input_path.display() + ); + } + } + + let output_paths = args.files.get(1..).unwrap_or(&[]); + + // Decide which capabilities our output formats can support. + let validation_caps = + output_paths + .iter() + .fold(naga::valid::Capabilities::all(), |caps, path| { + use naga::valid::Capabilities as C; + let missing = match Path::new(path).extension().and_then(|ex| ex.to_str()) { + Some("wgsl") => C::CLIP_DISTANCE | C::CULL_DISTANCE, + Some("metal") => C::CULL_DISTANCE, + _ => C::empty(), + }; + caps & !missing + }); + + // Validate the IR before compaction. + let info = match naga::valid::Validator::new(params.validation_flags, validation_caps) + .validate(&module) + { + Ok(info) => Some(info), + Err(error) => { + // Validation failure is not fatal. Just report the error. + if let Some(input) = &input_text { + let filename = input_path.file_name().and_then(std::ffi::OsStr::to_str); + emit_annotated_error(&error, filename.unwrap_or("input"), input); + } + print_err(&error); + None + } + }; + + // Compact the module, if requested. + let info = if args.compact || args.before_compaction.is_some() { + // Compact only if validation succeeded. Otherwise, compaction may panic. + if info.is_some() { + // Write out the module state before compaction, if requested. + if let Some(ref before_compaction) = args.before_compaction { + write_output(&module, &info, ¶ms, before_compaction)?; + } + + naga::compact::compact(&mut module); + + // Re-validate the IR after compaction. + match naga::valid::Validator::new(params.validation_flags, validation_caps) + .validate(&module) + { + Ok(info) => Some(info), + Err(error) => { + // Validation failure is not fatal. Just report the error. + eprintln!("Error validating compacted module:"); + if let Some(input) = &input_text { + let filename = input_path.file_name().and_then(std::ffi::OsStr::to_str); + emit_annotated_error(&error, filename.unwrap_or("input"), input); + } + print_err(&error); + None + } + } + } else { + eprintln!("Skipping compaction due to validation failure."); + None + } + } else { + info + }; + + // If no output was requested, then report validation results and stop here. + // + // If the user asked for output, don't stop: some output formats (".txt", + // ".dot", ".bin") can be generated even without a `ModuleInfo`. + if output_paths.is_empty() { + if info.is_some() { + println!("Validation successful"); + return Ok(()); + } else { + std::process::exit(-1); + } + } + + for output_path in output_paths { + write_output(&module, &info, ¶ms, output_path)?; + } + + Ok(()) +} + +struct Parsed { + module: naga::Module, + input_text: Option, +} + +fn parse_input( + input_path: &Path, + input: Vec, + params: &Parameters, +) -> Result> { + let (module, input_text) = match Path::new(&input_path) + .extension() + .ok_or(CliError("Input filename has no extension"))? + .to_str() + .ok_or(CliError("Input filename not valid unicode"))? + { + "bin" => (bincode::deserialize(&input)?, None), + "spv" => naga::front::spv::parse_u8_slice(&input, ¶ms.spv_in).map(|m| (m, None))?, + "wgsl" => { + let input = String::from_utf8(input)?; + let result = naga::front::wgsl::parse_str(&input); + match result { + Ok(v) => (v, Some(input)), + Err(ref e) => { + let message = format!( + "Could not parse WGSL:\n{}", + e.emit_to_string_with_path(&input, input_path) + ); + return Err(message.into()); + } + } + } + ext @ ("vert" | "frag" | "comp" | "glsl") => { + let input = String::from_utf8(input)?; + let mut parser = naga::front::glsl::Frontend::default(); + + ( + parser + .parse( + &naga::front::glsl::Options { + stage: match ext { + "vert" => naga::ShaderStage::Vertex, + "frag" => naga::ShaderStage::Fragment, + "comp" => naga::ShaderStage::Compute, + "glsl" => { + let internal_name = input_path.to_string_lossy(); + match Path::new(&internal_name[..internal_name.len()-5]) + .extension() + .ok_or(CliError("Input filename ending with .glsl has no internal extension"))? + .to_str() + .ok_or(CliError("Input filename not valid unicode"))? + { + "vert" => naga::ShaderStage::Vertex, + "frag" => naga::ShaderStage::Fragment, + "comp" => naga::ShaderStage::Compute, + _ => unreachable!(), + } + }, + _ => unreachable!(), + }, + defines: Default::default(), + }, + &input, + ) + .unwrap_or_else(|errors| { + let filename = input_path.file_name().and_then(std::ffi::OsStr::to_str); + emit_glsl_parser_error(errors, filename.unwrap_or("glsl"), &input); + std::process::exit(1); + }), + Some(input), + ) + } + _ => return Err(CliError("Unknown input file extension").into()), + }; + + Ok(Parsed { module, input_text }) +} + +fn write_output( + module: &naga::Module, + info: &Option, + params: &Parameters, + output_path: &str, +) -> Result<(), Box> { + match Path::new(&output_path) + .extension() + .ok_or(CliError("Output filename has no extension"))? + .to_str() + .ok_or(CliError("Output filename not valid unicode"))? + { + "txt" => { + use std::io::Write; + + let mut file = fs::File::create(output_path)?; + writeln!(file, "{module:#?}")?; + if let Some(ref info) = *info { + writeln!(file)?; + writeln!(file, "{info:#?}")?; + } + } + "bin" => { + let file = fs::File::create(output_path)?; + bincode::serialize_into(file, module)?; + } + "metal" => { + use naga::back::msl; + + let mut options = params.msl.clone(); + options.bounds_check_policies = params.bounds_check_policies; + + let pipeline_options = msl::PipelineOptions::default(); + let (msl, _) = msl::write_string( + module, + info.as_ref().ok_or(CliError( + "Generating metal output requires validation to \ + succeed, and it failed in a previous step", + ))?, + &options, + &pipeline_options, + ) + .unwrap_pretty(); + fs::write(output_path, msl)?; + } + "spv" => { + use naga::back::spv; + + let pipeline_options_owned; + let pipeline_options = match params.entry_point { + Some(ref name) => { + let ep_index = module + .entry_points + .iter() + .position(|ep| ep.name == *name) + .expect("Unable to find the entry point"); + pipeline_options_owned = spv::PipelineOptions { + entry_point: name.clone(), + shader_stage: module.entry_points[ep_index].stage, + }; + Some(&pipeline_options_owned) + } + None => None, + }; + + let spv = spv::write_vec( + module, + info.as_ref().ok_or(CliError( + "Generating SPIR-V output requires validation to \ + succeed, and it failed in a previous step", + ))?, + ¶ms.spv_out, + pipeline_options, + ) + .unwrap_pretty(); + let bytes = spv + .iter() + .fold(Vec::with_capacity(spv.len() * 4), |mut v, w| { + v.extend_from_slice(&w.to_le_bytes()); + v + }); + + fs::write(output_path, bytes.as_slice())?; + } + stage @ ("vert" | "frag" | "comp") => { + use naga::back::glsl; + + let pipeline_options = glsl::PipelineOptions { + entry_point: match params.entry_point { + Some(ref name) => name.clone(), + None => "main".to_string(), + }, + shader_stage: match stage { + "vert" => naga::ShaderStage::Vertex, + "frag" => naga::ShaderStage::Fragment, + "comp" => naga::ShaderStage::Compute, + _ => unreachable!(), + }, + multiview: None, + }; + + let mut buffer = String::new(); + let mut writer = glsl::Writer::new( + &mut buffer, + module, + info.as_ref().ok_or(CliError( + "Generating glsl output requires validation to \ + succeed, and it failed in a previous step", + ))?, + ¶ms.glsl, + &pipeline_options, + params.bounds_check_policies, + ) + .unwrap_pretty(); + writer.write()?; + fs::write(output_path, buffer)?; + } + "dot" => { + use naga::back::dot; + + let output = dot::write(module, info.as_ref(), params.dot.clone())?; + fs::write(output_path, output)?; + } + "hlsl" => { + use naga::back::hlsl; + let mut buffer = String::new(); + let mut writer = hlsl::Writer::new(&mut buffer, ¶ms.hlsl); + writer + .write( + module, + info.as_ref().ok_or(CliError( + "Generating hlsl output requires validation to \ + succeed, and it failed in a previous step", + ))?, + ) + .unwrap_pretty(); + fs::write(output_path, buffer)?; + } + "wgsl" => { + use naga::back::wgsl; + + let wgsl = wgsl::write_string( + module, + info.as_ref().ok_or(CliError( + "Generating wgsl output requires validation to \ + succeed, and it failed in a previous step", + ))?, + wgsl::WriterFlags::empty(), + ) + .unwrap_pretty(); + fs::write(output_path, wgsl)?; + } + other => { + println!("Unknown output extension: {other}"); + } + } + + Ok(()) +} + +fn bulk_validate(args: Args, params: &Parameters) -> Result<(), Box> { + let mut invalid = vec![]; + for input_path in args.files { + let path = Path::new(&input_path); + let input = fs::read(path)?; + + let Parsed { module, input_text } = match parse_input(path, input, params) { + Ok(parsed) => parsed, + Err(error) => { + invalid.push(input_path.clone()); + eprintln!("Error validating {}:", input_path); + eprintln!("{error}"); + continue; + } + }; + + let mut validator = + naga::valid::Validator::new(params.validation_flags, naga::valid::Capabilities::all()); + + if let Err(error) = validator.validate(&module) { + invalid.push(input_path.clone()); + eprintln!("Error validating {}:", input_path); + if let Some(input) = &input_text { + let filename = path.file_name().and_then(std::ffi::OsStr::to_str); + emit_annotated_error(&error, filename.unwrap_or("input"), input); + } + print_err(&error); + } + } + + if !invalid.is_empty() { + use std::fmt::Write; + let mut formatted = String::new(); + writeln!( + &mut formatted, + "Validation failed for the following inputs:" + ) + .unwrap(); + for path in invalid { + writeln!(&mut formatted, " {path}").unwrap(); + } + return Err(formatted.into()); + } + + Ok(()) +} + +use codespan_reporting::{ + diagnostic::{Diagnostic, Label}, + files::SimpleFile, + term::{ + self, + termcolor::{ColorChoice, StandardStream}, + }, +}; +use naga::WithSpan; + +pub fn emit_glsl_parser_error(errors: Vec, filename: &str, source: &str) { + let files = SimpleFile::new(filename, source); + let config = codespan_reporting::term::Config::default(); + let writer = StandardStream::stderr(ColorChoice::Auto); + + for err in errors { + let mut diagnostic = Diagnostic::error().with_message(err.kind.to_string()); + + if let Some(range) = err.meta.to_range() { + diagnostic = diagnostic.with_labels(vec![Label::primary((), range)]); + } + + term::emit(&mut writer.lock(), &config, &files, &diagnostic).expect("cannot write error"); + } +} + +pub fn emit_annotated_error(ann_err: &WithSpan, filename: &str, source: &str) { + let files = SimpleFile::new(filename, source); + let config = codespan_reporting::term::Config::default(); + let writer = StandardStream::stderr(ColorChoice::Auto); + + let diagnostic = Diagnostic::error().with_labels( + ann_err + .spans() + .map(|(span, desc)| { + Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned()) + }) + .collect(), + ); + + term::emit(&mut writer.lock(), &config, &files, &diagnostic).expect("cannot write error"); +} diff --git a/naga/.cargo/config.toml b/naga/.cargo/config.toml new file mode 100644 index 0000000000..4b01400617 --- /dev/null +++ b/naga/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --manifest-path xtask/Cargo.toml --" diff --git a/naga/.gitattributes b/naga/.gitattributes new file mode 100644 index 0000000000..622c348d1d --- /dev/null +++ b/naga/.gitattributes @@ -0,0 +1 @@ +tests/out/**/* text eol=lf diff --git a/naga/.gitignore b/naga/.gitignore new file mode 100644 index 0000000000..08a609ce9a --- /dev/null +++ b/naga/.gitignore @@ -0,0 +1,19 @@ +/target +**/*.rs.bk +Cargo.lock +.DS_Store +.fuse_hidden* +.idea +.vscode +*.swp +/*.dot +/*.metal +/*.metallib +/*.ron +/*.spv +/*.vert +/*.frag +/*.comp +/*.wgsl +/*.hlsl +/*.txt diff --git a/naga/CHANGELOG.md b/naga/CHANGELOG.md new file mode 100644 index 0000000000..d78bdb9163 --- /dev/null +++ b/naga/CHANGELOG.md @@ -0,0 +1,952 @@ +# Change Log + +For changelogs after v0.14, see [the wgpu changelog](../CHANGELOG.md). + +## v0.14 (2023-10-25) + +#### GENERAL + +- Add support for const-expressions. ([#2309](https://github.com/gfx-rs/naga/pull/2309)) **@teoxoy**, **@jimblandy** +- Add support for the `rgb10a2uint` storage format. ([#2525](https://github.com/gfx-rs/naga/pull/2525)) **@teoxoy** +- Implement module compaction for snapshot testing and the CLI. ([#2472](https://github.com/gfx-rs/naga/pull/2472)) **@jimblandy** +- Fix validation and GLSL parsing of `ldexp`. ([#2449](https://github.com/gfx-rs/naga/pull/2449)) **@fornwall** +- Add support for dual source blending. ([#2427](https://github.com/gfx-rs/naga/pull/2427)) **@freqmod** +- Bump `indexmap` to v2. ([#2426](https://github.com/gfx-rs/naga/pull/2426)) **@daxpedda** +- Bump MSRV to 1.65. ([#2420](https://github.com/gfx-rs/naga/pull/2420)) **@jimblandy** + +#### API + +- Split `UnaryOperator::Not` into `UnaryOperator::LogicalNot` & `UnaryOperator::BitwiseNot`. ([#2554](https://github.com/gfx-rs/naga/pull/2554)) **@teoxoy** +- Remove `IsFinite` & `IsNormal` relational functions. ([#2532](https://github.com/gfx-rs/naga/pull/2532)) **@teoxoy** +- Derive `PartialEq` on `Expression`. ([#2417](https://github.com/gfx-rs/naga/pull/2417)) **@robtfm** +- Use `FastIndexMap` for `SpecialTypes::predeclared_types`. ([#2495](https://github.com/gfx-rs/naga/pull/2495)) **@jimblandy** + +#### CLI + +- Change `--generate-debug-symbols` from an `option` to a `switch`. ([#2472](https://github.com/gfx-rs/naga/pull/2472)) **@jimblandy** +- Add support for `.{vert,frag,comp}.glsl` files. ([#2462](https://github.com/gfx-rs/naga/pull/2462)) **@eliemichel** + +#### VALIDATOR + +- Require `Capabilities::FLOAT64` for 64-bit floating-point literals. ([#2567](https://github.com/gfx-rs/naga/pull/2567)) **@jimblandy** +- Add `Capabilities::CUBE_ARRAY_TEXTURES`. ([#2530](https://github.com/gfx-rs/naga/pull/2530)) **@teoxoy** +- Disallow passing pointers to variables in the workgroup address space to functions. ([#2507](https://github.com/gfx-rs/naga/pull/2507)) **@teoxoy** +- Avoid OOM with large sparse resource bindings. ([#2561](https://github.com/gfx-rs/naga/pull/2561)) **@teoxoy** +- Require that `Function` and `Private` variables be `CONSTRUCTIBLE`. ([#2545](https://github.com/gfx-rs/naga/pull/2545)) **@jimblandy** +- Disallow floating-point NaNs and infinities. ([#2508](https://github.com/gfx-rs/naga/pull/2508)) **@teoxoy** +- Temporarily disable uniformity analysis for the fragment stage. ([#2515](https://github.com/gfx-rs/naga/pull/2515)) **@teoxoy** +- Validate that `textureSampleBias` is only used in the fragment stage. ([#2515](https://github.com/gfx-rs/naga/pull/2515)) **@teoxoy** +- Validate variable initializer for address spaces. ([#2513](https://github.com/gfx-rs/naga/pull/2513)) **@teoxoy** +- Prevent using multiple push constant variables in one entry point. ([#2484](https://github.com/gfx-rs/naga/pull/2484)) **@andriyDev** +- Validate `binding_array` variable address space. ([#2422](https://github.com/gfx-rs/naga/pull/2422)) **@teoxoy** +- Validate storage buffer access. ([#2415](https://github.com/gfx-rs/naga/pull/2415)) **@teoxoy** + +#### WGSL-IN + +- Fix expected min arg count of `textureLoad`. ([#2584](https://github.com/gfx-rs/naga/pull/2584)) **@teoxoy** +- Turn `Error::Other` into `Error::Internal`, to help devs. ([#2574](https://github.com/gfx-rs/naga/pull/2574)) **@jimblandy** +- Fix OOB typifier indexing. ([#2570](https://github.com/gfx-rs/naga/pull/2570)) **@teoxoy** +- Add support for the `bgra8unorm` storage format. ([#2542](https://github.com/gfx-rs/naga/pull/2542) & [#2550](https://github.com/gfx-rs/naga/pull/2550)) **@nical** +- Remove the `outerProduct` built-in function. ([#2535](https://github.com/gfx-rs/naga/pull/2535)) **@teoxoy** +- Add support for `i32` overload of the `sign` built-in function. ([#2463](https://github.com/gfx-rs/naga/pull/2463)) **@fornwall** +- Properly implement `modf` and `frexp`. ([#2454](https://github.com/gfx-rs/naga/pull/2454)) **@fornwall** +- Add support for scalar overloads of `all` & `any` built-in functions. ([#2445](https://github.com/gfx-rs/naga/pull/2445)) **@fornwall** +- Don't splat the left hand operand of a binary operation if it's not a scalar. ([#2444](https://github.com/gfx-rs/naga/pull/2444)) **@fornwall** +- Avoid splatting all binary operator expressions. ([#2440](https://github.com/gfx-rs/naga/pull/2440)) **@fornwall** +- Error on repeated or missing `@workgroup_size()`. ([#2435](https://github.com/gfx-rs/naga/pull/2435)) **@fornwall** +- Error on repeated attributes. ([#2428](https://github.com/gfx-rs/naga/pull/2428)) **@fornwall** +- Fix error message for invalid `texture{Load,Store}()` on arrayed textures. ([#2432](https://github.com/gfx-rs/naga/pull/2432)) **@fornwall** + +#### SPV-IN + +- Disable `Modf` & `Frexp` and translate `ModfStruct` & `FrexpStruct` to their IR equivalents. ([#2527](https://github.com/gfx-rs/naga/pull/2527)) **@teoxoy** +- Don't advertise support for `Capability::ImageMSArray` & `Capability::InterpolationFunction`. ([#2529](https://github.com/gfx-rs/naga/pull/2529)) **@teoxoy** +- Fix `OpImageQueries` to allow Uints. ([#2404](https://github.com/gfx-rs/naga/pull/2404)) **@evahop** + +#### GLSL-IN + +- Disable `modf` & `frexp`. ([#2527](https://github.com/gfx-rs/naga/pull/2527)) **@teoxoy** + +#### SPV-OUT + +- Require `ClipDistance` & `CullDistance` capabilities if necessary. ([#2528](https://github.com/gfx-rs/naga/pull/2528)) **@teoxoy** +- Change `naga::back::spv::DebugInfo::file_name` to a `&Path`. ([#2501](https://github.com/gfx-rs/naga/pull/2501)) **@jimblandy** +- Always give structs with runtime arrays a `Block` decoration. ([#2455](https://github.com/gfx-rs/naga/pull/2455)) **@TheoDulka** +- Decorate the result of the `OpLoad` with `NonUniform` (not the access chain) when loading images/samplers (resources in the Handle address space). ([#2422](https://github.com/gfx-rs/naga/pull/2422)) **@teoxoy** +- Cache `OpConstantNull`. ([#2414](https://github.com/gfx-rs/naga/pull/2414)) **@evahop** + +#### MSL-OUT + +- Add and fix minimum Metal version checks for optional functionality. ([#2486](https://github.com/gfx-rs/naga/pull/2486)) **@teoxoy** +- Make varyings' struct members unique. ([#2521](https://github.com/gfx-rs/naga/pull/2521)) **@evahop** + +#### GLSL-OUT + +- Cull functions that should not be available for a given stage. ([#2531](https://github.com/gfx-rs/naga/pull/2531)) **@teoxoy** +- Rename identifiers containing double underscores. ([#2510](https://github.com/gfx-rs/naga/pull/2510)) **@evahop** +- Polyfill `frexp`. ([#2504](https://github.com/gfx-rs/naga/pull/2504)) **@evahop** +- Add built-in functions to keywords. ([#2410](https://github.com/gfx-rs/naga/pull/2410)) **@fornwall** + +#### WGSL-OUT + +- Generate correct code for bit complement on integers. ([#2548](https://github.com/gfx-rs/naga/pull/2548)) **@jimblandy** +- Don't include type parameter in splat expressions. ([#2469](https://github.com/gfx-rs/naga/pull/2469)) **@jimblandy** + +## v0.13 (2023-07-21) + +#### GENERAL + +- Move from `make` to `cargo xtask` workflows. ([#2297](https://github.com/gfx-rs/naga/pull/2297)) **@ErichDonGubler** +- Omit non referenced expressions from output. ([#2378](https://github.com/gfx-rs/naga/pull/2378)) **@teoxoy** +- Bump `bitflags` to v2. ([#2358](https://github.com/gfx-rs/naga/pull/2358)) **@daxpedda** +- Implement `workgroupUniformLoad`. ([#2201](https://github.com/gfx-rs/naga/pull/2201)) **@DJMcNab** + +#### API + +- Expose early depth test field. ([#2393](https://github.com/gfx-rs/naga/pull/2393)) **@Joeoc2001** +- Split image bounds check policy. ([#2265](https://github.com/gfx-rs/naga/pull/2265)) **@teoxoy** +- Change type of constant sized arrays to `NonZeroU32`. ([#2337](https://github.com/gfx-rs/naga/pull/2337)) **@teoxoy** +- Introduce `GlobalCtx`. ([#2335](https://github.com/gfx-rs/naga/pull/2335)) **@teoxoy** +- Introduce `Expression::Literal`. ([#2333](https://github.com/gfx-rs/naga/pull/2333)) **@teoxoy** +- Introduce `Expression::ZeroValue`. ([#2332](https://github.com/gfx-rs/naga/pull/2332)) **@teoxoy** +- Add support for const-expressions (only at the API level, functionality is still WIP). ([#2266](https://github.com/gfx-rs/naga/pull/2266)) **@teoxoy**, **@jimblandy** + +#### DOCS + +- Document which expressions are in scope for a `break_if` expression. ([#2326](https://github.com/gfx-rs/naga/pull/2326)) **@jimblandy** + +#### VALIDATOR + +- Don't `use std::opsIndex`, used only when `"validate"` is on. ([#2383](https://github.com/gfx-rs/naga/pull/2383)) **@jimblandy** +- Remove unneeded `ConstantError::Unresolved{Component,Size}`. ([#2330](https://github.com/gfx-rs/naga/pull/2330)) **@ErichDonGubler** +- Remove `TypeError::UnresolvedBase`. ([#2308](https://github.com/gfx-rs/naga/pull/2308)) **@ErichDonGubler** + +#### WGSL-IN + +- Error on param redefinition. ([#2342](https://github.com/gfx-rs/naga/pull/2342)) **@SparkyPotato** + +#### SPV-IN + +- Improve documentation for SPIR-V control flow parsing. ([#2324](https://github.com/gfx-rs/naga/pull/2324)) **@jimblandy** +- Obey the `is_depth` field of `OpTypeImage`. ([#2341](https://github.com/gfx-rs/naga/pull/2341)) **@expenses** +- Convert conditional backedges to `break if`. ([#2290](https://github.com/gfx-rs/naga/pull/2290)) **@eddyb** + +#### GLSL-IN + +- Support commas in structure definitions. ([#2400](https://github.com/gfx-rs/naga/pull/2400)) **@fornwall** + +#### SPV-OUT + +- Add debug info. ([#2379](https://github.com/gfx-rs/naga/pull/2379)) **@wicast** +- Use `IndexSet` instead of `HashSet` for iterated sets (capabilities/extensions). ([#2389](https://github.com/gfx-rs/naga/pull/2389)) **@eddyb** +- Support array bindings of buffers. ([#2282](https://github.com/gfx-rs/naga/pull/2282)) **@kvark** + +#### MSL-OUT + +- Rename `allow_point_size` to `allow_and_force_point_size`. ([#2280](https://github.com/gfx-rs/naga/pull/2280)) **@teoxoy** +- Initialize arrays inline. ([#2331](https://github.com/gfx-rs/naga/pull/2331)) **@teoxoy** + +#### HLSL-OUT + +- Implement Pack/Unpack for HLSL. ([#2353](https://github.com/gfx-rs/naga/pull/2353)) **@Elabajaba** +- Complete HLSL reserved symbols. ([#2367](https://github.com/gfx-rs/naga/pull/2367)) **@teoxoy** +- Handle case insensitive FXC keywords. ([#2347](https://github.com/gfx-rs/naga/pull/2347)) **@PJB3005** +- Fix return type for firstbitlow/high. ([#2315](https://github.com/gfx-rs/naga/pull/2315)) **@evahop** + +#### GLSL-OUT + +- `textureSize` level must be a signed integer. ([#2397](https://github.com/gfx-rs/naga/pull/2397)) **@nical** +- Fix functions with array return type. ([#2382](https://github.com/gfx-rs/naga/pull/2382)) **@Gordon-F** + +#### WGSL-OUT + +- Output `@interpolate(flat)` attribute for integer locations. ([#2318](https://github.com/gfx-rs/naga/pull/2318)) **@expenses** + +## v0.12.3 (2023-07-09) + +#### WGSL-OUT + +- (Backport) Output `@interpolate(flat)` attribute for integer locations. ([#2318](https://github.com/gfx-rs/naga/pull/2318)) **@expenses** + +## v0.12.2 (2023-05-30) + +#### SPV-OUT + +- (Backport) Support array bindings of buffers. ([#2282](https://github.com/gfx-rs/naga/pull/2282)) **@kvark** + +## v0.12.1 (2023-05-18) + +#### SPV-IN + +- (Backport) Convert conditional backedges to `break if`. ([#2290](https://github.com/gfx-rs/naga/pull/2290)) **@eddyb** + +## v0.12 (2023-04-19) + +#### GENERAL + +- Allow `array_index` to be unsigned. ([#2298](https://github.com/gfx-rs/naga/pull/2298)) **@daxpedda** +- Add ray query support. ([#2256](https://github.com/gfx-rs/naga/pull/2256)) **@kvark** +- Add partial derivative builtins. ([#2277](https://github.com/gfx-rs/naga/pull/2277)) **@evahop** +- Skip `gl_PerVertex` unused builtins in the SPIR-V frontend. ([#2272](https://github.com/gfx-rs/naga/pull/2272)) **@teoxoy** +- Differentiate between `i32` and `u32` in switch statement cases. ([#2269](https://github.com/gfx-rs/naga/pull/2269)) **@evahop** +- Fix zero initialization of workgroup memory. ([#2259](https://github.com/gfx-rs/naga/pull/2259)) **@teoxoy** +- Add `countTrailingZeros`. ([#2243](https://github.com/gfx-rs/naga/pull/2243)) **@gents83** +- Fix texture built-ins where u32 was expected. ([#2245](https://github.com/gfx-rs/naga/pull/2245)) **@evahop** +- Add `countLeadingZeros`. ([#2226](https://github.com/gfx-rs/naga/pull/2226)) **@evahop** +- [glsl/hlsl-out] Write sizes of arrays behind pointers in function arguments. ([#2250](https://github.com/gfx-rs/naga/pull/2250)) **@pluiedev** + +#### VALIDATOR + +- Validate vertex stage returns the position built-in. ([#2264](https://github.com/gfx-rs/naga/pull/2264)) **@teoxoy** +- Enforce discard is only used in the fragment stage. ([#2262](https://github.com/gfx-rs/naga/pull/2262)) **@Uriopass** +- Add `Capabilities::MULTISAMPLED_SHADING`. ([#2255](https://github.com/gfx-rs/naga/pull/2255)) **@teoxoy** +- Add `Capabilities::EARLY_DEPTH_TEST`. ([#2255](https://github.com/gfx-rs/naga/pull/2255)) **@teoxoy** +- Add `Capabilities::MULTIVIEW`. ([#2255](https://github.com/gfx-rs/naga/pull/2255)) **@teoxoy** +- Improve forward declaration validation. ([#2232](https://github.com/gfx-rs/naga/pull/2232)) **@JCapucho** + +#### WGSL-IN + +- Use `alias` instead of `type` for type aliases. ([#2299](https://github.com/gfx-rs/naga/pull/2299)) **@FL33TW00D** +- Add predeclared vector and matrix type aliases. ([#2251](https://github.com/gfx-rs/naga/pull/2251)) **@evahop** +- Improve invalid assignment diagnostic. ([#2233](https://github.com/gfx-rs/naga/pull/2233)) **@SparkyPotato** +- Expect semicolons wherever required. ([#2233](https://github.com/gfx-rs/naga/pull/2233)) **@SparkyPotato** +- Fix panic on invalid zero array size. ([#2233](https://github.com/gfx-rs/naga/pull/2233)) **@SparkyPotato** +- Check for leading `{` while parsing a block. ([#2233](https://github.com/gfx-rs/naga/pull/2233)) **@SparkyPotato** + +#### SPV-IN + +- Don't apply interpolation to fragment shaders outputs. ([#2239](https://github.com/gfx-rs/naga/pull/2239)) **@JCapucho** + +#### GLSL-IN + +- Add switch implicit type conversion. ([#2273](https://github.com/gfx-rs/naga/pull/2273)) **@evahop** +- Document some fields of `naga::front::glsl::context::Context`. ([#2244](https://github.com/gfx-rs/naga/pull/2244)) **@jimblandy** +- Perform output parameters implicit casts. ([#2063](https://github.com/gfx-rs/naga/pull/2063)) **@JCapucho** +- Add `not` vector relational builtin. ([#2227](https://github.com/gfx-rs/naga/pull/2227)) **@JCapucho** +- Add double overloads for relational vector builtins. ([#2227](https://github.com/gfx-rs/naga/pull/2227)) **@JCapucho** +- Add bool overloads for relational vector builtins. ([#2227](https://github.com/gfx-rs/naga/pull/2227)) **@JCapucho** + +#### SPV-OUT + +- Fix invalid spirv being generated from integer dot products. ([#2291](https://github.com/gfx-rs/naga/pull/2291)) **@PyryM** +- Fix adding illegal decorators on fragment outputs. ([#2286](https://github.com/gfx-rs/naga/pull/2286)) **@Wumpf** +- Fix `countLeadingZeros` impl. ([#2258](https://github.com/gfx-rs/naga/pull/2258)) **@teoxoy** +- Cache constant composites. ([#2257](https://github.com/gfx-rs/naga/pull/2257)) **@evahop** +- Support SPIR-V version 1.4. ([#2230](https://github.com/gfx-rs/naga/pull/2230)) **@kvark** + +#### MSL-OUT + +- Replace `per_stage_map` with `per_entry_point_map` ([#2237](https://github.com/gfx-rs/naga/pull/2237)) **@armansito** +- Update `firstLeadingBit` for signed integers ([#2235](https://github.com/gfx-rs/naga/pull/2235)) **@evahop** + +#### HLSL-OUT + +- Use `Interlocked` intrinsic for atomic integers (#2294) ([#2294](https://github.com/gfx-rs/naga/pull/2294)) **@ErichDonGubler** +- Document storage access generation. ([#2295](https://github.com/gfx-rs/naga/pull/2295)) **@jimblandy** +- Emit constructor functions for arrays. ([#2281](https://github.com/gfx-rs/naga/pull/2281)) **@ErichDonGubler** +- Clear `named_expressions` inserted by duplicated blocks. ([#2116](https://github.com/gfx-rs/naga/pull/2116)) **@teoxoy** + +#### GLSL-OUT + +- Skip `invariant` for `gl_FragCoord` on WebGL2. ([#2254](https://github.com/gfx-rs/naga/pull/2254)) **@grovesNL** +- Inject default `gl_PointSize = 1.0` in vertex shaders if `FORCE_POINT_SIZE` option was set. ([#2223](https://github.com/gfx-rs/naga/pull/2223)) **@REASY** + +## v0.11.1 (2023-05-18) + +#### SPV-IN + +- (Backport) Convert conditional backedges to `break if`. ([#2290](https://github.com/gfx-rs/naga/pull/2290)) **@eddyb** + +## v0.11 (2023-01-25) + +- Move to the Rust 2021 edition ([#2085](https://github.com/gfx-rs/naga/pull/2085)) **@ErichDonGubler** +- Bump MSRV to 1.63 ([#2129](https://github.com/gfx-rs/naga/pull/2129)) **@teoxoy** + +#### API + +- Add handle validation pass to `Validator` ([#2090](https://github.com/gfx-rs/naga/pull/2090)) **@ErichDonGubler** +- Add `Range::new_from_bounds` ([#2148](https://github.com/gfx-rs/naga/pull/2148)) **@robtfm** + +#### DOCS + +- Fix docs for `Emit` statements ([#2208](https://github.com/gfx-rs/naga/pull/2208)) **@jimblandy** +- Fix invalid `<...>` URLs with code spans ([#2176](https://github.com/gfx-rs/naga/pull/2176)) **@ErichDonGubler** +- Explain how case clauses with multiple selectors are supported ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** +- Document `EarlyDepthTest` and `ConservativeDepth` syntax ([#2132](https://github.com/gfx-rs/naga/pull/2132)) **@coreh** + +#### VALIDATOR + +- Allow `u32` coordinates for `textureStore`/`textureLoad` ([#2172](https://github.com/gfx-rs/naga/pull/2172)) **@PENGUINLIONG** +- Fix array being flagged as constructible when its base isn't ([#2111](https://github.com/gfx-rs/naga/pull/2111)) **@teoxoy** +- Add `type_flags` to `ModuleInfo` ([#2111](https://github.com/gfx-rs/naga/pull/2111)) **@teoxoy** +- Remove overly restrictive array stride check ([#2215](https://github.com/gfx-rs/naga/pull/2215)) **@fintelia** +- Let the uniformity analysis trust the handle validation pass ([#2200](https://github.com/gfx-rs/naga/pull/2200)) **@jimblandy** +- Fix warnings when building tests without validation ([#2177](https://github.com/gfx-rs/naga/pull/2177)) **@jimblandy** +- Add `ValidationFlags::BINDINGS` ([#2156](https://github.com/gfx-rs/naga/pull/2156)) **@kvark** +- Fix `textureGather` on `texture_2d` ([#2138](https://github.com/gfx-rs/naga/pull/2138)) **@JMS55** + +#### ALL (FRONTENDS/BACKENDS) + +- Support 16-bit unorm/snorm formats ([#2210](https://github.com/gfx-rs/naga/pull/2210)) **@fintelia** +- Support `gl_PointCoord` ([#2180](https://github.com/gfx-rs/naga/pull/2180)) **@Neo-Zhixing** + +#### ALL BACKENDS + +- Add support for zero-initializing workgroup memory ([#2111](https://github.com/gfx-rs/naga/pull/2111)) **@teoxoy** + +#### WGSL-IN + +- Implement module-level scoping ([#2075](https://github.com/gfx-rs/naga/pull/2075)) **@SparkyPotato** +- Remove `isFinite` and `isNormal` ([#2218](https://github.com/gfx-rs/naga/pull/2218)) **@evahop** +- Update inverse hyperbolic built-ins ([#2218](https://github.com/gfx-rs/naga/pull/2218)) **@evahop** +- Add `refract` built-in ([#2218](https://github.com/gfx-rs/naga/pull/2218)) **@evahop** +- Update reserved keywords ([#2130](https://github.com/gfx-rs/naga/pull/2130)) **@teoxoy** +- Remove non-32bit integers ([#2146](https://github.com/gfx-rs/naga/pull/2146)) **@teoxoy** +- Remove `workgroup_size` builtin ([#2147](https://github.com/gfx-rs/naga/pull/2147)) **@teoxoy** +- Remove fallthrough statement ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** + +#### SPV-IN + +- Support binding arrays ([#2199](https://github.com/gfx-rs/naga/pull/2199)) **@Patryk27** + +#### GLSL-IN + +- Fix position propagation in lowering ([#2079](https://github.com/gfx-rs/naga/pull/2079)) **@JCapucho** +- Update initializer list type when parsing ([#2066](https://github.com/gfx-rs/naga/pull/2066)) **@JCapucho** +- Parenthesize unary negations to avoid `--` ([#2087](https://github.com/gfx-rs/naga/pull/2087)) **@ErichDonGubler** + +#### SPV-OUT + +- Add support for `atomicCompareExchangeWeak` ([#2165](https://github.com/gfx-rs/naga/pull/2165)) **@aweinstock314** +- Omit extra switch case blocks where possible ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** +- Fix switch cases after default not being output ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** + +#### MSL-OUT + +- Don't panic on missing bindings ([#2175](https://github.com/gfx-rs/naga/pull/2175)) **@kvark** +- Omit extra switch case blocks where possible ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** +- Fix `textureGather` compatibility on macOS 10.13 ([#2104](https://github.com/gfx-rs/naga/pull/2104)) **@xiaopengli89** +- Fix incorrect atomic bounds check on metal back-end ([#2099](https://github.com/gfx-rs/naga/pull/2099)) **@raphlinus** +- Parenthesize unary negations to avoid `--` ([#2087](https://github.com/gfx-rs/naga/pull/2087)) **@ErichDonGubler** + +#### HLSL-OUT + +- Simplify `write_default_init` ([#2111](https://github.com/gfx-rs/naga/pull/2111)) **@teoxoy** +- Omit extra switch case blocks where possible ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** +- Properly implement bitcast ([#2097](https://github.com/gfx-rs/naga/pull/2097)) **@cwfitzgerald** +- Fix storage access chain through a matrix ([#2097](https://github.com/gfx-rs/naga/pull/2097)) **@cwfitzgerald** +- Workaround FXC Bug in Matrix Indexing ([#2096](https://github.com/gfx-rs/naga/pull/2096)) **@cwfitzgerald** +- Parenthesize unary negations to avoid `--` ([#2087](https://github.com/gfx-rs/naga/pull/2087)) **@ErichDonGubler** + +#### GLSL-OUT + +- Introduce a flag to include unused items ([#2205](https://github.com/gfx-rs/naga/pull/2205)) **@robtfm** +- Use `fma` polyfill for versions below gles 320 ([#2197](https://github.com/gfx-rs/naga/pull/2197)) **@teoxoy** +- Emit reflection info for non-struct uniforms ([#2189](https://github.com/gfx-rs/naga/pull/2189)) **@Rainb0wCodes** +- Introduce a new block for switch cases ([#2126](https://github.com/gfx-rs/naga/pull/2126)) **@teoxoy** + +#### WGSL-OUT + +- Write correct scalar kind when `width != 4` ([#1514](https://github.com/gfx-rs/naga/pull/1514)) **@fintelia** + +## v0.10.1 (2023-06-21) + +SPV-OUT +- Backport #2389 (Use `IndexSet` instead of `HashSet` for iterated sets (capabilities/extensions)) by @eddyb, @jimblandy in https://github.com/gfx-rs/naga/pull/2391 + +SPV-IN +- Backport #2290 (Convert conditional backedges to `break if`) by @eddyb in https://github.com/gfx-rs/naga/pull/2387 + +## v0.10 (2022-10-05) + +- Make termcolor dependency optional by @AldaronLau in https://github.com/gfx-rs/naga/pull/2014 +- Fix clippy lints for 1.63 by @JCapucho in https://github.com/gfx-rs/naga/pull/2026 +- Saturate by @evahop in https://github.com/gfx-rs/naga/pull/2025 +- Use `Option::as_deref` as appropriate. by @jimblandy in https://github.com/gfx-rs/naga/pull/2040 +- Explicitely enable std for indexmap by @maxammann in https://github.com/gfx-rs/naga/pull/2062 +- Fix compiler warning by @Gordon-F in https://github.com/gfx-rs/naga/pull/2074 + +API +- Implement `Clone` for `Module` by @daxpedda in https://github.com/gfx-rs/naga/pull/2013 +- Remove the glsl-validate feature by @JCapucho in https://github.com/gfx-rs/naga/pull/2045 + +DOCS +- Document arithmetic binary operation type rules. by @jimblandy in https://github.com/gfx-rs/naga/pull/2051 + +VALIDATOR +- Add `emit_to_{stderr,string}` helpers to validation error by @nolanderc in https://github.com/gfx-rs/naga/pull/2012 +- Check regular functions don't have bindings by @JCapucho in https://github.com/gfx-rs/naga/pull/2050 + +WGSL-IN +- Update reserved WGSL keywords by @norepimorphism in https://github.com/gfx-rs/naga/pull/2009 +- Implement lexical scopes by @JCapucho in https://github.com/gfx-rs/naga/pull/2024 +- Rename `Scope` to `Rule`, since we now have lexical scope. by @jimblandy in https://github.com/gfx-rs/naga/pull/2042 +- Splat on compound assignments by @JCapucho in https://github.com/gfx-rs/naga/pull/2049 +- Fix bad span in assignment lhs error by @JCapucho in https://github.com/gfx-rs/naga/pull/2054 +- Fix inclusion of trivia in spans by @SparkyPotato in https://github.com/gfx-rs/naga/pull/2055 +- Improve assignment diagnostics by @SparkyPotato in https://github.com/gfx-rs/naga/pull/2056 +- Break up long string, reformat rest of file. by @jimblandy in https://github.com/gfx-rs/naga/pull/2057 +- Fix line endings on wgsl reserved words list. by @jimblandy in https://github.com/gfx-rs/naga/pull/2059 + +GLSL-IN +- Add support for .length() by @SpaceCat-Chan in https://github.com/gfx-rs/naga/pull/2017 +- Fix missing stores for local declarations by @adeline-sparks in https://github.com/gfx-rs/naga/pull/2029 +- Migrate to `SymbolTable` by @JCapucho in https://github.com/gfx-rs/naga/pull/2044 +- Update initializer list type when parsing by @JCapucho in https://github.com/gfx-rs/naga/pull/2066 + +SPV-OUT +- Don't decorate varyings with interpolation modes at pipeline start/end by @nical in https://github.com/gfx-rs/naga/pull/2038 +- Decorate integer builtins as Flat in the spirv writer by @nical in https://github.com/gfx-rs/naga/pull/2035 +- Properly combine the fixes for #2035 and #2038. by @jimblandy in https://github.com/gfx-rs/naga/pull/2041 +- Don't emit no-op `OpBitCast` instructions. by @jimblandy in https://github.com/gfx-rs/naga/pull/2043 + +HLSL-OUT +- Use the namer to sanitise entrypoint input/output struct names by @expenses in https://github.com/gfx-rs/naga/pull/2001 +- Handle Unpack2x16float in hlsl by @expenses in https://github.com/gfx-rs/naga/pull/2002 +- Add support for push constants by @JCapucho in https://github.com/gfx-rs/naga/pull/2005 + +DOT-OUT +- Improvements by @JCapucho in https://github.com/gfx-rs/naga/pull/1987 + +## v0.9 (2022-06-30) + +- Fix minimal-versions of dependencies ([#1840](https://github.com/gfx-rs/naga/pull/1840)) **@teoxoy** +- Update MSRV to 1.56 ([#1838](https://github.com/gfx-rs/naga/pull/1838)) **@teoxoy** + +API + +- Rename `TypeFlags` `INTERFACE`/`HOST_SHARED` to `IO_SHARED`/`HOST_SHAREABLE` ([#1872](https://github.com/gfx-rs/naga/pull/1872)) **@jimblandy** +- Expose more error information ([#1827](https://github.com/gfx-rs/naga/pull/1827), [#1937](https://github.com/gfx-rs/naga/pull/1937)) **@jakobhellermann** **@nical** **@jimblandy** +- Do not unconditionally make error output colorful ([#1707](https://github.com/gfx-rs/naga/pull/1707)) **@rhysd** +- Rename `StorageClass` to `AddressSpace` ([#1699](https://github.com/gfx-rs/naga/pull/1699)) **@kvark** +- Add a way to emit errors to a path ([#1640](https://github.com/gfx-rs/naga/pull/1640)) **@laptou** + +CLI + +- Add `bincode` representation ([#1729](https://github.com/gfx-rs/naga/pull/1729)) **@kvark** +- Include file path in WGSL parse error ([#1708](https://github.com/gfx-rs/naga/pull/1708)) **@rhysd** +- Add `--version` flag ([#1706](https://github.com/gfx-rs/naga/pull/1706)) **@rhysd** +- Support reading input from stdin via `--stdin-file-path` ([#1701](https://github.com/gfx-rs/naga/pull/1701)) **@rhysd** +- Use `panic = "abort"` ([#1597](https://github.com/gfx-rs/naga/pull/1597)) **@jrmuizel** + +DOCS + +- Standardize some docs ([#1660](https://github.com/gfx-rs/naga/pull/1660)) **@NoelTautges** +- Document `TypeInner::BindingArray` ([#1859](https://github.com/gfx-rs/naga/pull/1859)) **@jimblandy** +- Clarify accepted types for `Expression::AccessIndex` ([#1862](https://github.com/gfx-rs/naga/pull/1862)) **@NoelTautges** +- Document `proc::layouter` ([#1693](https://github.com/gfx-rs/naga/pull/1693)) **@jimblandy** +- Document Naga's promises around validation and panics ([#1828](https://github.com/gfx-rs/naga/pull/1828)) **@jimblandy** +- `FunctionInfo` doc fixes ([#1726](https://github.com/gfx-rs/naga/pull/1726)) **@jimblandy** + +VALIDATOR + +- Forbid returning pointers and atomics from functions ([#911](https://github.com/gfx-rs/naga/pull/911)) **@jimblandy** +- Let validation check for more unsupported builtins ([#1962](https://github.com/gfx-rs/naga/pull/1962)) **@jimblandy** +- Fix `Capabilities::SAMPLER_NON_UNIFORM_INDEXING` bitflag ([#1915](https://github.com/gfx-rs/naga/pull/1915)) **@cwfitzgerald** +- Properly check that user-defined IO uses IO-shareable types ([#912](https://github.com/gfx-rs/naga/pull/912)) **@jimblandy** +- Validate `ValuePointer` exactly like a `Pointer` to a `Scalar` ([#1875](https://github.com/gfx-rs/naga/pull/1875)) **@jimblandy** +- Reject empty structs ([#1826](https://github.com/gfx-rs/naga/pull/1826)) **@jimblandy** +- Validate uniform address space layout constraints ([#1812](https://github.com/gfx-rs/naga/pull/1812)) **@teoxoy** +- Improve `AddressSpace` related error messages ([#1710](https://github.com/gfx-rs/naga/pull/1710)) **@kvark** + +WGSL-IN + +Main breaking changes + +- Commas to separate struct members (comma after last member is optional) + - `struct S { a: f32; b: i32; }` -> `struct S { a: f32, b: i32 }` +- Attribute syntax + - `[[binding(0), group(0)]]` -> `@binding(0) @group(0)` +- Entry point stage attributes + - `@stage(vertex)` -> `@vertex` + - `@stage(fragment)` -> `@fragment` + - `@stage(compute)` -> `@compute` +- Function renames + - `smoothStep` -> `smoothstep` + - `findLsb` -> `firstTrailingBit` + - `findMsb` -> `firstLeadingBit` + +Specification Changes (relavant changes have also been applied to the WGSL backend) + +- Add support for `break if` ([#1993](https://github.com/gfx-rs/naga/pull/1993)) **@JCapucho** +- Update number literal format ([#1863](https://github.com/gfx-rs/naga/pull/1863)) **@teoxoy** +- Allow non-ascii characters in identifiers ([#1849](https://github.com/gfx-rs/naga/pull/1849)) **@teoxoy** +- Update reserved keywords ([#1847](https://github.com/gfx-rs/naga/pull/1847), [#1870](https://github.com/gfx-rs/naga/pull/1870), [#1905](https://github.com/gfx-rs/naga/pull/1905)) **@teoxoy** **@Gordon-F** +- Update entry point stage attributes ([#1833](https://github.com/gfx-rs/naga/pull/1833)) **@Gordon-F** +- Make colon in case optional ([#1801](https://github.com/gfx-rs/naga/pull/1801)) **@Gordon-F** +- Rename `smoothStep` to `smoothstep` ([#1800](https://github.com/gfx-rs/naga/pull/1800)) **@Gordon-F** +- Make semicolon after struct declaration optional ([#1791](https://github.com/gfx-rs/naga/pull/1791)) **@stshine** +- Use commas to separate struct members instead of semicolons ([#1773](https://github.com/gfx-rs/naga/pull/1773)) **@Gordon-F** +- Rename `findLsb`/`findMsb` to `firstTrailingBit`/`firstLeadingBit` ([#1735](https://github.com/gfx-rs/naga/pull/1735)) **@kvark** +- Make parenthesis optional for `if` and `switch` statements ([#1725](https://github.com/gfx-rs/naga/pull/1725)) **@Gordon-F** +- Declare attribtues with `@attrib` instead of `[[attrib]]` ([#1676](https://github.com/gfx-rs/naga/pull/1676)) **@kvark** +- Allow non-structure buffer types ([#1682](https://github.com/gfx-rs/naga/pull/1682)) **@kvark** +- Remove `stride` attribute ([#1681](https://github.com/gfx-rs/naga/pull/1681)) **@kvark** + +Improvements + +- Implement complete validation for size and align attributes ([#1979](https://github.com/gfx-rs/naga/pull/1979)) **@teoxoy** +- Implement `firstTrailingBit`/`firstLeadingBit` u32 overloads ([#1865](https://github.com/gfx-rs/naga/pull/1865)) **@teoxoy** +- Add error for non-floating-point matrix ([#1917](https://github.com/gfx-rs/naga/pull/1917)) **@grovesNL** +- Implement partial vector & matrix identity constructors ([#1916](https://github.com/gfx-rs/naga/pull/1916)) **@teoxoy** +- Implement phony assignment ([#1866](https://github.com/gfx-rs/naga/pull/1866), [#1869](https://github.com/gfx-rs/naga/pull/1869)) **@teoxoy** +- Fix being able to match `~=` as LogicalOperation ([#1849](https://github.com/gfx-rs/naga/pull/1849)) **@teoxoy** +- Implement Binding Arrays ([#1845](https://github.com/gfx-rs/naga/pull/1845)) **@cwfitzgerald** +- Implement unary vector operators ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Implement zero value constructors and constructors that infer their type from their parameters ([#1790](https://github.com/gfx-rs/naga/pull/1790)) **@teoxoy** +- Implement invariant attribute ([#1789](https://github.com/gfx-rs/naga/pull/1789), [#1822](https://github.com/gfx-rs/naga/pull/1822)) **@teoxoy** **@jimblandy** +- Implement increment and decrement statements ([#1788](https://github.com/gfx-rs/naga/pull/1788), [#1912](https://github.com/gfx-rs/naga/pull/1912)) **@teoxoy** +- Implement `while` loop ([#1787](https://github.com/gfx-rs/naga/pull/1787)) **@teoxoy** +- Fix array size on globals ([#1717](https://github.com/gfx-rs/naga/pull/1717)) **@jimblandy** +- Implement integer vector overloads for `dot` function ([#1689](https://github.com/gfx-rs/naga/pull/1689)) **@francesco-cattoglio** +- Implement block comments ([#1675](https://github.com/gfx-rs/naga/pull/1675)) **@kocsis1david** +- Implement assignment binary operators ([#1662](https://github.com/gfx-rs/naga/pull/1662)) **@kvark** +- Implement `radians`/`degrees` builtin functions ([#1627](https://github.com/gfx-rs/naga/pull/1627)) **@encounter** +- Implement `findLsb`/`findMsb` builtin functions ([#1473](https://github.com/gfx-rs/naga/pull/1473)) **@fintelia** +- Implement `textureGather`/`textureGatherCompare` builtin functions ([#1596](https://github.com/gfx-rs/naga/pull/1596)) **@kvark** + +SPV-IN + +- Implement `OpBitReverse` and `OpBitCount` ([#1954](https://github.com/gfx-rs/naga/pull/1954)) **@JCapucho** +- Add `MultiView` to `SUPPORTED_CAPABILITIES` ([#1934](https://github.com/gfx-rs/naga/pull/1934)) **@expenses** +- Translate `OpSMod` and `OpFMod` correctly ([#1867](https://github.com/gfx-rs/naga/pull/1867), [#1995](https://github.com/gfx-rs/naga/pull/1995)) **@teoxoy** **@JCapucho** +- Error on unsupported `MatrixStride` ([#1805](https://github.com/gfx-rs/naga/pull/1805)) **@teoxoy** +- Align array stride for undecorated arrays ([#1724](https://github.com/gfx-rs/naga/pull/1724)) **@JCapucho** + +GLSL-IN + +- Don't allow empty last case in switch ([#1981](https://github.com/gfx-rs/naga/pull/1981)) **@JCapucho** +- Fix last case falltrough and empty switch ([#1981](https://github.com/gfx-rs/naga/pull/1981)) **@JCapucho** +- Splat inputs for smoothstep if needed ([#1976](https://github.com/gfx-rs/naga/pull/1976)) **@JCapucho** +- Fix parameter not changing to depth ([#1967](https://github.com/gfx-rs/naga/pull/1967)) **@JCapucho** +- Fix matrix multiplication check ([#1953](https://github.com/gfx-rs/naga/pull/1953)) **@JCapucho** +- Fix panic (stop emitter in conditional) ([#1952](https://github.com/gfx-rs/naga/pull/1952)) **@JCapucho** +- Translate `mod` fn correctly ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** +- Make the ternary operator behave as an if ([#1877](https://github.com/gfx-rs/naga/pull/1877)) **@JCapucho** +- Add support for `clamp` function ([#1502](https://github.com/gfx-rs/naga/pull/1502)) **@sjinno** +- Better errors for bad constant expression ([#1501](https://github.com/gfx-rs/naga/pull/1501)) **@sjinno** +- Error on a `matCx2` used with the `std140` layout ([#1806](https://github.com/gfx-rs/naga/pull/1806)) **@teoxoy** +- Allow nested accesses in lhs positions ([#1794](https://github.com/gfx-rs/naga/pull/1794)) **@JCapucho** +- Use forced conversions for vector/matrix constructors ([#1796](https://github.com/gfx-rs/naga/pull/1796)) **@JCapucho** +- Add support for `barrier` function ([#1793](https://github.com/gfx-rs/naga/pull/1793)) **@fintelia** +- Fix panic (resume expression emit after `imageStore`) ([#1795](https://github.com/gfx-rs/naga/pull/1795)) **@JCapucho** +- Allow multiple array specifiers ([#1780](https://github.com/gfx-rs/naga/pull/1780)) **@JCapucho** +- Fix memory qualifiers being inverted ([#1779](https://github.com/gfx-rs/naga/pull/1779)) **@JCapucho** +- Support arrays as input/output types ([#1759](https://github.com/gfx-rs/naga/pull/1759)) **@JCapucho** +- Fix freestanding constructor parsing ([#1758](https://github.com/gfx-rs/naga/pull/1758)) **@JCapucho** +- Fix matrix - scalar operations ([#1757](https://github.com/gfx-rs/naga/pull/1757)) **@JCapucho** +- Fix matrix - matrix division ([#1757](https://github.com/gfx-rs/naga/pull/1757)) **@JCapucho** +- Fix matrix comparisons ([#1757](https://github.com/gfx-rs/naga/pull/1757)) **@JCapucho** +- Add support for `texelFetchOffset` ([#1746](https://github.com/gfx-rs/naga/pull/1746)) **@JCapucho** +- Inject `sampler2DMSArray` builtins on use ([#1737](https://github.com/gfx-rs/naga/pull/1737)) **@JCapucho** +- Inject `samplerCubeArray` builtins on use ([#1736](https://github.com/gfx-rs/naga/pull/1736)) **@JCapucho** +- Add support for image builtin functions ([#1723](https://github.com/gfx-rs/naga/pull/1723)) **@JCapucho** +- Add support for image declarations ([#1723](https://github.com/gfx-rs/naga/pull/1723)) **@JCapucho** +- Texture builtins fixes ([#1719](https://github.com/gfx-rs/naga/pull/1719)) **@JCapucho** +- Type qualifiers rework ([#1713](https://github.com/gfx-rs/naga/pull/1713)) **@JCapucho** +- `texelFetch` accept multisampled textures ([#1715](https://github.com/gfx-rs/naga/pull/1715)) **@JCapucho** +- Fix panic when culling nested block ([#1714](https://github.com/gfx-rs/naga/pull/1714)) **@JCapucho** +- Fix composite constructors ([#1631](https://github.com/gfx-rs/naga/pull/1631)) **@JCapucho** +- Fix using swizzle as out arguments ([#1632](https://github.com/gfx-rs/naga/pull/1632)) **@JCapucho** + +SPV-OUT + +- Implement `reverseBits` and `countOneBits` ([#1897](https://github.com/gfx-rs/naga/pull/1897)) **@hasali19** +- Use `OpCopyObject` for matrix identity casts ([#1916](https://github.com/gfx-rs/naga/pull/1916)) **@teoxoy** +- Use `OpCopyObject` for bool - bool conversion due to `OpBitcast` not being feasible for booleans ([#1916](https://github.com/gfx-rs/naga/pull/1916)) **@teoxoy** +- Zero init variables in function and private address spaces ([#1871](https://github.com/gfx-rs/naga/pull/1871)) **@teoxoy** +- Use `SRem` instead of `SMod` ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** +- Add support for integer vector - scalar multiplication ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Add support for matrix addition and subtraction ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Emit required decorations on wrapper struct types ([#1815](https://github.com/gfx-rs/naga/pull/1815)) **@jimblandy** +- Decorate array and struct type layouts unconditionally ([#1815](https://github.com/gfx-rs/naga/pull/1815)) **@jimblandy** +- Fix wrong `MatrixStride` for `matCx2` and `mat2xR` ([#1781](https://github.com/gfx-rs/naga/pull/1781)) **@teoxoy** +- Use `OpImageQuerySize` for MS images ([#1742](https://github.com/gfx-rs/naga/pull/1742)) **@JCapucho** + +MSL-OUT + +- Insert padding initialization for global constants ([#1988](https://github.com/gfx-rs/naga/pull/1988)) **@teoxoy** +- Don't rely on cached expressions ([#1975](https://github.com/gfx-rs/naga/pull/1975)) **@JCapucho** +- Fix pointers to private or workgroup address spaces possibly being read only ([#1901](https://github.com/gfx-rs/naga/pull/1901)) **@teoxoy** +- Zero init variables in function address space ([#1871](https://github.com/gfx-rs/naga/pull/1871)) **@teoxoy** +- Make binding arrays play nice with bounds checks ([#1855](https://github.com/gfx-rs/naga/pull/1855)) **@cwfitzgerald** +- Permit `invariant` qualifier on vertex shader outputs ([#1821](https://github.com/gfx-rs/naga/pull/1821)) **@jimblandy** +- Fix packed `vec3` stores ([#1816](https://github.com/gfx-rs/naga/pull/1816)) **@teoxoy** +- Actually test push constants to be used ([#1767](https://github.com/gfx-rs/naga/pull/1767)) **@kvark** +- Properly rename entry point arguments for struct members ([#1766](https://github.com/gfx-rs/naga/pull/1766)) **@jimblandy** +- Qualify read-only storage with const ([#1763](https://github.com/gfx-rs/naga/pull/1763)) **@kvark** +- Fix not unary operator for integer scalars ([#1760](https://github.com/gfx-rs/naga/pull/1760)) **@vincentisambart** +- Add bounds checks for `ImageLoad` and `ImageStore` ([#1730](https://github.com/gfx-rs/naga/pull/1730)) **@jimblandy** +- Fix resource bindings for non-structures ([#1718](https://github.com/gfx-rs/naga/pull/1718)) **@kvark** +- Always check whether _buffer_sizes arg is needed ([#1717](https://github.com/gfx-rs/naga/pull/1717)) **@jimblandy** +- WGSL storage address space should always correspond to MSL device address space ([#1711](https://github.com/gfx-rs/naga/pull/1711)) **@wtholliday** +- Mitigation for MSL atomic bounds check ([#1703](https://github.com/gfx-rs/naga/pull/1703)) **@glalonde** + +HLSL-OUT + +- More `matCx2` fixes (#1989) ([#1989](https://github.com/gfx-rs/naga/pull/1989)) **@teoxoy** +- Fix fallthrough in switch statements ([#1920](https://github.com/gfx-rs/naga/pull/1920)) **@teoxoy** +- Fix missing break statements ([#1919](https://github.com/gfx-rs/naga/pull/1919)) **@teoxoy** +- Fix `countOneBits` and `reverseBits` for signed integers ([#1928](https://github.com/gfx-rs/naga/pull/1928)) **@hasali19** +- Fix array constructor return type ([#1914](https://github.com/gfx-rs/naga/pull/1914)) **@teoxoy** +- Fix hlsl output for writes to scalar/vector storage buffer ([#1903](https://github.com/gfx-rs/naga/pull/1903)) **@hasali19** +- Use `fmod` instead of `%` ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** +- Use wrapped constructors when loading from storage address space ([#1893](https://github.com/gfx-rs/naga/pull/1893)) **@teoxoy** +- Zero init struct constructor ([#1890](https://github.com/gfx-rs/naga/pull/1890)) **@teoxoy** +- Flesh out matrix handling documentation ([#1850](https://github.com/gfx-rs/naga/pull/1850)) **@jimblandy** +- Emit `row_major` qualifier on matrix uniform globals ([#1846](https://github.com/gfx-rs/naga/pull/1846)) **@jimblandy** +- Fix bool splat ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Add more padding when necessary ([#1814](https://github.com/gfx-rs/naga/pull/1814)) **@teoxoy** +- Support multidimensional arrays ([#1814](https://github.com/gfx-rs/naga/pull/1814)) **@teoxoy** +- Don't output interpolation modifier if it's the default ([#1809](https://github.com/gfx-rs/naga/pull/1809)) **@NoelTautges** +- Fix `matCx2` translation for uniform buffers ([#1802](https://github.com/gfx-rs/naga/pull/1802)) **@teoxoy** +- Fix modifiers not being written in the vertex output and fragment input structs ([#1789](https://github.com/gfx-rs/naga/pull/1789)) **@teoxoy** +- Fix matrix not being declared as transposed ([#1784](https://github.com/gfx-rs/naga/pull/1784)) **@teoxoy** +- Insert padding between struct members ([#1786](https://github.com/gfx-rs/naga/pull/1786)) **@teoxoy** +- Fix not unary operator for integer scalars ([#1760](https://github.com/gfx-rs/naga/pull/1760)) **@vincentisambart** + +GLSL-OUT + +- Fix vector bitcasts (#1966) ([#1966](https://github.com/gfx-rs/naga/pull/1966)) **@expenses** +- Perform casts in int only math functions ([#1978](https://github.com/gfx-rs/naga/pull/1978)) **@JCapucho** +- Don't rely on cached expressions ([#1975](https://github.com/gfx-rs/naga/pull/1975)) **@JCapucho** +- Fix type error for `countOneBits` implementation ([#1897](https://github.com/gfx-rs/naga/pull/1897)) **@hasali19** +- Fix storage format for `Rgba8Unorm` ([#1955](https://github.com/gfx-rs/naga/pull/1955)) **@JCapucho** +- Implement bounds checks for `ImageLoad` ([#1889](https://github.com/gfx-rs/naga/pull/1889)) **@JCapucho** +- Fix feature search in expressions ([#1887](https://github.com/gfx-rs/naga/pull/1887)) **@JCapucho** +- Emit globals of any type ([#1823](https://github.com/gfx-rs/naga/pull/1823)) **@jimblandy** +- Add support for boolean vector `~`, `|` and `&` ops ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Fix array function arguments ([#1814](https://github.com/gfx-rs/naga/pull/1814)) **@teoxoy** +- Write constant sized array type for uniform ([#1768](https://github.com/gfx-rs/naga/pull/1768)) **@hatoo** +- Texture function fixes ([#1742](https://github.com/gfx-rs/naga/pull/1742)) **@JCapucho** +- Push constants use anonymous uniforms ([#1683](https://github.com/gfx-rs/naga/pull/1683)) **@JCapucho** +- Add support for push constant emulation ([#1672](https://github.com/gfx-rs/naga/pull/1672)) **@JCapucho** +- Skip unsized types if unused ([#1649](https://github.com/gfx-rs/naga/pull/1649)) **@kvark** +- Write struct and array initializers ([#1644](https://github.com/gfx-rs/naga/pull/1644)) **@JCapucho** + + +## v0.8.5 (2022-01-25) + +MSL-OUT + +- Make VS-output positions invariant on even more systems ([#1697](https://github.com/gfx-rs/naga/pull/1697)) **@cwfitzgerald** +- Improve support for point primitives ([#1696](https://github.com/gfx-rs/naga/pull/1696)) **@kvark** + + +## v0.8.4 (2022-01-24) + +MSL-OUT + +- Make VS-output positions invariant if possible ([#1687](https://github.com/gfx-rs/naga/pull/1687)) **@kvark** + +GLSL-OUT + +- Fix `floatBitsToUint` spelling ([#1688](https://github.com/gfx-rs/naga/pull/1688)) **@cwfitzgerald** +- Call proper memory barrier functions ([#1680](https://github.com/gfx-rs/naga/pull/1680)) **@francesco-cattoglio** + + +## v0.8.3 (2022-01-20) + +- Don't pin `indexmap` version ([#1666](https://github.com/gfx-rs/naga/pull/1666)) **@a1phyr** + +MSL-OUT + +- Fix support for point primitives ([#1674](https://github.com/gfx-rs/naga/pull/1674)) **@kvark** + +GLSL-OUT + +- Fix sampler association ([#1671](https://github.com/gfx-rs/naga/pull/1671)) **@JCapucho** + + +## v0.8.2 (2022-01-11) + +VALIDATOR + +- Check structure resource types ([#1639](https://github.com/gfx-rs/naga/pull/1639)) **@kvark** + +WGSL-IN + +- Improve type mismatch errors ([#1658](https://github.com/gfx-rs/naga/pull/1658)) **@Gordon-F** + +SPV-IN + +- Implement more sign agnostic operations ([#1651](https://github.com/gfx-rs/naga/pull/1651), [#1650](https://github.com/gfx-rs/naga/pull/1650)) **@JCapucho** + +SPV-OUT + +- Fix modulo operator (use `OpFRem` instead of `OpFMod`) ([#1653](https://github.com/gfx-rs/naga/pull/1653)) **@JCapucho** + +MSL-OUT + +- Fix `texture1d` accesses ([#1647](https://github.com/gfx-rs/naga/pull/1647)) **@jimblandy** +- Fix data packing functions ([#1637](https://github.com/gfx-rs/naga/pull/1637)) **@phoekz** + + +## v0.8.1 (2021-12-29) + +API + +- Make `WithSpan` clonable ([#1620](https://github.com/gfx-rs/naga/pull/1620)) **@jakobhellermann** + +MSL-OUT + +- Fix packed vec access ([#1634](https://github.com/gfx-rs/naga/pull/1634)) **@kvark** +- Fix packed float support ([#1630](https://github.com/gfx-rs/naga/pull/1630)) **@kvark** + +HLSL-OUT + +- Support arrays of matrices ([#1629](https://github.com/gfx-rs/naga/pull/1629)) **@kvark** +- Use `mad` instead of `fma` function ([#1580](https://github.com/gfx-rs/naga/pull/1580)) **@parasyte** + +GLSL-OUT + +- Fix conflicting names for globals ([#1616](https://github.com/gfx-rs/naga/pull/1616)) **@Gordon-F** +- Fix `fma` function ([#1580](https://github.com/gfx-rs/naga/pull/1580)) **@parasyte** + + +## v0.8 (2021-12-18) + - development release for wgpu-0.12 + - lots of fixes in all parts + - validator: + - now gated by `validate` feature + - nicely detailed error messages with spans + - API: + - image gather operations + - WGSL-in: + - remove `[[block]]` attribute + - `elseif` is removed in favor of `else if` + - MSL-out: + - full out-of-bounds checking + +## v0.7.3 (2021-12-14) + - API: + - `view_index` builtin + - GLSL-out: + - reflect textures without samplers + - SPV-out: + - fix incorrect pack/unpack + +## v0.7.2 (2021-12-01) + - validator: + - check stores for proper pointer class + - HLSL-out: + - fix stores into `mat3` + - respect array strides + - SPV-out: + - fix multi-word constants + - WGSL-in: + - permit names starting with underscores + - SPV-in: + - cull unused builtins + - support empty debug labels + - GLSL-in: + - don't panic on invalid integer operations + +## v0.7.1 (2021-10-12) + - implement casts from and to booleans in the backends + +## v0.7 (2021-10-07) + - development release for wgpu-0.11 + - API: + - bit extraction and packing functions + - hyperbolic trigonometry functions + - validation is gated by a cargo feature + - `view_index` builtin + - separate bounds checking policies for locals/buffers/textures + - IR: + - types and constants are guaranteed to be unique + - WGSL-in: + - new hex literal parser + - updated list of reserved words + - rewritten logic for resolving references and pointers + - `switch` can use unsigned selectors + - GLSL-in: + - better support for texture sampling + - better logic for auto-splatting scalars + - GLSL-out: + - fixed storage buffer layout + - fix module operator + - HLSL-out: + - fixed texture queries + - SPV-in: + - control flow handling is rewritten from scratch + - SPV-out: + - fully covered out-of-bounds checking + - option to emit point size + - option to clamp output depth + +## v0.6.3 (2021-09-08) + - Reduced heap allocations when generating WGSL, HLSL, and GLSL + - WGSL-in: + - support module-scope `let` type inference + - SPV-in: + - fix depth sampling with projection + - HLSL-out: + - fix local struct construction + - GLSL-out: + - fix `select()` order + - SPV-out: + - allow working around Adreno issue with `OpName` + +## v0.6.2 (2021-09-01) + - SPV-out fixes: + - requested capabilities for 1D and cube images, storage formats + - handling `break` and `continue` in a `switch` statement + - avoid generating duplicate `OpTypeImage` types + - HLSL-out fixes: + - fix output struct member names + - MSL-out fixes: + - fix packing of fields in interface structs + - GLSL-out fixes: + - fix non-fallthrough `switch` cases + - GLSL-in fixes: + - avoid infinite loop on invalid statements + +## v0.6.1 (2021-08-24) + - HLSL-out fixes: + - array arguments + - pointers to array arguments + - switch statement + - rewritten interface matching + - SPV-in fixes: + - array storage texture stores + - tracking sampling across function parameters + - updated petgraph dependencies + - MSL-out: + - gradient sampling + - GLSL-out: + - modulo operator on floats + +## v0.6 (2021-08-18) + - development release for wgpu-0.10 + - API: + - atomic types and functions + - storage access is moved from global variables to the storage class and storage texture type + - new built-ins: `primitive_index` and `num_workgroups` + - support for multi-sampled depth images + - WGSL: + - `select()` order of true/false is swapped + - HLSL backend is vastly improved and now usable + - GLSL frontend is heavily reworked + +## v0.5 (2021-06-18) + - development release for wgpu-0.9 + - API: + - barriers + - dynamic indexing of matrices and arrays is only allowed on variables + - validator now accepts a list of IR capabilities to allow + - improved documentation + - Infrastructure: + - much richer test suite, focused around consuming or emitting WGSL + - lazy testing on large shader corpuses + - the binary is moved to a sub-crate "naga-cli" + - Frontends: + - GLSL frontend: + - rewritten from scratch and effectively revived, no longer depends on `pomelo` + - only supports 440/450/460 versions for now + - has optional support for codespan messages + - SPIRV frontend has improved CFG resolution (still with issues unresolved) + - WGSL got better error messages, workgroup memory support + - Backends: + - general: better expression naming and emitting + - new HLSL backend (in progress) + - MSL: + - support `ArraySize` expression + - better texture sampling instructions + - GLSL: + - multisampling on GLES + - WGSL is vastly improved and now usable + +## v0.4.2 (2021-05-28) + - SPIR-V frontend: + - fix image stores + - fix matrix stride check + - SPIR-V backend: + - fix auto-deriving the capabilities + - GLSL backend: + - support sample interpolation + - write out swizzled vector accesses + +## v0.4.1 (2021-05-14) + - numerous additions and improvements to SPIR-V frontend: + - int8, in16, int64 + - null constant initializers for structs and matrices + - `OpArrayLength`, `OpCopyMemory`, `OpInBoundsAccessChain`, `OpLogicalXxxEqual` + - outer product + - fix struct size alignment + - initialize built-ins with default values + - fix read-only decorations on struct members + - fix struct size alignment in WGSL + - fix `fwidth` in WGSL + - fix scalars arrays in GLSL backend + +## v0.4 (2021-04-29) + - development release for wgpu-0.8 + - API: + - expressions are explicitly emitted with `Statement::Emit` + - entry points have inputs in arguments and outputs in the result type + - `input`/`output` storage classes are gone, but `push_constant` is added + - `Interpolation` is moved into `Binding::Location` variant + - real pointer semantics with required `Expression::Load` + - `TypeInner::ValuePointer` is added + - image query expressions are added + - new `Statement::ImageStore` + - all function calls are `Statement::Call` + - `GlobalUse` is moved out into processing + - `Header` is removed + - entry points are an array instead of a map + - new `Swizzle` and `Splat` expressions + - interpolation qualifiers are extended and required + - struct member layout is based on the byte offsets + - Infrastructure: + - control flow uniformity analysis + - texture-sampler combination gathering + - `CallGraph` processor is moved out into `glsl` backend + - `Interface` is removed, instead the analysis produces `ModuleInfo` with all the derived info + - validation of statement tree, expressions, and constants + - code linting is more strict for matches + - new GraphViz `dot` backend for pretty visualization of the IR + - Metal support for inlined samplers + - `convert` example is transformed into the default binary target named `naga` + - lots of frontend and backend fixes + +## v0.3.2 (2021-02-15) + - fix logical expression types + - fix _FragDepth_ semantics + - spv-in: + - derive block status of structures + - spv-out: + - add lots of missing math functions + - implement discard + +## v0.3.1 (2021-01-31) + - wgsl: + - support constant array sizes + - spv-out: + - fix block decorations on nested structures + - fix fixed-size arrays + - fix matrix decorations inside structures + - implement read-only decorations + +## v0.3 (2021-01-30) + - development release for wgpu-0.7 + - API: + - math functions + - type casts + - updated storage classes + - updated image sub-types + - image sampling/loading options + - storage images + - interpolation qualifiers + - early and conservative depth + - Processors: + - name manager + - automatic layout + - termination analysis + - validation of types, constants, variables, and entry points + +## v0.2 (2020-08-17) + - development release for wgpu-0.6 + +## v0.1 (2020-02-26) + - initial release diff --git a/naga/Cargo.toml b/naga/Cargo.toml new file mode 100644 index 0000000000..094c4779a5 --- /dev/null +++ b/naga/Cargo.toml @@ -0,0 +1,78 @@ +[package] +name = "naga" +version = "0.14.2" +authors = ["gfx-rs developers"] +edition = "2021" +description = "Shader translation infrastructure" +repository = "https://github.com/gfx-rs/wgpu/tree/trunk/naga" +keywords = ["shader", "SPIR-V", "GLSL", "MSL"] +license = "MIT OR Apache-2.0" +exclude = ["bin/**/*", "tests/**/*", "Cargo.lock", "target/**/*"] +resolver = "2" +rust-version = "1.70" +autotests = false + +[[test]] +name = "naga-test" +path = "tests/root.rs" + +[package.metadata.docs.rs] +all-features = true + +[features] +default = [] +clone = [] +dot-out = [] +glsl-in = ["pp-rs"] +glsl-out = [] +msl-out = [] +serialize = ["serde", "bitflags/serde", "indexmap/serde"] +deserialize = ["serde", "bitflags/serde", "indexmap/serde"] +arbitrary = ["dep:arbitrary", "bitflags/arbitrary", "indexmap/arbitrary"] +spv-in = ["petgraph", "spirv"] +spv-out = ["spirv"] +wgsl-in = ["hexf-parse", "unicode-xid", "compact"] +wgsl-out = [] +hlsl-out = [] +compact = [] + +[[bench]] +name = "criterion" +harness = false + +[dependencies] +arbitrary = { version = "1.3", features = ["derive"], optional = true } +bitflags = "2.2" +bit-set = "0.5" +termcolor = { version = "1.4.1" } +# remove termcolor dep when updating to the next version of codespan-reporting +# termcolor minimum version was wrong and was fixed in +# https://github.com/brendanzab/codespan/commit/e99c867339a877731437e7ee6a903a3d03b5439e +codespan-reporting = { version = "0.11.0" } +rustc-hash = "1.1.0" +indexmap = { version = "2", features = ["std"] } +log = "0.4" +num-traits = "0.2" +spirv = { version = "0.3", optional = true } +thiserror = "1.0.56" +serde = { version = "1.0.195", features = ["derive"], optional = true } +petgraph = { version = "0.6", optional = true } +pp-rs = { version = "0.2.1", optional = true } +hexf-parse = { version = "0.2.1", optional = true } +unicode-xid = { version = "0.2.3", optional = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +criterion = { version = "0.5", features = [] } + +[dev-dependencies] +bincode = "1" +diff = "0.1" +env_logger = "0.10" +hlsl-snapshots = { version = "0.1.0", path = "./hlsl-snapshots" } +# Require at least version 0.7.1 of ron, this version changed how floating points are +# serialized by forcing them to always have the decimal part, this makes it backwards +# incompatible with our tests because we do a syntatic diff and not a semantic one. +ron = "0.8.0" +rspirv = { version = "0.11", git = "https://github.com/gfx-rs/rspirv", rev = "b969f175d5663258b4891e44b76c1544da9661ab" } +serde = { version = "1.0", features = ["derive"] } +spirv = { version = "0.3", features = ["deserialize"] } diff --git a/naga/README.md b/naga/README.md new file mode 100644 index 0000000000..b7f352fc91 --- /dev/null +++ b/naga/README.md @@ -0,0 +1,86 @@ +# Naga + +[![Matrix](https://img.shields.io/badge/Matrix-%23naga%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#naga:matrix.org) +[![Crates.io](https://img.shields.io/crates/v/naga.svg?label=naga)](https://crates.io/crates/naga) +[![Docs.rs](https://docs.rs/naga/badge.svg)](https://docs.rs/naga) +[![Build Status](https://github.com/gfx-rs/naga/workflows/pipeline/badge.svg)](https://github.com/gfx-rs/naga/actions) +![MSRV](https://img.shields.io/badge/rustc-1.70+-blue.svg) +[![codecov.io](https://codecov.io/gh/gfx-rs/naga/branch/master/graph/badge.svg?token=9VOKYO8BM2)](https://codecov.io/gh/gfx-rs/naga) + +The shader translation library for the needs of [wgpu](https://github.com/gfx-rs/wgpu). + +## Supported end-points + +Front-end | Status | Feature | Notes | +--------------- | ------------------ | ------- | ----- | +SPIR-V (binary) | :white_check_mark: | spv-in | | +WGSL | :white_check_mark: | wgsl-in | Fully validated | +GLSL | :ok: | glsl-in | GLSL 440+ and Vulkan semantics only | + +Back-end | Status | Feature | Notes | +--------------- | ------------------ | -------- | ----- | +SPIR-V | :white_check_mark: | spv-out | | +WGSL | :ok: | wgsl-out | | +Metal | :white_check_mark: | msl-out | | +HLSL | :white_check_mark: | hlsl-out | Shader Model 5.0+ (DirectX 11+) | +GLSL | :ok: | glsl-out | GLSL 330+ and GLSL ES 300+ | +AIR | | | | +DXIL/DXIR | | | | +DXBC | | | | +DOT (GraphViz) | :ok: | dot-out | Not a shading language | + +:white_check_mark: = Primary support — :ok: = Secondary support — :construction: = Unsupported, but support in progress + +## Conversion tool + +Naga can be used as a CLI, which allows testing the conversion of different code paths. + +First, install `naga-cli` from crates.io or directly from GitHub. + +```bash +# release version +cargo install naga-cli + +# development version +cargo install naga-cli --git https://github.com/gfx-rs/naga.git +``` + +Then, you can run `naga` command. + +```bash +naga my_shader.wgsl # validate only +naga my_shader.spv my_shader.txt # dump the IR module into a file +naga my_shader.spv my_shader.metal --flow-dir flow-dir # convert the SPV to Metal, also dump the SPIR-V flow graph to `flow-dir` +naga my_shader.wgsl my_shader.vert --profile es310 # convert the WGSL to GLSL vertex stage under ES 3.20 profile +``` + +As naga includes a default binary target, you can also use `cargo run` without installation. This is useful when you develop naga itself or investigate the behavior of naga at a specific commit (e.g. [wgpu](https://github.com/gfx-rs/wgpu) might pin a different version of naga than the `HEAD` of this repository). + +```bash +cargo run my_shader.wgsl +``` + +## Development workflow + +The main instrument aiding the development is the good old `cargo test --all-features --workspace`, +which will run the unit tests and also update all the snapshots. You'll see these +changes in git before committing the code. + +If working on a particular front-end or back-end, it may be convenient to +enable the relevant features in `Cargo.toml`, e.g. +```toml +default = ["spv-out"] #TEMP! +``` +This allows IDE basic checks to report errors there unless your IDE is sufficiently configurable already. + +Finally, when changes to the snapshots are made, we should verify that the produced shaders +are indeed valid for the target platforms they are compiled for: +```bash +cargo xtask validate spv # for Vulkan shaders, requires SPIRV-Tools installed +cargo xtask validate msl # for Metal shaders, requires XCode command-line tools installed +cargo xtask validate glsl # for OpenGL shaders, requires GLSLang installed +cargo xtask validate dot # for dot files, requires GraphViz installed +cargo xtask validate wgsl # for WGSL shaders +cargo xtask validate hlsl dxc # for HLSL shaders via DXC +cargo xtask validate hlsl fxc # for HLSL shaders via FXC +``` diff --git a/naga/benches/criterion.rs b/naga/benches/criterion.rs new file mode 100644 index 0000000000..e57c58a847 --- /dev/null +++ b/naga/benches/criterion.rs @@ -0,0 +1,273 @@ +#![cfg(not(target_arch = "wasm32"))] +#![allow(clippy::needless_borrowed_reference)] + +use criterion::*; +use std::{fs, path::PathBuf, slice}; + +fn gather_inputs(folder: &str, extension: &str) -> Vec> { + let mut list = Vec::new(); + let read_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join(folder) + .read_dir() + .unwrap(); + for file_entry in read_dir { + match file_entry { + Ok(entry) => match entry.path().extension() { + Some(ostr) if ostr == extension => { + let input = fs::read(entry.path()).unwrap_or_default(); + list.push(input.into_boxed_slice()); + } + _ => continue, + }, + Err(e) => { + log::warn!("Skipping file: {:?}", e); + continue; + } + } + } + list +} + +fn parse_glsl(stage: naga::ShaderStage, inputs: &[Box<[u8]>]) { + let mut parser = naga::front::glsl::Frontend::default(); + let options = naga::front::glsl::Options { + stage, + defines: Default::default(), + }; + for input in inputs.iter() { + let string = std::str::from_utf8(input).unwrap(); + parser.parse(&options, string).unwrap(); + } +} + +fn frontends(c: &mut Criterion) { + let mut group = c.benchmark_group("front"); + #[cfg(all(feature = "wgsl-in", feature = "serialize", feature = "deserialize"))] + group.bench_function("bin", |b| { + let inputs_wgsl = gather_inputs("tests/in", "wgsl"); + let mut frontend = naga::front::wgsl::Frontend::new(); + let inputs_bin = inputs_wgsl + .iter() + .map(|input| { + let string = std::str::from_utf8(input).unwrap(); + let module = frontend.parse(string).unwrap(); + bincode::serialize(&module).unwrap() + }) + .collect::>(); + b.iter(move || { + for input in inputs_bin.iter() { + bincode::deserialize::(input).unwrap(); + } + }); + }); + #[cfg(feature = "wgsl-in")] + group.bench_function("wgsl", |b| { + let inputs_wgsl = gather_inputs("tests/in", "wgsl"); + let inputs = inputs_wgsl + .iter() + .map(|input| std::str::from_utf8(input).unwrap()) + .collect::>(); + let mut frontend = naga::front::wgsl::Frontend::new(); + b.iter(move || { + for &input in inputs.iter() { + frontend.parse(input).unwrap(); + } + }); + }); + #[cfg(feature = "spv-in")] + group.bench_function("spv", |b| { + let inputs = gather_inputs("tests/in/spv", "spv"); + b.iter(move || { + let options = naga::front::spv::Options::default(); + for input in inputs.iter() { + let spv = + unsafe { slice::from_raw_parts(input.as_ptr() as *const u32, input.len() / 4) }; + let parser = naga::front::spv::Frontend::new(spv.iter().cloned(), &options); + parser.parse().unwrap(); + } + }); + }); + #[cfg(feature = "glsl-in")] + group.bench_function("glsl", |b| { + let vert = gather_inputs("tests/in/glsl", "vert"); + b.iter(move || parse_glsl(naga::ShaderStage::Vertex, &vert)); + let frag = gather_inputs("tests/in/glsl", "frag"); + b.iter(move || parse_glsl(naga::ShaderStage::Vertex, &frag)); + //TODO: hangs for some reason! + //let comp = gather_inputs("tests/in/glsl", "comp"); + //b.iter(move || parse_glsl(naga::ShaderStage::Compute, &comp)); + }); +} + +#[cfg(feature = "wgsl-in")] +fn gather_modules() -> Vec { + let inputs = gather_inputs("tests/in", "wgsl"); + let mut frontend = naga::front::wgsl::Frontend::new(); + inputs + .iter() + .map(|input| { + let string = std::str::from_utf8(input).unwrap(); + frontend.parse(string).unwrap() + }) + .collect() +} +#[cfg(not(feature = "wgsl-in"))] +fn gather_modules() -> Vec { + Vec::new() +} + +fn validation(c: &mut Criterion) { + let inputs = gather_modules(); + let mut group = c.benchmark_group("valid"); + group.bench_function("safe", |b| { + let mut validator = naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::all(), + ); + b.iter(|| { + for input in inputs.iter() { + validator.validate(input).unwrap(); + } + }); + }); + group.bench_function("unsafe", |b| { + let mut validator = naga::valid::Validator::new( + naga::valid::ValidationFlags::empty(), + naga::valid::Capabilities::all(), + ); + b.iter(|| { + for input in inputs.iter() { + validator.validate(input).unwrap(); + } + }); + }); +} + +fn backends(c: &mut Criterion) { + let inputs = { + let mut validator = naga::valid::Validator::new( + naga::valid::ValidationFlags::empty(), + naga::valid::Capabilities::default(), + ); + let input_modules = gather_modules(); + input_modules + .into_iter() + .flat_map(|module| validator.validate(&module).ok().map(|info| (module, info))) + .collect::>() + }; + + let mut group = c.benchmark_group("back"); + #[cfg(feature = "wgsl-out")] + group.bench_function("wgsl", |b| { + b.iter(|| { + let mut string = String::new(); + let flags = naga::back::wgsl::WriterFlags::empty(); + for &(ref module, ref info) in inputs.iter() { + let mut writer = naga::back::wgsl::Writer::new(&mut string, flags); + writer.write(module, info).unwrap(); + string.clear(); + } + }); + }); + + #[cfg(feature = "spv-out")] + group.bench_function("spv", |b| { + b.iter(|| { + let mut data = Vec::new(); + let options = naga::back::spv::Options::default(); + for &(ref module, ref info) in inputs.iter() { + let mut writer = naga::back::spv::Writer::new(&options).unwrap(); + writer.write(module, info, None, &None, &mut data).unwrap(); + data.clear(); + } + }); + }); + #[cfg(feature = "spv-out")] + group.bench_function("spv-separate", |b| { + b.iter(|| { + let mut data = Vec::new(); + let options = naga::back::spv::Options::default(); + for &(ref module, ref info) in inputs.iter() { + let mut writer = naga::back::spv::Writer::new(&options).unwrap(); + for ep in module.entry_points.iter() { + let pipeline_options = naga::back::spv::PipelineOptions { + shader_stage: ep.stage, + entry_point: ep.name.clone(), + }; + writer + .write(module, info, Some(&pipeline_options), &None, &mut data) + .unwrap(); + data.clear(); + } + } + }); + }); + + #[cfg(feature = "msl-out")] + group.bench_function("msl", |b| { + b.iter(|| { + let mut string = String::new(); + let options = naga::back::msl::Options::default(); + for &(ref module, ref info) in inputs.iter() { + let pipeline_options = naga::back::msl::PipelineOptions::default(); + let mut writer = naga::back::msl::Writer::new(&mut string); + writer + .write(module, info, &options, &pipeline_options) + .unwrap(); + string.clear(); + } + }); + }); + + #[cfg(feature = "hlsl-out")] + group.bench_function("hlsl", |b| { + b.iter(|| { + let options = naga::back::hlsl::Options::default(); + let mut string = String::new(); + for &(ref module, ref info) in inputs.iter() { + let mut writer = naga::back::hlsl::Writer::new(&mut string, &options); + let _ = writer.write(module, info); // may fail on unimplemented things + string.clear(); + } + }); + }); + + #[cfg(feature = "glsl-out")] + group.bench_function("glsl-separate", |b| { + b.iter(|| { + let mut string = String::new(); + let options = naga::back::glsl::Options { + version: naga::back::glsl::Version::new_gles(320), + writer_flags: naga::back::glsl::WriterFlags::empty(), + binding_map: Default::default(), + zero_initialize_workgroup_memory: true, + }; + for &(ref module, ref info) in inputs.iter() { + for ep in module.entry_points.iter() { + let pipeline_options = naga::back::glsl::PipelineOptions { + shader_stage: ep.stage, + entry_point: ep.name.clone(), + multiview: None, + }; + + // might be `Err` if missing features + if let Ok(mut writer) = naga::back::glsl::Writer::new( + &mut string, + module, + info, + &options, + &pipeline_options, + naga::proc::BoundsCheckPolicies::default(), + ) { + let _ = writer.write(); // might be `Err` if unsupported + } + + string.clear(); + } + } + }); + }); +} + +criterion_group!(criterion, frontends, validation, backends,); +criterion_main!(criterion); diff --git a/naga/fuzz/.gitignore b/naga/fuzz/.gitignore new file mode 100644 index 0000000000..a0925114d6 --- /dev/null +++ b/naga/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +artifacts diff --git a/naga/fuzz/Cargo.toml b/naga/fuzz/Cargo.toml new file mode 100644 index 0000000000..4285142c06 --- /dev/null +++ b/naga/fuzz/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "naga-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" +license = "MIT OR Apache-2.0" + +[package.metadata] +cargo-fuzz = true + +[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dependencies] +arbitrary = { version = "1.3.2", features = ["derive"] } +libfuzzer-sys = "0.4" + +[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dependencies.naga] +path = ".." +version = "0.14.0" +features = ["arbitrary", "spv-in", "wgsl-in", "glsl-in"] + +[[bin]] +name = "spv_parser" +path = "fuzz_targets/spv_parser.rs" +test = false +doc = false + +[[bin]] +name = "wgsl_parser" +path = "fuzz_targets/wgsl_parser.rs" +test = false +doc = false + +[[bin]] +name = "glsl_parser" +path = "fuzz_targets/glsl_parser.rs" +test = false +doc = false + +[[bin]] +name = "ir" +path = "fuzz_targets/ir.rs" +test = false +doc = false diff --git a/naga/fuzz/fuzz_targets/glsl_parser.rs b/naga/fuzz/fuzz_targets/glsl_parser.rs new file mode 100644 index 0000000000..aed7ba981b --- /dev/null +++ b/naga/fuzz/fuzz_targets/glsl_parser.rs @@ -0,0 +1,49 @@ +#![no_main] +#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] +mod fuzz { + use arbitrary::Arbitrary; + use libfuzzer_sys::fuzz_target; + use naga::{ + front::glsl::{Frontend, Options}, + FastHashMap, ShaderStage, + }; + + #[derive(Debug, Arbitrary)] + enum ShaderStageProxy { + Vertex, + Fragment, + Compute, + } + + impl From for ShaderStage { + fn from(proxy: ShaderStageProxy) -> Self { + match proxy { + ShaderStageProxy::Vertex => ShaderStage::Vertex, + ShaderStageProxy::Fragment => ShaderStage::Fragment, + ShaderStageProxy::Compute => ShaderStage::Compute, + } + } + } + + #[derive(Debug, Arbitrary)] + struct OptionsProxy { + pub stage: ShaderStageProxy, + pub defines: FastHashMap, + } + + impl From for Options { + fn from(proxy: OptionsProxy) -> Self { + Options { + stage: proxy.stage.into(), + defines: proxy.defines, + } + } + } + + fuzz_target!(|data: (OptionsProxy, String)| { + let (options, source) = data; + // Ensure the parser can handle potentially malformed strings without crashing. + let mut parser = Frontend::default(); + let _result = parser.parse(&options.into(), &source); + }); +} diff --git a/naga/fuzz/fuzz_targets/ir.rs b/naga/fuzz/fuzz_targets/ir.rs new file mode 100644 index 0000000000..6768917c3b --- /dev/null +++ b/naga/fuzz/fuzz_targets/ir.rs @@ -0,0 +1,14 @@ +#![no_main] +#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] +mod fuzz { + use libfuzzer_sys::fuzz_target; + + fuzz_target!(|module: naga::Module| { + use naga::valid as v; + // Check if the module validates without errors. + //TODO: may also fuzz the flags and capabilities + let mut validator = + v::Validator::new(v::ValidationFlags::all(), v::Capabilities::default()); + let _result = validator.validate(&module); + }); +} diff --git a/naga/fuzz/fuzz_targets/spv_parser.rs b/naga/fuzz/fuzz_targets/spv_parser.rs new file mode 100644 index 0000000000..2b0fae2960 --- /dev/null +++ b/naga/fuzz/fuzz_targets/spv_parser.rs @@ -0,0 +1,12 @@ +#![no_main] +#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] +mod fuzz { + use libfuzzer_sys::fuzz_target; + use naga::front::spv::{Frontend, Options}; + + fuzz_target!(|data: Vec| { + // Ensure the parser can handle potentially malformed data without crashing. + let options = Options::default(); + let _result = Frontend::new(data.into_iter(), &options).parse(); + }); +} diff --git a/naga/fuzz/fuzz_targets/wgsl_parser.rs b/naga/fuzz/fuzz_targets/wgsl_parser.rs new file mode 100644 index 0000000000..7513d63d1d --- /dev/null +++ b/naga/fuzz/fuzz_targets/wgsl_parser.rs @@ -0,0 +1,11 @@ +#![no_main] +#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] +mod fuzz { + use libfuzzer_sys::fuzz_target; + use naga::front::wgsl::Frontend; + + fuzz_target!(|data: String| { + // Ensure the parser can handle potentially malformed strings without crashing. + let _result = Frontend::new().parse(&data); + }); +} diff --git a/naga/hlsl-snapshots/Cargo.toml b/naga/hlsl-snapshots/Cargo.toml new file mode 100644 index 0000000000..09104adfbc --- /dev/null +++ b/naga/hlsl-snapshots/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "hlsl-snapshots" +version = "0.1.0" +edition = "2021" +publish = false +license = "MIT OR Apache-2.0" + +[lib] +name = "hlsl_snapshots" +path = "src/lib.rs" +test = false + +[dependencies] +anyhow = "1" +nanoserde = "0.1.32" diff --git a/naga/hlsl-snapshots/src/lib.rs b/naga/hlsl-snapshots/src/lib.rs new file mode 100644 index 0000000000..616aa73f01 --- /dev/null +++ b/naga/hlsl-snapshots/src/lib.rs @@ -0,0 +1,97 @@ +use std::{error::Error, fmt::Display, fs, io, path::Path}; + +use anyhow::{anyhow, ensure}; +use nanoserde::{self, DeRon, DeRonErr, SerRon}; + +#[derive(Debug)] +struct BadRonParse(BadRonParseKind); + +impl Display for BadRonParse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "failed to read RON configuration of HLSL snapshot test") + } +} + +impl Error for BadRonParse { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.0) + } +} + +#[derive(Debug)] +enum BadRonParseKind { + Read { source: io::Error }, + Parse { source: DeRonErr }, + Empty, +} + +impl Display for BadRonParseKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BadRonParseKind::Read { source } => Display::fmt(source, f), + BadRonParseKind::Parse { source } => Display::fmt(source, f), + BadRonParseKind::Empty => write!(f, "no configuration was specified"), + } + } +} + +impl Error for BadRonParseKind { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + BadRonParseKind::Read { source } => source.source(), + BadRonParseKind::Parse { source } => source.source(), + BadRonParseKind::Empty => None, + } + } +} + +#[derive(Debug, DeRon, SerRon)] +pub struct Config { + pub vertex: Vec, + pub fragment: Vec, + pub compute: Vec, +} + +impl Config { + pub fn empty() -> Self { + Self { + vertex: Default::default(), + fragment: Default::default(), + compute: Default::default(), + } + } + + pub fn from_path(path: impl AsRef) -> anyhow::Result { + let path = path.as_ref(); + let raw_config = fs::read_to_string(path) + .map_err(|source| BadRonParse(BadRonParseKind::Read { source }))?; + let config = Config::deserialize_ron(&raw_config) + .map_err(|source| BadRonParse(BadRonParseKind::Parse { source }))?; + ensure!(!config.is_empty(), BadRonParse(BadRonParseKind::Empty)); + Ok(config) + } + + pub fn to_file(&self, path: impl AsRef) -> anyhow::Result<()> { + let path = path.as_ref(); + let mut s = self.serialize_ron(); + s.push('\n'); + fs::write(path, &s).map_err(|e| anyhow!("failed to write to {}: {e}", path.display())) + } + + pub fn is_empty(&self) -> bool { + let Self { + vertex, + fragment, + compute, + } = self; + vertex.is_empty() && fragment.is_empty() && compute.is_empty() + } +} + +#[derive(Debug, DeRon, SerRon)] +pub struct ConfigItem { + pub entry_point: String, + /// See also + /// . + pub target_profile: String, +} diff --git a/naga/src/arena.rs b/naga/src/arena.rs new file mode 100644 index 0000000000..c37538667f --- /dev/null +++ b/naga/src/arena.rs @@ -0,0 +1,772 @@ +use std::{cmp::Ordering, fmt, hash, marker::PhantomData, num::NonZeroU32, ops}; + +/// An unique index in the arena array that a handle points to. +/// The "non-zero" part ensures that an `Option>` has +/// the same size and representation as `Handle`. +type Index = NonZeroU32; + +use crate::{FastIndexSet, Span}; + +#[derive(Clone, Copy, Debug, thiserror::Error, PartialEq)] +#[error("Handle {index} of {kind} is either not present, or inaccessible yet")] +pub struct BadHandle { + pub kind: &'static str, + pub index: usize, +} + +impl BadHandle { + fn new(handle: Handle) -> Self { + Self { + kind: std::any::type_name::(), + index: handle.index(), + } + } +} + +/// A strongly typed reference to an arena item. +/// +/// A `Handle` value can be used as an index into an [`Arena`] or [`UniqueArena`]. +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +#[cfg_attr( + any(feature = "serialize", feature = "deserialize"), + serde(transparent) +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct Handle { + index: Index, + #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(skip))] + marker: PhantomData, +} + +impl Clone for Handle { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Handle {} + +impl PartialEq for Handle { + fn eq(&self, other: &Self) -> bool { + self.index == other.index + } +} + +impl Eq for Handle {} + +impl PartialOrd for Handle { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Handle { + fn cmp(&self, other: &Self) -> Ordering { + self.index.cmp(&other.index) + } +} + +impl fmt::Debug for Handle { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "[{}]", self.index) + } +} + +impl hash::Hash for Handle { + fn hash(&self, hasher: &mut H) { + self.index.hash(hasher) + } +} + +impl Handle { + #[cfg(test)] + pub const DUMMY: Self = Handle { + index: unsafe { NonZeroU32::new_unchecked(u32::MAX) }, + marker: PhantomData, + }; + + pub(crate) const fn new(index: Index) -> Self { + Handle { + index, + marker: PhantomData, + } + } + + /// Returns the zero-based index of this handle. + pub const fn index(self) -> usize { + let index = self.index.get() - 1; + index as usize + } + + /// Convert a `usize` index into a `Handle`. + fn from_usize(index: usize) -> Self { + let handle_index = u32::try_from(index + 1) + .ok() + .and_then(Index::new) + .expect("Failed to insert into arena. Handle overflows"); + Handle::new(handle_index) + } + + /// Convert a `usize` index into a `Handle`, without range checks. + const unsafe fn from_usize_unchecked(index: usize) -> Self { + Handle::new(Index::new_unchecked((index + 1) as u32)) + } +} + +/// A strongly typed range of handles. +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +#[cfg_attr( + any(feature = "serialize", feature = "deserialize"), + serde(transparent) +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct Range { + inner: ops::Range, + #[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(skip))] + marker: PhantomData, +} + +impl Range { + pub(crate) const fn erase_type(self) -> Range<()> { + let Self { inner, marker: _ } = self; + Range { + inner, + marker: PhantomData, + } + } +} + +// NOTE: Keep this diagnostic in sync with that of [`BadHandle`]. +#[derive(Clone, Debug, thiserror::Error)] +#[error("Handle range {range:?} of {kind} is either not present, or inaccessible yet")] +pub struct BadRangeError { + // This error is used for many `Handle` types, but there's no point in making this generic, so + // we just flatten them all to `Handle<()>` here. + kind: &'static str, + range: Range<()>, +} + +impl BadRangeError { + pub fn new(range: Range) -> Self { + Self { + kind: std::any::type_name::(), + range: range.erase_type(), + } + } +} + +impl Clone for Range { + fn clone(&self) -> Self { + Range { + inner: self.inner.clone(), + marker: self.marker, + } + } +} + +impl fmt::Debug for Range { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "[{}..{}]", self.inner.start + 1, self.inner.end) + } +} + +impl Iterator for Range { + type Item = Handle; + fn next(&mut self) -> Option { + if self.inner.start < self.inner.end { + self.inner.start += 1; + Some(Handle { + index: NonZeroU32::new(self.inner.start).unwrap(), + marker: self.marker, + }) + } else { + None + } + } +} + +impl Range { + /// Return a range enclosing handles `first` through `last`, inclusive. + pub fn new_from_bounds(first: Handle, last: Handle) -> Self { + Self { + inner: (first.index() as u32)..(last.index() as u32 + 1), + marker: Default::default(), + } + } + + /// return the first and last handles included in `self`. + /// + /// If `self` is an empty range, there are no handles included, so + /// return `None`. + pub fn first_and_last(&self) -> Option<(Handle, Handle)> { + if self.inner.start < self.inner.end { + Some(( + // `Range::new_from_bounds` expects a 1-based, start- and + // end-inclusive range, but `self.inner` is a zero-based, + // end-exclusive range. + Handle::new(Index::new(self.inner.start + 1).unwrap()), + Handle::new(Index::new(self.inner.end).unwrap()), + )) + } else { + None + } + } + + /// Return the zero-based index range covered by `self`. + pub fn zero_based_index_range(&self) -> ops::Range { + self.inner.clone() + } + + /// Construct a `Range` that covers the zero-based indices in `inner`. + pub fn from_zero_based_index_range(inner: ops::Range, arena: &Arena) -> Self { + // Since `inner` is a `Range`, we only need to check that + // the start and end are well-ordered, and that the end fits + // within `arena`. + assert!(inner.start <= inner.end); + assert!(inner.end as usize <= arena.len()); + Self { + inner, + marker: Default::default(), + } + } +} + +/// An arena holding some kind of component (e.g., type, constant, +/// instruction, etc.) that can be referenced. +/// +/// Adding new items to the arena produces a strongly-typed [`Handle`]. +/// The arena can be indexed using the given handle to obtain +/// a reference to the stored item. +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "serialize", serde(transparent))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(test, derive(PartialEq))] +pub struct Arena { + /// Values of this arena. + data: Vec, + #[cfg_attr(feature = "serialize", serde(skip))] + span_info: Vec, +} + +impl Default for Arena { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Debug for Arena { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_map().entries(self.iter()).finish() + } +} + +impl Arena { + /// Create a new arena with no initial capacity allocated. + pub const fn new() -> Self { + Arena { + data: Vec::new(), + span_info: Vec::new(), + } + } + + /// Extracts the inner vector. + #[allow(clippy::missing_const_for_fn)] // ignore due to requirement of #![feature(const_precise_live_drops)] + pub fn into_inner(self) -> Vec { + self.data + } + + /// Returns the current number of items stored in this arena. + pub fn len(&self) -> usize { + self.data.len() + } + + /// Returns `true` if the arena contains no elements. + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Returns an iterator over the items stored in this arena, returning both + /// the item's handle and a reference to it. + pub fn iter(&self) -> impl DoubleEndedIterator, &T)> { + self.data + .iter() + .enumerate() + .map(|(i, v)| unsafe { (Handle::from_usize_unchecked(i), v) }) + } + + /// Returns a iterator over the items stored in this arena, + /// returning both the item's handle and a mutable reference to it. + pub fn iter_mut(&mut self) -> impl DoubleEndedIterator, &mut T)> { + self.data + .iter_mut() + .enumerate() + .map(|(i, v)| unsafe { (Handle::from_usize_unchecked(i), v) }) + } + + /// Adds a new value to the arena, returning a typed handle. + pub fn append(&mut self, value: T, span: Span) -> Handle { + let index = self.data.len(); + self.data.push(value); + self.span_info.push(span); + Handle::from_usize(index) + } + + /// Fetch a handle to an existing type. + pub fn fetch_if bool>(&self, fun: F) -> Option> { + self.data + .iter() + .position(fun) + .map(|index| unsafe { Handle::from_usize_unchecked(index) }) + } + + /// Adds a value with a custom check for uniqueness: + /// returns a handle pointing to + /// an existing element if the check succeeds, or adds a new + /// element otherwise. + pub fn fetch_if_or_append bool>( + &mut self, + value: T, + span: Span, + fun: F, + ) -> Handle { + if let Some(index) = self.data.iter().position(|d| fun(d, &value)) { + unsafe { Handle::from_usize_unchecked(index) } + } else { + self.append(value, span) + } + } + + /// Adds a value with a check for uniqueness, where the check is plain comparison. + pub fn fetch_or_append(&mut self, value: T, span: Span) -> Handle + where + T: PartialEq, + { + self.fetch_if_or_append(value, span, T::eq) + } + + pub fn try_get(&self, handle: Handle) -> Result<&T, BadHandle> { + self.data + .get(handle.index()) + .ok_or_else(|| BadHandle::new(handle)) + } + + /// Get a mutable reference to an element in the arena. + pub fn get_mut(&mut self, handle: Handle) -> &mut T { + self.data.get_mut(handle.index()).unwrap() + } + + /// Get the range of handles from a particular number of elements to the end. + pub fn range_from(&self, old_length: usize) -> Range { + Range { + inner: old_length as u32..self.data.len() as u32, + marker: PhantomData, + } + } + + /// Clears the arena keeping all allocations + pub fn clear(&mut self) { + self.data.clear() + } + + pub fn get_span(&self, handle: Handle) -> Span { + *self + .span_info + .get(handle.index()) + .unwrap_or(&Span::default()) + } + + /// Assert that `handle` is valid for this arena. + pub fn check_contains_handle(&self, handle: Handle) -> Result<(), BadHandle> { + if handle.index() < self.data.len() { + Ok(()) + } else { + Err(BadHandle::new(handle)) + } + } + + /// Assert that `range` is valid for this arena. + pub fn check_contains_range(&self, range: &Range) -> Result<(), BadRangeError> { + // Since `range.inner` is a `Range`, we only need to check that the + // start precedes the end, and that the end is in range. + if range.inner.start > range.inner.end { + return Err(BadRangeError::new(range.clone())); + } + + // Empty ranges are tolerated: they can be produced by compaction. + if range.inner.start == range.inner.end { + return Ok(()); + } + + // `range.inner` is zero-based, but end-exclusive, so `range.inner.end` + // is actually the right one-based index for the last handle within the + // range. + let last_handle = Handle::new(range.inner.end.try_into().unwrap()); + if self.check_contains_handle(last_handle).is_err() { + return Err(BadRangeError::new(range.clone())); + } + + Ok(()) + } + + #[cfg(feature = "compact")] + pub(crate) fn retain_mut

(&mut self, mut predicate: P) + where + P: FnMut(Handle, &mut T) -> bool, + { + let mut index = 0; + let mut retained = 0; + self.data.retain_mut(|elt| { + let handle = Handle::new(Index::new(index as u32 + 1).unwrap()); + let keep = predicate(handle, elt); + + // Since `predicate` needs mutable access to each element, + // we can't feasibly call it twice, so we have to compact + // spans by hand in parallel as part of this iteration. + if keep { + self.span_info[retained] = self.span_info[index]; + retained += 1; + } + + index += 1; + keep + }); + + self.span_info.truncate(retained); + } +} + +#[cfg(feature = "deserialize")] +impl<'de, T> serde::Deserialize<'de> for Arena +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let data = Vec::deserialize(deserializer)?; + let span_info = std::iter::repeat(Span::default()) + .take(data.len()) + .collect(); + + Ok(Self { data, span_info }) + } +} + +impl ops::Index> for Arena { + type Output = T; + fn index(&self, handle: Handle) -> &T { + &self.data[handle.index()] + } +} + +impl ops::IndexMut> for Arena { + fn index_mut(&mut self, handle: Handle) -> &mut T { + &mut self.data[handle.index()] + } +} + +impl ops::Index> for Arena { + type Output = [T]; + fn index(&self, range: Range) -> &[T] { + &self.data[range.inner.start as usize..range.inner.end as usize] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn append_non_unique() { + let mut arena: Arena = Arena::new(); + let t1 = arena.append(0, Default::default()); + let t2 = arena.append(0, Default::default()); + assert!(t1 != t2); + assert!(arena[t1] == arena[t2]); + } + + #[test] + fn append_unique() { + let mut arena: Arena = Arena::new(); + let t1 = arena.append(0, Default::default()); + let t2 = arena.append(1, Default::default()); + assert!(t1 != t2); + assert!(arena[t1] != arena[t2]); + } + + #[test] + fn fetch_or_append_non_unique() { + let mut arena: Arena = Arena::new(); + let t1 = arena.fetch_or_append(0, Default::default()); + let t2 = arena.fetch_or_append(0, Default::default()); + assert!(t1 == t2); + assert!(arena[t1] == arena[t2]) + } + + #[test] + fn fetch_or_append_unique() { + let mut arena: Arena = Arena::new(); + let t1 = arena.fetch_or_append(0, Default::default()); + let t2 = arena.fetch_or_append(1, Default::default()); + assert!(t1 != t2); + assert!(arena[t1] != arena[t2]); + } +} + +/// An arena whose elements are guaranteed to be unique. +/// +/// A `UniqueArena` holds a set of unique values of type `T`, each with an +/// associated [`Span`]. Inserting a value returns a `Handle`, which can be +/// used to index the `UniqueArena` and obtain shared access to the `T` element. +/// Access via a `Handle` is an array lookup - no hash lookup is necessary. +/// +/// The element type must implement `Eq` and `Hash`. Insertions of equivalent +/// elements, according to `Eq`, all return the same `Handle`. +/// +/// Once inserted, elements may not be mutated. +/// +/// `UniqueArena` is similar to [`Arena`]: If `Arena` is vector-like, +/// `UniqueArena` is `HashSet`-like. +#[cfg_attr(feature = "clone", derive(Clone))] +pub struct UniqueArena { + set: FastIndexSet, + + /// Spans for the elements, indexed by handle. + /// + /// The length of this vector is always equal to `set.len()`. `FastIndexSet` + /// promises that its elements "are indexed in a compact range, without + /// holes in the range 0..set.len()", so we can always use the indices + /// returned by insertion as indices into this vector. + span_info: Vec, +} + +impl UniqueArena { + /// Create a new arena with no initial capacity allocated. + pub fn new() -> Self { + UniqueArena { + set: FastIndexSet::default(), + span_info: Vec::new(), + } + } + + /// Return the current number of items stored in this arena. + pub fn len(&self) -> usize { + self.set.len() + } + + /// Return `true` if the arena contains no elements. + pub fn is_empty(&self) -> bool { + self.set.is_empty() + } + + /// Clears the arena, keeping all allocations. + pub fn clear(&mut self) { + self.set.clear(); + self.span_info.clear(); + } + + /// Return the span associated with `handle`. + /// + /// If a value has been inserted multiple times, the span returned is the + /// one provided with the first insertion. + pub fn get_span(&self, handle: Handle) -> Span { + *self + .span_info + .get(handle.index()) + .unwrap_or(&Span::default()) + } + + #[cfg(feature = "compact")] + pub(crate) fn drain_all(&mut self) -> UniqueArenaDrain { + UniqueArenaDrain { + inner_elts: self.set.drain(..), + inner_spans: self.span_info.drain(..), + index: Index::new(1).unwrap(), + } + } +} + +#[cfg(feature = "compact")] +pub(crate) struct UniqueArenaDrain<'a, T> { + inner_elts: indexmap::set::Drain<'a, T>, + inner_spans: std::vec::Drain<'a, Span>, + index: Index, +} + +#[cfg(feature = "compact")] +impl<'a, T> Iterator for UniqueArenaDrain<'a, T> { + type Item = (Handle, T, Span); + + fn next(&mut self) -> Option { + match self.inner_elts.next() { + Some(elt) => { + let handle = Handle::new(self.index); + self.index = self.index.checked_add(1).unwrap(); + let span = self.inner_spans.next().unwrap(); + Some((handle, elt, span)) + } + None => None, + } + } +} + +impl UniqueArena { + /// Returns an iterator over the items stored in this arena, returning both + /// the item's handle and a reference to it. + pub fn iter(&self) -> impl DoubleEndedIterator, &T)> { + self.set.iter().enumerate().map(|(i, v)| { + let position = i + 1; + let index = unsafe { Index::new_unchecked(position as u32) }; + (Handle::new(index), v) + }) + } + + /// Insert a new value into the arena. + /// + /// Return a [`Handle`], which can be used to index this arena to get a + /// shared reference to the element. + /// + /// If this arena already contains an element that is `Eq` to `value`, + /// return a `Handle` to the existing element, and drop `value`. + /// + /// If `value` is inserted into the arena, associate `span` with + /// it. An element's span can be retrieved with the [`get_span`] + /// method. + /// + /// [`Handle`]: Handle + /// [`get_span`]: UniqueArena::get_span + pub fn insert(&mut self, value: T, span: Span) -> Handle { + let (index, added) = self.set.insert_full(value); + + if added { + debug_assert!(index == self.span_info.len()); + self.span_info.push(span); + } + + debug_assert!(self.set.len() == self.span_info.len()); + + Handle::from_usize(index) + } + + /// Replace an old value with a new value. + /// + /// # Panics + /// + /// - if the old value is not in the arena + /// - if the new value already exists in the arena + pub fn replace(&mut self, old: Handle, new: T) { + let (index, added) = self.set.insert_full(new); + assert!(added && index == self.set.len() - 1); + + self.set.swap_remove_index(old.index()).unwrap(); + } + + /// Return this arena's handle for `value`, if present. + /// + /// If this arena already contains an element equal to `value`, + /// return its handle. Otherwise, return `None`. + pub fn get(&self, value: &T) -> Option> { + self.set + .get_index_of(value) + .map(|index| unsafe { Handle::from_usize_unchecked(index) }) + } + + /// Return this arena's value at `handle`, if that is a valid handle. + pub fn get_handle(&self, handle: Handle) -> Result<&T, BadHandle> { + self.set + .get_index(handle.index()) + .ok_or_else(|| BadHandle::new(handle)) + } + + /// Assert that `handle` is valid for this arena. + pub fn check_contains_handle(&self, handle: Handle) -> Result<(), BadHandle> { + if handle.index() < self.set.len() { + Ok(()) + } else { + Err(BadHandle::new(handle)) + } + } +} + +impl Default for UniqueArena { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Debug for UniqueArena { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_map().entries(self.iter()).finish() + } +} + +impl ops::Index> for UniqueArena { + type Output = T; + fn index(&self, handle: Handle) -> &T { + &self.set[handle.index()] + } +} + +#[cfg(feature = "serialize")] +impl serde::Serialize for UniqueArena +where + T: Eq + hash::Hash + serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.set.serialize(serializer) + } +} + +#[cfg(feature = "deserialize")] +impl<'de, T> serde::Deserialize<'de> for UniqueArena +where + T: Eq + hash::Hash + serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let set = FastIndexSet::deserialize(deserializer)?; + let span_info = std::iter::repeat(Span::default()).take(set.len()).collect(); + + Ok(Self { set, span_info }) + } +} + +//Note: largely borrowed from `HashSet` implementation +#[cfg(feature = "arbitrary")] +impl<'a, T> arbitrary::Arbitrary<'a> for UniqueArena +where + T: Eq + hash::Hash + arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let mut arena = Self::default(); + for elem in u.arbitrary_iter()? { + arena.set.insert(elem?); + arena.span_info.push(Span::UNDEFINED); + } + Ok(arena) + } + + fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result { + let mut arena = Self::default(); + for elem in u.arbitrary_take_rest_iter()? { + arena.set.insert(elem?); + arena.span_info.push(Span::UNDEFINED); + } + Ok(arena) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + let depth_hint = ::size_hint(depth); + arbitrary::size_hint::and(depth_hint, (0, None)) + } +} diff --git a/naga/src/back/dot/mod.rs b/naga/src/back/dot/mod.rs new file mode 100644 index 0000000000..1556371df1 --- /dev/null +++ b/naga/src/back/dot/mod.rs @@ -0,0 +1,703 @@ +/*! +Backend for [DOT][dot] (Graphviz). + +This backend writes a graph in the DOT language, for the ease +of IR inspection and debugging. + +[dot]: https://graphviz.org/doc/info/lang.html +*/ + +use crate::{ + arena::Handle, + valid::{FunctionInfo, ModuleInfo}, +}; + +use std::{ + borrow::Cow, + fmt::{Error as FmtError, Write as _}, +}; + +/// Configuration options for the dot backend +#[derive(Clone, Default)] +pub struct Options { + /// Only emit function bodies + pub cfg_only: bool, +} + +/// Identifier used to address a graph node +type NodeId = usize; + +/// Stores the target nodes for control flow statements +#[derive(Default, Clone, Copy)] +struct Targets { + /// The node, if some, where continue operations will land + continue_target: Option, + /// The node, if some, where break operations will land + break_target: Option, +} + +/// Stores information about the graph of statements +#[derive(Default)] +struct StatementGraph { + /// List of node names + nodes: Vec<&'static str>, + /// List of edges of the control flow, the items are defined as + /// (from, to, label) + flow: Vec<(NodeId, NodeId, &'static str)>, + /// List of implicit edges of the control flow, used for jump + /// operations such as continue or break, the items are defined as + /// (from, to, label, color_id) + jumps: Vec<(NodeId, NodeId, &'static str, usize)>, + /// List of dependency relationships between a statement node and + /// expressions + dependencies: Vec<(NodeId, Handle, &'static str)>, + /// List of expression emitted by statement node + emits: Vec<(NodeId, Handle)>, + /// List of function call by statement node + calls: Vec<(NodeId, Handle)>, +} + +impl StatementGraph { + /// Adds a new block to the statement graph, returning the first and last node, respectively + fn add(&mut self, block: &[crate::Statement], targets: Targets) -> (NodeId, NodeId) { + use crate::Statement as S; + + // The first node of the block isn't a statement but a virtual node + let root = self.nodes.len(); + self.nodes.push(if root == 0 { "Root" } else { "Node" }); + // Track the last placed node, this will be returned to the caller and + // will also be used to generate the control flow edges + let mut last_node = root; + for statement in block { + // Reserve a new node for the current statement and link it to the + // node of the previous statement + let id = self.nodes.len(); + self.flow.push((last_node, id, "")); + self.nodes.push(""); // reserve space + + // Track the node identifier for the merge node, the merge node is + // the last node of a statement, normally this is the node itself, + // but for control flow statements such as `if`s and `switch`s this + // is a virtual node where all branches merge back. + let mut merge_id = id; + + self.nodes[id] = match *statement { + S::Emit(ref range) => { + for handle in range.clone() { + self.emits.push((id, handle)); + } + "Emit" + } + S::Kill => "Kill", //TODO: link to the beginning + S::Break => { + // Try to link to the break target, otherwise produce + // a broken connection + if let Some(target) = targets.break_target { + self.jumps.push((id, target, "Break", 5)) + } else { + self.jumps.push((id, root, "Broken", 7)) + } + "Break" + } + S::Continue => { + // Try to link to the continue target, otherwise produce + // a broken connection + if let Some(target) = targets.continue_target { + self.jumps.push((id, target, "Continue", 5)) + } else { + self.jumps.push((id, root, "Broken", 7)) + } + "Continue" + } + S::Barrier(_flags) => "Barrier", + S::Block(ref b) => { + let (other, last) = self.add(b, targets); + self.flow.push((id, other, "")); + // All following nodes should connect to the end of the block + // statement so change the merge id to it. + merge_id = last; + "Block" + } + S::If { + condition, + ref accept, + ref reject, + } => { + self.dependencies.push((id, condition, "condition")); + let (accept_id, accept_last) = self.add(accept, targets); + self.flow.push((id, accept_id, "accept")); + let (reject_id, reject_last) = self.add(reject, targets); + self.flow.push((id, reject_id, "reject")); + + // Create a merge node, link the branches to it and set it + // as the merge node to make the next statement node link to it + merge_id = self.nodes.len(); + self.nodes.push("Merge"); + self.flow.push((accept_last, merge_id, "")); + self.flow.push((reject_last, merge_id, "")); + + "If" + } + S::Switch { + selector, + ref cases, + } => { + self.dependencies.push((id, selector, "selector")); + + // Create a merge node and set it as the merge node to make + // the next statement node link to it + merge_id = self.nodes.len(); + self.nodes.push("Merge"); + + // Create a new targets structure and set the break target + // to the merge node + let mut targets = targets; + targets.break_target = Some(merge_id); + + for case in cases { + let (case_id, case_last) = self.add(&case.body, targets); + let label = match case.value { + crate::SwitchValue::Default => "default", + _ => "case", + }; + self.flow.push((id, case_id, label)); + // Link the last node of the branch to the merge node + self.flow.push((case_last, merge_id, "")); + } + "Switch" + } + S::Loop { + ref body, + ref continuing, + break_if, + } => { + // Create a new targets structure and set the break target + // to the merge node, this must happen before generating the + // continuing block since it can break. + let mut targets = targets; + targets.break_target = Some(id); + + let (continuing_id, continuing_last) = self.add(continuing, targets); + + // Set the the continue target to the beginning + // of the newly generated continuing block + targets.continue_target = Some(continuing_id); + + let (body_id, body_last) = self.add(body, targets); + + self.flow.push((id, body_id, "body")); + + // Link the last node of the body to the continuing block + self.flow.push((body_last, continuing_id, "continuing")); + // Link the last node of the continuing block back to the + // beginning of the loop body + self.flow.push((continuing_last, body_id, "continuing")); + + if let Some(expr) = break_if { + self.dependencies.push((continuing_id, expr, "break if")); + } + + "Loop" + } + S::Return { value } => { + if let Some(expr) = value { + self.dependencies.push((id, expr, "value")); + } + "Return" + } + S::Store { pointer, value } => { + self.dependencies.push((id, value, "value")); + self.emits.push((id, pointer)); + "Store" + } + S::ImageStore { + image, + coordinate, + array_index, + value, + } => { + self.dependencies.push((id, image, "image")); + self.dependencies.push((id, coordinate, "coordinate")); + if let Some(expr) = array_index { + self.dependencies.push((id, expr, "array_index")); + } + self.dependencies.push((id, value, "value")); + "ImageStore" + } + S::Call { + function, + ref arguments, + result, + } => { + for &arg in arguments { + self.dependencies.push((id, arg, "arg")); + } + if let Some(expr) = result { + self.emits.push((id, expr)); + } + self.calls.push((id, function)); + "Call" + } + S::Atomic { + pointer, + ref fun, + value, + result, + } => { + self.emits.push((id, result)); + self.dependencies.push((id, pointer, "pointer")); + self.dependencies.push((id, value, "value")); + if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun { + self.dependencies.push((id, cmp, "cmp")); + } + "Atomic" + } + S::WorkGroupUniformLoad { pointer, result } => { + self.emits.push((id, result)); + self.dependencies.push((id, pointer, "pointer")); + "WorkGroupUniformLoad" + } + S::RayQuery { query, ref fun } => { + self.dependencies.push((id, query, "query")); + match *fun { + crate::RayQueryFunction::Initialize { + acceleration_structure, + descriptor, + } => { + self.dependencies.push(( + id, + acceleration_structure, + "acceleration_structure", + )); + self.dependencies.push((id, descriptor, "descriptor")); + "RayQueryInitialize" + } + crate::RayQueryFunction::Proceed { result } => { + self.emits.push((id, result)); + "RayQueryProceed" + } + crate::RayQueryFunction::Terminate => "RayQueryTerminate", + } + } + }; + // Set the last node to the merge node + last_node = merge_id; + } + (root, last_node) + } +} + +#[allow(clippy::manual_unwrap_or)] +fn name(option: &Option) -> &str { + match *option { + Some(ref name) => name, + None => "", + } +} + +/// set39 color scheme from +const COLORS: &[&str] = &[ + "white", // pattern starts at 1 + "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", + "#d9d9d9", +]; + +fn write_fun( + output: &mut String, + prefix: String, + fun: &crate::Function, + info: Option<&FunctionInfo>, + options: &Options, +) -> Result<(), FmtError> { + writeln!(output, "\t\tnode [ style=filled ]")?; + + if !options.cfg_only { + for (handle, var) in fun.local_variables.iter() { + writeln!( + output, + "\t\t{}_l{} [ shape=hexagon label=\"{:?} '{}'\" ]", + prefix, + handle.index(), + handle, + name(&var.name), + )?; + } + + write_function_expressions(output, &prefix, fun, info)?; + } + + let mut sg = StatementGraph::default(); + sg.add(&fun.body, Targets::default()); + for (index, label) in sg.nodes.into_iter().enumerate() { + writeln!( + output, + "\t\t{prefix}_s{index} [ shape=square label=\"{label}\" ]", + )?; + } + for (from, to, label) in sg.flow { + writeln!( + output, + "\t\t{prefix}_s{from} -> {prefix}_s{to} [ arrowhead=tee label=\"{label}\" ]", + )?; + } + for (from, to, label, color_id) in sg.jumps { + writeln!( + output, + "\t\t{}_s{} -> {}_s{} [ arrowhead=tee style=dashed color=\"{}\" label=\"{}\" ]", + prefix, from, prefix, to, COLORS[color_id], label, + )?; + } + + if !options.cfg_only { + for (to, expr, label) in sg.dependencies { + writeln!( + output, + "\t\t{}_e{} -> {}_s{} [ label=\"{}\" ]", + prefix, + expr.index(), + prefix, + to, + label, + )?; + } + for (from, to) in sg.emits { + writeln!( + output, + "\t\t{}_s{} -> {}_e{} [ style=dotted ]", + prefix, + from, + prefix, + to.index(), + )?; + } + } + + for (from, function) in sg.calls { + writeln!( + output, + "\t\t{}_s{} -> f{}_s0", + prefix, + from, + function.index(), + )?; + } + + Ok(()) +} + +fn write_function_expressions( + output: &mut String, + prefix: &str, + fun: &crate::Function, + info: Option<&FunctionInfo>, +) -> Result<(), FmtError> { + enum Payload<'a> { + Arguments(&'a [Handle]), + Local(Handle), + Global(Handle), + } + + let mut edges = crate::FastHashMap::<&str, _>::default(); + let mut payload = None; + for (handle, expression) in fun.expressions.iter() { + use crate::Expression as E; + let (label, color_id) = match *expression { + E::Literal(_) => ("Literal".into(), 2), + E::Constant(_) => ("Constant".into(), 2), + E::ZeroValue(_) => ("ZeroValue".into(), 2), + E::Compose { ref components, .. } => { + payload = Some(Payload::Arguments(components)); + ("Compose".into(), 3) + } + E::Access { base, index } => { + edges.insert("base", base); + edges.insert("index", index); + ("Access".into(), 1) + } + E::AccessIndex { base, index } => { + edges.insert("base", base); + (format!("AccessIndex[{index}]").into(), 1) + } + E::Splat { size, value } => { + edges.insert("value", value); + (format!("Splat{size:?}").into(), 3) + } + E::Swizzle { + size, + vector, + pattern, + } => { + edges.insert("vector", vector); + (format!("Swizzle{:?}", &pattern[..size as usize]).into(), 3) + } + E::FunctionArgument(index) => (format!("Argument[{index}]").into(), 1), + E::GlobalVariable(h) => { + payload = Some(Payload::Global(h)); + ("Global".into(), 2) + } + E::LocalVariable(h) => { + payload = Some(Payload::Local(h)); + ("Local".into(), 1) + } + E::Load { pointer } => { + edges.insert("pointer", pointer); + ("Load".into(), 4) + } + E::ImageSample { + image, + sampler, + gather, + coordinate, + array_index, + offset: _, + level, + depth_ref, + } => { + edges.insert("image", image); + edges.insert("sampler", sampler); + edges.insert("coordinate", coordinate); + if let Some(expr) = array_index { + edges.insert("array_index", expr); + } + match level { + crate::SampleLevel::Auto => {} + crate::SampleLevel::Zero => {} + crate::SampleLevel::Exact(expr) => { + edges.insert("level", expr); + } + crate::SampleLevel::Bias(expr) => { + edges.insert("bias", expr); + } + crate::SampleLevel::Gradient { x, y } => { + edges.insert("grad_x", x); + edges.insert("grad_y", y); + } + } + if let Some(expr) = depth_ref { + edges.insert("depth_ref", expr); + } + let string = match gather { + Some(component) => Cow::Owned(format!("ImageGather{component:?}")), + _ => Cow::Borrowed("ImageSample"), + }; + (string, 5) + } + E::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => { + edges.insert("image", image); + edges.insert("coordinate", coordinate); + if let Some(expr) = array_index { + edges.insert("array_index", expr); + } + if let Some(sample) = sample { + edges.insert("sample", sample); + } + if let Some(level) = level { + edges.insert("level", level); + } + ("ImageLoad".into(), 5) + } + E::ImageQuery { image, query } => { + edges.insert("image", image); + let args = match query { + crate::ImageQuery::Size { level } => { + if let Some(expr) = level { + edges.insert("level", expr); + } + Cow::from("ImageSize") + } + _ => Cow::Owned(format!("{query:?}")), + }; + (args, 7) + } + E::Unary { op, expr } => { + edges.insert("expr", expr); + (format!("{op:?}").into(), 6) + } + E::Binary { op, left, right } => { + edges.insert("left", left); + edges.insert("right", right); + (format!("{op:?}").into(), 6) + } + E::Select { + condition, + accept, + reject, + } => { + edges.insert("condition", condition); + edges.insert("accept", accept); + edges.insert("reject", reject); + ("Select".into(), 3) + } + E::Derivative { axis, ctrl, expr } => { + edges.insert("", expr); + (format!("d{axis:?}{ctrl:?}").into(), 8) + } + E::Relational { fun, argument } => { + edges.insert("arg", argument); + (format!("{fun:?}").into(), 6) + } + E::Math { + fun, + arg, + arg1, + arg2, + arg3, + } => { + edges.insert("arg", arg); + if let Some(expr) = arg1 { + edges.insert("arg1", expr); + } + if let Some(expr) = arg2 { + edges.insert("arg2", expr); + } + if let Some(expr) = arg3 { + edges.insert("arg3", expr); + } + (format!("{fun:?}").into(), 7) + } + E::As { + kind, + expr, + convert, + } => { + edges.insert("", expr); + let string = match convert { + Some(width) => format!("Convert<{kind:?},{width}>"), + None => format!("Bitcast<{kind:?}>"), + }; + (string.into(), 3) + } + E::CallResult(_function) => ("CallResult".into(), 4), + E::AtomicResult { .. } => ("AtomicResult".into(), 4), + E::WorkGroupUniformLoadResult { .. } => ("WorkGroupUniformLoadResult".into(), 4), + E::ArrayLength(expr) => { + edges.insert("", expr); + ("ArrayLength".into(), 7) + } + E::RayQueryProceedResult => ("rayQueryProceedResult".into(), 4), + E::RayQueryGetIntersection { query, committed } => { + edges.insert("", query); + let ty = if committed { "Committed" } else { "Candidate" }; + (format!("rayQueryGet{}Intersection", ty).into(), 4) + } + }; + + // give uniform expressions an outline + let color_attr = match info { + Some(info) if info[handle].uniformity.non_uniform_result.is_none() => "fillcolor", + _ => "color", + }; + writeln!( + output, + "\t\t{}_e{} [ {}=\"{}\" label=\"{:?} {}\" ]", + prefix, + handle.index(), + color_attr, + COLORS[color_id], + handle, + label, + )?; + + for (key, edge) in edges.drain() { + writeln!( + output, + "\t\t{}_e{} -> {}_e{} [ label=\"{}\" ]", + prefix, + edge.index(), + prefix, + handle.index(), + key, + )?; + } + match payload.take() { + Some(Payload::Arguments(list)) => { + write!(output, "\t\t{{")?; + for &comp in list { + write!(output, " {}_e{}", prefix, comp.index())?; + } + writeln!(output, " }} -> {}_e{}", prefix, handle.index())?; + } + Some(Payload::Local(h)) => { + writeln!( + output, + "\t\t{}_l{} -> {}_e{}", + prefix, + h.index(), + prefix, + handle.index(), + )?; + } + Some(Payload::Global(h)) => { + writeln!( + output, + "\t\tg{} -> {}_e{} [fillcolor=gray]", + h.index(), + prefix, + handle.index(), + )?; + } + None => {} + } + } + + Ok(()) +} + +/// Write shader module to a [`String`]. +pub fn write( + module: &crate::Module, + mod_info: Option<&ModuleInfo>, + options: Options, +) -> Result { + use std::fmt::Write as _; + + let mut output = String::new(); + output += "digraph Module {\n"; + + if !options.cfg_only { + writeln!(output, "\tsubgraph cluster_globals {{")?; + writeln!(output, "\t\tlabel=\"Globals\"")?; + for (handle, var) in module.global_variables.iter() { + writeln!( + output, + "\t\tg{} [ shape=hexagon label=\"{:?} {:?}/'{}'\" ]", + handle.index(), + handle, + var.space, + name(&var.name), + )?; + } + writeln!(output, "\t}}")?; + } + + for (handle, fun) in module.functions.iter() { + let prefix = format!("f{}", handle.index()); + writeln!(output, "\tsubgraph cluster_{prefix} {{")?; + writeln!( + output, + "\t\tlabel=\"Function{:?}/'{}'\"", + handle, + name(&fun.name) + )?; + let info = mod_info.map(|a| &a[handle]); + write_fun(&mut output, prefix, fun, info, &options)?; + writeln!(output, "\t}}")?; + } + for (ep_index, ep) in module.entry_points.iter().enumerate() { + let prefix = format!("ep{ep_index}"); + writeln!(output, "\tsubgraph cluster_{prefix} {{")?; + writeln!(output, "\t\tlabel=\"{:?}/'{}'\"", ep.stage, ep.name)?; + let info = mod_info.map(|a| a.get_entry_point(ep_index)); + write_fun(&mut output, prefix, &ep.function, info, &options)?; + writeln!(output, "\t}}")?; + } + + output += "}\n"; + Ok(output) +} diff --git a/naga/src/back/glsl/features.rs b/naga/src/back/glsl/features.rs new file mode 100644 index 0000000000..aaebfde9cb --- /dev/null +++ b/naga/src/back/glsl/features.rs @@ -0,0 +1,536 @@ +use super::{BackendResult, Error, Version, Writer}; +use crate::{ + back::glsl::{Options, WriterFlags}, + AddressSpace, Binding, Expression, Handle, ImageClass, ImageDimension, Interpolation, Sampling, + Scalar, ScalarKind, ShaderStage, StorageFormat, Type, TypeInner, +}; +use std::fmt::Write; + +bitflags::bitflags! { + /// Structure used to encode additions to GLSL that aren't supported by all versions. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct Features: u32 { + /// Buffer address space support. + const BUFFER_STORAGE = 1; + const ARRAY_OF_ARRAYS = 1 << 1; + /// 8 byte floats. + const DOUBLE_TYPE = 1 << 2; + /// More image formats. + const FULL_IMAGE_FORMATS = 1 << 3; + const MULTISAMPLED_TEXTURES = 1 << 4; + const MULTISAMPLED_TEXTURE_ARRAYS = 1 << 5; + const CUBE_TEXTURES_ARRAY = 1 << 6; + const COMPUTE_SHADER = 1 << 7; + /// Image load and early depth tests. + const IMAGE_LOAD_STORE = 1 << 8; + const CONSERVATIVE_DEPTH = 1 << 9; + /// Interpolation and auxiliary qualifiers. + /// + /// Perspective, Flat, and Centroid are available in all GLSL versions we support. + const NOPERSPECTIVE_QUALIFIER = 1 << 11; + const SAMPLE_QUALIFIER = 1 << 12; + const CLIP_DISTANCE = 1 << 13; + const CULL_DISTANCE = 1 << 14; + /// Sample ID. + const SAMPLE_VARIABLES = 1 << 15; + /// Arrays with a dynamic length. + const DYNAMIC_ARRAY_SIZE = 1 << 16; + const MULTI_VIEW = 1 << 17; + /// Texture samples query + const TEXTURE_SAMPLES = 1 << 18; + /// Texture levels query + const TEXTURE_LEVELS = 1 << 19; + /// Image size query + const IMAGE_SIZE = 1 << 20; + /// Dual source blending + const DUAL_SOURCE_BLENDING = 1 << 21; + /// Instance index + /// + /// We can always support this, either through the language or a polyfill + const INSTANCE_INDEX = 1 << 22; + } +} + +/// Helper structure used to store the required [`Features`] needed to output a +/// [`Module`](crate::Module) +/// +/// Provides helper methods to check for availability and writing required extensions +pub struct FeaturesManager(Features); + +impl FeaturesManager { + /// Creates a new [`FeaturesManager`] instance + pub const fn new() -> Self { + Self(Features::empty()) + } + + /// Adds to the list of required [`Features`] + pub fn request(&mut self, features: Features) { + self.0 |= features + } + + /// Checks if the list of features [`Features`] contains the specified [`Features`] + pub fn contains(&mut self, features: Features) -> bool { + self.0.contains(features) + } + + /// Checks that all required [`Features`] are available for the specified + /// [`Version`] otherwise returns an [`Error::MissingFeatures`]. + pub fn check_availability(&self, version: Version) -> BackendResult { + // Will store all the features that are unavailable + let mut missing = Features::empty(); + + // Helper macro to check for feature availability + macro_rules! check_feature { + // Used when only core glsl supports the feature + ($feature:ident, $core:literal) => { + if self.0.contains(Features::$feature) + && (version < Version::Desktop($core) || version.is_es()) + { + missing |= Features::$feature; + } + }; + // Used when both core and es support the feature + ($feature:ident, $core:literal, $es:literal) => { + if self.0.contains(Features::$feature) + && (version < Version::Desktop($core) || version < Version::new_gles($es)) + { + missing |= Features::$feature; + } + }; + } + + check_feature!(COMPUTE_SHADER, 420, 310); + check_feature!(BUFFER_STORAGE, 400, 310); + check_feature!(DOUBLE_TYPE, 150); + check_feature!(CUBE_TEXTURES_ARRAY, 130, 310); + check_feature!(MULTISAMPLED_TEXTURES, 150, 300); + check_feature!(MULTISAMPLED_TEXTURE_ARRAYS, 150, 310); + check_feature!(ARRAY_OF_ARRAYS, 120, 310); + check_feature!(IMAGE_LOAD_STORE, 130, 310); + check_feature!(CONSERVATIVE_DEPTH, 130, 300); + check_feature!(NOPERSPECTIVE_QUALIFIER, 130); + check_feature!(SAMPLE_QUALIFIER, 400, 320); + check_feature!(CLIP_DISTANCE, 130, 300 /* with extension */); + check_feature!(CULL_DISTANCE, 450, 300 /* with extension */); + check_feature!(SAMPLE_VARIABLES, 400, 300); + check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310); + check_feature!(DUAL_SOURCE_BLENDING, 330, 300 /* with extension */); + match version { + Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300), + _ => check_feature!(MULTI_VIEW, 140, 310), + }; + // Only available on glsl core, this means that opengl es can't query the number + // of samples nor levels in a image and neither do bound checks on the sample nor + // the level argument of texelFecth + check_feature!(TEXTURE_SAMPLES, 150); + check_feature!(TEXTURE_LEVELS, 130); + check_feature!(IMAGE_SIZE, 430, 310); + + // Return an error if there are missing features + if missing.is_empty() { + Ok(()) + } else { + Err(Error::MissingFeatures(missing)) + } + } + + /// Helper method used to write all needed extensions + /// + /// # Notes + /// This won't check for feature availability so it might output extensions that aren't even + /// supported.[`check_availability`](Self::check_availability) will check feature availability + pub fn write(&self, options: &Options, mut out: impl Write) -> BackendResult { + if self.0.contains(Features::COMPUTE_SHADER) && !options.version.is_es() { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_compute_shader.txt + writeln!(out, "#extension GL_ARB_compute_shader : require")?; + } + + if self.0.contains(Features::BUFFER_STORAGE) && !options.version.is_es() { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_storage_buffer_object.txt + writeln!( + out, + "#extension GL_ARB_shader_storage_buffer_object : require" + )?; + } + + if self.0.contains(Features::DOUBLE_TYPE) && options.version < Version::Desktop(400) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_gpu_shader_fp64.txt + writeln!(out, "#extension GL_ARB_gpu_shader_fp64 : require")?; + } + + if self.0.contains(Features::CUBE_TEXTURES_ARRAY) { + if options.version.is_es() { + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_cube_map_array.txt + writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?; + } else if options.version < Version::Desktop(400) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_cube_map_array.txt + writeln!(out, "#extension GL_ARB_texture_cube_map_array : require")?; + } + } + + if self.0.contains(Features::MULTISAMPLED_TEXTURE_ARRAYS) && options.version.is_es() { + // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_texture_storage_multisample_2d_array.txt + writeln!( + out, + "#extension GL_OES_texture_storage_multisample_2d_array : require" + )?; + } + + if self.0.contains(Features::ARRAY_OF_ARRAYS) && options.version < Version::Desktop(430) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_arrays_of_arrays.txt + writeln!(out, "#extension ARB_arrays_of_arrays : require")?; + } + + if self.0.contains(Features::IMAGE_LOAD_STORE) { + if self.0.contains(Features::FULL_IMAGE_FORMATS) && options.version.is_es() { + // https://www.khronos.org/registry/OpenGL/extensions/NV/NV_image_formats.txt + writeln!(out, "#extension GL_NV_image_formats : require")?; + } + + if options.version < Version::Desktop(420) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_image_load_store.txt + writeln!(out, "#extension GL_ARB_shader_image_load_store : require")?; + } + } + + if self.0.contains(Features::CONSERVATIVE_DEPTH) { + if options.version.is_es() { + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_conservative_depth.txt + writeln!(out, "#extension GL_EXT_conservative_depth : require")?; + } + + if options.version < Version::Desktop(420) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_conservative_depth.txt + writeln!(out, "#extension GL_ARB_conservative_depth : require")?; + } + } + + if (self.0.contains(Features::CLIP_DISTANCE) || self.0.contains(Features::CULL_DISTANCE)) + && options.version.is_es() + { + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_clip_cull_distance.txt + writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?; + } + + if self.0.contains(Features::SAMPLE_VARIABLES) && options.version.is_es() { + // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_sample_variables.txt + writeln!(out, "#extension GL_OES_sample_variables : require")?; + } + + if self.0.contains(Features::MULTI_VIEW) { + if let Version::Embedded { is_webgl: true, .. } = options.version { + // https://www.khronos.org/registry/OpenGL/extensions/OVR/OVR_multiview2.txt + writeln!(out, "#extension GL_OVR_multiview2 : require")?; + } else { + // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_multiview.txt + writeln!(out, "#extension GL_EXT_multiview : require")?; + } + } + + if self.0.contains(Features::TEXTURE_SAMPLES) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_texture_image_samples.txt + writeln!( + out, + "#extension GL_ARB_shader_texture_image_samples : require" + )?; + } + + if self.0.contains(Features::TEXTURE_LEVELS) && options.version < Version::Desktop(430) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_query_levels.txt + writeln!(out, "#extension GL_ARB_texture_query_levels : require")?; + } + if self.0.contains(Features::DUAL_SOURCE_BLENDING) && options.version.is_es() { + // https://registry.khronos.org/OpenGL/extensions/EXT/EXT_blend_func_extended.txt + writeln!(out, "#extension GL_EXT_blend_func_extended : require")?; + } + + if self.0.contains(Features::INSTANCE_INDEX) { + if options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS) { + // https://registry.khronos.org/OpenGL/extensions/ARB/ARB_shader_draw_parameters.txt + writeln!(out, "#extension GL_ARB_shader_draw_parameters : require")?; + } + } + + Ok(()) + } +} + +impl<'a, W> Writer<'a, W> { + /// Helper method that searches the module for all the needed [`Features`] + /// + /// # Errors + /// If the version doesn't support any of the needed [`Features`] a + /// [`Error::MissingFeatures`] will be returned + pub(super) fn collect_required_features(&mut self) -> BackendResult { + let ep_info = self.info.get_entry_point(self.entry_point_idx as usize); + + if let Some(depth_test) = self.entry_point.early_depth_test { + // If IMAGE_LOAD_STORE is supported for this version of GLSL + if self.options.version.supports_early_depth_test() { + self.features.request(Features::IMAGE_LOAD_STORE); + } + + if depth_test.conservative.is_some() { + self.features.request(Features::CONSERVATIVE_DEPTH); + } + } + + for arg in self.entry_point.function.arguments.iter() { + self.varying_required_features(arg.binding.as_ref(), arg.ty); + } + if let Some(ref result) = self.entry_point.function.result { + self.varying_required_features(result.binding.as_ref(), result.ty); + } + + if let ShaderStage::Compute = self.entry_point.stage { + self.features.request(Features::COMPUTE_SHADER) + } + + if self.multiview.is_some() { + self.features.request(Features::MULTI_VIEW); + } + + for (ty_handle, ty) in self.module.types.iter() { + match ty.inner { + TypeInner::Scalar(scalar) + | TypeInner::Vector { scalar, .. } + | TypeInner::Matrix { scalar, .. } => self.scalar_required_features(scalar), + TypeInner::Array { base, size, .. } => { + if let TypeInner::Array { .. } = self.module.types[base].inner { + self.features.request(Features::ARRAY_OF_ARRAYS) + } + + // If the array is dynamically sized + if size == crate::ArraySize::Dynamic { + let mut is_used = false; + + // Check if this type is used in a global that is needed by the current entrypoint + for (global_handle, global) in self.module.global_variables.iter() { + // Skip unused globals + if ep_info[global_handle].is_empty() { + continue; + } + + // If this array is the type of a global, then this array is used + if global.ty == ty_handle { + is_used = true; + break; + } + + // If the type of this global is a struct + if let crate::TypeInner::Struct { ref members, .. } = + self.module.types[global.ty].inner + { + // Check the last element of the struct to see if it's type uses + // this array + if let Some(last) = members.last() { + if last.ty == ty_handle { + is_used = true; + break; + } + } + } + } + + // If this dynamically size array is used, we need dynamic array size support + if is_used { + self.features.request(Features::DYNAMIC_ARRAY_SIZE); + } + } + } + TypeInner::Image { + dim, + arrayed, + class, + } => { + if arrayed && dim == ImageDimension::Cube { + self.features.request(Features::CUBE_TEXTURES_ARRAY) + } + + match class { + ImageClass::Sampled { multi: true, .. } + | ImageClass::Depth { multi: true } => { + self.features.request(Features::MULTISAMPLED_TEXTURES); + if arrayed { + self.features.request(Features::MULTISAMPLED_TEXTURE_ARRAYS); + } + } + ImageClass::Storage { format, .. } => match format { + StorageFormat::R8Unorm + | StorageFormat::R8Snorm + | StorageFormat::R8Uint + | StorageFormat::R8Sint + | StorageFormat::R16Uint + | StorageFormat::R16Sint + | StorageFormat::R16Float + | StorageFormat::Rg8Unorm + | StorageFormat::Rg8Snorm + | StorageFormat::Rg8Uint + | StorageFormat::Rg8Sint + | StorageFormat::Rg16Uint + | StorageFormat::Rg16Sint + | StorageFormat::Rg16Float + | StorageFormat::Rgb10a2Uint + | StorageFormat::Rgb10a2Unorm + | StorageFormat::Rg11b10Float + | StorageFormat::Rg32Uint + | StorageFormat::Rg32Sint + | StorageFormat::Rg32Float => { + self.features.request(Features::FULL_IMAGE_FORMATS) + } + _ => {} + }, + ImageClass::Sampled { multi: false, .. } + | ImageClass::Depth { multi: false } => {} + } + } + _ => {} + } + } + + let mut push_constant_used = false; + + for (handle, global) in self.module.global_variables.iter() { + if ep_info[handle].is_empty() { + continue; + } + match global.space { + AddressSpace::WorkGroup => self.features.request(Features::COMPUTE_SHADER), + AddressSpace::Storage { .. } => self.features.request(Features::BUFFER_STORAGE), + AddressSpace::PushConstant => { + if push_constant_used { + return Err(Error::MultiplePushConstants); + } + push_constant_used = true; + } + _ => {} + } + } + + // We will need to pass some of the members to a closure, so we need + // to separate them otherwise the borrow checker will complain, this + // shouldn't be needed in rust 2021 + let &mut Self { + module, + info, + ref mut features, + entry_point, + entry_point_idx, + ref policies, + .. + } = self; + + // Loop trough all expressions in both functions and the entry point + // to check for needed features + for (expressions, info) in module + .functions + .iter() + .map(|(h, f)| (&f.expressions, &info[h])) + .chain(std::iter::once(( + &entry_point.function.expressions, + info.get_entry_point(entry_point_idx as usize), + ))) + { + for (_, expr) in expressions.iter() { + match *expr { + // Check for queries that neeed aditonal features + Expression::ImageQuery { + image, + query, + .. + } => match query { + // Storage images use `imageSize` which is only available + // in glsl > 420 + // + // layers queries are also implemented as size queries + crate::ImageQuery::Size { .. } | crate::ImageQuery::NumLayers => { + if let TypeInner::Image { + class: crate::ImageClass::Storage { .. }, .. + } = *info[image].ty.inner_with(&module.types) { + features.request(Features::IMAGE_SIZE) + } + }, + crate::ImageQuery::NumLevels => features.request(Features::TEXTURE_LEVELS), + crate::ImageQuery::NumSamples => features.request(Features::TEXTURE_SAMPLES), + } + , + // Check for image loads that needs bound checking on the sample + // or level argument since this requires a feature + Expression::ImageLoad { + sample, level, .. + } => { + if policies.image_load != crate::proc::BoundsCheckPolicy::Unchecked { + if sample.is_some() { + features.request(Features::TEXTURE_SAMPLES) + } + + if level.is_some() { + features.request(Features::TEXTURE_LEVELS) + } + } + } + _ => {} + } + } + } + + self.features.check_availability(self.options.version) + } + + /// Helper method that checks the [`Features`] needed by a scalar + fn scalar_required_features(&mut self, scalar: Scalar) { + if scalar.kind == ScalarKind::Float && scalar.width == 8 { + self.features.request(Features::DOUBLE_TYPE); + } + } + + fn varying_required_features(&mut self, binding: Option<&Binding>, ty: Handle) { + match self.module.types[ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + for member in members { + self.varying_required_features(member.binding.as_ref(), member.ty); + } + } + _ => { + if let Some(binding) = binding { + match *binding { + Binding::BuiltIn(built_in) => match built_in { + crate::BuiltIn::ClipDistance => { + self.features.request(Features::CLIP_DISTANCE) + } + crate::BuiltIn::CullDistance => { + self.features.request(Features::CULL_DISTANCE) + } + crate::BuiltIn::SampleIndex => { + self.features.request(Features::SAMPLE_VARIABLES) + } + crate::BuiltIn::ViewIndex => { + self.features.request(Features::MULTI_VIEW) + } + crate::BuiltIn::InstanceIndex => { + self.features.request(Features::INSTANCE_INDEX) + } + _ => {} + }, + Binding::Location { + location: _, + interpolation, + sampling, + second_blend_source, + } => { + if interpolation == Some(Interpolation::Linear) { + self.features.request(Features::NOPERSPECTIVE_QUALIFIER); + } + if sampling == Some(Sampling::Sample) { + self.features.request(Features::SAMPLE_QUALIFIER); + } + if second_blend_source { + self.features.request(Features::DUAL_SOURCE_BLENDING); + } + } + } + } + } + } + } +} diff --git a/naga/src/back/glsl/keywords.rs b/naga/src/back/glsl/keywords.rs new file mode 100644 index 0000000000..857c935e68 --- /dev/null +++ b/naga/src/back/glsl/keywords.rs @@ -0,0 +1,484 @@ +pub const RESERVED_KEYWORDS: &[&str] = &[ + // + // GLSL 4.6 keywords, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L2004-L2322 + // GLSL ES 3.2 keywords, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/es/3.2/GLSL_ES_Specification_3.20.html#L2166-L2478 + // + // Note: The GLSL ES 3.2 keywords are the same as GLSL 4.6 keywords with some residing in the reserved section. + // The only exception are the missing Vulkan keywords which I think is an oversight (see https://github.com/KhronosGroup/OpenGL-Registry/issues/585). + // + "const", + "uniform", + "buffer", + "shared", + "attribute", + "varying", + "coherent", + "volatile", + "restrict", + "readonly", + "writeonly", + "atomic_uint", + "layout", + "centroid", + "flat", + "smooth", + "noperspective", + "patch", + "sample", + "invariant", + "precise", + "break", + "continue", + "do", + "for", + "while", + "switch", + "case", + "default", + "if", + "else", + "subroutine", + "in", + "out", + "inout", + "int", + "void", + "bool", + "true", + "false", + "float", + "double", + "discard", + "return", + "vec2", + "vec3", + "vec4", + "ivec2", + "ivec3", + "ivec4", + "bvec2", + "bvec3", + "bvec4", + "uint", + "uvec2", + "uvec3", + "uvec4", + "dvec2", + "dvec3", + "dvec4", + "mat2", + "mat3", + "mat4", + "mat2x2", + "mat2x3", + "mat2x4", + "mat3x2", + "mat3x3", + "mat3x4", + "mat4x2", + "mat4x3", + "mat4x4", + "dmat2", + "dmat3", + "dmat4", + "dmat2x2", + "dmat2x3", + "dmat2x4", + "dmat3x2", + "dmat3x3", + "dmat3x4", + "dmat4x2", + "dmat4x3", + "dmat4x4", + "lowp", + "mediump", + "highp", + "precision", + "sampler1D", + "sampler1DShadow", + "sampler1DArray", + "sampler1DArrayShadow", + "isampler1D", + "isampler1DArray", + "usampler1D", + "usampler1DArray", + "sampler2D", + "sampler2DShadow", + "sampler2DArray", + "sampler2DArrayShadow", + "isampler2D", + "isampler2DArray", + "usampler2D", + "usampler2DArray", + "sampler2DRect", + "sampler2DRectShadow", + "isampler2DRect", + "usampler2DRect", + "sampler2DMS", + "isampler2DMS", + "usampler2DMS", + "sampler2DMSArray", + "isampler2DMSArray", + "usampler2DMSArray", + "sampler3D", + "isampler3D", + "usampler3D", + "samplerCube", + "samplerCubeShadow", + "isamplerCube", + "usamplerCube", + "samplerCubeArray", + "samplerCubeArrayShadow", + "isamplerCubeArray", + "usamplerCubeArray", + "samplerBuffer", + "isamplerBuffer", + "usamplerBuffer", + "image1D", + "iimage1D", + "uimage1D", + "image1DArray", + "iimage1DArray", + "uimage1DArray", + "image2D", + "iimage2D", + "uimage2D", + "image2DArray", + "iimage2DArray", + "uimage2DArray", + "image2DRect", + "iimage2DRect", + "uimage2DRect", + "image2DMS", + "iimage2DMS", + "uimage2DMS", + "image2DMSArray", + "iimage2DMSArray", + "uimage2DMSArray", + "image3D", + "iimage3D", + "uimage3D", + "imageCube", + "iimageCube", + "uimageCube", + "imageCubeArray", + "iimageCubeArray", + "uimageCubeArray", + "imageBuffer", + "iimageBuffer", + "uimageBuffer", + "struct", + // Vulkan keywords + "texture1D", + "texture1DArray", + "itexture1D", + "itexture1DArray", + "utexture1D", + "utexture1DArray", + "texture2D", + "texture2DArray", + "itexture2D", + "itexture2DArray", + "utexture2D", + "utexture2DArray", + "texture2DRect", + "itexture2DRect", + "utexture2DRect", + "texture2DMS", + "itexture2DMS", + "utexture2DMS", + "texture2DMSArray", + "itexture2DMSArray", + "utexture2DMSArray", + "texture3D", + "itexture3D", + "utexture3D", + "textureCube", + "itextureCube", + "utextureCube", + "textureCubeArray", + "itextureCubeArray", + "utextureCubeArray", + "textureBuffer", + "itextureBuffer", + "utextureBuffer", + "sampler", + "samplerShadow", + "subpassInput", + "isubpassInput", + "usubpassInput", + "subpassInputMS", + "isubpassInputMS", + "usubpassInputMS", + // Reserved keywords + "common", + "partition", + "active", + "asm", + "class", + "union", + "enum", + "typedef", + "template", + "this", + "resource", + "goto", + "inline", + "noinline", + "public", + "static", + "extern", + "external", + "interface", + "long", + "short", + "half", + "fixed", + "unsigned", + "superp", + "input", + "output", + "hvec2", + "hvec3", + "hvec4", + "fvec2", + "fvec3", + "fvec4", + "filter", + "sizeof", + "cast", + "namespace", + "using", + "sampler3DRect", + // + // GLSL 4.6 Built-In Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13314 + // + // Angle and Trigonometry Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13469-L13561C5 + "radians", + "degrees", + "sin", + "cos", + "tan", + "asin", + "acos", + "atan", + "sinh", + "cosh", + "tanh", + "asinh", + "acosh", + "atanh", + // Exponential Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13569-L13620 + "pow", + "exp", + "log", + "exp2", + "log2", + "sqrt", + "inversesqrt", + // Common Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13628-L13908 + "abs", + "sign", + "floor", + "trunc", + "round", + "roundEven", + "ceil", + "fract", + "mod", + "modf", + "min", + "max", + "clamp", + "mix", + "step", + "smoothstep", + "isnan", + "isinf", + "floatBitsToInt", + "floatBitsToUint", + "intBitsToFloat", + "uintBitsToFloat", + "fma", + "frexp", + "ldexp", + // Floating-Point Pack and Unpack Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L13916-L14007 + "packUnorm2x16", + "packSnorm2x16", + "packUnorm4x8", + "packSnorm4x8", + "unpackUnorm2x16", + "unpackSnorm2x16", + "unpackUnorm4x8", + "unpackSnorm4x8", + "packHalf2x16", + "unpackHalf2x16", + "packDouble2x32", + "unpackDouble2x32", + // Geometric Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14014-L14121 + "length", + "distance", + "dot", + "cross", + "normalize", + "ftransform", + "faceforward", + "reflect", + "refract", + // Matrix Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14151-L14215 + "matrixCompMult", + "outerProduct", + "transpose", + "determinant", + "inverse", + // Vector Relational Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14259-L14322 + "lessThan", + "lessThanEqual", + "greaterThan", + "greaterThanEqual", + "equal", + "notEqual", + "any", + "all", + "not", + // Integer Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14335-L14432 + "uaddCarry", + "usubBorrow", + "umulExtended", + "imulExtended", + "bitfieldExtract", + "bitfieldInsert", + "bitfieldReverse", + "bitCount", + "findLSB", + "findMSB", + // Texture Query Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14645-L14732 + "textureSize", + "textureQueryLod", + "textureQueryLevels", + "textureSamples", + // Texel Lookup Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L14736-L14997 + "texture", + "textureProj", + "textureLod", + "textureOffset", + "texelFetch", + "texelFetchOffset", + "textureProjOffset", + "textureLodOffset", + "textureProjLod", + "textureProjLodOffset", + "textureGrad", + "textureGradOffset", + "textureProjGrad", + "textureProjGradOffset", + // Texture Gather Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15077-L15154 + "textureGather", + "textureGatherOffset", + "textureGatherOffsets", + // Compatibility Profile Texture Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15161-L15220 + "texture1D", + "texture1DProj", + "texture1DLod", + "texture1DProjLod", + "texture2D", + "texture2DProj", + "texture2DLod", + "texture2DProjLod", + "texture3D", + "texture3DProj", + "texture3DLod", + "texture3DProjLod", + "textureCube", + "textureCubeLod", + "shadow1D", + "shadow2D", + "shadow1DProj", + "shadow2DProj", + "shadow1DLod", + "shadow2DLod", + "shadow1DProjLod", + "shadow2DProjLod", + // Atomic Counter Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15241-L15531 + "atomicCounterIncrement", + "atomicCounterDecrement", + "atomicCounter", + "atomicCounterAdd", + "atomicCounterSubtract", + "atomicCounterMin", + "atomicCounterMax", + "atomicCounterAnd", + "atomicCounterOr", + "atomicCounterXor", + "atomicCounterExchange", + "atomicCounterCompSwap", + // Atomic Memory Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15563-L15624 + "atomicAdd", + "atomicMin", + "atomicMax", + "atomicAnd", + "atomicOr", + "atomicXor", + "atomicExchange", + "atomicCompSwap", + // Image Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15763-L15878 + "imageSize", + "imageSamples", + "imageLoad", + "imageStore", + "imageAtomicAdd", + "imageAtomicMin", + "imageAtomicMax", + "imageAtomicAnd", + "imageAtomicOr", + "imageAtomicXor", + "imageAtomicExchange", + "imageAtomicCompSwap", + // Geometry Shader Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L15886-L15932 + "EmitStreamVertex", + "EndStreamPrimitive", + "EmitVertex", + "EndPrimitive", + // Fragment Processing Functions, Derivative Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16041-L16114 + "dFdx", + "dFdy", + "dFdxFine", + "dFdyFine", + "dFdxCoarse", + "dFdyCoarse", + "fwidth", + "fwidthFine", + "fwidthCoarse", + // Fragment Processing Functions, Interpolation Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16150-L16198 + "interpolateAtCentroid", + "interpolateAtSample", + "interpolateAtOffset", + // Noise Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16214-L16243 + "noise1", + "noise2", + "noise3", + "noise4", + // Shader Invocation Control Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16255-L16276 + "barrier", + // Shader Memory Control Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16336-L16382 + "memoryBarrier", + "memoryBarrierAtomicCounter", + "memoryBarrierBuffer", + "memoryBarrierShared", + "memoryBarrierImage", + "groupMemoryBarrier", + // Subpass-Input Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16451-L16470 + "subpassLoad", + // Shader Invocation Group Functions, from https://github.com/KhronosGroup/OpenGL-Registry/blob/d00e11dc1a1ffba581d633f21f70202051248d5c/specs/gl/GLSLangSpec.4.60.html#L16483-L16511 + "anyInvocation", + "allInvocations", + "allInvocationsEqual", + // + // entry point name (should not be shadowed) + // + "main", + // Naga utilities: + super::MODF_FUNCTION, + super::FREXP_FUNCTION, + super::FIRST_INSTANCE_BINDING, +]; diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs new file mode 100644 index 0000000000..e1dc906630 --- /dev/null +++ b/naga/src/back/glsl/mod.rs @@ -0,0 +1,4532 @@ +/*! +Backend for [GLSL][glsl] (OpenGL Shading Language). + +The main structure is [`Writer`], it maintains internal state that is used +to output a [`Module`](crate::Module) into glsl + +# Supported versions +### Core +- 330 +- 400 +- 410 +- 420 +- 430 +- 450 + +### ES +- 300 +- 310 + +[glsl]: https://www.khronos.org/registry/OpenGL/index_gl.php +*/ + +// GLSL is mostly a superset of C but it also removes some parts of it this is a list of relevant +// aspects for this backend. +// +// The most notable change is the introduction of the version preprocessor directive that must +// always be the first line of a glsl file and is written as +// `#version number profile` +// `number` is the version itself (i.e. 300) and `profile` is the +// shader profile we only support "core" and "es", the former is used in desktop applications and +// the later is used in embedded contexts, mobile devices and browsers. Each one as it's own +// versions (at the time of writing this the latest version for "core" is 460 and for "es" is 320) +// +// Other important preprocessor addition is the extension directive which is written as +// `#extension name: behaviour` +// Extensions provide increased features in a plugin fashion but they aren't required to be +// supported hence why they are called extensions, that's why `behaviour` is used it specifies +// whether the extension is strictly required or if it should only be enabled if needed. In our case +// when we use extensions we set behaviour to `require` always. +// +// The only thing that glsl removes that makes a difference are pointers. +// +// Additions that are relevant for the backend are the discard keyword, the introduction of +// vector, matrices, samplers, image types and functions that provide common shader operations + +pub use features::Features; + +use crate::{ + back, + proc::{self, NameKey}, + valid, Handle, ShaderStage, TypeInner, +}; +use features::FeaturesManager; +use std::{ + cmp::Ordering, + fmt, + fmt::{Error as FmtError, Write}, + mem, +}; +use thiserror::Error; + +/// Contains the features related code and the features querying method +mod features; +/// Contains a constant with a slice of all the reserved keywords RESERVED_KEYWORDS +mod keywords; + +/// List of supported `core` GLSL versions. +pub const SUPPORTED_CORE_VERSIONS: &[u16] = &[140, 150, 330, 400, 410, 420, 430, 440, 450, 460]; +/// List of supported `es` GLSL versions. +pub const SUPPORTED_ES_VERSIONS: &[u16] = &[300, 310, 320]; + +/// The suffix of the variable that will hold the calculated clamped level +/// of detail for bounds checking in `ImageLoad` +const CLAMPED_LOD_SUFFIX: &str = "_clamped_lod"; + +pub(crate) const MODF_FUNCTION: &str = "naga_modf"; +pub(crate) const FREXP_FUNCTION: &str = "naga_frexp"; + +// Must match code in glsl_built_in +pub const FIRST_INSTANCE_BINDING: &str = "naga_vs_first_instance"; + +/// Mapping between resources and bindings. +pub type BindingMap = std::collections::BTreeMap; + +impl crate::AtomicFunction { + const fn to_glsl(self) -> &'static str { + match self { + Self::Add | Self::Subtract => "Add", + Self::And => "And", + Self::InclusiveOr => "Or", + Self::ExclusiveOr => "Xor", + Self::Min => "Min", + Self::Max => "Max", + Self::Exchange { compare: None } => "Exchange", + Self::Exchange { compare: Some(_) } => "", //TODO + } + } +} + +impl crate::AddressSpace { + const fn is_buffer(&self) -> bool { + match *self { + crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } => true, + _ => false, + } + } + + /// Whether a variable with this address space can be initialized + const fn initializable(&self) -> bool { + match *self { + crate::AddressSpace::Function | crate::AddressSpace::Private => true, + crate::AddressSpace::WorkGroup + | crate::AddressSpace::Uniform + | crate::AddressSpace::Storage { .. } + | crate::AddressSpace::Handle + | crate::AddressSpace::PushConstant => false, + } + } +} + +/// A GLSL version. +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub enum Version { + /// `core` GLSL. + Desktop(u16), + /// `es` GLSL. + Embedded { version: u16, is_webgl: bool }, +} + +impl Version { + /// Create a new gles version + pub const fn new_gles(version: u16) -> Self { + Self::Embedded { + version, + is_webgl: false, + } + } + + /// Returns true if self is `Version::Embedded` (i.e. is a es version) + const fn is_es(&self) -> bool { + match *self { + Version::Desktop(_) => false, + Version::Embedded { .. } => true, + } + } + + /// Returns true if targetting WebGL + const fn is_webgl(&self) -> bool { + match *self { + Version::Desktop(_) => false, + Version::Embedded { is_webgl, .. } => is_webgl, + } + } + + /// Checks the list of currently supported versions and returns true if it contains the + /// specified version + /// + /// # Notes + /// As an invalid version number will never be added to the supported version list + /// so this also checks for version validity + fn is_supported(&self) -> bool { + match *self { + Version::Desktop(v) => SUPPORTED_CORE_VERSIONS.contains(&v), + Version::Embedded { version: v, .. } => SUPPORTED_ES_VERSIONS.contains(&v), + } + } + + fn supports_io_locations(&self) -> bool { + *self >= Version::Desktop(330) || *self >= Version::new_gles(300) + } + + /// Checks if the version supports all of the explicit layouts: + /// - `location=` qualifiers for bindings + /// - `binding=` qualifiers for resources + /// + /// Note: `location=` for vertex inputs and fragment outputs is supported + /// unconditionally for GLES 300. + fn supports_explicit_locations(&self) -> bool { + *self >= Version::Desktop(410) || *self >= Version::new_gles(310) + } + + fn supports_early_depth_test(&self) -> bool { + *self >= Version::Desktop(130) || *self >= Version::new_gles(310) + } + + fn supports_std430_layout(&self) -> bool { + *self >= Version::Desktop(430) || *self >= Version::new_gles(310) + } + + fn supports_fma_function(&self) -> bool { + *self >= Version::Desktop(400) || *self >= Version::new_gles(320) + } + + fn supports_integer_functions(&self) -> bool { + *self >= Version::Desktop(400) || *self >= Version::new_gles(310) + } + + fn supports_frexp_function(&self) -> bool { + *self >= Version::Desktop(400) || *self >= Version::new_gles(310) + } + + fn supports_derivative_control(&self) -> bool { + *self >= Version::Desktop(450) + } +} + +impl PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + match (*self, *other) { + (Version::Desktop(x), Version::Desktop(y)) => Some(x.cmp(&y)), + (Version::Embedded { version: x, .. }, Version::Embedded { version: y, .. }) => { + Some(x.cmp(&y)) + } + _ => None, + } + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Version::Desktop(v) => write!(f, "{v} core"), + Version::Embedded { version: v, .. } => write!(f, "{v} es"), + } + } +} + +bitflags::bitflags! { + /// Configuration flags for the [`Writer`]. + #[cfg_attr(feature = "serialize", derive(serde::Serialize))] + #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct WriterFlags: u32 { + /// Flip output Y and extend Z from (0, 1) to (-1, 1). + const ADJUST_COORDINATE_SPACE = 0x1; + /// Supports GL_EXT_texture_shadow_lod on the host, which provides + /// additional functions on shadows and arrays of shadows. + const TEXTURE_SHADOW_LOD = 0x2; + /// Supports ARB_shader_draw_parameters on the host, which provides + /// support for `gl_BaseInstanceARB`, `gl_BaseVertexARB`, and `gl_DrawIDARB`. + const DRAW_PARAMETERS = 0x4; + /// Include unused global variables, constants and functions. By default the output will exclude + /// global variables that are not used in the specified entrypoint (including indirect use), + /// all constant declarations, and functions that use excluded global variables. + const INCLUDE_UNUSED_ITEMS = 0x10; + /// Emit `PointSize` output builtin to vertex shaders, which is + /// required for drawing with `PointList` topology. + /// + /// https://registry.khronos.org/OpenGL/specs/es/3.2/GLSL_ES_Specification_3.20.html#built-in-language-variables + /// The variable gl_PointSize is intended for a shader to write the size of the point to be rasterized. It is measured in pixels. + /// If gl_PointSize is not written to, its value is undefined in subsequent pipe stages. + const FORCE_POINT_SIZE = 0x20; + } +} + +/// Configuration used in the [`Writer`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct Options { + /// The GLSL version to be used. + pub version: Version, + /// Configuration flags for the [`Writer`]. + pub writer_flags: WriterFlags, + /// Map of resources association to binding locations. + pub binding_map: BindingMap, + /// Should workgroup variables be zero initialized (by polyfilling)? + pub zero_initialize_workgroup_memory: bool, +} + +impl Default for Options { + fn default() -> Self { + Options { + version: Version::new_gles(310), + writer_flags: WriterFlags::ADJUST_COORDINATE_SPACE, + binding_map: BindingMap::default(), + zero_initialize_workgroup_memory: true, + } + } +} + +/// A subset of options meant to be changed per pipeline. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct PipelineOptions { + /// The stage of the entry point. + pub shader_stage: ShaderStage, + /// The name of the entry point. + /// + /// If no entry point that matches is found while creating a [`Writer`], a error will be thrown. + pub entry_point: String, + /// How many views to render to, if doing multiview rendering. + pub multiview: Option, +} + +#[derive(Debug)] +pub struct VaryingLocation { + /// The location of the global. + /// This corresponds to `layout(location = ..)` in GLSL. + pub location: u32, + /// The index which can be used for dual source blending. + /// This corresponds to `layout(index = ..)` in GLSL. + pub index: u32, +} + +/// Reflection info for texture mappings and uniforms. +#[derive(Debug)] +pub struct ReflectionInfo { + /// Mapping between texture names and variables/samplers. + pub texture_mapping: crate::FastHashMap, + /// Mapping between uniform variables and names. + pub uniforms: crate::FastHashMap, String>, + /// Mapping between names and attribute locations. + pub varying: crate::FastHashMap, + /// List of push constant items in the shader. + pub push_constant_items: Vec, +} + +/// Mapping between a texture and its sampler, if it exists. +/// +/// GLSL pre-Vulkan has no concept of separate textures and samplers. Instead, everything is a +/// `gsamplerN` where `g` is the scalar type and `N` is the dimension. But naga uses separate textures +/// and samplers in the IR, so the backend produces a [`FastHashMap`](crate::FastHashMap) with the texture name +/// as a key and a [`TextureMapping`] as a value. This way, the user knows where to bind. +/// +/// [`Storage`](crate::ImageClass::Storage) images produce `gimageN` and don't have an associated sampler, +/// so the [`sampler`](Self::sampler) field will be [`None`]. +#[derive(Debug, Clone)] +pub struct TextureMapping { + /// Handle to the image global variable. + pub texture: Handle, + /// Handle to the associated sampler global variable, if it exists. + pub sampler: Option>, +} + +/// All information to bind a single uniform value to the shader. +/// +/// Push constants are emulated using traditional uniforms in OpenGL. +/// +/// These are composed of a set of primatives (scalar, vector, matrix) that +/// are given names. Because they are not backed by the concept of a buffer, +/// we must do the work of calculating the offset of each primative in the +/// push constant block. +#[derive(Debug, Clone)] +pub struct PushConstantItem { + /// GL uniform name for the item. This name is the same as if you were + /// to access it directly from a GLSL shader. + /// + /// The with the following example, the following names will be generated, + /// one name per GLSL uniform. + /// + /// ```glsl + /// struct InnerStruct { + /// value: f32, + /// } + /// + /// struct PushConstant { + /// InnerStruct inner; + /// vec4 array[2]; + /// } + /// + /// uniform PushConstants _push_constant_binding_cs; + /// ``` + /// + /// ```text + /// - _push_constant_binding_cs.inner.value + /// - _push_constant_binding_cs.array[0] + /// - _push_constant_binding_cs.array[1] + /// ``` + /// + pub access_path: String, + /// Type of the uniform. This will only ever be a scalar, vector, or matrix. + pub ty: Handle, + /// The offset in the push constant memory block this uniform maps to. + /// + /// The size of the uniform can be derived from the type. + pub offset: u32, +} + +/// Helper structure that generates a number +#[derive(Default)] +struct IdGenerator(u32); + +impl IdGenerator { + /// Generates a number that's guaranteed to be unique for this `IdGenerator` + fn generate(&mut self) -> u32 { + // It's just an increasing number but it does the job + let ret = self.0; + self.0 += 1; + ret + } +} + +/// Assorted options needed for generting varyings. +#[derive(Clone, Copy)] +struct VaryingOptions { + output: bool, + targetting_webgl: bool, + draw_parameters: bool, +} + +impl VaryingOptions { + const fn from_writer_options(options: &Options, output: bool) -> Self { + Self { + output, + targetting_webgl: options.version.is_webgl(), + draw_parameters: options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS), + } + } +} + +/// Helper wrapper used to get a name for a varying +/// +/// Varying have different naming schemes depending on their binding: +/// - Varyings with builtin bindings get the from [`glsl_built_in`]. +/// - Varyings with location bindings are named `_S_location_X` where `S` is a +/// prefix identifying which pipeline stage the varying connects, and `X` is +/// the location. +struct VaryingName<'a> { + binding: &'a crate::Binding, + stage: ShaderStage, + options: VaryingOptions, +} +impl fmt::Display for VaryingName<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self.binding { + crate::Binding::Location { + second_blend_source: true, + .. + } => { + write!(f, "_fs2p_location1",) + } + crate::Binding::Location { location, .. } => { + let prefix = match (self.stage, self.options.output) { + (ShaderStage::Compute, _) => unreachable!(), + // pipeline to vertex + (ShaderStage::Vertex, false) => "p2vs", + // vertex to fragment + (ShaderStage::Vertex, true) | (ShaderStage::Fragment, false) => "vs2fs", + // fragment to pipeline + (ShaderStage::Fragment, true) => "fs2p", + }; + write!(f, "_{prefix}_location{location}",) + } + crate::Binding::BuiltIn(built_in) => { + write!(f, "{}", glsl_built_in(built_in, self.options)) + } + } + } +} + +impl ShaderStage { + const fn to_str(self) -> &'static str { + match self { + ShaderStage::Compute => "cs", + ShaderStage::Fragment => "fs", + ShaderStage::Vertex => "vs", + } + } +} + +/// Shorthand result used internally by the backend +type BackendResult = Result; + +/// A GLSL compilation error. +#[derive(Debug, Error)] +pub enum Error { + /// A error occurred while writing to the output. + #[error("Format error")] + FmtError(#[from] FmtError), + /// The specified [`Version`] doesn't have all required [`Features`]. + /// + /// Contains the missing [`Features`]. + #[error("The selected version doesn't support {0:?}")] + MissingFeatures(Features), + /// [`AddressSpace::PushConstant`](crate::AddressSpace::PushConstant) was used more than + /// once in the entry point, which isn't supported. + #[error("Multiple push constants aren't supported")] + MultiplePushConstants, + /// The specified [`Version`] isn't supported. + #[error("The specified version isn't supported")] + VersionNotSupported, + /// The entry point couldn't be found. + #[error("The requested entry point couldn't be found")] + EntryPointNotFound, + /// A call was made to an unsupported external. + #[error("A call was made to an unsupported external: {0}")] + UnsupportedExternal(String), + /// A scalar with an unsupported width was requested. + #[error("A scalar with an unsupported width was requested: {0:?}")] + UnsupportedScalar(crate::Scalar), + /// A image was used with multiple samplers, which isn't supported. + #[error("A image was used with multiple samplers")] + ImageMultipleSamplers, + #[error("{0}")] + Custom(String), +} + +/// Binary operation with a different logic on the GLSL side. +enum BinaryOperation { + /// Vector comparison should use the function like `greaterThan()`, etc. + VectorCompare, + /// Vector component wise operation; used to polyfill unsupported ops like `|` and `&` for `bvecN`'s + VectorComponentWise, + /// GLSL `%` is SPIR-V `OpUMod/OpSMod` and `mod()` is `OpFMod`, but [`BinaryOperator::Modulo`](crate::BinaryOperator::Modulo) is `OpFRem`. + Modulo, + /// Any plain operation. No additional logic required. + Other, +} + +/// Writer responsible for all code generation. +pub struct Writer<'a, W> { + // Inputs + /// The module being written. + module: &'a crate::Module, + /// The module analysis. + info: &'a valid::ModuleInfo, + /// The output writer. + out: W, + /// User defined configuration to be used. + options: &'a Options, + /// The bound checking policies to be used + policies: proc::BoundsCheckPolicies, + + // Internal State + /// Features manager used to store all the needed features and write them. + features: FeaturesManager, + namer: proc::Namer, + /// A map with all the names needed for writing the module + /// (generated by a [`Namer`](crate::proc::Namer)). + names: crate::FastHashMap, + /// A map with the names of global variables needed for reflections. + reflection_names_globals: crate::FastHashMap, String>, + /// The selected entry point. + entry_point: &'a crate::EntryPoint, + /// The index of the selected entry point. + entry_point_idx: proc::EntryPointIndex, + /// A generator for unique block numbers. + block_id: IdGenerator, + /// Set of expressions that have associated temporary variables. + named_expressions: crate::NamedExpressions, + /// Set of expressions that need to be baked to avoid unnecessary repetition in output + need_bake_expressions: back::NeedBakeExpressions, + /// How many views to render to, if doing multiview rendering. + multiview: Option, + /// Mapping of varying variables to their location. Needed for reflections. + varying: crate::FastHashMap, +} + +impl<'a, W: Write> Writer<'a, W> { + /// Creates a new [`Writer`] instance. + /// + /// # Errors + /// - If the version specified is invalid or supported. + /// - If the entry point couldn't be found in the module. + /// - If the version specified doesn't support some used features. + pub fn new( + out: W, + module: &'a crate::Module, + info: &'a valid::ModuleInfo, + options: &'a Options, + pipeline_options: &'a PipelineOptions, + policies: proc::BoundsCheckPolicies, + ) -> Result { + // Check if the requested version is supported + if !options.version.is_supported() { + log::error!("Version {}", options.version); + return Err(Error::VersionNotSupported); + } + + // Try to find the entry point and corresponding index + let ep_idx = module + .entry_points + .iter() + .position(|ep| { + pipeline_options.shader_stage == ep.stage && pipeline_options.entry_point == ep.name + }) + .ok_or(Error::EntryPointNotFound)?; + + // Generate a map with names required to write the module + let mut names = crate::FastHashMap::default(); + let mut namer = proc::Namer::default(); + namer.reset( + module, + keywords::RESERVED_KEYWORDS, + &[], + &[], + &[ + "gl_", // all GL built-in variables + "_group", // all normal bindings + "_push_constant_binding_", // all push constant bindings + ], + &mut names, + ); + + // Build the instance + let mut this = Self { + module, + info, + out, + options, + policies, + + namer, + features: FeaturesManager::new(), + names, + reflection_names_globals: crate::FastHashMap::default(), + entry_point: &module.entry_points[ep_idx], + entry_point_idx: ep_idx as u16, + multiview: pipeline_options.multiview, + block_id: IdGenerator::default(), + named_expressions: Default::default(), + need_bake_expressions: Default::default(), + varying: Default::default(), + }; + + // Find all features required to print this module + this.collect_required_features()?; + + Ok(this) + } + + /// Writes the [`Module`](crate::Module) as glsl to the output + /// + /// # Notes + /// If an error occurs while writing, the output might have been written partially + /// + /// # Panics + /// Might panic if the module is invalid + pub fn write(&mut self) -> Result { + // We use `writeln!(self.out)` throughout the write to add newlines + // to make the output more readable + + let es = self.options.version.is_es(); + + // Write the version (It must be the first thing or it isn't a valid glsl output) + writeln!(self.out, "#version {}", self.options.version)?; + // Write all the needed extensions + // + // This used to be the last thing being written as it allowed to search for features while + // writing the module saving some loops but some older versions (420 or less) required the + // extensions to appear before being used, even though extensions are part of the + // preprocessor not the processor ¯\_(ツ)_/¯ + self.features.write(self.options, &mut self.out)?; + + // Write the additional extensions + if self + .options + .writer_flags + .contains(WriterFlags::TEXTURE_SHADOW_LOD) + { + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_shadow_lod.txt + writeln!(self.out, "#extension GL_EXT_texture_shadow_lod : require")?; + } + + // glsl es requires a precision to be specified for floats and ints + // TODO: Should this be user configurable? + if es { + writeln!(self.out)?; + writeln!(self.out, "precision highp float;")?; + writeln!(self.out, "precision highp int;")?; + writeln!(self.out)?; + } + + if self.entry_point.stage == ShaderStage::Compute { + let workgroup_size = self.entry_point.workgroup_size; + writeln!( + self.out, + "layout(local_size_x = {}, local_size_y = {}, local_size_z = {}) in;", + workgroup_size[0], workgroup_size[1], workgroup_size[2] + )?; + writeln!(self.out)?; + } + + if self.entry_point.stage == ShaderStage::Vertex + && !self + .options + .writer_flags + .contains(WriterFlags::DRAW_PARAMETERS) + && self.features.contains(Features::INSTANCE_INDEX) + { + writeln!(self.out, "uniform uint {FIRST_INSTANCE_BINDING};")?; + writeln!(self.out)?; + } + + // Enable early depth tests if needed + if let Some(depth_test) = self.entry_point.early_depth_test { + // If early depth test is supported for this version of GLSL + if self.options.version.supports_early_depth_test() { + writeln!(self.out, "layout(early_fragment_tests) in;")?; + + if let Some(conservative) = depth_test.conservative { + use crate::ConservativeDepth as Cd; + + let depth = match conservative { + Cd::GreaterEqual => "greater", + Cd::LessEqual => "less", + Cd::Unchanged => "unchanged", + }; + writeln!(self.out, "layout (depth_{depth}) out float gl_FragDepth;")?; + } + writeln!(self.out)?; + } else { + log::warn!( + "Early depth testing is not supported for this version of GLSL: {}", + self.options.version + ); + } + } + + if self.entry_point.stage == ShaderStage::Vertex && self.options.version.is_webgl() { + if let Some(multiview) = self.multiview.as_ref() { + writeln!(self.out, "layout(num_views = {multiview}) in;")?; + writeln!(self.out)?; + } + } + + // Write struct types. + // + // This are always ordered because the IR is structured in a way that + // you can't make a struct without adding all of its members first. + for (handle, ty) in self.module.types.iter() { + if let TypeInner::Struct { ref members, .. } = ty.inner { + // Structures ending with runtime-sized arrays can only be + // rendered as shader storage blocks in GLSL, not stand-alone + // struct types. + if !self.module.types[members.last().unwrap().ty] + .inner + .is_dynamically_sized(&self.module.types) + { + let name = &self.names[&NameKey::Type(handle)]; + write!(self.out, "struct {name} ")?; + self.write_struct_body(handle, members)?; + writeln!(self.out, ";")?; + } + } + } + + // Write functions to create special types. + for (type_key, struct_ty) in self.module.special_types.predeclared_types.iter() { + match type_key { + &crate::PredeclaredType::ModfResult { size, width } + | &crate::PredeclaredType::FrexpResult { size, width } => { + let arg_type_name_owner; + let arg_type_name = if let Some(size) = size { + arg_type_name_owner = + format!("{}vec{}", if width == 8 { "d" } else { "" }, size as u8); + &arg_type_name_owner + } else if width == 8 { + "double" + } else { + "float" + }; + + let other_type_name_owner; + let (defined_func_name, called_func_name, other_type_name) = + if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) { + (MODF_FUNCTION, "modf", arg_type_name) + } else { + let other_type_name = if let Some(size) = size { + other_type_name_owner = format!("ivec{}", size as u8); + &other_type_name_owner + } else { + "int" + }; + (FREXP_FUNCTION, "frexp", other_type_name) + }; + + let struct_name = &self.names[&NameKey::Type(*struct_ty)]; + + writeln!(self.out)?; + if !self.options.version.supports_frexp_function() + && matches!(type_key, &crate::PredeclaredType::FrexpResult { .. }) + { + writeln!( + self.out, + "{struct_name} {defined_func_name}({arg_type_name} arg) {{ + {other_type_name} other = arg == {arg_type_name}(0) ? {other_type_name}(0) : {other_type_name}({arg_type_name}(1) + log2(arg)); + {arg_type_name} fract = arg * exp2({arg_type_name}(-other)); + return {struct_name}(fract, other); +}}", + )?; + } else { + writeln!( + self.out, + "{struct_name} {defined_func_name}({arg_type_name} arg) {{ + {other_type_name} other; + {arg_type_name} fract = {called_func_name}(arg, other); + return {struct_name}(fract, other); +}}", + )?; + } + } + &crate::PredeclaredType::AtomicCompareExchangeWeakResult { .. } => {} + } + } + + // Write all named constants + let mut constants = self + .module + .constants + .iter() + .filter(|&(_, c)| c.name.is_some()) + .peekable(); + while let Some((handle, _)) = constants.next() { + self.write_global_constant(handle)?; + // Add extra newline for readability on last iteration + if constants.peek().is_none() { + writeln!(self.out)?; + } + } + + let ep_info = self.info.get_entry_point(self.entry_point_idx as usize); + + // Write the globals + // + // Unless explicitly disabled with WriterFlags::INCLUDE_UNUSED_ITEMS, + // we filter all globals that aren't used by the selected entry point as they might be + // interfere with each other (i.e. two globals with the same location but different with + // different classes) + let include_unused = self + .options + .writer_flags + .contains(WriterFlags::INCLUDE_UNUSED_ITEMS); + for (handle, global) in self.module.global_variables.iter() { + let is_unused = ep_info[handle].is_empty(); + if !include_unused && is_unused { + continue; + } + + match self.module.types[global.ty].inner { + // We treat images separately because they might require + // writing the storage format + TypeInner::Image { + mut dim, + arrayed, + class, + } => { + // Gather the storage format if needed + let storage_format_access = match self.module.types[global.ty].inner { + TypeInner::Image { + class: crate::ImageClass::Storage { format, access }, + .. + } => Some((format, access)), + _ => None, + }; + + if dim == crate::ImageDimension::D1 && es { + dim = crate::ImageDimension::D2 + } + + // Gether the location if needed + let layout_binding = if self.options.version.supports_explicit_locations() { + let br = global.binding.as_ref().unwrap(); + self.options.binding_map.get(br).cloned() + } else { + None + }; + + // Write all the layout qualifiers + if layout_binding.is_some() || storage_format_access.is_some() { + write!(self.out, "layout(")?; + if let Some(binding) = layout_binding { + write!(self.out, "binding = {binding}")?; + } + if let Some((format, _)) = storage_format_access { + let format_str = glsl_storage_format(format)?; + let separator = match layout_binding { + Some(_) => ",", + None => "", + }; + write!(self.out, "{separator}{format_str}")?; + } + write!(self.out, ") ")?; + } + + if let Some((_, access)) = storage_format_access { + self.write_storage_access(access)?; + } + + // All images in glsl are `uniform` + // The trailing space is important + write!(self.out, "uniform ")?; + + // write the type + // + // This is way we need the leading space because `write_image_type` doesn't add + // any spaces at the beginning or end + self.write_image_type(dim, arrayed, class)?; + + // Finally write the name and end the global with a `;` + // The leading space is important + let global_name = self.get_global_name(handle, global); + writeln!(self.out, " {global_name};")?; + writeln!(self.out)?; + + self.reflection_names_globals.insert(handle, global_name); + } + // glsl has no concept of samplers so we just ignore it + TypeInner::Sampler { .. } => continue, + // All other globals are written by `write_global` + _ => { + self.write_global(handle, global)?; + // Add a newline (only for readability) + writeln!(self.out)?; + } + } + } + + for arg in self.entry_point.function.arguments.iter() { + self.write_varying(arg.binding.as_ref(), arg.ty, false)?; + } + if let Some(ref result) = self.entry_point.function.result { + self.write_varying(result.binding.as_ref(), result.ty, true)?; + } + writeln!(self.out)?; + + // Write all regular functions + for (handle, function) in self.module.functions.iter() { + // Check that the function doesn't use globals that aren't supported + // by the current entry point + if !include_unused && !ep_info.dominates_global_use(&self.info[handle]) { + continue; + } + + let fun_info = &self.info[handle]; + + // Skip functions that that are not compatible with this entry point's stage. + // + // When validation is enabled, it rejects modules whose entry points try to call + // incompatible functions, so if we got this far, then any functions incompatible + // with our selected entry point must not be used. + // + // When validation is disabled, `fun_info.available_stages` is always just + // `ShaderStages::all()`, so this will write all functions in the module, and + // the downstream GLSL compiler will catch any problems. + if !fun_info.available_stages.contains(ep_info.available_stages) { + continue; + } + + // Write the function + self.write_function(back::FunctionType::Function(handle), function, fun_info)?; + + writeln!(self.out)?; + } + + self.write_function( + back::FunctionType::EntryPoint(self.entry_point_idx), + &self.entry_point.function, + ep_info, + )?; + + // Add newline at the end of file + writeln!(self.out)?; + + // Collect all reflection info and return it to the user + self.collect_reflection_info() + } + + fn write_array_size( + &mut self, + base: Handle, + size: crate::ArraySize, + ) -> BackendResult { + write!(self.out, "[")?; + + // Write the array size + // Writes nothing if `ArraySize::Dynamic` + match size { + crate::ArraySize::Constant(size) => { + write!(self.out, "{size}")?; + } + crate::ArraySize::Dynamic => (), + } + + write!(self.out, "]")?; + + if let TypeInner::Array { + base: next_base, + size: next_size, + .. + } = self.module.types[base].inner + { + self.write_array_size(next_base, next_size)?; + } + + Ok(()) + } + + /// Helper method used to write value types + /// + /// # Notes + /// Adds no trailing or leading whitespace + fn write_value_type(&mut self, inner: &TypeInner) -> BackendResult { + match *inner { + // Scalars are simple we just get the full name from `glsl_scalar` + TypeInner::Scalar(scalar) + | TypeInner::Atomic(scalar) + | TypeInner::ValuePointer { + size: None, + scalar, + space: _, + } => write!(self.out, "{}", glsl_scalar(scalar)?.full)?, + // Vectors are just `gvecN` where `g` is the scalar prefix and `N` is the vector size + TypeInner::Vector { size, scalar } + | TypeInner::ValuePointer { + size: Some(size), + scalar, + space: _, + } => write!(self.out, "{}vec{}", glsl_scalar(scalar)?.prefix, size as u8)?, + // Matrices are written with `gmatMxN` where `g` is the scalar prefix (only floats and + // doubles are allowed), `M` is the columns count and `N` is the rows count + // + // glsl supports a matrix shorthand `gmatN` where `N` = `M` but it doesn't justify the + // extra branch to write matrices this way + TypeInner::Matrix { + columns, + rows, + scalar, + } => write!( + self.out, + "{}mat{}x{}", + glsl_scalar(scalar)?.prefix, + columns as u8, + rows as u8 + )?, + // GLSL arrays are written as `type name[size]` + // Here we only write the size of the array i.e. `[size]` + // Base `type` and `name` should be written outside + TypeInner::Array { base, size, .. } => self.write_array_size(base, size)?, + // Write all variants instead of `_` so that if new variants are added a + // no exhaustiveness error is thrown + TypeInner::Pointer { .. } + | TypeInner::Struct { .. } + | TypeInner::Image { .. } + | TypeInner::Sampler { .. } + | TypeInner::AccelerationStructure + | TypeInner::RayQuery + | TypeInner::BindingArray { .. } => { + return Err(Error::Custom(format!("Unable to write type {inner:?}"))) + } + } + + Ok(()) + } + + /// Helper method used to write non image/sampler types + /// + /// # Notes + /// Adds no trailing or leading whitespace + fn write_type(&mut self, ty: Handle) -> BackendResult { + match self.module.types[ty].inner { + // glsl has no pointer types so just write types as normal and loads are skipped + TypeInner::Pointer { base, .. } => self.write_type(base), + // glsl structs are written as just the struct name + TypeInner::Struct { .. } => { + // Get the struct name + let name = &self.names[&NameKey::Type(ty)]; + write!(self.out, "{name}")?; + Ok(()) + } + // glsl array has the size separated from the base type + TypeInner::Array { base, .. } => self.write_type(base), + ref other => self.write_value_type(other), + } + } + + /// Helper method to write a image type + /// + /// # Notes + /// Adds no leading or trailing whitespace + fn write_image_type( + &mut self, + dim: crate::ImageDimension, + arrayed: bool, + class: crate::ImageClass, + ) -> BackendResult { + // glsl images consist of four parts the scalar prefix, the image "type", the dimensions + // and modifiers + // + // There exists two image types + // - sampler - for sampled images + // - image - for storage images + // + // There are three possible modifiers that can be used together and must be written in + // this order to be valid + // - MS - used if it's a multisampled image + // - Array - used if it's an image array + // - Shadow - used if it's a depth image + use crate::ImageClass as Ic; + + let (base, kind, ms, comparison) = match class { + Ic::Sampled { kind, multi: true } => ("sampler", kind, "MS", ""), + Ic::Sampled { kind, multi: false } => ("sampler", kind, "", ""), + Ic::Depth { multi: true } => ("sampler", crate::ScalarKind::Float, "MS", ""), + Ic::Depth { multi: false } => ("sampler", crate::ScalarKind::Float, "", "Shadow"), + Ic::Storage { format, .. } => ("image", format.into(), "", ""), + }; + + let precision = if self.options.version.is_es() { + "highp " + } else { + "" + }; + + write!( + self.out, + "{}{}{}{}{}{}{}", + precision, + glsl_scalar(crate::Scalar { kind, width: 4 })?.prefix, + base, + glsl_dimension(dim), + ms, + if arrayed { "Array" } else { "" }, + comparison + )?; + + Ok(()) + } + + /// Helper method used to write non images/sampler globals + /// + /// # Notes + /// Adds a newline + /// + /// # Panics + /// If the global has type sampler + fn write_global( + &mut self, + handle: Handle, + global: &crate::GlobalVariable, + ) -> BackendResult { + if self.options.version.supports_explicit_locations() { + if let Some(ref br) = global.binding { + match self.options.binding_map.get(br) { + Some(binding) => { + let layout = match global.space { + crate::AddressSpace::Storage { .. } => { + if self.options.version.supports_std430_layout() { + "std430, " + } else { + "std140, " + } + } + crate::AddressSpace::Uniform => "std140, ", + _ => "", + }; + write!(self.out, "layout({layout}binding = {binding}) ")? + } + None => { + log::debug!("unassigned binding for {:?}", global.name); + if let crate::AddressSpace::Storage { .. } = global.space { + if self.options.version.supports_std430_layout() { + write!(self.out, "layout(std430) ")? + } + } + } + } + } + } + + if let crate::AddressSpace::Storage { access } = global.space { + self.write_storage_access(access)?; + } + + if let Some(storage_qualifier) = glsl_storage_qualifier(global.space) { + write!(self.out, "{storage_qualifier} ")?; + } + + match global.space { + crate::AddressSpace::Private => { + self.write_simple_global(handle, global)?; + } + crate::AddressSpace::WorkGroup => { + self.write_simple_global(handle, global)?; + } + crate::AddressSpace::PushConstant => { + self.write_simple_global(handle, global)?; + } + crate::AddressSpace::Uniform => { + self.write_interface_block(handle, global)?; + } + crate::AddressSpace::Storage { .. } => { + self.write_interface_block(handle, global)?; + } + // A global variable in the `Function` address space is a + // contradiction in terms. + crate::AddressSpace::Function => unreachable!(), + // Textures and samplers are handled directly in `Writer::write`. + crate::AddressSpace::Handle => unreachable!(), + } + + Ok(()) + } + + fn write_simple_global( + &mut self, + handle: Handle, + global: &crate::GlobalVariable, + ) -> BackendResult { + self.write_type(global.ty)?; + write!(self.out, " ")?; + self.write_global_name(handle, global)?; + + if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner { + self.write_array_size(base, size)?; + } + + if global.space.initializable() && is_value_init_supported(self.module, global.ty) { + write!(self.out, " = ")?; + if let Some(init) = global.init { + self.write_const_expr(init)?; + } else { + self.write_zero_init_value(global.ty)?; + } + } + + writeln!(self.out, ";")?; + + if let crate::AddressSpace::PushConstant = global.space { + let global_name = self.get_global_name(handle, global); + self.reflection_names_globals.insert(handle, global_name); + } + + Ok(()) + } + + /// Write an interface block for a single Naga global. + /// + /// Write `block_name { members }`. Since `block_name` must be unique + /// between blocks and structs, we add `_block_ID` where `ID` is a + /// `IdGenerator` generated number. Write `members` in the same way we write + /// a struct's members. + fn write_interface_block( + &mut self, + handle: Handle, + global: &crate::GlobalVariable, + ) -> BackendResult { + // Write the block name, it's just the struct name appended with `_block_ID` + let ty_name = &self.names[&NameKey::Type(global.ty)]; + let block_name = format!( + "{}_block_{}{:?}", + // avoid double underscores as they are reserved in GLSL + ty_name.trim_end_matches('_'), + self.block_id.generate(), + self.entry_point.stage, + ); + write!(self.out, "{block_name} ")?; + self.reflection_names_globals.insert(handle, block_name); + + match self.module.types[global.ty].inner { + crate::TypeInner::Struct { ref members, .. } + if self.module.types[members.last().unwrap().ty] + .inner + .is_dynamically_sized(&self.module.types) => + { + // Structs with dynamically sized arrays must have their + // members lifted up as members of the interface block. GLSL + // can't write such struct types anyway. + self.write_struct_body(global.ty, members)?; + write!(self.out, " ")?; + self.write_global_name(handle, global)?; + } + _ => { + // A global of any other type is written as the sole member + // of the interface block. Since the interface block is + // anonymous, this becomes visible in the global scope. + write!(self.out, "{{ ")?; + self.write_type(global.ty)?; + write!(self.out, " ")?; + self.write_global_name(handle, global)?; + if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner { + self.write_array_size(base, size)?; + } + write!(self.out, "; }}")?; + } + } + + writeln!(self.out, ";")?; + + Ok(()) + } + + /// Helper method used to find which expressions of a given function require baking + /// + /// # Notes + /// Clears `need_bake_expressions` set before adding to it + fn update_expressions_to_bake(&mut self, func: &crate::Function, info: &valid::FunctionInfo) { + use crate::Expression; + self.need_bake_expressions.clear(); + for (fun_handle, expr) in func.expressions.iter() { + let expr_info = &info[fun_handle]; + let min_ref_count = func.expressions[fun_handle].bake_ref_count(); + if min_ref_count <= expr_info.ref_count { + self.need_bake_expressions.insert(fun_handle); + } + + let inner = expr_info.ty.inner_with(&self.module.types); + + if let Expression::Math { fun, arg, arg1, .. } = *expr { + match fun { + crate::MathFunction::Dot => { + // if the expression is a Dot product with integer arguments, + // then the args needs baking as well + if let TypeInner::Scalar(crate::Scalar { kind, .. }) = *inner { + match kind { + crate::ScalarKind::Sint | crate::ScalarKind::Uint => { + self.need_bake_expressions.insert(arg); + self.need_bake_expressions.insert(arg1.unwrap()); + } + _ => {} + } + } + } + crate::MathFunction::CountLeadingZeros => { + if let Some(crate::ScalarKind::Sint) = inner.scalar_kind() { + self.need_bake_expressions.insert(arg); + } + } + _ => {} + } + } + } + } + + /// Helper method used to get a name for a global + /// + /// Globals have different naming schemes depending on their binding: + /// - Globals without bindings use the name from the [`Namer`](crate::proc::Namer) + /// - Globals with resource binding are named `_group_X_binding_Y` where `X` + /// is the group and `Y` is the binding + fn get_global_name( + &self, + handle: Handle, + global: &crate::GlobalVariable, + ) -> String { + match (&global.binding, global.space) { + (&Some(ref br), _) => { + format!( + "_group_{}_binding_{}_{}", + br.group, + br.binding, + self.entry_point.stage.to_str() + ) + } + (&None, crate::AddressSpace::PushConstant) => { + format!("_push_constant_binding_{}", self.entry_point.stage.to_str()) + } + (&None, _) => self.names[&NameKey::GlobalVariable(handle)].clone(), + } + } + + /// Helper method used to write a name for a global without additional heap allocation + fn write_global_name( + &mut self, + handle: Handle, + global: &crate::GlobalVariable, + ) -> BackendResult { + match (&global.binding, global.space) { + (&Some(ref br), _) => write!( + self.out, + "_group_{}_binding_{}_{}", + br.group, + br.binding, + self.entry_point.stage.to_str() + )?, + (&None, crate::AddressSpace::PushConstant) => write!( + self.out, + "_push_constant_binding_{}", + self.entry_point.stage.to_str() + )?, + (&None, _) => write!( + self.out, + "{}", + &self.names[&NameKey::GlobalVariable(handle)] + )?, + } + + Ok(()) + } + + /// Write a GLSL global that will carry a Naga entry point's argument or return value. + /// + /// A Naga entry point's arguments and return value are rendered in GLSL as + /// variables at global scope with the `in` and `out` storage qualifiers. + /// The code we generate for `main` loads from all the `in` globals into + /// appropriately named locals. Before it returns, `main` assigns the + /// components of its return value into all the `out` globals. + /// + /// This function writes a declaration for one such GLSL global, + /// representing a value passed into or returned from [`self.entry_point`] + /// that has a [`Location`] binding. The global's name is generated based on + /// the location index and the shader stages being connected; see + /// [`VaryingName`]. This means we don't need to know the names of + /// arguments, just their types and bindings. + /// + /// Emit nothing for entry point arguments or return values with [`BuiltIn`] + /// bindings; `main` will read from or assign to the appropriate GLSL + /// special variable; these are pre-declared. As an exception, we do declare + /// `gl_Position` or `gl_FragCoord` with the `invariant` qualifier if + /// needed. + /// + /// Use `output` together with [`self.entry_point.stage`] to determine which + /// shader stages are being connected, and choose the `in` or `out` storage + /// qualifier. + /// + /// [`self.entry_point`]: Writer::entry_point + /// [`self.entry_point.stage`]: crate::EntryPoint::stage + /// [`Location`]: crate::Binding::Location + /// [`BuiltIn`]: crate::Binding::BuiltIn + fn write_varying( + &mut self, + binding: Option<&crate::Binding>, + ty: Handle, + output: bool, + ) -> Result<(), Error> { + // For a struct, emit a separate global for each member with a binding. + if let crate::TypeInner::Struct { ref members, .. } = self.module.types[ty].inner { + for member in members { + self.write_varying(member.binding.as_ref(), member.ty, output)?; + } + return Ok(()); + } + + let binding = match binding { + None => return Ok(()), + Some(binding) => binding, + }; + + let (location, interpolation, sampling, second_blend_source) = match *binding { + crate::Binding::Location { + location, + interpolation, + sampling, + second_blend_source, + } => (location, interpolation, sampling, second_blend_source), + crate::Binding::BuiltIn(built_in) => { + if let crate::BuiltIn::Position { invariant: true } = built_in { + match (self.options.version, self.entry_point.stage) { + ( + Version::Embedded { + version: 300, + is_webgl: true, + }, + ShaderStage::Fragment, + ) => { + // `invariant gl_FragCoord` is not allowed in WebGL2 and possibly + // OpenGL ES in general (waiting on confirmation). + // + // See https://github.com/KhronosGroup/WebGL/issues/3518 + } + _ => { + writeln!( + self.out, + "invariant {};", + glsl_built_in( + built_in, + VaryingOptions::from_writer_options(self.options, output) + ) + )?; + } + } + } + return Ok(()); + } + }; + + // Write the interpolation modifier if needed + // + // We ignore all interpolation and auxiliary modifiers that aren't used in fragment + // shaders' input globals or vertex shaders' output globals. + let emit_interpolation_and_auxiliary = match self.entry_point.stage { + ShaderStage::Vertex => output, + ShaderStage::Fragment => !output, + ShaderStage::Compute => false, + }; + + // Write the I/O locations, if allowed + let io_location = if self.options.version.supports_explicit_locations() + || !emit_interpolation_and_auxiliary + { + if self.options.version.supports_io_locations() { + if second_blend_source { + write!(self.out, "layout(location = {location}, index = 1) ")?; + } else { + write!(self.out, "layout(location = {location}) ")?; + } + None + } else { + Some(VaryingLocation { + location, + index: second_blend_source as u32, + }) + } + } else { + None + }; + + // Write the interpolation qualifier. + if let Some(interp) = interpolation { + if emit_interpolation_and_auxiliary { + write!(self.out, "{} ", glsl_interpolation(interp))?; + } + } + + // Write the sampling auxiliary qualifier. + // + // Before GLSL 4.2, the `centroid` and `sample` qualifiers were required to appear + // immediately before the `in` / `out` qualifier, so we'll just follow that rule + // here, regardless of the version. + if let Some(sampling) = sampling { + if emit_interpolation_and_auxiliary { + if let Some(qualifier) = glsl_sampling(sampling) { + write!(self.out, "{qualifier} ")?; + } + } + } + + // Write the input/output qualifier. + write!(self.out, "{} ", if output { "out" } else { "in" })?; + + // Write the type + // `write_type` adds no leading or trailing spaces + self.write_type(ty)?; + + // Finally write the global name and end the global with a `;` and a newline + // Leading space is important + let vname = VaryingName { + binding: &crate::Binding::Location { + location, + interpolation: None, + sampling: None, + second_blend_source, + }, + stage: self.entry_point.stage, + options: VaryingOptions::from_writer_options(self.options, output), + }; + writeln!(self.out, " {vname};")?; + + if let Some(location) = io_location { + self.varying.insert(vname.to_string(), location); + } + + Ok(()) + } + + /// Helper method used to write functions (both entry points and regular functions) + /// + /// # Notes + /// Adds a newline + fn write_function( + &mut self, + ty: back::FunctionType, + func: &crate::Function, + info: &valid::FunctionInfo, + ) -> BackendResult { + // Create a function context for the function being written + let ctx = back::FunctionCtx { + ty, + info, + expressions: &func.expressions, + named_expressions: &func.named_expressions, + }; + + self.named_expressions.clear(); + self.update_expressions_to_bake(func, info); + + // Write the function header + // + // glsl headers are the same as in c: + // `ret_type name(args)` + // `ret_type` is the return type + // `name` is the function name + // `args` is a comma separated list of `type name` + // | - `type` is the argument type + // | - `name` is the argument name + + // Start by writing the return type if any otherwise write void + // This is the only place where `void` is a valid type + // (though it's more a keyword than a type) + if let back::FunctionType::EntryPoint(_) = ctx.ty { + write!(self.out, "void")?; + } else if let Some(ref result) = func.result { + self.write_type(result.ty)?; + if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner { + self.write_array_size(base, size)? + } + } else { + write!(self.out, "void")?; + } + + // Write the function name and open parentheses for the argument list + let function_name = match ctx.ty { + back::FunctionType::Function(handle) => &self.names[&NameKey::Function(handle)], + back::FunctionType::EntryPoint(_) => "main", + }; + write!(self.out, " {function_name}(")?; + + // Write the comma separated argument list + // + // We need access to `Self` here so we use the reference passed to the closure as an + // argument instead of capturing as that would cause a borrow checker error + let arguments = match ctx.ty { + back::FunctionType::EntryPoint(_) => &[][..], + back::FunctionType::Function(_) => &func.arguments, + }; + let arguments: Vec<_> = arguments + .iter() + .enumerate() + .filter(|&(_, arg)| match self.module.types[arg.ty].inner { + TypeInner::Sampler { .. } => false, + _ => true, + }) + .collect(); + self.write_slice(&arguments, |this, _, &(i, arg)| { + // Write the argument type + match this.module.types[arg.ty].inner { + // We treat images separately because they might require + // writing the storage format + TypeInner::Image { + dim, + arrayed, + class, + } => { + // Write the storage format if needed + if let TypeInner::Image { + class: crate::ImageClass::Storage { format, .. }, + .. + } = this.module.types[arg.ty].inner + { + write!(this.out, "layout({}) ", glsl_storage_format(format)?)?; + } + + // write the type + // + // This is way we need the leading space because `write_image_type` doesn't add + // any spaces at the beginning or end + this.write_image_type(dim, arrayed, class)?; + } + TypeInner::Pointer { base, .. } => { + // write parameter qualifiers + write!(this.out, "inout ")?; + this.write_type(base)?; + } + // All other types are written by `write_type` + _ => { + this.write_type(arg.ty)?; + } + } + + // Write the argument name + // The leading space is important + write!(this.out, " {}", &this.names[&ctx.argument_key(i as u32)])?; + + // Write array size + match this.module.types[arg.ty].inner { + TypeInner::Array { base, size, .. } => { + this.write_array_size(base, size)?; + } + TypeInner::Pointer { base, .. } => { + if let TypeInner::Array { base, size, .. } = this.module.types[base].inner { + this.write_array_size(base, size)?; + } + } + _ => {} + } + + Ok(()) + })?; + + // Close the parentheses and open braces to start the function body + writeln!(self.out, ") {{")?; + + if self.options.zero_initialize_workgroup_memory + && ctx.ty.is_compute_entry_point(self.module) + { + self.write_workgroup_variables_initialization(&ctx)?; + } + + // Compose the function arguments from globals, in case of an entry point. + if let back::FunctionType::EntryPoint(ep_index) = ctx.ty { + let stage = self.module.entry_points[ep_index as usize].stage; + for (index, arg) in func.arguments.iter().enumerate() { + write!(self.out, "{}", back::INDENT)?; + self.write_type(arg.ty)?; + let name = &self.names[&NameKey::EntryPointArgument(ep_index, index as u32)]; + write!(self.out, " {name}")?; + write!(self.out, " = ")?; + match self.module.types[arg.ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + self.write_type(arg.ty)?; + write!(self.out, "(")?; + for (index, member) in members.iter().enumerate() { + let varying_name = VaryingName { + binding: member.binding.as_ref().unwrap(), + stage, + options: VaryingOptions::from_writer_options(self.options, false), + }; + if index != 0 { + write!(self.out, ", ")?; + } + write!(self.out, "{varying_name}")?; + } + writeln!(self.out, ");")?; + } + _ => { + let varying_name = VaryingName { + binding: arg.binding.as_ref().unwrap(), + stage, + options: VaryingOptions::from_writer_options(self.options, false), + }; + writeln!(self.out, "{varying_name};")?; + } + } + } + } + + // Write all function locals + // Locals are `type name (= init)?;` where the init part (including the =) are optional + // + // Always adds a newline + for (handle, local) in func.local_variables.iter() { + // Write indentation (only for readability) and the type + // `write_type` adds no trailing space + write!(self.out, "{}", back::INDENT)?; + self.write_type(local.ty)?; + + // Write the local name + // The leading space is important + write!(self.out, " {}", self.names[&ctx.name_key(handle)])?; + // Write size for array type + if let TypeInner::Array { base, size, .. } = self.module.types[local.ty].inner { + self.write_array_size(base, size)?; + } + // Write the local initializer if needed + if let Some(init) = local.init { + // Put the equal signal only if there's a initializer + // The leading and trailing spaces aren't needed but help with readability + write!(self.out, " = ")?; + + // Write the constant + // `write_constant` adds no trailing or leading space/newline + self.write_expr(init, &ctx)?; + } else if is_value_init_supported(self.module, local.ty) { + write!(self.out, " = ")?; + self.write_zero_init_value(local.ty)?; + } + + // Finish the local with `;` and add a newline (only for readability) + writeln!(self.out, ";")? + } + + // Write the function body (statement list) + for sta in func.body.iter() { + // Write a statement, the indentation should always be 1 when writing the function body + // `write_stmt` adds a newline + self.write_stmt(sta, &ctx, back::Level(1))?; + } + + // Close braces and add a newline + writeln!(self.out, "}}")?; + + Ok(()) + } + + fn write_workgroup_variables_initialization( + &mut self, + ctx: &back::FunctionCtx, + ) -> BackendResult { + let mut vars = self + .module + .global_variables + .iter() + .filter(|&(handle, var)| { + !ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup + }) + .peekable(); + + if vars.peek().is_some() { + let level = back::Level(1); + + writeln!(self.out, "{level}if (gl_LocalInvocationID == uvec3(0u)) {{")?; + + for (handle, var) in vars { + let name = &self.names[&NameKey::GlobalVariable(handle)]; + write!(self.out, "{}{} = ", level.next(), name)?; + self.write_zero_init_value(var.ty)?; + writeln!(self.out, ";")?; + } + + writeln!(self.out, "{level}}}")?; + self.write_barrier(crate::Barrier::WORK_GROUP, level)?; + } + + Ok(()) + } + + /// Write a list of comma separated `T` values using a writer function `F`. + /// + /// The writer function `F` receives a mutable reference to `self` that if needed won't cause + /// borrow checker issues (using for example a closure with `self` will cause issues), the + /// second argument is the 0 based index of the element on the list, and the last element is + /// a reference to the element `T` being written + /// + /// # Notes + /// - Adds no newlines or leading/trailing whitespace + /// - The last element won't have a trailing `,` + fn write_slice BackendResult>( + &mut self, + data: &[T], + mut f: F, + ) -> BackendResult { + // Loop through `data` invoking `f` for each element + for (index, item) in data.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + f(self, index as u32, item)?; + } + + Ok(()) + } + + /// Helper method used to write global constants + fn write_global_constant(&mut self, handle: Handle) -> BackendResult { + write!(self.out, "const ")?; + let constant = &self.module.constants[handle]; + self.write_type(constant.ty)?; + let name = &self.names[&NameKey::Constant(handle)]; + write!(self.out, " {name}")?; + if let TypeInner::Array { base, size, .. } = self.module.types[constant.ty].inner { + self.write_array_size(base, size)?; + } + write!(self.out, " = ")?; + self.write_const_expr(constant.init)?; + writeln!(self.out, ";")?; + Ok(()) + } + + /// Helper method used to output a dot product as an arithmetic expression + /// + fn write_dot_product( + &mut self, + arg: Handle, + arg1: Handle, + size: usize, + ctx: &back::FunctionCtx, + ) -> BackendResult { + // Write parantheses around the dot product expression to prevent operators + // with different precedences from applying earlier. + write!(self.out, "(")?; + + // Cycle trough all the components of the vector + for index in 0..size { + let component = back::COMPONENTS[index]; + // Write the addition to the previous product + // This will print an extra '+' at the beginning but that is fine in glsl + write!(self.out, " + ")?; + // Write the first vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.write_expr(arg, ctx)?; + // Access the current component on the first vector + write!(self.out, ".{component} * ")?; + // Write the second vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.write_expr(arg1, ctx)?; + // Access the current component on the second vector + write!(self.out, ".{component}")?; + } + + write!(self.out, ")")?; + Ok(()) + } + + /// Helper method used to write structs + /// + /// # Notes + /// Ends in a newline + fn write_struct_body( + &mut self, + handle: Handle, + members: &[crate::StructMember], + ) -> BackendResult { + // glsl structs are written as in C + // `struct name() { members };` + // | `struct` is a keyword + // | `name` is the struct name + // | `members` is a semicolon separated list of `type name` + // | `type` is the member type + // | `name` is the member name + writeln!(self.out, "{{")?; + + for (idx, member) in members.iter().enumerate() { + // The indentation is only for readability + write!(self.out, "{}", back::INDENT)?; + + match self.module.types[member.ty].inner { + TypeInner::Array { + base, + size, + stride: _, + } => { + self.write_type(base)?; + write!( + self.out, + " {}", + &self.names[&NameKey::StructMember(handle, idx as u32)] + )?; + // Write [size] + self.write_array_size(base, size)?; + // Newline is important + writeln!(self.out, ";")?; + } + _ => { + // Write the member type + // Adds no trailing space + self.write_type(member.ty)?; + + // Write the member name and put a semicolon + // The leading space is important + // All members must have a semicolon even the last one + writeln!( + self.out, + " {};", + &self.names[&NameKey::StructMember(handle, idx as u32)] + )?; + } + } + } + + write!(self.out, "}}")?; + Ok(()) + } + + /// Helper method used to write statements + /// + /// # Notes + /// Always adds a newline + fn write_stmt( + &mut self, + sta: &crate::Statement, + ctx: &back::FunctionCtx, + level: back::Level, + ) -> BackendResult { + use crate::Statement; + + match *sta { + // This is where we can generate intermediate constants for some expression types. + Statement::Emit(ref range) => { + for handle in range.clone() { + let ptr_class = ctx.resolve_type(handle, &self.module.types).pointer_space(); + let expr_name = if ptr_class.is_some() { + // GLSL can't save a pointer-valued expression in a variable, + // but we shouldn't ever need to: they should never be named expressions, + // and none of the expression types flagged by bake_ref_count can be pointer-valued. + None + } else if let Some(name) = ctx.named_expressions.get(&handle) { + // Front end provides names for all variables at the start of writing. + // But we write them to step by step. We need to recache them + // Otherwise, we could accidentally write variable name instead of full expression. + // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords. + Some(self.namer.call(name)) + } else if self.need_bake_expressions.contains(&handle) { + Some(format!("{}{}", back::BAKE_PREFIX, handle.index())) + } else { + None + }; + + // If we are going to write an `ImageLoad` next and the target image + // is sampled and we are using the `Restrict` policy for bounds + // checking images we need to write a local holding the clamped lod. + if let crate::Expression::ImageLoad { + image, + level: Some(level_expr), + .. + } = ctx.expressions[handle] + { + if let TypeInner::Image { + class: crate::ImageClass::Sampled { .. }, + .. + } = *ctx.resolve_type(image, &self.module.types) + { + if let proc::BoundsCheckPolicy::Restrict = self.policies.image_load { + write!(self.out, "{level}")?; + self.write_clamped_lod(ctx, handle, image, level_expr)? + } + } + } + + if let Some(name) = expr_name { + write!(self.out, "{level}")?; + self.write_named_expr(handle, name, handle, ctx)?; + } + } + } + // Blocks are simple we just need to write the block statements between braces + // We could also just print the statements but this is more readable and maps more + // closely to the IR + Statement::Block(ref block) => { + write!(self.out, "{level}")?; + writeln!(self.out, "{{")?; + for sta in block.iter() { + // Increase the indentation to help with readability + self.write_stmt(sta, ctx, level.next())? + } + writeln!(self.out, "{level}}}")? + } + // Ifs are written as in C: + // ``` + // if(condition) { + // accept + // } else { + // reject + // } + // ``` + Statement::If { + condition, + ref accept, + ref reject, + } => { + write!(self.out, "{level}")?; + write!(self.out, "if (")?; + self.write_expr(condition, ctx)?; + writeln!(self.out, ") {{")?; + + for sta in accept { + // Increase indentation to help with readability + self.write_stmt(sta, ctx, level.next())?; + } + + // If there are no statements in the reject block we skip writing it + // This is only for readability + if !reject.is_empty() { + writeln!(self.out, "{level}}} else {{")?; + + for sta in reject { + // Increase indentation to help with readability + self.write_stmt(sta, ctx, level.next())?; + } + } + + writeln!(self.out, "{level}}}")? + } + // Switch are written as in C: + // ``` + // switch (selector) { + // // Fallthrough + // case label: + // block + // // Non fallthrough + // case label: + // block + // break; + // default: + // block + // } + // ``` + // Where the `default` case happens isn't important but we put it last + // so that we don't need to print a `break` for it + Statement::Switch { + selector, + ref cases, + } => { + // Start the switch + write!(self.out, "{level}")?; + write!(self.out, "switch(")?; + self.write_expr(selector, ctx)?; + writeln!(self.out, ") {{")?; + + // Write all cases + let l2 = level.next(); + for case in cases { + match case.value { + crate::SwitchValue::I32(value) => write!(self.out, "{l2}case {value}:")?, + crate::SwitchValue::U32(value) => write!(self.out, "{l2}case {value}u:")?, + crate::SwitchValue::Default => write!(self.out, "{l2}default:")?, + } + + let write_block_braces = !(case.fall_through && case.body.is_empty()); + if write_block_braces { + writeln!(self.out, " {{")?; + } else { + writeln!(self.out)?; + } + + for sta in case.body.iter() { + self.write_stmt(sta, ctx, l2.next())?; + } + + if !case.fall_through && case.body.last().map_or(true, |s| !s.is_terminator()) { + writeln!(self.out, "{}break;", l2.next())?; + } + + if write_block_braces { + writeln!(self.out, "{l2}}}")?; + } + } + + writeln!(self.out, "{level}}}")? + } + // Loops in naga IR are based on wgsl loops, glsl can emulate the behaviour by using a + // while true loop and appending the continuing block to the body resulting on: + // ``` + // bool loop_init = true; + // while(true) { + // if (!loop_init) { } + // loop_init = false; + // + // } + // ``` + Statement::Loop { + ref body, + ref continuing, + break_if, + } => { + if !continuing.is_empty() || break_if.is_some() { + let gate_name = self.namer.call("loop_init"); + writeln!(self.out, "{level}bool {gate_name} = true;")?; + writeln!(self.out, "{level}while(true) {{")?; + let l2 = level.next(); + let l3 = l2.next(); + writeln!(self.out, "{l2}if (!{gate_name}) {{")?; + for sta in continuing { + self.write_stmt(sta, ctx, l3)?; + } + if let Some(condition) = break_if { + write!(self.out, "{l3}if (")?; + self.write_expr(condition, ctx)?; + writeln!(self.out, ") {{")?; + writeln!(self.out, "{}break;", l3.next())?; + writeln!(self.out, "{l3}}}")?; + } + writeln!(self.out, "{l2}}}")?; + writeln!(self.out, "{}{} = false;", level.next(), gate_name)?; + } else { + writeln!(self.out, "{level}while(true) {{")?; + } + for sta in body { + self.write_stmt(sta, ctx, level.next())?; + } + writeln!(self.out, "{level}}}")? + } + // Break, continue and return as written as in C + // `break;` + Statement::Break => { + write!(self.out, "{level}")?; + writeln!(self.out, "break;")? + } + // `continue;` + Statement::Continue => { + write!(self.out, "{level}")?; + writeln!(self.out, "continue;")? + } + // `return expr;`, `expr` is optional + Statement::Return { value } => { + write!(self.out, "{level}")?; + match ctx.ty { + back::FunctionType::Function(_) => { + write!(self.out, "return")?; + // Write the expression to be returned if needed + if let Some(expr) = value { + write!(self.out, " ")?; + self.write_expr(expr, ctx)?; + } + writeln!(self.out, ";")?; + } + back::FunctionType::EntryPoint(ep_index) => { + let mut has_point_size = false; + let ep = &self.module.entry_points[ep_index as usize]; + if let Some(ref result) = ep.function.result { + let value = value.unwrap(); + match self.module.types[result.ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + let temp_struct_name = match ctx.expressions[value] { + crate::Expression::Compose { .. } => { + let return_struct = "_tmp_return"; + write!( + self.out, + "{} {} = ", + &self.names[&NameKey::Type(result.ty)], + return_struct + )?; + self.write_expr(value, ctx)?; + writeln!(self.out, ";")?; + write!(self.out, "{level}")?; + Some(return_struct) + } + _ => None, + }; + + for (index, member) in members.iter().enumerate() { + if let Some(crate::Binding::BuiltIn( + crate::BuiltIn::PointSize, + )) = member.binding + { + has_point_size = true; + } + + let varying_name = VaryingName { + binding: member.binding.as_ref().unwrap(), + stage: ep.stage, + options: VaryingOptions::from_writer_options( + self.options, + true, + ), + }; + write!(self.out, "{varying_name} = ")?; + + if let Some(struct_name) = temp_struct_name { + write!(self.out, "{struct_name}")?; + } else { + self.write_expr(value, ctx)?; + } + + // Write field name + writeln!( + self.out, + ".{};", + &self.names + [&NameKey::StructMember(result.ty, index as u32)] + )?; + write!(self.out, "{level}")?; + } + } + _ => { + let name = VaryingName { + binding: result.binding.as_ref().unwrap(), + stage: ep.stage, + options: VaryingOptions::from_writer_options( + self.options, + true, + ), + }; + write!(self.out, "{name} = ")?; + self.write_expr(value, ctx)?; + writeln!(self.out, ";")?; + write!(self.out, "{level}")?; + } + } + } + + let is_vertex_stage = self.module.entry_points[ep_index as usize].stage + == ShaderStage::Vertex; + if is_vertex_stage + && self + .options + .writer_flags + .contains(WriterFlags::ADJUST_COORDINATE_SPACE) + { + writeln!( + self.out, + "gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w);", + )?; + write!(self.out, "{level}")?; + } + + if is_vertex_stage + && self + .options + .writer_flags + .contains(WriterFlags::FORCE_POINT_SIZE) + && !has_point_size + { + writeln!(self.out, "gl_PointSize = 1.0;")?; + write!(self.out, "{level}")?; + } + writeln!(self.out, "return;")?; + } + } + } + // This is one of the places were glsl adds to the syntax of C in this case the discard + // keyword which ceases all further processing in a fragment shader, it's called OpKill + // in spir-v that's why it's called `Statement::Kill` + Statement::Kill => writeln!(self.out, "{level}discard;")?, + Statement::Barrier(flags) => { + self.write_barrier(flags, level)?; + } + // Stores in glsl are just variable assignments written as `pointer = value;` + Statement::Store { pointer, value } => { + write!(self.out, "{level}")?; + self.write_expr(pointer, ctx)?; + write!(self.out, " = ")?; + self.write_expr(value, ctx)?; + writeln!(self.out, ";")? + } + Statement::WorkGroupUniformLoad { pointer, result } => { + // GLSL doesn't have pointers, which means that this backend needs to ensure that + // the actual "loading" is happening between the two barriers. + // This is done in `Emit` by never emitting a variable name for pointer variables + self.write_barrier(crate::Barrier::WORK_GROUP, level)?; + + let result_name = format!("{}{}", back::BAKE_PREFIX, result.index()); + write!(self.out, "{level}")?; + // Expressions cannot have side effects, so just writing the expression here is fine. + self.write_named_expr(pointer, result_name, result, ctx)?; + + self.write_barrier(crate::Barrier::WORK_GROUP, level)?; + } + // Stores a value into an image. + Statement::ImageStore { + image, + coordinate, + array_index, + value, + } => { + write!(self.out, "{level}")?; + self.write_image_store(ctx, image, coordinate, array_index, value)? + } + // A `Call` is written `name(arguments)` where `arguments` is a comma separated expressions list + Statement::Call { + function, + ref arguments, + result, + } => { + write!(self.out, "{level}")?; + if let Some(expr) = result { + let name = format!("{}{}", back::BAKE_PREFIX, expr.index()); + let result = self.module.functions[function].result.as_ref().unwrap(); + self.write_type(result.ty)?; + write!(self.out, " {name}")?; + if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner + { + self.write_array_size(base, size)? + } + write!(self.out, " = ")?; + self.named_expressions.insert(expr, name); + } + write!(self.out, "{}(", &self.names[&NameKey::Function(function)])?; + let arguments: Vec<_> = arguments + .iter() + .enumerate() + .filter_map(|(i, arg)| { + let arg_ty = self.module.functions[function].arguments[i].ty; + match self.module.types[arg_ty].inner { + TypeInner::Sampler { .. } => None, + _ => Some(*arg), + } + }) + .collect(); + self.write_slice(&arguments, |this, _, arg| this.write_expr(*arg, ctx))?; + writeln!(self.out, ");")? + } + Statement::Atomic { + pointer, + ref fun, + value, + result, + } => { + write!(self.out, "{level}")?; + let res_name = format!("{}{}", back::BAKE_PREFIX, result.index()); + let res_ty = ctx.resolve_type(result, &self.module.types); + self.write_value_type(res_ty)?; + write!(self.out, " {res_name} = ")?; + self.named_expressions.insert(result, res_name); + + let fun_str = fun.to_glsl(); + write!(self.out, "atomic{fun_str}(")?; + self.write_expr(pointer, ctx)?; + write!(self.out, ", ")?; + // handle the special cases + match *fun { + crate::AtomicFunction::Subtract => { + // we just wrote `InterlockedAdd`, so negate the argument + write!(self.out, "-")?; + } + crate::AtomicFunction::Exchange { compare: Some(_) } => { + return Err(Error::Custom( + "atomic CompareExchange is not implemented".to_string(), + )); + } + _ => {} + } + self.write_expr(value, ctx)?; + writeln!(self.out, ");")?; + } + Statement::RayQuery { .. } => unreachable!(), + } + + Ok(()) + } + + /// Write a const expression. + /// + /// Write `expr`, a handle to an [`Expression`] in the current [`Module`]'s + /// constant expression arena, as GLSL expression. + /// + /// # Notes + /// Adds no newlines or leading/trailing whitespace + /// + /// [`Expression`]: crate::Expression + /// [`Module`]: crate::Module + fn write_const_expr(&mut self, expr: Handle) -> BackendResult { + self.write_possibly_const_expr( + expr, + &self.module.const_expressions, + |expr| &self.info[expr], + |writer, expr| writer.write_const_expr(expr), + ) + } + + /// Write [`Expression`] variants that can occur in both runtime and const expressions. + /// + /// Write `expr`, a handle to an [`Expression`] in the arena `expressions`, + /// as as GLSL expression. This must be one of the [`Expression`] variants + /// that is allowed to occur in constant expressions. + /// + /// Use `write_expression` to write subexpressions. + /// + /// This is the common code for `write_expr`, which handles arbitrary + /// runtime expressions, and `write_const_expr`, which only handles + /// const-expressions. Each of those callers passes itself (essentially) as + /// the `write_expression` callback, so that subexpressions are restricted + /// to the appropriate variants. + /// + /// # Notes + /// Adds no newlines or leading/trailing whitespace + /// + /// [`Expression`]: crate::Expression + fn write_possibly_const_expr<'w, I, E>( + &'w mut self, + expr: Handle, + expressions: &crate::Arena, + info: I, + write_expression: E, + ) -> BackendResult + where + I: Fn(Handle) -> &'w proc::TypeResolution, + E: Fn(&mut Self, Handle) -> BackendResult, + { + use crate::Expression; + + match expressions[expr] { + Expression::Literal(literal) => { + match literal { + // Floats are written using `Debug` instead of `Display` because it always appends the + // decimal part even it's zero which is needed for a valid glsl float constant + crate::Literal::F64(value) => write!(self.out, "{:?}LF", value)?, + crate::Literal::F32(value) => write!(self.out, "{:?}", value)?, + // Unsigned integers need a `u` at the end + // + // While `core` doesn't necessarily need it, it's allowed and since `es` needs it we + // always write it as the extra branch wouldn't have any benefit in readability + crate::Literal::U32(value) => write!(self.out, "{}u", value)?, + crate::Literal::I32(value) => write!(self.out, "{}", value)?, + crate::Literal::Bool(value) => write!(self.out, "{}", value)?, + crate::Literal::I64(_) => { + return Err(Error::Custom("GLSL has no 64-bit integer type".into())); + } + crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { + return Err(Error::Custom( + "Abstract types should not appear in IR presented to backends".into(), + )); + } + } + } + Expression::Constant(handle) => { + let constant = &self.module.constants[handle]; + if constant.name.is_some() { + write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; + } else { + self.write_const_expr(constant.init)?; + } + } + Expression::ZeroValue(ty) => { + self.write_zero_init_value(ty)?; + } + Expression::Compose { ty, ref components } => { + self.write_type(ty)?; + + if let TypeInner::Array { base, size, .. } = self.module.types[ty].inner { + self.write_array_size(base, size)?; + } + + write!(self.out, "(")?; + for (index, component) in components.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + write_expression(self, *component)?; + } + write!(self.out, ")")? + } + // `Splat` needs to actually write down a vector, it's not always inferred in GLSL. + Expression::Splat { size: _, value } => { + let resolved = info(expr).inner_with(&self.module.types); + self.write_value_type(resolved)?; + write!(self.out, "(")?; + write_expression(self, value)?; + write!(self.out, ")")? + } + _ => unreachable!(), + } + + Ok(()) + } + + /// Helper method to write expressions + /// + /// # Notes + /// Doesn't add any newlines or leading/trailing spaces + fn write_expr( + &mut self, + expr: Handle, + ctx: &back::FunctionCtx, + ) -> BackendResult { + use crate::Expression; + + if let Some(name) = self.named_expressions.get(&expr) { + write!(self.out, "{name}")?; + return Ok(()); + } + + match ctx.expressions[expr] { + Expression::Literal(_) + | Expression::Constant(_) + | Expression::ZeroValue(_) + | Expression::Compose { .. } + | Expression::Splat { .. } => { + self.write_possibly_const_expr( + expr, + ctx.expressions, + |expr| &ctx.info[expr].ty, + |writer, expr| writer.write_expr(expr, ctx), + )?; + } + // `Access` is applied to arrays, vectors and matrices and is written as indexing + Expression::Access { base, index } => { + self.write_expr(base, ctx)?; + write!(self.out, "[")?; + self.write_expr(index, ctx)?; + write!(self.out, "]")? + } + // `AccessIndex` is the same as `Access` except that the index is a constant and it can + // be applied to structs, in this case we need to find the name of the field at that + // index and write `base.field_name` + Expression::AccessIndex { base, index } => { + self.write_expr(base, ctx)?; + + let base_ty_res = &ctx.info[base].ty; + let mut resolved = base_ty_res.inner_with(&self.module.types); + let base_ty_handle = match *resolved { + TypeInner::Pointer { base, space: _ } => { + resolved = &self.module.types[base].inner; + Some(base) + } + _ => base_ty_res.handle(), + }; + + match *resolved { + TypeInner::Vector { .. } => { + // Write vector access as a swizzle + write!(self.out, ".{}", back::COMPONENTS[index as usize])? + } + TypeInner::Matrix { .. } + | TypeInner::Array { .. } + | TypeInner::ValuePointer { .. } => write!(self.out, "[{index}]")?, + TypeInner::Struct { .. } => { + // This will never panic in case the type is a `Struct`, this is not true + // for other types so we can only check while inside this match arm + let ty = base_ty_handle.unwrap(); + + write!( + self.out, + ".{}", + &self.names[&NameKey::StructMember(ty, index)] + )? + } + ref other => return Err(Error::Custom(format!("Cannot index {other:?}"))), + } + } + // `Swizzle` adds a few letters behind the dot. + Expression::Swizzle { + size, + vector, + pattern, + } => { + self.write_expr(vector, ctx)?; + write!(self.out, ".")?; + for &sc in pattern[..size as usize].iter() { + self.out.write_char(back::COMPONENTS[sc as usize])?; + } + } + // Function arguments are written as the argument name + Expression::FunctionArgument(pos) => { + write!(self.out, "{}", &self.names[&ctx.argument_key(pos)])? + } + // Global variables need some special work for their name but + // `get_global_name` does the work for us + Expression::GlobalVariable(handle) => { + let global = &self.module.global_variables[handle]; + self.write_global_name(handle, global)? + } + // A local is written as it's name + Expression::LocalVariable(handle) => { + write!(self.out, "{}", self.names[&ctx.name_key(handle)])? + } + // glsl has no pointers so there's no load operation, just write the pointer expression + Expression::Load { pointer } => self.write_expr(pointer, ctx)?, + // `ImageSample` is a bit complicated compared to the rest of the IR. + // + // First there are three variations depending whether the sample level is explicitly set, + // if it's automatic or it it's bias: + // `texture(image, coordinate)` - Automatic sample level + // `texture(image, coordinate, bias)` - Bias sample level + // `textureLod(image, coordinate, level)` - Zero or Exact sample level + // + // Furthermore if `depth_ref` is some we need to append it to the coordinate vector + Expression::ImageSample { + image, + sampler: _, //TODO? + gather, + coordinate, + array_index, + offset, + level, + depth_ref, + } => { + let dim = match *ctx.resolve_type(image, &self.module.types) { + TypeInner::Image { dim, .. } => dim, + _ => unreachable!(), + }; + + if dim == crate::ImageDimension::Cube + && array_index.is_some() + && depth_ref.is_some() + { + match level { + crate::SampleLevel::Zero + | crate::SampleLevel::Exact(_) + | crate::SampleLevel::Gradient { .. } + | crate::SampleLevel::Bias(_) => { + return Err(Error::Custom(String::from( + "gsamplerCubeArrayShadow isn't supported in textureGrad, \ + textureLod or texture with bias", + ))) + } + crate::SampleLevel::Auto => {} + } + } + + // textureLod on sampler2DArrayShadow and samplerCubeShadow does not exist in GLSL. + // To emulate this, we will have to use textureGrad with a constant gradient of 0. + let workaround_lod_array_shadow_as_grad = (array_index.is_some() + || dim == crate::ImageDimension::Cube) + && depth_ref.is_some() + && gather.is_none() + && !self + .options + .writer_flags + .contains(WriterFlags::TEXTURE_SHADOW_LOD); + + //Write the function to be used depending on the sample level + let fun_name = match level { + crate::SampleLevel::Zero if gather.is_some() => "textureGather", + crate::SampleLevel::Auto | crate::SampleLevel::Bias(_) => "texture", + crate::SampleLevel::Zero | crate::SampleLevel::Exact(_) => { + if workaround_lod_array_shadow_as_grad { + "textureGrad" + } else { + "textureLod" + } + } + crate::SampleLevel::Gradient { .. } => "textureGrad", + }; + let offset_name = match offset { + Some(_) => "Offset", + None => "", + }; + + write!(self.out, "{fun_name}{offset_name}(")?; + + // Write the image that will be used + self.write_expr(image, ctx)?; + // The space here isn't required but it helps with readability + write!(self.out, ", ")?; + + // We need to get the coordinates vector size to later build a vector that's `size + 1` + // if `depth_ref` is some, if it isn't a vector we panic as that's not a valid expression + let mut coord_dim = match *ctx.resolve_type(coordinate, &self.module.types) { + TypeInner::Vector { size, .. } => size as u8, + TypeInner::Scalar { .. } => 1, + _ => unreachable!(), + }; + + if array_index.is_some() { + coord_dim += 1; + } + let merge_depth_ref = depth_ref.is_some() && gather.is_none() && coord_dim < 4; + if merge_depth_ref { + coord_dim += 1; + } + + let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es(); + let is_vec = tex_1d_hack || coord_dim != 1; + // Compose a new texture coordinates vector + if is_vec { + write!(self.out, "vec{}(", coord_dim + tex_1d_hack as u8)?; + } + self.write_expr(coordinate, ctx)?; + if tex_1d_hack { + write!(self.out, ", 0.0")?; + } + if let Some(expr) = array_index { + write!(self.out, ", ")?; + self.write_expr(expr, ctx)?; + } + if merge_depth_ref { + write!(self.out, ", ")?; + self.write_expr(depth_ref.unwrap(), ctx)?; + } + if is_vec { + write!(self.out, ")")?; + } + + if let (Some(expr), false) = (depth_ref, merge_depth_ref) { + write!(self.out, ", ")?; + self.write_expr(expr, ctx)?; + } + + match level { + // Auto needs no more arguments + crate::SampleLevel::Auto => (), + // Zero needs level set to 0 + crate::SampleLevel::Zero => { + if workaround_lod_array_shadow_as_grad { + let vec_dim = match dim { + crate::ImageDimension::Cube => 3, + _ => 2, + }; + write!(self.out, ", vec{vec_dim}(0.0), vec{vec_dim}(0.0)")?; + } else if gather.is_none() { + write!(self.out, ", 0.0")?; + } + } + // Exact and bias require another argument + crate::SampleLevel::Exact(expr) => { + if workaround_lod_array_shadow_as_grad { + log::warn!("Unable to `textureLod` a shadow array, ignoring the LOD"); + write!(self.out, ", vec2(0,0), vec2(0,0)")?; + } else { + write!(self.out, ", ")?; + self.write_expr(expr, ctx)?; + } + } + crate::SampleLevel::Bias(_) => { + // This needs to be done after the offset writing + } + crate::SampleLevel::Gradient { x, y } => { + // If we are using sampler2D to replace sampler1D, we also + // need to make sure to use vec2 gradients + if tex_1d_hack { + write!(self.out, ", vec2(")?; + self.write_expr(x, ctx)?; + write!(self.out, ", 0.0)")?; + write!(self.out, ", vec2(")?; + self.write_expr(y, ctx)?; + write!(self.out, ", 0.0)")?; + } else { + write!(self.out, ", ")?; + self.write_expr(x, ctx)?; + write!(self.out, ", ")?; + self.write_expr(y, ctx)?; + } + } + } + + if let Some(constant) = offset { + write!(self.out, ", ")?; + if tex_1d_hack { + write!(self.out, "ivec2(")?; + } + self.write_const_expr(constant)?; + if tex_1d_hack { + write!(self.out, ", 0)")?; + } + } + + // Bias is always the last argument + if let crate::SampleLevel::Bias(expr) = level { + write!(self.out, ", ")?; + self.write_expr(expr, ctx)?; + } + + if let (Some(component), None) = (gather, depth_ref) { + write!(self.out, ", {}", component as usize)?; + } + + // End the function + write!(self.out, ")")? + } + Expression::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => self.write_image_load(expr, ctx, image, coordinate, array_index, sample, level)?, + // Query translates into one of the: + // - textureSize/imageSize + // - textureQueryLevels + // - textureSamples/imageSamples + Expression::ImageQuery { image, query } => { + use crate::ImageClass; + + // This will only panic if the module is invalid + let (dim, class) = match *ctx.resolve_type(image, &self.module.types) { + TypeInner::Image { + dim, + arrayed: _, + class, + } => (dim, class), + _ => unreachable!(), + }; + let components = match dim { + crate::ImageDimension::D1 => 1, + crate::ImageDimension::D2 => 2, + crate::ImageDimension::D3 => 3, + crate::ImageDimension::Cube => 2, + }; + + if let crate::ImageQuery::Size { .. } = query { + match components { + 1 => write!(self.out, "uint(")?, + _ => write!(self.out, "uvec{components}(")?, + } + } else { + write!(self.out, "uint(")?; + } + + match query { + crate::ImageQuery::Size { level } => { + match class { + ImageClass::Sampled { multi, .. } | ImageClass::Depth { multi } => { + write!(self.out, "textureSize(")?; + self.write_expr(image, ctx)?; + if let Some(expr) = level { + let cast_to_int = matches!( + *ctx.resolve_type(expr, &self.module.types), + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }) + ); + + write!(self.out, ", ")?; + + if cast_to_int { + write!(self.out, "int(")?; + } + + self.write_expr(expr, ctx)?; + + if cast_to_int { + write!(self.out, ")")?; + } + } else if !multi { + // All textureSize calls requires an lod argument + // except for multisampled samplers + write!(self.out, ", 0")?; + } + } + ImageClass::Storage { .. } => { + write!(self.out, "imageSize(")?; + self.write_expr(image, ctx)?; + } + } + write!(self.out, ")")?; + if components != 1 || self.options.version.is_es() { + write!(self.out, ".{}", &"xyz"[..components])?; + } + } + crate::ImageQuery::NumLevels => { + write!(self.out, "textureQueryLevels(",)?; + self.write_expr(image, ctx)?; + write!(self.out, ")",)?; + } + crate::ImageQuery::NumLayers => { + let fun_name = match class { + ImageClass::Sampled { .. } | ImageClass::Depth { .. } => "textureSize", + ImageClass::Storage { .. } => "imageSize", + }; + write!(self.out, "{fun_name}(")?; + self.write_expr(image, ctx)?; + // All textureSize calls requires an lod argument + // except for multisampled samplers + if class.is_multisampled() { + write!(self.out, ", 0")?; + } + write!(self.out, ")")?; + if components != 1 || self.options.version.is_es() { + write!(self.out, ".{}", back::COMPONENTS[components])?; + } + } + crate::ImageQuery::NumSamples => { + let fun_name = match class { + ImageClass::Sampled { .. } | ImageClass::Depth { .. } => { + "textureSamples" + } + ImageClass::Storage { .. } => "imageSamples", + }; + write!(self.out, "{fun_name}(")?; + self.write_expr(image, ctx)?; + write!(self.out, ")",)?; + } + } + + write!(self.out, ")")?; + } + Expression::Unary { op, expr } => { + let operator_or_fn = match op { + crate::UnaryOperator::Negate => "-", + crate::UnaryOperator::LogicalNot => { + match *ctx.resolve_type(expr, &self.module.types) { + TypeInner::Vector { .. } => "not", + _ => "!", + } + } + crate::UnaryOperator::BitwiseNot => "~", + }; + write!(self.out, "{operator_or_fn}(")?; + + self.write_expr(expr, ctx)?; + + write!(self.out, ")")? + } + // `Binary` we just write `left op right`, except when dealing with + // comparison operations on vectors as they are implemented with + // builtin functions. + // Once again we wrap everything in parentheses to avoid precedence issues + Expression::Binary { + mut op, + left, + right, + } => { + // Holds `Some(function_name)` if the binary operation is + // implemented as a function call + use crate::{BinaryOperator as Bo, ScalarKind as Sk, TypeInner as Ti}; + + let left_inner = ctx.resolve_type(left, &self.module.types); + let right_inner = ctx.resolve_type(right, &self.module.types); + + let function = match (left_inner, right_inner) { + (&Ti::Vector { scalar, .. }, &Ti::Vector { .. }) => match op { + Bo::Less + | Bo::LessEqual + | Bo::Greater + | Bo::GreaterEqual + | Bo::Equal + | Bo::NotEqual => BinaryOperation::VectorCompare, + Bo::Modulo if scalar.kind == Sk::Float => BinaryOperation::Modulo, + Bo::And if scalar.kind == Sk::Bool => { + op = crate::BinaryOperator::LogicalAnd; + BinaryOperation::VectorComponentWise + } + Bo::InclusiveOr if scalar.kind == Sk::Bool => { + op = crate::BinaryOperator::LogicalOr; + BinaryOperation::VectorComponentWise + } + _ => BinaryOperation::Other, + }, + _ => match (left_inner.scalar_kind(), right_inner.scalar_kind()) { + (Some(Sk::Float), _) | (_, Some(Sk::Float)) => match op { + Bo::Modulo => BinaryOperation::Modulo, + _ => BinaryOperation::Other, + }, + (Some(Sk::Bool), Some(Sk::Bool)) => match op { + Bo::InclusiveOr => { + op = crate::BinaryOperator::LogicalOr; + BinaryOperation::Other + } + Bo::And => { + op = crate::BinaryOperator::LogicalAnd; + BinaryOperation::Other + } + _ => BinaryOperation::Other, + }, + _ => BinaryOperation::Other, + }, + }; + + match function { + BinaryOperation::VectorCompare => { + let op_str = match op { + Bo::Less => "lessThan(", + Bo::LessEqual => "lessThanEqual(", + Bo::Greater => "greaterThan(", + Bo::GreaterEqual => "greaterThanEqual(", + Bo::Equal => "equal(", + Bo::NotEqual => "notEqual(", + _ => unreachable!(), + }; + write!(self.out, "{op_str}")?; + self.write_expr(left, ctx)?; + write!(self.out, ", ")?; + self.write_expr(right, ctx)?; + write!(self.out, ")")?; + } + BinaryOperation::VectorComponentWise => { + self.write_value_type(left_inner)?; + write!(self.out, "(")?; + + let size = match *left_inner { + Ti::Vector { size, .. } => size, + _ => unreachable!(), + }; + + for i in 0..size as usize { + if i != 0 { + write!(self.out, ", ")?; + } + + self.write_expr(left, ctx)?; + write!(self.out, ".{}", back::COMPONENTS[i])?; + + write!(self.out, " {} ", back::binary_operation_str(op))?; + + self.write_expr(right, ctx)?; + write!(self.out, ".{}", back::COMPONENTS[i])?; + } + + write!(self.out, ")")?; + } + // TODO: handle undefined behavior of BinaryOperator::Modulo + // + // sint: + // if right == 0 return 0 + // if left == min(type_of(left)) && right == -1 return 0 + // if sign(left) == -1 || sign(right) == -1 return result as defined by WGSL + // + // uint: + // if right == 0 return 0 + // + // float: + // if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798 + BinaryOperation::Modulo => { + write!(self.out, "(")?; + + // write `e1 - e2 * trunc(e1 / e2)` + self.write_expr(left, ctx)?; + write!(self.out, " - ")?; + self.write_expr(right, ctx)?; + write!(self.out, " * ")?; + write!(self.out, "trunc(")?; + self.write_expr(left, ctx)?; + write!(self.out, " / ")?; + self.write_expr(right, ctx)?; + write!(self.out, ")")?; + + write!(self.out, ")")?; + } + BinaryOperation::Other => { + write!(self.out, "(")?; + + self.write_expr(left, ctx)?; + write!(self.out, " {} ", back::binary_operation_str(op))?; + self.write_expr(right, ctx)?; + + write!(self.out, ")")?; + } + } + } + // `Select` is written as `condition ? accept : reject` + // We wrap everything in parentheses to avoid precedence issues + Expression::Select { + condition, + accept, + reject, + } => { + let cond_ty = ctx.resolve_type(condition, &self.module.types); + let vec_select = if let TypeInner::Vector { .. } = *cond_ty { + true + } else { + false + }; + + // TODO: Boolean mix on desktop required GL_EXT_shader_integer_mix + if vec_select { + // Glsl defines that for mix when the condition is a boolean the first element + // is picked if condition is false and the second if condition is true + write!(self.out, "mix(")?; + self.write_expr(reject, ctx)?; + write!(self.out, ", ")?; + self.write_expr(accept, ctx)?; + write!(self.out, ", ")?; + self.write_expr(condition, ctx)?; + } else { + write!(self.out, "(")?; + self.write_expr(condition, ctx)?; + write!(self.out, " ? ")?; + self.write_expr(accept, ctx)?; + write!(self.out, " : ")?; + self.write_expr(reject, ctx)?; + } + + write!(self.out, ")")? + } + // `Derivative` is a function call to a glsl provided function + Expression::Derivative { axis, ctrl, expr } => { + use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; + let fun_name = if self.options.version.supports_derivative_control() { + match (axis, ctrl) { + (Axis::X, Ctrl::Coarse) => "dFdxCoarse", + (Axis::X, Ctrl::Fine) => "dFdxFine", + (Axis::X, Ctrl::None) => "dFdx", + (Axis::Y, Ctrl::Coarse) => "dFdyCoarse", + (Axis::Y, Ctrl::Fine) => "dFdyFine", + (Axis::Y, Ctrl::None) => "dFdy", + (Axis::Width, Ctrl::Coarse) => "fwidthCoarse", + (Axis::Width, Ctrl::Fine) => "fwidthFine", + (Axis::Width, Ctrl::None) => "fwidth", + } + } else { + match axis { + Axis::X => "dFdx", + Axis::Y => "dFdy", + Axis::Width => "fwidth", + } + }; + write!(self.out, "{fun_name}(")?; + self.write_expr(expr, ctx)?; + write!(self.out, ")")? + } + // `Relational` is a normal function call to some glsl provided functions + Expression::Relational { fun, argument } => { + use crate::RelationalFunction as Rf; + + let fun_name = match fun { + Rf::IsInf => "isinf", + Rf::IsNan => "isnan", + Rf::All => "all", + Rf::Any => "any", + }; + write!(self.out, "{fun_name}(")?; + + self.write_expr(argument, ctx)?; + + write!(self.out, ")")? + } + Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + } => { + use crate::MathFunction as Mf; + + let fun_name = match fun { + // comparison + Mf::Abs => "abs", + Mf::Min => "min", + Mf::Max => "max", + Mf::Clamp => "clamp", + Mf::Saturate => { + write!(self.out, "clamp(")?; + + self.write_expr(arg, ctx)?; + + match *ctx.resolve_type(arg, &self.module.types) { + crate::TypeInner::Vector { size, .. } => write!( + self.out, + ", vec{}(0.0), vec{0}(1.0)", + back::vector_size_str(size) + )?, + _ => write!(self.out, ", 0.0, 1.0")?, + } + + write!(self.out, ")")?; + + return Ok(()); + } + // trigonometry + Mf::Cos => "cos", + Mf::Cosh => "cosh", + Mf::Sin => "sin", + Mf::Sinh => "sinh", + Mf::Tan => "tan", + Mf::Tanh => "tanh", + Mf::Acos => "acos", + Mf::Asin => "asin", + Mf::Atan => "atan", + Mf::Asinh => "asinh", + Mf::Acosh => "acosh", + Mf::Atanh => "atanh", + Mf::Radians => "radians", + Mf::Degrees => "degrees", + // glsl doesn't have atan2 function + // use two-argument variation of the atan function + Mf::Atan2 => "atan", + // decomposition + Mf::Ceil => "ceil", + Mf::Floor => "floor", + Mf::Round => "roundEven", + Mf::Fract => "fract", + Mf::Trunc => "trunc", + Mf::Modf => MODF_FUNCTION, + Mf::Frexp => FREXP_FUNCTION, + Mf::Ldexp => "ldexp", + // exponent + Mf::Exp => "exp", + Mf::Exp2 => "exp2", + Mf::Log => "log", + Mf::Log2 => "log2", + Mf::Pow => "pow", + // geometry + Mf::Dot => match *ctx.resolve_type(arg, &self.module.types) { + crate::TypeInner::Vector { + scalar: + crate::Scalar { + kind: crate::ScalarKind::Float, + .. + }, + .. + } => "dot", + crate::TypeInner::Vector { size, .. } => { + return self.write_dot_product(arg, arg1.unwrap(), size as usize, ctx) + } + _ => unreachable!( + "Correct TypeInner for dot product should be already validated" + ), + }, + Mf::Outer => "outerProduct", + Mf::Cross => "cross", + Mf::Distance => "distance", + Mf::Length => "length", + Mf::Normalize => "normalize", + Mf::FaceForward => "faceforward", + Mf::Reflect => "reflect", + Mf::Refract => "refract", + // computational + Mf::Sign => "sign", + Mf::Fma => { + if self.options.version.supports_fma_function() { + // Use the fma function when available + "fma" + } else { + // No fma support. Transform the function call into an arithmetic expression + write!(self.out, "(")?; + + self.write_expr(arg, ctx)?; + write!(self.out, " * ")?; + + let arg1 = + arg1.ok_or_else(|| Error::Custom("Missing fma arg1".to_owned()))?; + self.write_expr(arg1, ctx)?; + write!(self.out, " + ")?; + + let arg2 = + arg2.ok_or_else(|| Error::Custom("Missing fma arg2".to_owned()))?; + self.write_expr(arg2, ctx)?; + write!(self.out, ")")?; + + return Ok(()); + } + } + Mf::Mix => "mix", + Mf::Step => "step", + Mf::SmoothStep => "smoothstep", + Mf::Sqrt => "sqrt", + Mf::InverseSqrt => "inversesqrt", + Mf::Inverse => "inverse", + Mf::Transpose => "transpose", + Mf::Determinant => "determinant", + // bits + Mf::CountTrailingZeros => { + match *ctx.resolve_type(arg, &self.module.types) { + crate::TypeInner::Vector { size, scalar, .. } => { + let s = back::vector_size_str(size); + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "min(uvec{s}(findLSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")), uvec{s}(32u))")?; + } else { + write!(self.out, "ivec{s}(min(uvec{s}(findLSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")), uvec{s}(32u)))")?; + } + } + crate::TypeInner::Scalar(scalar) => { + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "min(uint(findLSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")), 32u)")?; + } else { + write!(self.out, "int(min(uint(findLSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")), 32u))")?; + } + } + _ => unreachable!(), + }; + return Ok(()); + } + Mf::CountLeadingZeros => { + if self.options.version.supports_integer_functions() { + match *ctx.resolve_type(arg, &self.module.types) { + crate::TypeInner::Vector { size, scalar } => { + let s = back::vector_size_str(size); + + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "uvec{s}(ivec{s}(31) - findMSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, "))")?; + } else { + write!(self.out, "mix(ivec{s}(31) - findMSB(")?; + self.write_expr(arg, ctx)?; + write!(self.out, "), ivec{s}(0), lessThan(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ", ivec{s}(0)))")?; + } + } + crate::TypeInner::Scalar(scalar) => { + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "uint(31 - findMSB(")?; + } else { + write!(self.out, "(")?; + self.write_expr(arg, ctx)?; + write!(self.out, " < 0 ? 0 : 31 - findMSB(")?; + } + + self.write_expr(arg, ctx)?; + write!(self.out, "))")?; + } + _ => unreachable!(), + }; + } else { + match *ctx.resolve_type(arg, &self.module.types) { + crate::TypeInner::Vector { size, scalar } => { + let s = back::vector_size_str(size); + + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "uvec{s}(")?; + write!(self.out, "vec{s}(31.0) - floor(log2(vec{s}(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") + 0.5)))")?; + } else { + write!(self.out, "ivec{s}(")?; + write!(self.out, "mix(vec{s}(31.0) - floor(log2(vec{s}(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") + 0.5)), ")?; + write!(self.out, "vec{s}(0.0), lessThan(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ", ivec{s}(0u))))")?; + } + } + crate::TypeInner::Scalar(scalar) => { + if let crate::ScalarKind::Uint = scalar.kind { + write!(self.out, "uint(31.0 - floor(log2(float(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") + 0.5)))")?; + } else { + write!(self.out, "(")?; + self.write_expr(arg, ctx)?; + write!(self.out, " < 0 ? 0 : int(")?; + write!(self.out, "31.0 - floor(log2(float(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ") + 0.5))))")?; + } + } + _ => unreachable!(), + }; + } + + return Ok(()); + } + Mf::CountOneBits => "bitCount", + Mf::ReverseBits => "bitfieldReverse", + Mf::ExtractBits => "bitfieldExtract", + Mf::InsertBits => "bitfieldInsert", + Mf::FindLsb => "findLSB", + Mf::FindMsb => "findMSB", + // data packing + Mf::Pack4x8snorm => "packSnorm4x8", + Mf::Pack4x8unorm => "packUnorm4x8", + Mf::Pack2x16snorm => "packSnorm2x16", + Mf::Pack2x16unorm => "packUnorm2x16", + Mf::Pack2x16float => "packHalf2x16", + // data unpacking + Mf::Unpack4x8snorm => "unpackSnorm4x8", + Mf::Unpack4x8unorm => "unpackUnorm4x8", + Mf::Unpack2x16snorm => "unpackSnorm2x16", + Mf::Unpack2x16unorm => "unpackUnorm2x16", + Mf::Unpack2x16float => "unpackHalf2x16", + }; + + let extract_bits = fun == Mf::ExtractBits; + let insert_bits = fun == Mf::InsertBits; + + // Some GLSL functions always return signed integers (like findMSB), + // so they need to be cast to uint if the argument is also an uint. + let ret_might_need_int_to_uint = + matches!(fun, Mf::FindLsb | Mf::FindMsb | Mf::CountOneBits | Mf::Abs); + + // Some GLSL functions only accept signed integers (like abs), + // so they need their argument cast from uint to int. + let arg_might_need_uint_to_int = matches!(fun, Mf::Abs); + + // Check if the argument is an unsigned integer and return the vector size + // in case it's a vector + let maybe_uint_size = match *ctx.resolve_type(arg, &self.module.types) { + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }) => Some(None), + crate::TypeInner::Vector { + scalar: + crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }, + size, + } => Some(Some(size)), + _ => None, + }; + + // Cast to uint if the function needs it + if ret_might_need_int_to_uint { + if let Some(maybe_size) = maybe_uint_size { + match maybe_size { + Some(size) => write!(self.out, "uvec{}(", size as u8)?, + None => write!(self.out, "uint(")?, + } + } + } + + write!(self.out, "{fun_name}(")?; + + // Cast to int if the function needs it + if arg_might_need_uint_to_int { + if let Some(maybe_size) = maybe_uint_size { + match maybe_size { + Some(size) => write!(self.out, "ivec{}(", size as u8)?, + None => write!(self.out, "int(")?, + } + } + } + + self.write_expr(arg, ctx)?; + + // Close the cast from uint to int + if arg_might_need_uint_to_int && maybe_uint_size.is_some() { + write!(self.out, ")")? + } + + if let Some(arg) = arg1 { + write!(self.out, ", ")?; + if extract_bits { + write!(self.out, "int(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")")?; + } else { + self.write_expr(arg, ctx)?; + } + } + if let Some(arg) = arg2 { + write!(self.out, ", ")?; + if extract_bits || insert_bits { + write!(self.out, "int(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")")?; + } else { + self.write_expr(arg, ctx)?; + } + } + if let Some(arg) = arg3 { + write!(self.out, ", ")?; + if insert_bits { + write!(self.out, "int(")?; + self.write_expr(arg, ctx)?; + write!(self.out, ")")?; + } else { + self.write_expr(arg, ctx)?; + } + } + write!(self.out, ")")?; + + // Close the cast from int to uint + if ret_might_need_int_to_uint && maybe_uint_size.is_some() { + write!(self.out, ")")? + } + } + // `As` is always a call. + // If `convert` is true the function name is the type + // Else the function name is one of the glsl provided bitcast functions + Expression::As { + expr, + kind: target_kind, + convert, + } => { + let inner = ctx.resolve_type(expr, &self.module.types); + match convert { + Some(width) => { + // this is similar to `write_type`, but with the target kind + let scalar = glsl_scalar(crate::Scalar { + kind: target_kind, + width, + })?; + match *inner { + TypeInner::Matrix { columns, rows, .. } => write!( + self.out, + "{}mat{}x{}", + scalar.prefix, columns as u8, rows as u8 + )?, + TypeInner::Vector { size, .. } => { + write!(self.out, "{}vec{}", scalar.prefix, size as u8)? + } + _ => write!(self.out, "{}", scalar.full)?, + } + + write!(self.out, "(")?; + self.write_expr(expr, ctx)?; + write!(self.out, ")")? + } + None => { + use crate::ScalarKind as Sk; + + let target_vector_type = match *inner { + TypeInner::Vector { size, scalar } => Some(TypeInner::Vector { + size, + scalar: crate::Scalar { + kind: target_kind, + width: scalar.width, + }, + }), + _ => None, + }; + + let source_kind = inner.scalar_kind().unwrap(); + + match (source_kind, target_kind, target_vector_type) { + // No conversion needed + (Sk::Sint, Sk::Sint, _) + | (Sk::Uint, Sk::Uint, _) + | (Sk::Float, Sk::Float, _) + | (Sk::Bool, Sk::Bool, _) => { + self.write_expr(expr, ctx)?; + return Ok(()); + } + + // Cast to/from floats + (Sk::Float, Sk::Sint, _) => write!(self.out, "floatBitsToInt")?, + (Sk::Float, Sk::Uint, _) => write!(self.out, "floatBitsToUint")?, + (Sk::Sint, Sk::Float, _) => write!(self.out, "intBitsToFloat")?, + (Sk::Uint, Sk::Float, _) => write!(self.out, "uintBitsToFloat")?, + + // Cast between vector types + (_, _, Some(vector)) => { + self.write_value_type(&vector)?; + } + + // There is no way to bitcast between Uint/Sint in glsl. Use constructor conversion + (Sk::Uint | Sk::Bool, Sk::Sint, None) => write!(self.out, "int")?, + (Sk::Sint | Sk::Bool, Sk::Uint, None) => write!(self.out, "uint")?, + (Sk::Bool, Sk::Float, None) => write!(self.out, "float")?, + (Sk::Sint | Sk::Uint | Sk::Float, Sk::Bool, None) => { + write!(self.out, "bool")? + } + + (Sk::AbstractInt | Sk::AbstractFloat, _, _) + | (_, Sk::AbstractInt | Sk::AbstractFloat, _) => unreachable!(), + }; + + write!(self.out, "(")?; + self.write_expr(expr, ctx)?; + write!(self.out, ")")?; + } + } + } + // These expressions never show up in `Emit`. + Expression::CallResult(_) + | Expression::AtomicResult { .. } + | Expression::RayQueryProceedResult + | Expression::WorkGroupUniformLoadResult { .. } => unreachable!(), + // `ArrayLength` is written as `expr.length()` and we convert it to a uint + Expression::ArrayLength(expr) => { + write!(self.out, "uint(")?; + self.write_expr(expr, ctx)?; + write!(self.out, ".length())")? + } + // not supported yet + Expression::RayQueryGetIntersection { .. } => unreachable!(), + } + + Ok(()) + } + + /// Helper function to write the local holding the clamped lod + fn write_clamped_lod( + &mut self, + ctx: &back::FunctionCtx, + expr: Handle, + image: Handle, + level_expr: Handle, + ) -> Result<(), Error> { + // Define our local and start a call to `clamp` + write!( + self.out, + "int {}{}{} = clamp(", + back::BAKE_PREFIX, + expr.index(), + CLAMPED_LOD_SUFFIX + )?; + // Write the lod that will be clamped + self.write_expr(level_expr, ctx)?; + // Set the min value to 0 and start a call to `textureQueryLevels` to get + // the maximum value + write!(self.out, ", 0, textureQueryLevels(")?; + // Write the target image as an argument to `textureQueryLevels` + self.write_expr(image, ctx)?; + // Close the call to `textureQueryLevels` subtract 1 from it since + // the lod argument is 0 based, close the `clamp` call and end the + // local declaration statement. + writeln!(self.out, ") - 1);")?; + + Ok(()) + } + + // Helper method used to retrieve how many elements a coordinate vector + // for the images operations need. + fn get_coordinate_vector_size(&self, dim: crate::ImageDimension, arrayed: bool) -> u8 { + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es(); + // Get how many components the coordinate vector needs for the dimensions only + let tex_coord_size = match dim { + crate::ImageDimension::D1 => 1, + crate::ImageDimension::D2 => 2, + crate::ImageDimension::D3 => 3, + crate::ImageDimension::Cube => 2, + }; + // Calculate the true size of the coordinate vector by adding 1 for arrayed images + // and another 1 if we need to workaround 1D images by making them 2D + tex_coord_size + tex_1d_hack as u8 + arrayed as u8 + } + + /// Helper method to write the coordinate vector for image operations + fn write_texture_coord( + &mut self, + ctx: &back::FunctionCtx, + vector_size: u8, + coordinate: Handle, + array_index: Option>, + // Emulate 1D images as 2D for profiles that don't support it (glsl es) + tex_1d_hack: bool, + ) -> Result<(), Error> { + match array_index { + // If the image needs an array indice we need to add it to the end of our + // coordinate vector, to do so we will use the `ivec(ivec, scalar)` + // constructor notation (NOTE: the inner `ivec` can also be a scalar, this + // is important for 1D arrayed images). + Some(layer_expr) => { + write!(self.out, "ivec{vector_size}(")?; + self.write_expr(coordinate, ctx)?; + write!(self.out, ", ")?; + // If we are replacing sampler1D with sampler2D we also need + // to add another zero to the coordinates vector for the y component + if tex_1d_hack { + write!(self.out, "0, ")?; + } + self.write_expr(layer_expr, ctx)?; + write!(self.out, ")")?; + } + // Otherwise write just the expression (and the 1D hack if needed) + None => { + let uvec_size = match *ctx.resolve_type(coordinate, &self.module.types) { + TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }) => Some(None), + TypeInner::Vector { + size, + scalar: + crate::Scalar { + kind: crate::ScalarKind::Uint, + .. + }, + } => Some(Some(size as u32)), + _ => None, + }; + if tex_1d_hack { + write!(self.out, "ivec2(")?; + } else if uvec_size.is_some() { + match uvec_size { + Some(None) => write!(self.out, "int(")?, + Some(Some(size)) => write!(self.out, "ivec{size}(")?, + _ => {} + } + } + self.write_expr(coordinate, ctx)?; + if tex_1d_hack { + write!(self.out, ", 0)")?; + } else if uvec_size.is_some() { + write!(self.out, ")")?; + } + } + } + + Ok(()) + } + + /// Helper method to write the `ImageStore` statement + fn write_image_store( + &mut self, + ctx: &back::FunctionCtx, + image: Handle, + coordinate: Handle, + array_index: Option>, + value: Handle, + ) -> Result<(), Error> { + use crate::ImageDimension as IDim; + + // NOTE: openGL requires that `imageStore`s have no effets when the texel is invalid + // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20) + + // This will only panic if the module is invalid + let dim = match *ctx.resolve_type(image, &self.module.types) { + TypeInner::Image { dim, .. } => dim, + _ => unreachable!(), + }; + + // Begin our call to `imageStore` + write!(self.out, "imageStore(")?; + self.write_expr(image, ctx)?; + // Separate the image argument from the coordinates + write!(self.out, ", ")?; + + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); + // Write the coordinate vector + self.write_texture_coord( + ctx, + // Get the size of the coordinate vector + self.get_coordinate_vector_size(dim, array_index.is_some()), + coordinate, + array_index, + tex_1d_hack, + )?; + + // Separate the coordinate from the value to write and write the expression + // of the value to write. + write!(self.out, ", ")?; + self.write_expr(value, ctx)?; + // End the call to `imageStore` and the statement. + writeln!(self.out, ");")?; + + Ok(()) + } + + /// Helper method for writing an `ImageLoad` expression. + #[allow(clippy::too_many_arguments)] + fn write_image_load( + &mut self, + handle: Handle, + ctx: &back::FunctionCtx, + image: Handle, + coordinate: Handle, + array_index: Option>, + sample: Option>, + level: Option>, + ) -> Result<(), Error> { + use crate::ImageDimension as IDim; + + // `ImageLoad` is a bit complicated. + // There are two functions one for sampled + // images another for storage images, the former uses `texelFetch` and the + // latter uses `imageLoad`. + // + // Furthermore we have `level` which is always `Some` for sampled images + // and `None` for storage images, so we end up with two functions: + // - `texelFetch(image, coordinate, level)` for sampled images + // - `imageLoad(image, coordinate)` for storage images + // + // Finally we also have to consider bounds checking, for storage images + // this is easy since openGL requires that invalid texels always return + // 0, for sampled images we need to either verify that all arguments are + // in bounds (`ReadZeroSkipWrite`) or make them a valid texel (`Restrict`). + + // This will only panic if the module is invalid + let (dim, class) = match *ctx.resolve_type(image, &self.module.types) { + TypeInner::Image { + dim, + arrayed: _, + class, + } => (dim, class), + _ => unreachable!(), + }; + + // Get the name of the function to be used for the load operation + // and the policy to be used with it. + let (fun_name, policy) = match class { + // Sampled images inherit the policy from the user passed policies + crate::ImageClass::Sampled { .. } => ("texelFetch", self.policies.image_load), + crate::ImageClass::Storage { .. } => { + // OpenGL ES 3.1 mentiones in Chapter "8.22 Texture Image Loads and Stores" that: + // "Invalid image loads will return a vector where the value of R, G, and B components + // is 0 and the value of the A component is undefined." + // + // OpenGL 4.2 Core mentiones in Chapter "3.9.20 Texture Image Loads and Stores" that: + // "Invalid image loads will return zero." + // + // So, we only inject bounds checks for ES + let policy = if self.options.version.is_es() { + self.policies.image_load + } else { + proc::BoundsCheckPolicy::Unchecked + }; + ("imageLoad", policy) + } + // TODO: Is there even a function for this? + crate::ImageClass::Depth { multi: _ } => { + return Err(Error::Custom( + "WGSL `textureLoad` from depth textures is not supported in GLSL".to_string(), + )) + } + }; + + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); + // Get the size of the coordinate vector + let vector_size = self.get_coordinate_vector_size(dim, array_index.is_some()); + + if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { + // To write the bounds checks for `ReadZeroSkipWrite` we will use a + // ternary operator since we are in the middle of an expression and + // need to return a value. + // + // NOTE: glsl does short circuit when evaluating logical + // expressions so we can be sure that after we test a + // condition it will be true for the next ones + + // Write parantheses around the ternary operator to prevent problems with + // expressions emitted before or after it having more precedence + write!(self.out, "(",)?; + + // The lod check needs to precede the size check since we need + // to use the lod to get the size of the image at that level. + if let Some(level_expr) = level { + self.write_expr(level_expr, ctx)?; + write!(self.out, " < textureQueryLevels(",)?; + self.write_expr(image, ctx)?; + // Chain the next check + write!(self.out, ") && ")?; + } + + // Check that the sample arguments doesn't exceed the number of samples + if let Some(sample_expr) = sample { + self.write_expr(sample_expr, ctx)?; + write!(self.out, " < textureSamples(",)?; + self.write_expr(image, ctx)?; + // Chain the next check + write!(self.out, ") && ")?; + } + + // We now need to write the size checks for the coordinates and array index + // first we write the comparation function in case the image is 1D non arrayed + // (and no 1D to 2D hack was needed) we are comparing scalars so the less than + // operator will suffice, but otherwise we'll be comparing two vectors so we'll + // need to use the `lessThan` function but it returns a vector of booleans (one + // for each comparison) so we need to fold it all in one scalar boolean, since + // we want all comparisons to pass we use the `all` function which will only + // return `true` if all the elements of the boolean vector are also `true`. + // + // So we'll end with one of the following forms + // - `coord < textureSize(image, lod)` for 1D images + // - `all(lessThan(coord, textureSize(image, lod)))` for normal images + // - `all(lessThan(ivec(coord, array_index), textureSize(image, lod)))` + // for arrayed images + // - `all(lessThan(coord, textureSize(image)))` for multi sampled images + + if vector_size != 1 { + write!(self.out, "all(lessThan(")?; + } + + // Write the coordinate vector + self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; + + if vector_size != 1 { + // If we used the `lessThan` function we need to separate the + // coordinates from the image size. + write!(self.out, ", ")?; + } else { + // If we didn't use it (ie. 1D images) we perform the comparsion + // using the less than operator. + write!(self.out, " < ")?; + } + + // Call `textureSize` to get our image size + write!(self.out, "textureSize(")?; + self.write_expr(image, ctx)?; + // `textureSize` uses the lod as a second argument for mipmapped images + if let Some(level_expr) = level { + // Separate the image from the lod + write!(self.out, ", ")?; + self.write_expr(level_expr, ctx)?; + } + // Close the `textureSize` call + write!(self.out, ")")?; + + if vector_size != 1 { + // Close the `all` and `lessThan` calls + write!(self.out, "))")?; + } + + // Finally end the condition part of the ternary operator + write!(self.out, " ? ")?; + } + + // Begin the call to the function used to load the texel + write!(self.out, "{fun_name}(")?; + self.write_expr(image, ctx)?; + write!(self.out, ", ")?; + + // If we are using `Restrict` bounds checking we need to pass valid texel + // coordinates, to do so we use the `clamp` function to get a value between + // 0 and the image size - 1 (indexing begins at 0) + if let proc::BoundsCheckPolicy::Restrict = policy { + write!(self.out, "clamp(")?; + } + + // Write the coordinate vector + self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; + + // If we are using `Restrict` bounds checking we need to write the rest of the + // clamp we initiated before writing the coordinates. + if let proc::BoundsCheckPolicy::Restrict = policy { + // Write the min value 0 + if vector_size == 1 { + write!(self.out, ", 0")?; + } else { + write!(self.out, ", ivec{vector_size}(0)")?; + } + // Start the `textureSize` call to use as the max value. + write!(self.out, ", textureSize(")?; + self.write_expr(image, ctx)?; + // If the image is mipmapped we need to add the lod argument to the + // `textureSize` call, but this needs to be the clamped lod, this should + // have been generated earlier and put in a local. + if class.is_mipmapped() { + write!( + self.out, + ", {}{}{}", + back::BAKE_PREFIX, + handle.index(), + CLAMPED_LOD_SUFFIX + )?; + } + // Close the `textureSize` call + write!(self.out, ")")?; + + // Subtract 1 from the `textureSize` call since the coordinates are zero based. + if vector_size == 1 { + write!(self.out, " - 1")?; + } else { + write!(self.out, " - ivec{vector_size}(1)")?; + } + + // Close the `clamp` call + write!(self.out, ")")?; + + // Add the clamped lod (if present) as the second argument to the + // image load function. + if level.is_some() { + write!( + self.out, + ", {}{}{}", + back::BAKE_PREFIX, + handle.index(), + CLAMPED_LOD_SUFFIX + )?; + } + + // If a sample argument is needed we need to clamp it between 0 and + // the number of samples the image has. + if let Some(sample_expr) = sample { + write!(self.out, ", clamp(")?; + self.write_expr(sample_expr, ctx)?; + // Set the min value to 0 and start the call to `textureSamples` + write!(self.out, ", 0, textureSamples(")?; + self.write_expr(image, ctx)?; + // Close the `textureSamples` call, subtract 1 from it since the sample + // argument is zero based, and close the `clamp` call + writeln!(self.out, ") - 1)")?; + } + } else if let Some(sample_or_level) = sample.or(level) { + // If no bounds checking is need just add the sample or level argument + // after the coordinates + write!(self.out, ", ")?; + self.write_expr(sample_or_level, ctx)?; + } + + // Close the image load function. + write!(self.out, ")")?; + + // If we were using the `ReadZeroSkipWrite` policy we need to end the first branch + // (which is taken if the condition is `true`) with a colon (`:`) and write the + // second branch which is just a 0 value. + if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { + // Get the kind of the output value. + let kind = match class { + // Only sampled images can reach here since storage images + // don't need bounds checks and depth images aren't implmented + crate::ImageClass::Sampled { kind, .. } => kind, + _ => unreachable!(), + }; + + // End the first branch + write!(self.out, " : ")?; + // Write the 0 value + write!( + self.out, + "{}vec4(", + glsl_scalar(crate::Scalar { kind, width: 4 })?.prefix, + )?; + self.write_zero_init_scalar(kind)?; + // Close the zero value constructor + write!(self.out, ")")?; + // Close the parantheses surrounding our ternary + write!(self.out, ")")?; + } + + Ok(()) + } + + fn write_named_expr( + &mut self, + handle: Handle, + name: String, + // The expression which is being named. + // Generally, this is the same as handle, except in WorkGroupUniformLoad + named: Handle, + ctx: &back::FunctionCtx, + ) -> BackendResult { + match ctx.info[named].ty { + proc::TypeResolution::Handle(ty_handle) => match self.module.types[ty_handle].inner { + TypeInner::Struct { .. } => { + let ty_name = &self.names[&NameKey::Type(ty_handle)]; + write!(self.out, "{ty_name}")?; + } + _ => { + self.write_type(ty_handle)?; + } + }, + proc::TypeResolution::Value(ref inner) => { + self.write_value_type(inner)?; + } + } + + let resolved = ctx.resolve_type(named, &self.module.types); + + write!(self.out, " {name}")?; + if let TypeInner::Array { base, size, .. } = *resolved { + self.write_array_size(base, size)?; + } + write!(self.out, " = ")?; + self.write_expr(handle, ctx)?; + writeln!(self.out, ";")?; + self.named_expressions.insert(named, name); + + Ok(()) + } + + /// Helper function that write string with default zero initialization for supported types + fn write_zero_init_value(&mut self, ty: Handle) -> BackendResult { + let inner = &self.module.types[ty].inner; + match *inner { + TypeInner::Scalar(scalar) | TypeInner::Atomic(scalar) => { + self.write_zero_init_scalar(scalar.kind)?; + } + TypeInner::Vector { scalar, .. } => { + self.write_value_type(inner)?; + write!(self.out, "(")?; + self.write_zero_init_scalar(scalar.kind)?; + write!(self.out, ")")?; + } + TypeInner::Matrix { .. } => { + self.write_value_type(inner)?; + write!(self.out, "(")?; + self.write_zero_init_scalar(crate::ScalarKind::Float)?; + write!(self.out, ")")?; + } + TypeInner::Array { base, size, .. } => { + let count = match size + .to_indexable_length(self.module) + .expect("Bad array size") + { + proc::IndexableLength::Known(count) => count, + proc::IndexableLength::Dynamic => return Ok(()), + }; + self.write_type(base)?; + self.write_array_size(base, size)?; + write!(self.out, "(")?; + for _ in 1..count { + self.write_zero_init_value(base)?; + write!(self.out, ", ")?; + } + // write last parameter without comma and space + self.write_zero_init_value(base)?; + write!(self.out, ")")?; + } + TypeInner::Struct { ref members, .. } => { + let name = &self.names[&NameKey::Type(ty)]; + write!(self.out, "{name}(")?; + for (index, member) in members.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + self.write_zero_init_value(member.ty)?; + } + write!(self.out, ")")?; + } + _ => unreachable!(), + } + + Ok(()) + } + + /// Helper function that write string with zero initialization for scalar + fn write_zero_init_scalar(&mut self, kind: crate::ScalarKind) -> BackendResult { + match kind { + crate::ScalarKind::Bool => write!(self.out, "false")?, + crate::ScalarKind::Uint => write!(self.out, "0u")?, + crate::ScalarKind::Float => write!(self.out, "0.0")?, + crate::ScalarKind::Sint => write!(self.out, "0")?, + crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => { + return Err(Error::Custom( + "Abstract types should not appear in IR presented to backends".to_string(), + )) + } + } + + Ok(()) + } + + /// Issue a memory barrier. Please note that to ensure visibility, + /// OpenGL always requires a call to the `barrier()` function after a `memoryBarrier*()` + fn write_barrier(&mut self, flags: crate::Barrier, level: back::Level) -> BackendResult { + if flags.contains(crate::Barrier::STORAGE) { + writeln!(self.out, "{level}memoryBarrierBuffer();")?; + } + if flags.contains(crate::Barrier::WORK_GROUP) { + writeln!(self.out, "{level}memoryBarrierShared();")?; + } + writeln!(self.out, "{level}barrier();")?; + Ok(()) + } + + /// Helper function that return the glsl storage access string of [`StorageAccess`](crate::StorageAccess) + /// + /// glsl allows adding both `readonly` and `writeonly` but this means that + /// they can only be used to query information about the resource which isn't what + /// we want here so when storage access is both `LOAD` and `STORE` add no modifiers + fn write_storage_access(&mut self, storage_access: crate::StorageAccess) -> BackendResult { + if !storage_access.contains(crate::StorageAccess::STORE) { + write!(self.out, "readonly ")?; + } + if !storage_access.contains(crate::StorageAccess::LOAD) { + write!(self.out, "writeonly ")?; + } + Ok(()) + } + + /// Helper method used to produce the reflection info that's returned to the user + fn collect_reflection_info(&mut self) -> Result { + use std::collections::hash_map::Entry; + let info = self.info.get_entry_point(self.entry_point_idx as usize); + let mut texture_mapping = crate::FastHashMap::default(); + let mut uniforms = crate::FastHashMap::default(); + + for sampling in info.sampling_set.iter() { + let tex_name = self.reflection_names_globals[&sampling.image].clone(); + + match texture_mapping.entry(tex_name) { + Entry::Vacant(v) => { + v.insert(TextureMapping { + texture: sampling.image, + sampler: Some(sampling.sampler), + }); + } + Entry::Occupied(e) => { + if e.get().sampler != Some(sampling.sampler) { + log::error!("Conflicting samplers for {}", e.key()); + return Err(Error::ImageMultipleSamplers); + } + } + } + } + + let mut push_constant_info = None; + for (handle, var) in self.module.global_variables.iter() { + if info[handle].is_empty() { + continue; + } + match self.module.types[var.ty].inner { + crate::TypeInner::Image { .. } => { + let tex_name = self.reflection_names_globals[&handle].clone(); + match texture_mapping.entry(tex_name) { + Entry::Vacant(v) => { + v.insert(TextureMapping { + texture: handle, + sampler: None, + }); + } + Entry::Occupied(_) => { + // already used with a sampler, do nothing + } + } + } + _ => match var.space { + crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } => { + let name = self.reflection_names_globals[&handle].clone(); + uniforms.insert(handle, name); + } + crate::AddressSpace::PushConstant => { + let name = self.reflection_names_globals[&handle].clone(); + push_constant_info = Some((name, var.ty)); + } + _ => (), + }, + } + } + + let mut push_constant_segments = Vec::new(); + let mut push_constant_items = vec![]; + + if let Some((name, ty)) = push_constant_info { + // We don't have a layouter available to us, so we need to create one. + // + // This is potentially a bit wasteful, but the set of types in the program + // shouldn't be too large. + let mut layouter = crate::proc::Layouter::default(); + layouter.update(self.module.to_ctx()).unwrap(); + + // We start with the name of the binding itself. + push_constant_segments.push(name); + + // We then recursively collect all the uniform fields of the push constant. + self.collect_push_constant_items( + ty, + &mut push_constant_segments, + &layouter, + &mut 0, + &mut push_constant_items, + ); + } + + Ok(ReflectionInfo { + texture_mapping, + uniforms, + varying: mem::take(&mut self.varying), + push_constant_items, + }) + } + + fn collect_push_constant_items( + &mut self, + ty: Handle, + segments: &mut Vec, + layouter: &crate::proc::Layouter, + offset: &mut u32, + items: &mut Vec, + ) { + // At this point in the recursion, `segments` contains the path + // needed to access `ty` from the root. + + let layout = &layouter[ty]; + *offset = layout.alignment.round_up(*offset); + match self.module.types[ty].inner { + // All these types map directly to GL uniforms. + TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => { + // Build the full name, by combining all current segments. + let name: String = segments.iter().map(String::as_str).collect(); + items.push(PushConstantItem { + access_path: name, + offset: *offset, + ty, + }); + *offset += layout.size; + } + // Arrays are recursed into. + TypeInner::Array { base, size, .. } => { + let crate::ArraySize::Constant(count) = size else { + unreachable!("Cannot have dynamic arrays in push constants"); + }; + + for i in 0..count.get() { + // Add the array accessor and recurse. + segments.push(format!("[{}]", i)); + self.collect_push_constant_items(base, segments, layouter, offset, items); + segments.pop(); + } + + // Ensure the stride is kept by rounding up to the alignment. + *offset = layout.alignment.round_up(*offset) + } + TypeInner::Struct { ref members, .. } => { + for (index, member) in members.iter().enumerate() { + // Add struct accessor and recurse. + segments.push(format!( + ".{}", + self.names[&NameKey::StructMember(ty, index as u32)] + )); + self.collect_push_constant_items(member.ty, segments, layouter, offset, items); + segments.pop(); + } + + // Ensure ending padding is kept by rounding up to the alignment. + *offset = layout.alignment.round_up(*offset) + } + _ => unreachable!(), + } + } +} + +/// Structure returned by [`glsl_scalar`] +/// +/// It contains both a prefix used in other types and the full type name +struct ScalarString<'a> { + /// The prefix used to compose other types + prefix: &'a str, + /// The name of the scalar type + full: &'a str, +} + +/// Helper function that returns scalar related strings +/// +/// Check [`ScalarString`] for the information provided +/// +/// # Errors +/// If a [`Float`](crate::ScalarKind::Float) with an width that isn't 4 or 8 +const fn glsl_scalar(scalar: crate::Scalar) -> Result, Error> { + use crate::ScalarKind as Sk; + + Ok(match scalar.kind { + Sk::Sint => ScalarString { + prefix: "i", + full: "int", + }, + Sk::Uint => ScalarString { + prefix: "u", + full: "uint", + }, + Sk::Float => match scalar.width { + 4 => ScalarString { + prefix: "", + full: "float", + }, + 8 => ScalarString { + prefix: "d", + full: "double", + }, + _ => return Err(Error::UnsupportedScalar(scalar)), + }, + Sk::Bool => ScalarString { + prefix: "b", + full: "bool", + }, + Sk::AbstractInt | Sk::AbstractFloat => { + return Err(Error::UnsupportedScalar(scalar)); + } + }) +} + +/// Helper function that returns the glsl variable name for a builtin +const fn glsl_built_in(built_in: crate::BuiltIn, options: VaryingOptions) -> &'static str { + use crate::BuiltIn as Bi; + + match built_in { + Bi::Position { .. } => { + if options.output { + "gl_Position" + } else { + "gl_FragCoord" + } + } + Bi::ViewIndex if options.targetting_webgl => "int(gl_ViewID_OVR)", + Bi::ViewIndex => "gl_ViewIndex", + // vertex + Bi::BaseInstance => "uint(gl_BaseInstance)", + Bi::BaseVertex => "uint(gl_BaseVertex)", + Bi::ClipDistance => "gl_ClipDistance", + Bi::CullDistance => "gl_CullDistance", + Bi::InstanceIndex => { + if options.draw_parameters { + "(uint(gl_InstanceID) + uint(gl_BaseInstanceARB))" + } else { + // Must match FISRT_INSTANCE_BINDING + "(uint(gl_InstanceID) + naga_vs_first_instance)" + } + } + Bi::PointSize => "gl_PointSize", + Bi::VertexIndex => "uint(gl_VertexID)", + // fragment + Bi::FragDepth => "gl_FragDepth", + Bi::PointCoord => "gl_PointCoord", + Bi::FrontFacing => "gl_FrontFacing", + Bi::PrimitiveIndex => "uint(gl_PrimitiveID)", + Bi::SampleIndex => "gl_SampleID", + Bi::SampleMask => { + if options.output { + "gl_SampleMask" + } else { + "gl_SampleMaskIn" + } + } + // compute + Bi::GlobalInvocationId => "gl_GlobalInvocationID", + Bi::LocalInvocationId => "gl_LocalInvocationID", + Bi::LocalInvocationIndex => "gl_LocalInvocationIndex", + Bi::WorkGroupId => "gl_WorkGroupID", + Bi::WorkGroupSize => "gl_WorkGroupSize", + Bi::NumWorkGroups => "gl_NumWorkGroups", + } +} + +/// Helper function that returns the string corresponding to the address space +const fn glsl_storage_qualifier(space: crate::AddressSpace) -> Option<&'static str> { + use crate::AddressSpace as As; + + match space { + As::Function => None, + As::Private => None, + As::Storage { .. } => Some("buffer"), + As::Uniform => Some("uniform"), + As::Handle => Some("uniform"), + As::WorkGroup => Some("shared"), + As::PushConstant => Some("uniform"), + } +} + +/// Helper function that returns the string corresponding to the glsl interpolation qualifier +const fn glsl_interpolation(interpolation: crate::Interpolation) -> &'static str { + use crate::Interpolation as I; + + match interpolation { + I::Perspective => "smooth", + I::Linear => "noperspective", + I::Flat => "flat", + } +} + +/// Return the GLSL auxiliary qualifier for the given sampling value. +const fn glsl_sampling(sampling: crate::Sampling) -> Option<&'static str> { + use crate::Sampling as S; + + match sampling { + S::Center => None, + S::Centroid => Some("centroid"), + S::Sample => Some("sample"), + } +} + +/// Helper function that returns the glsl dimension string of [`ImageDimension`](crate::ImageDimension) +const fn glsl_dimension(dim: crate::ImageDimension) -> &'static str { + use crate::ImageDimension as IDim; + + match dim { + IDim::D1 => "1D", + IDim::D2 => "2D", + IDim::D3 => "3D", + IDim::Cube => "Cube", + } +} + +/// Helper function that returns the glsl storage format string of [`StorageFormat`](crate::StorageFormat) +fn glsl_storage_format(format: crate::StorageFormat) -> Result<&'static str, Error> { + use crate::StorageFormat as Sf; + + Ok(match format { + Sf::R8Unorm => "r8", + Sf::R8Snorm => "r8_snorm", + Sf::R8Uint => "r8ui", + Sf::R8Sint => "r8i", + Sf::R16Uint => "r16ui", + Sf::R16Sint => "r16i", + Sf::R16Float => "r16f", + Sf::Rg8Unorm => "rg8", + Sf::Rg8Snorm => "rg8_snorm", + Sf::Rg8Uint => "rg8ui", + Sf::Rg8Sint => "rg8i", + Sf::R32Uint => "r32ui", + Sf::R32Sint => "r32i", + Sf::R32Float => "r32f", + Sf::Rg16Uint => "rg16ui", + Sf::Rg16Sint => "rg16i", + Sf::Rg16Float => "rg16f", + Sf::Rgba8Unorm => "rgba8", + Sf::Rgba8Snorm => "rgba8_snorm", + Sf::Rgba8Uint => "rgba8ui", + Sf::Rgba8Sint => "rgba8i", + Sf::Rgb10a2Uint => "rgb10_a2ui", + Sf::Rgb10a2Unorm => "rgb10_a2", + Sf::Rg11b10Float => "r11f_g11f_b10f", + Sf::Rg32Uint => "rg32ui", + Sf::Rg32Sint => "rg32i", + Sf::Rg32Float => "rg32f", + Sf::Rgba16Uint => "rgba16ui", + Sf::Rgba16Sint => "rgba16i", + Sf::Rgba16Float => "rgba16f", + Sf::Rgba32Uint => "rgba32ui", + Sf::Rgba32Sint => "rgba32i", + Sf::Rgba32Float => "rgba32f", + Sf::R16Unorm => "r16", + Sf::R16Snorm => "r16_snorm", + Sf::Rg16Unorm => "rg16", + Sf::Rg16Snorm => "rg16_snorm", + Sf::Rgba16Unorm => "rgba16", + Sf::Rgba16Snorm => "rgba16_snorm", + + Sf::Bgra8Unorm => { + return Err(Error::Custom( + "Support format BGRA8 is not implemented".into(), + )) + } + }) +} + +fn is_value_init_supported(module: &crate::Module, ty: Handle) -> bool { + match module.types[ty].inner { + TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => true, + TypeInner::Array { base, size, .. } => { + size != crate::ArraySize::Dynamic && is_value_init_supported(module, base) + } + TypeInner::Struct { ref members, .. } => members + .iter() + .all(|member| is_value_init_supported(module, member.ty)), + _ => false, + } +} diff --git a/naga/src/back/hlsl/conv.rs b/naga/src/back/hlsl/conv.rs new file mode 100644 index 0000000000..b6918ddc42 --- /dev/null +++ b/naga/src/back/hlsl/conv.rs @@ -0,0 +1,222 @@ +use std::borrow::Cow; + +use crate::proc::Alignment; + +use super::Error; + +impl crate::ScalarKind { + pub(super) fn to_hlsl_cast(self) -> &'static str { + match self { + Self::Float => "asfloat", + Self::Sint => "asint", + Self::Uint => "asuint", + Self::Bool | Self::AbstractInt | Self::AbstractFloat => unreachable!(), + } + } +} + +impl crate::Scalar { + /// Helper function that returns scalar related strings + /// + /// + pub(super) const fn to_hlsl_str(self) -> Result<&'static str, Error> { + match self.kind { + crate::ScalarKind::Sint => Ok("int"), + crate::ScalarKind::Uint => Ok("uint"), + crate::ScalarKind::Float => match self.width { + 2 => Ok("half"), + 4 => Ok("float"), + 8 => Ok("double"), + _ => Err(Error::UnsupportedScalar(self)), + }, + crate::ScalarKind::Bool => Ok("bool"), + crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => { + Err(Error::UnsupportedScalar(self)) + } + } + } +} + +impl crate::TypeInner { + pub(super) const fn is_matrix(&self) -> bool { + match *self { + Self::Matrix { .. } => true, + _ => false, + } + } + + pub(super) fn size_hlsl(&self, gctx: crate::proc::GlobalCtx) -> u32 { + match *self { + Self::Matrix { + columns, + rows, + scalar, + } => { + let stride = Alignment::from(rows) * scalar.width as u32; + let last_row_size = rows as u32 * scalar.width as u32; + ((columns as u32 - 1) * stride) + last_row_size + } + Self::Array { base, size, stride } => { + let count = match size { + crate::ArraySize::Constant(size) => size.get(), + // A dynamically-sized array has to have at least one element + crate::ArraySize::Dynamic => 1, + }; + let last_el_size = gctx.types[base].inner.size_hlsl(gctx); + ((count - 1) * stride) + last_el_size + } + _ => self.size(gctx), + } + } + + /// Used to generate the name of the wrapped type constructor + pub(super) fn hlsl_type_id<'a>( + base: crate::Handle, + gctx: crate::proc::GlobalCtx, + names: &'a crate::FastHashMap, + ) -> Result, Error> { + Ok(match gctx.types[base].inner { + crate::TypeInner::Scalar(scalar) => Cow::Borrowed(scalar.to_hlsl_str()?), + crate::TypeInner::Vector { size, scalar } => Cow::Owned(format!( + "{}{}", + scalar.to_hlsl_str()?, + crate::back::vector_size_str(size) + )), + crate::TypeInner::Matrix { + columns, + rows, + scalar, + } => Cow::Owned(format!( + "{}{}x{}", + scalar.to_hlsl_str()?, + crate::back::vector_size_str(columns), + crate::back::vector_size_str(rows), + )), + crate::TypeInner::Array { + base, + size: crate::ArraySize::Constant(size), + .. + } => Cow::Owned(format!( + "array{size}_{}_", + Self::hlsl_type_id(base, gctx, names)? + )), + crate::TypeInner::Struct { .. } => { + Cow::Borrowed(&names[&crate::proc::NameKey::Type(base)]) + } + _ => unreachable!(), + }) + } +} + +impl crate::StorageFormat { + pub(super) const fn to_hlsl_str(self) -> &'static str { + match self { + Self::R16Float => "float", + Self::R8Unorm | Self::R16Unorm => "unorm float", + Self::R8Snorm | Self::R16Snorm => "snorm float", + Self::R8Uint | Self::R16Uint => "uint", + Self::R8Sint | Self::R16Sint => "int", + + Self::Rg16Float => "float2", + Self::Rg8Unorm | Self::Rg16Unorm => "unorm float2", + Self::Rg8Snorm | Self::Rg16Snorm => "snorm float2", + + Self::Rg8Sint | Self::Rg16Sint => "int2", + Self::Rg8Uint | Self::Rg16Uint => "uint2", + + Self::Rg11b10Float => "float3", + + Self::Rgba16Float | Self::R32Float | Self::Rg32Float | Self::Rgba32Float => "float4", + Self::Rgba8Unorm | Self::Bgra8Unorm | Self::Rgba16Unorm | Self::Rgb10a2Unorm => { + "unorm float4" + } + Self::Rgba8Snorm | Self::Rgba16Snorm => "snorm float4", + + Self::Rgba8Uint + | Self::Rgba16Uint + | Self::R32Uint + | Self::Rg32Uint + | Self::Rgba32Uint + | Self::Rgb10a2Uint => "uint4", + Self::Rgba8Sint + | Self::Rgba16Sint + | Self::R32Sint + | Self::Rg32Sint + | Self::Rgba32Sint => "int4", + } + } +} + +impl crate::BuiltIn { + pub(super) fn to_hlsl_str(self) -> Result<&'static str, Error> { + Ok(match self { + Self::Position { .. } => "SV_Position", + // vertex + Self::ClipDistance => "SV_ClipDistance", + Self::CullDistance => "SV_CullDistance", + Self::InstanceIndex => "SV_InstanceID", + Self::VertexIndex => "SV_VertexID", + // fragment + Self::FragDepth => "SV_Depth", + Self::FrontFacing => "SV_IsFrontFace", + Self::PrimitiveIndex => "SV_PrimitiveID", + Self::SampleIndex => "SV_SampleIndex", + Self::SampleMask => "SV_Coverage", + // compute + Self::GlobalInvocationId => "SV_DispatchThreadID", + Self::LocalInvocationId => "SV_GroupThreadID", + Self::LocalInvocationIndex => "SV_GroupIndex", + Self::WorkGroupId => "SV_GroupID", + // The specific semantic we use here doesn't matter, because references + // to this field will get replaced with references to `SPECIAL_CBUF_VAR` + // in `Writer::write_expr`. + Self::NumWorkGroups => "SV_GroupID", + Self::BaseInstance | Self::BaseVertex | Self::WorkGroupSize => { + return Err(Error::Unimplemented(format!("builtin {self:?}"))) + } + Self::PointSize | Self::ViewIndex | Self::PointCoord => { + return Err(Error::Custom(format!("Unsupported builtin {self:?}"))) + } + }) + } +} + +impl crate::Interpolation { + /// Return the string corresponding to the HLSL interpolation qualifier. + pub(super) const fn to_hlsl_str(self) -> Option<&'static str> { + match self { + // Would be "linear", but it's the default interpolation in SM4 and up + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-struct#interpolation-modifiers-introduced-in-shader-model-4 + Self::Perspective => None, + Self::Linear => Some("noperspective"), + Self::Flat => Some("nointerpolation"), + } + } +} + +impl crate::Sampling { + /// Return the HLSL auxiliary qualifier for the given sampling value. + pub(super) const fn to_hlsl_str(self) -> Option<&'static str> { + match self { + Self::Center => None, + Self::Centroid => Some("centroid"), + Self::Sample => Some("sample"), + } + } +} + +impl crate::AtomicFunction { + /// Return the HLSL suffix for the `InterlockedXxx` method. + pub(super) const fn to_hlsl_suffix(self) -> &'static str { + match self { + Self::Add | Self::Subtract => "Add", + Self::And => "And", + Self::InclusiveOr => "Or", + Self::ExclusiveOr => "Xor", + Self::Min => "Min", + Self::Max => "Max", + Self::Exchange { compare: None } => "Exchange", + Self::Exchange { .. } => "", //TODO + } + } +} diff --git a/naga/src/back/hlsl/help.rs b/naga/src/back/hlsl/help.rs new file mode 100644 index 0000000000..fa6062a1ad --- /dev/null +++ b/naga/src/back/hlsl/help.rs @@ -0,0 +1,1138 @@ +/*! +Helpers for the hlsl backend + +Important note about `Expression::ImageQuery`/`Expression::ArrayLength` and hlsl backend: + +Due to implementation of `GetDimensions` function in hlsl () +backend can't work with it as an expression. +Instead, it generates a unique wrapped function per `Expression::ImageQuery`, based on texture info and query function. +See `WrappedImageQuery` struct that represents a unique function and will be generated before writing all statements and expressions. +This allowed to works with `Expression::ImageQuery` as expression and write wrapped function. + +For example: +```wgsl +let dim_1d = textureDimensions(image_1d); +``` + +```hlsl +int NagaDimensions1D(Texture1D) +{ + uint4 ret; + image_1d.GetDimensions(ret.x); + return ret.x; +} + +int dim_1d = NagaDimensions1D(image_1d); +``` +*/ + +use super::{super::FunctionCtx, BackendResult}; +use crate::{arena::Handle, proc::NameKey}; +use std::fmt::Write; + +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub(super) struct WrappedArrayLength { + pub(super) writable: bool, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub(super) struct WrappedImageQuery { + pub(super) dim: crate::ImageDimension, + pub(super) arrayed: bool, + pub(super) class: crate::ImageClass, + pub(super) query: ImageQuery, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub(super) struct WrappedConstructor { + pub(super) ty: Handle, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub(super) struct WrappedStructMatrixAccess { + pub(super) ty: Handle, + pub(super) index: u32, +} + +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub(super) struct WrappedMatCx2 { + pub(super) columns: crate::VectorSize, +} + +/// HLSL backend requires its own `ImageQuery` enum. +/// +/// It is used inside `WrappedImageQuery` and should be unique per ImageQuery function. +/// IR version can't be unique per function, because it's store mipmap level as an expression. +/// +/// For example: +/// ```wgsl +/// let dim_cube_array_lod = textureDimensions(image_cube_array, 1); +/// let dim_cube_array_lod2 = textureDimensions(image_cube_array, 1); +/// ``` +/// +/// ```ir +/// ImageQuery { +/// image: [1], +/// query: Size { +/// level: Some( +/// [1], +/// ), +/// }, +/// }, +/// ImageQuery { +/// image: [1], +/// query: Size { +/// level: Some( +/// [2], +/// ), +/// }, +/// }, +/// ``` +/// +/// HLSL should generate only 1 function for this case. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub(super) enum ImageQuery { + Size, + SizeLevel, + NumLevels, + NumLayers, + NumSamples, +} + +impl From for ImageQuery { + fn from(q: crate::ImageQuery) -> Self { + use crate::ImageQuery as Iq; + match q { + Iq::Size { level: Some(_) } => ImageQuery::SizeLevel, + Iq::Size { level: None } => ImageQuery::Size, + Iq::NumLevels => ImageQuery::NumLevels, + Iq::NumLayers => ImageQuery::NumLayers, + Iq::NumSamples => ImageQuery::NumSamples, + } + } +} + +impl<'a, W: Write> super::Writer<'a, W> { + pub(super) fn write_image_type( + &mut self, + dim: crate::ImageDimension, + arrayed: bool, + class: crate::ImageClass, + ) -> BackendResult { + let access_str = match class { + crate::ImageClass::Storage { .. } => "RW", + _ => "", + }; + let dim_str = dim.to_hlsl_str(); + let arrayed_str = if arrayed { "Array" } else { "" }; + write!(self.out, "{access_str}Texture{dim_str}{arrayed_str}")?; + match class { + crate::ImageClass::Depth { multi } => { + let multi_str = if multi { "MS" } else { "" }; + write!(self.out, "{multi_str}")? + } + crate::ImageClass::Sampled { kind, multi } => { + let multi_str = if multi { "MS" } else { "" }; + let scalar_kind_str = crate::Scalar { kind, width: 4 }.to_hlsl_str()?; + write!(self.out, "{multi_str}<{scalar_kind_str}4>")? + } + crate::ImageClass::Storage { format, .. } => { + let storage_format_str = format.to_hlsl_str(); + write!(self.out, "<{storage_format_str}>")? + } + } + Ok(()) + } + + pub(super) fn write_wrapped_array_length_function_name( + &mut self, + query: WrappedArrayLength, + ) -> BackendResult { + let access_str = if query.writable { "RW" } else { "" }; + write!(self.out, "NagaBufferLength{access_str}",)?; + + Ok(()) + } + + /// Helper function that write wrapped function for `Expression::ArrayLength` + /// + /// + pub(super) fn write_wrapped_array_length_function( + &mut self, + wal: WrappedArrayLength, + ) -> BackendResult { + use crate::back::INDENT; + + const ARGUMENT_VARIABLE_NAME: &str = "buffer"; + const RETURN_VARIABLE_NAME: &str = "ret"; + + // Write function return type and name + write!(self.out, "uint ")?; + self.write_wrapped_array_length_function_name(wal)?; + + // Write function parameters + write!(self.out, "(")?; + let access_str = if wal.writable { "RW" } else { "" }; + writeln!( + self.out, + "{access_str}ByteAddressBuffer {ARGUMENT_VARIABLE_NAME})" + )?; + // Write function body + writeln!(self.out, "{{")?; + + // Write `GetDimensions` function. + writeln!(self.out, "{INDENT}uint {RETURN_VARIABLE_NAME};")?; + writeln!( + self.out, + "{INDENT}{ARGUMENT_VARIABLE_NAME}.GetDimensions({RETURN_VARIABLE_NAME});" + )?; + + // Write return value + writeln!(self.out, "{INDENT}return {RETURN_VARIABLE_NAME};")?; + + // End of function body + writeln!(self.out, "}}")?; + // Write extra new line + writeln!(self.out)?; + + Ok(()) + } + + pub(super) fn write_wrapped_image_query_function_name( + &mut self, + query: WrappedImageQuery, + ) -> BackendResult { + let dim_str = query.dim.to_hlsl_str(); + let class_str = match query.class { + crate::ImageClass::Sampled { multi: true, .. } => "MS", + crate::ImageClass::Depth { multi: true } => "DepthMS", + crate::ImageClass::Depth { multi: false } => "Depth", + crate::ImageClass::Sampled { multi: false, .. } => "", + crate::ImageClass::Storage { .. } => "RW", + }; + let arrayed_str = if query.arrayed { "Array" } else { "" }; + let query_str = match query.query { + ImageQuery::Size => "Dimensions", + ImageQuery::SizeLevel => "MipDimensions", + ImageQuery::NumLevels => "NumLevels", + ImageQuery::NumLayers => "NumLayers", + ImageQuery::NumSamples => "NumSamples", + }; + + write!(self.out, "Naga{class_str}{query_str}{dim_str}{arrayed_str}")?; + + Ok(()) + } + + /// Helper function that write wrapped function for `Expression::ImageQuery` + /// + /// + pub(super) fn write_wrapped_image_query_function( + &mut self, + module: &crate::Module, + wiq: WrappedImageQuery, + expr_handle: Handle, + func_ctx: &FunctionCtx, + ) -> BackendResult { + use crate::{ + back::{COMPONENTS, INDENT}, + ImageDimension as IDim, + }; + + const ARGUMENT_VARIABLE_NAME: &str = "tex"; + const RETURN_VARIABLE_NAME: &str = "ret"; + const MIP_LEVEL_PARAM: &str = "mip_level"; + + // Write function return type and name + let ret_ty = func_ctx.resolve_type(expr_handle, &module.types); + self.write_value_type(module, ret_ty)?; + write!(self.out, " ")?; + self.write_wrapped_image_query_function_name(wiq)?; + + // Write function parameters + write!(self.out, "(")?; + // Texture always first parameter + self.write_image_type(wiq.dim, wiq.arrayed, wiq.class)?; + write!(self.out, " {ARGUMENT_VARIABLE_NAME}")?; + // Mipmap is a second parameter if exists + if let ImageQuery::SizeLevel = wiq.query { + write!(self.out, ", uint {MIP_LEVEL_PARAM}")?; + } + writeln!(self.out, ")")?; + + // Write function body + writeln!(self.out, "{{")?; + + let array_coords = usize::from(wiq.arrayed); + // extra parameter is the mip level count or the sample count + let extra_coords = match wiq.class { + crate::ImageClass::Storage { .. } => 0, + crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } => 1, + }; + + // GetDimensions Overloaded Methods + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-to-getdimensions#overloaded-methods + let (ret_swizzle, number_of_params) = match wiq.query { + ImageQuery::Size | ImageQuery::SizeLevel => { + let ret = match wiq.dim { + IDim::D1 => "x", + IDim::D2 => "xy", + IDim::D3 => "xyz", + IDim::Cube => "xy", + }; + (ret, ret.len() + array_coords + extra_coords) + } + ImageQuery::NumLevels | ImageQuery::NumSamples | ImageQuery::NumLayers => { + if wiq.arrayed || wiq.dim == IDim::D3 { + ("w", 4) + } else { + ("z", 3) + } + } + }; + + // Write `GetDimensions` function. + writeln!(self.out, "{INDENT}uint4 {RETURN_VARIABLE_NAME};")?; + write!(self.out, "{INDENT}{ARGUMENT_VARIABLE_NAME}.GetDimensions(")?; + match wiq.query { + ImageQuery::SizeLevel => { + write!(self.out, "{MIP_LEVEL_PARAM}, ")?; + } + _ => match wiq.class { + crate::ImageClass::Sampled { multi: true, .. } + | crate::ImageClass::Depth { multi: true } + | crate::ImageClass::Storage { .. } => {} + _ => { + // Write zero mipmap level for supported types + write!(self.out, "0, ")?; + } + }, + } + + for component in COMPONENTS[..number_of_params - 1].iter() { + write!(self.out, "{RETURN_VARIABLE_NAME}.{component}, ")?; + } + + // write last parameter without comma and space for last parameter + write!( + self.out, + "{}.{}", + RETURN_VARIABLE_NAME, + COMPONENTS[number_of_params - 1] + )?; + + writeln!(self.out, ");")?; + + // Write return value + writeln!( + self.out, + "{INDENT}return {RETURN_VARIABLE_NAME}.{ret_swizzle};" + )?; + + // End of function body + writeln!(self.out, "}}")?; + // Write extra new line + writeln!(self.out)?; + + Ok(()) + } + + pub(super) fn write_wrapped_constructor_function_name( + &mut self, + module: &crate::Module, + constructor: WrappedConstructor, + ) -> BackendResult { + let name = crate::TypeInner::hlsl_type_id(constructor.ty, module.to_ctx(), &self.names)?; + write!(self.out, "Construct{name}")?; + Ok(()) + } + + /// Helper function that write wrapped function for `Expression::Compose` for structures. + pub(super) fn write_wrapped_constructor_function( + &mut self, + module: &crate::Module, + constructor: WrappedConstructor, + ) -> BackendResult { + use crate::back::INDENT; + + const ARGUMENT_VARIABLE_NAME: &str = "arg"; + const RETURN_VARIABLE_NAME: &str = "ret"; + + // Write function return type and name + if let crate::TypeInner::Array { base, size, .. } = module.types[constructor.ty].inner { + write!(self.out, "typedef ")?; + self.write_type(module, constructor.ty)?; + write!(self.out, " ret_")?; + self.write_wrapped_constructor_function_name(module, constructor)?; + self.write_array_size(module, base, size)?; + writeln!(self.out, ";")?; + + write!(self.out, "ret_")?; + self.write_wrapped_constructor_function_name(module, constructor)?; + } else { + self.write_type(module, constructor.ty)?; + } + write!(self.out, " ")?; + self.write_wrapped_constructor_function_name(module, constructor)?; + + // Write function parameters + write!(self.out, "(")?; + + let mut write_arg = |i, ty| -> BackendResult { + if i != 0 { + write!(self.out, ", ")?; + } + self.write_type(module, ty)?; + write!(self.out, " {ARGUMENT_VARIABLE_NAME}{i}")?; + if let crate::TypeInner::Array { base, size, .. } = module.types[ty].inner { + self.write_array_size(module, base, size)?; + } + Ok(()) + }; + + match module.types[constructor.ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + for (i, member) in members.iter().enumerate() { + write_arg(i, member.ty)?; + } + } + crate::TypeInner::Array { + base, + size: crate::ArraySize::Constant(size), + .. + } => { + for i in 0..size.get() as usize { + write_arg(i, base)?; + } + } + _ => unreachable!(), + }; + + write!(self.out, ")")?; + + // Write function body + writeln!(self.out, " {{")?; + + match module.types[constructor.ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + let struct_name = &self.names[&NameKey::Type(constructor.ty)]; + writeln!( + self.out, + "{INDENT}{struct_name} {RETURN_VARIABLE_NAME} = ({struct_name})0;" + )?; + for (i, member) in members.iter().enumerate() { + let field_name = &self.names[&NameKey::StructMember(constructor.ty, i as u32)]; + + match module.types[member.ty].inner { + crate::TypeInner::Matrix { + columns, + rows: crate::VectorSize::Bi, + .. + } if member.binding.is_none() => { + for j in 0..columns as u8 { + writeln!( + self.out, + "{INDENT}{RETURN_VARIABLE_NAME}.{field_name}_{j} = {ARGUMENT_VARIABLE_NAME}{i}[{j}];" + )?; + } + } + ref other => { + // We cast arrays of native HLSL `floatCx2`s to arrays of `matCx2`s + // (where the inner matrix is represented by a struct with C `float2` members). + // See the module-level block comment in mod.rs for details. + if let Some(super::writer::MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = super::writer::get_inner_matrix_data(module, member.ty) + { + write!( + self.out, + "{}{}.{} = (__mat{}x2", + INDENT, RETURN_VARIABLE_NAME, field_name, columns as u8 + )?; + if let crate::TypeInner::Array { base, size, .. } = *other { + self.write_array_size(module, base, size)?; + } + writeln!(self.out, "){ARGUMENT_VARIABLE_NAME}{i};",)?; + } else { + writeln!( + self.out, + "{INDENT}{RETURN_VARIABLE_NAME}.{field_name} = {ARGUMENT_VARIABLE_NAME}{i};", + )?; + } + } + } + } + } + crate::TypeInner::Array { + base, + size: crate::ArraySize::Constant(size), + .. + } => { + write!(self.out, "{INDENT}")?; + self.write_type(module, base)?; + write!(self.out, " {RETURN_VARIABLE_NAME}")?; + self.write_array_size(module, base, crate::ArraySize::Constant(size))?; + write!(self.out, " = {{ ")?; + for i in 0..size.get() { + if i != 0 { + write!(self.out, ", ")?; + } + write!(self.out, "{ARGUMENT_VARIABLE_NAME}{i}")?; + } + writeln!(self.out, " }};",)?; + } + _ => unreachable!(), + } + + // Write return value + writeln!(self.out, "{INDENT}return {RETURN_VARIABLE_NAME};")?; + + // End of function body + writeln!(self.out, "}}")?; + // Write extra new line + writeln!(self.out)?; + + Ok(()) + } + + pub(super) fn write_wrapped_struct_matrix_get_function_name( + &mut self, + access: WrappedStructMatrixAccess, + ) -> BackendResult { + let name = &self.names[&NameKey::Type(access.ty)]; + let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; + write!(self.out, "GetMat{field_name}On{name}")?; + Ok(()) + } + + /// Writes a function used to get a matCx2 from within a structure. + pub(super) fn write_wrapped_struct_matrix_get_function( + &mut self, + module: &crate::Module, + access: WrappedStructMatrixAccess, + ) -> BackendResult { + use crate::back::INDENT; + + const STRUCT_ARGUMENT_VARIABLE_NAME: &str = "obj"; + + // Write function return type and name + let member = match module.types[access.ty].inner { + crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize], + _ => unreachable!(), + }; + let ret_ty = &module.types[member.ty].inner; + self.write_value_type(module, ret_ty)?; + write!(self.out, " ")?; + self.write_wrapped_struct_matrix_get_function_name(access)?; + + // Write function parameters + write!(self.out, "(")?; + let struct_name = &self.names[&NameKey::Type(access.ty)]; + write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}")?; + + // Write function body + writeln!(self.out, ") {{")?; + + // Write return value + write!(self.out, "{INDENT}return ")?; + self.write_value_type(module, ret_ty)?; + write!(self.out, "(")?; + let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; + match module.types[member.ty].inner { + crate::TypeInner::Matrix { columns, .. } => { + for i in 0..columns as u8 { + if i != 0 { + write!(self.out, ", ")?; + } + write!(self.out, "{STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i}")?; + } + } + _ => unreachable!(), + } + writeln!(self.out, ");")?; + + // End of function body + writeln!(self.out, "}}")?; + // Write extra new line + writeln!(self.out)?; + + Ok(()) + } + + pub(super) fn write_wrapped_struct_matrix_set_function_name( + &mut self, + access: WrappedStructMatrixAccess, + ) -> BackendResult { + let name = &self.names[&NameKey::Type(access.ty)]; + let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; + write!(self.out, "SetMat{field_name}On{name}")?; + Ok(()) + } + + /// Writes a function used to set a matCx2 from within a structure. + pub(super) fn write_wrapped_struct_matrix_set_function( + &mut self, + module: &crate::Module, + access: WrappedStructMatrixAccess, + ) -> BackendResult { + use crate::back::INDENT; + + const STRUCT_ARGUMENT_VARIABLE_NAME: &str = "obj"; + const MATRIX_ARGUMENT_VARIABLE_NAME: &str = "mat"; + + // Write function return type and name + write!(self.out, "void ")?; + self.write_wrapped_struct_matrix_set_function_name(access)?; + + // Write function parameters + write!(self.out, "(")?; + let struct_name = &self.names[&NameKey::Type(access.ty)]; + write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}, ")?; + let member = match module.types[access.ty].inner { + crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize], + _ => unreachable!(), + }; + self.write_type(module, member.ty)?; + write!(self.out, " {MATRIX_ARGUMENT_VARIABLE_NAME}")?; + // Write function body + writeln!(self.out, ") {{")?; + + let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; + + match module.types[member.ty].inner { + crate::TypeInner::Matrix { columns, .. } => { + for i in 0..columns as u8 { + writeln!( + self.out, + "{INDENT}{STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i} = {MATRIX_ARGUMENT_VARIABLE_NAME}[{i}];" + )?; + } + } + _ => unreachable!(), + } + + // End of function body + writeln!(self.out, "}}")?; + // Write extra new line + writeln!(self.out)?; + + Ok(()) + } + + pub(super) fn write_wrapped_struct_matrix_set_vec_function_name( + &mut self, + access: WrappedStructMatrixAccess, + ) -> BackendResult { + let name = &self.names[&NameKey::Type(access.ty)]; + let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; + write!(self.out, "SetMatVec{field_name}On{name}")?; + Ok(()) + } + + /// Writes a function used to set a vec2 on a matCx2 from within a structure. + pub(super) fn write_wrapped_struct_matrix_set_vec_function( + &mut self, + module: &crate::Module, + access: WrappedStructMatrixAccess, + ) -> BackendResult { + use crate::back::INDENT; + + const STRUCT_ARGUMENT_VARIABLE_NAME: &str = "obj"; + const VECTOR_ARGUMENT_VARIABLE_NAME: &str = "vec"; + const MATRIX_INDEX_ARGUMENT_VARIABLE_NAME: &str = "mat_idx"; + + // Write function return type and name + write!(self.out, "void ")?; + self.write_wrapped_struct_matrix_set_vec_function_name(access)?; + + // Write function parameters + write!(self.out, "(")?; + let struct_name = &self.names[&NameKey::Type(access.ty)]; + write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}, ")?; + let member = match module.types[access.ty].inner { + crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize], + _ => unreachable!(), + }; + let vec_ty = match module.types[member.ty].inner { + crate::TypeInner::Matrix { rows, scalar, .. } => { + crate::TypeInner::Vector { size: rows, scalar } + } + _ => unreachable!(), + }; + self.write_value_type(module, &vec_ty)?; + write!( + self.out, + " {VECTOR_ARGUMENT_VARIABLE_NAME}, uint {MATRIX_INDEX_ARGUMENT_VARIABLE_NAME}" + )?; + + // Write function body + writeln!(self.out, ") {{")?; + + writeln!( + self.out, + "{INDENT}switch({MATRIX_INDEX_ARGUMENT_VARIABLE_NAME}) {{" + )?; + + let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; + + match module.types[member.ty].inner { + crate::TypeInner::Matrix { columns, .. } => { + for i in 0..columns as u8 { + writeln!( + self.out, + "{INDENT}case {i}: {{ {STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i} = {VECTOR_ARGUMENT_VARIABLE_NAME}; break; }}" + )?; + } + } + _ => unreachable!(), + } + + writeln!(self.out, "{INDENT}}}")?; + + // End of function body + writeln!(self.out, "}}")?; + // Write extra new line + writeln!(self.out)?; + + Ok(()) + } + + pub(super) fn write_wrapped_struct_matrix_set_scalar_function_name( + &mut self, + access: WrappedStructMatrixAccess, + ) -> BackendResult { + let name = &self.names[&NameKey::Type(access.ty)]; + let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; + write!(self.out, "SetMatScalar{field_name}On{name}")?; + Ok(()) + } + + /// Writes a function used to set a float on a matCx2 from within a structure. + pub(super) fn write_wrapped_struct_matrix_set_scalar_function( + &mut self, + module: &crate::Module, + access: WrappedStructMatrixAccess, + ) -> BackendResult { + use crate::back::INDENT; + + const STRUCT_ARGUMENT_VARIABLE_NAME: &str = "obj"; + const SCALAR_ARGUMENT_VARIABLE_NAME: &str = "scalar"; + const MATRIX_INDEX_ARGUMENT_VARIABLE_NAME: &str = "mat_idx"; + const VECTOR_INDEX_ARGUMENT_VARIABLE_NAME: &str = "vec_idx"; + + // Write function return type and name + write!(self.out, "void ")?; + self.write_wrapped_struct_matrix_set_scalar_function_name(access)?; + + // Write function parameters + write!(self.out, "(")?; + let struct_name = &self.names[&NameKey::Type(access.ty)]; + write!(self.out, "{struct_name} {STRUCT_ARGUMENT_VARIABLE_NAME}, ")?; + let member = match module.types[access.ty].inner { + crate::TypeInner::Struct { ref members, .. } => &members[access.index as usize], + _ => unreachable!(), + }; + let scalar_ty = match module.types[member.ty].inner { + crate::TypeInner::Matrix { scalar, .. } => crate::TypeInner::Scalar(scalar), + _ => unreachable!(), + }; + self.write_value_type(module, &scalar_ty)?; + write!( + self.out, + " {SCALAR_ARGUMENT_VARIABLE_NAME}, uint {MATRIX_INDEX_ARGUMENT_VARIABLE_NAME}, uint {VECTOR_INDEX_ARGUMENT_VARIABLE_NAME}" + )?; + + // Write function body + writeln!(self.out, ") {{")?; + + writeln!( + self.out, + "{INDENT}switch({MATRIX_INDEX_ARGUMENT_VARIABLE_NAME}) {{" + )?; + + let field_name = &self.names[&NameKey::StructMember(access.ty, access.index)]; + + match module.types[member.ty].inner { + crate::TypeInner::Matrix { columns, .. } => { + for i in 0..columns as u8 { + writeln!( + self.out, + "{INDENT}case {i}: {{ {STRUCT_ARGUMENT_VARIABLE_NAME}.{field_name}_{i}[{VECTOR_INDEX_ARGUMENT_VARIABLE_NAME}] = {SCALAR_ARGUMENT_VARIABLE_NAME}; break; }}" + )?; + } + } + _ => unreachable!(), + } + + writeln!(self.out, "{INDENT}}}")?; + + // End of function body + writeln!(self.out, "}}")?; + // Write extra new line + writeln!(self.out)?; + + Ok(()) + } + + /// Write functions to create special types. + pub(super) fn write_special_functions(&mut self, module: &crate::Module) -> BackendResult { + for (type_key, struct_ty) in module.special_types.predeclared_types.iter() { + match type_key { + &crate::PredeclaredType::ModfResult { size, width } + | &crate::PredeclaredType::FrexpResult { size, width } => { + let arg_type_name_owner; + let arg_type_name = if let Some(size) = size { + arg_type_name_owner = format!( + "{}{}", + if width == 8 { "double" } else { "float" }, + size as u8 + ); + &arg_type_name_owner + } else if width == 8 { + "double" + } else { + "float" + }; + + let (defined_func_name, called_func_name, second_field_name, sign_multiplier) = + if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) { + (super::writer::MODF_FUNCTION, "modf", "whole", "") + } else { + ( + super::writer::FREXP_FUNCTION, + "frexp", + "exp_", + "sign(arg) * ", + ) + }; + + let struct_name = &self.names[&NameKey::Type(*struct_ty)]; + + writeln!( + self.out, + "{struct_name} {defined_func_name}({arg_type_name} arg) {{ + {arg_type_name} other; + {struct_name} result; + result.fract = {sign_multiplier}{called_func_name}(arg, other); + result.{second_field_name} = other; + return result; +}}" + )?; + writeln!(self.out)?; + } + &crate::PredeclaredType::AtomicCompareExchangeWeakResult { .. } => {} + } + } + + Ok(()) + } + + /// Helper function that writes compose wrapped functions + pub(super) fn write_wrapped_compose_functions( + &mut self, + module: &crate::Module, + expressions: &crate::Arena, + ) -> BackendResult { + for (handle, _) in expressions.iter() { + if let crate::Expression::Compose { ty, .. } = expressions[handle] { + match module.types[ty].inner { + crate::TypeInner::Struct { .. } | crate::TypeInner::Array { .. } => { + let constructor = WrappedConstructor { ty }; + if self.wrapped.constructors.insert(constructor) { + self.write_wrapped_constructor_function(module, constructor)?; + } + } + _ => {} + }; + } + } + Ok(()) + } + + /// Helper function that writes various wrapped functions + pub(super) fn write_wrapped_functions( + &mut self, + module: &crate::Module, + func_ctx: &FunctionCtx, + ) -> BackendResult { + self.write_wrapped_compose_functions(module, func_ctx.expressions)?; + + for (handle, _) in func_ctx.expressions.iter() { + match func_ctx.expressions[handle] { + crate::Expression::ArrayLength(expr) => { + let global_expr = match func_ctx.expressions[expr] { + crate::Expression::GlobalVariable(_) => expr, + crate::Expression::AccessIndex { base, index: _ } => base, + ref other => unreachable!("Array length of {:?}", other), + }; + let global_var = match func_ctx.expressions[global_expr] { + crate::Expression::GlobalVariable(var_handle) => { + &module.global_variables[var_handle] + } + ref other => unreachable!("Array length of base {:?}", other), + }; + let storage_access = match global_var.space { + crate::AddressSpace::Storage { access } => access, + _ => crate::StorageAccess::default(), + }; + let wal = WrappedArrayLength { + writable: storage_access.contains(crate::StorageAccess::STORE), + }; + + if self.wrapped.array_lengths.insert(wal) { + self.write_wrapped_array_length_function(wal)?; + } + } + crate::Expression::ImageQuery { image, query } => { + let wiq = match *func_ctx.resolve_type(image, &module.types) { + crate::TypeInner::Image { + dim, + arrayed, + class, + } => WrappedImageQuery { + dim, + arrayed, + class, + query: query.into(), + }, + _ => unreachable!("we only query images"), + }; + + if self.wrapped.image_queries.insert(wiq) { + self.write_wrapped_image_query_function(module, wiq, handle, func_ctx)?; + } + } + // Write `WrappedConstructor` for structs that are loaded from `AddressSpace::Storage` + // since they will later be used by the fn `write_storage_load` + crate::Expression::Load { pointer } => { + let pointer_space = func_ctx + .resolve_type(pointer, &module.types) + .pointer_space(); + + if let Some(crate::AddressSpace::Storage { .. }) = pointer_space { + if let Some(ty) = func_ctx.info[handle].ty.handle() { + write_wrapped_constructor(self, ty, module)?; + } + } + + fn write_wrapped_constructor( + writer: &mut super::Writer<'_, W>, + ty: Handle, + module: &crate::Module, + ) -> BackendResult { + match module.types[ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + for member in members { + write_wrapped_constructor(writer, member.ty, module)?; + } + + let constructor = WrappedConstructor { ty }; + if writer.wrapped.constructors.insert(constructor) { + writer + .write_wrapped_constructor_function(module, constructor)?; + } + } + crate::TypeInner::Array { base, .. } => { + write_wrapped_constructor(writer, base, module)?; + + let constructor = WrappedConstructor { ty }; + if writer.wrapped.constructors.insert(constructor) { + writer + .write_wrapped_constructor_function(module, constructor)?; + } + } + _ => {} + }; + + Ok(()) + } + } + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s + // (see top level module docs for details). + // + // The functions injected here are required to get the matrix accesses working. + crate::Expression::AccessIndex { base, index } => { + let base_ty_res = &func_ctx.info[base].ty; + let mut resolved = base_ty_res.inner_with(&module.types); + let base_ty_handle = match *resolved { + crate::TypeInner::Pointer { base, .. } => { + resolved = &module.types[base].inner; + Some(base) + } + _ => base_ty_res.handle(), + }; + if let crate::TypeInner::Struct { ref members, .. } = *resolved { + let member = &members[index as usize]; + + match module.types[member.ty].inner { + crate::TypeInner::Matrix { + rows: crate::VectorSize::Bi, + .. + } if member.binding.is_none() => { + let ty = base_ty_handle.unwrap(); + let access = WrappedStructMatrixAccess { ty, index }; + + if self.wrapped.struct_matrix_access.insert(access) { + self.write_wrapped_struct_matrix_get_function(module, access)?; + self.write_wrapped_struct_matrix_set_function(module, access)?; + self.write_wrapped_struct_matrix_set_vec_function( + module, access, + )?; + self.write_wrapped_struct_matrix_set_scalar_function( + module, access, + )?; + } + } + _ => {} + } + } + } + _ => {} + }; + } + + Ok(()) + } + + pub(super) fn write_texture_coordinates( + &mut self, + kind: &str, + coordinate: Handle, + array_index: Option>, + mip_level: Option>, + module: &crate::Module, + func_ctx: &FunctionCtx, + ) -> BackendResult { + // HLSL expects the array index to be merged with the coordinate + let extra = array_index.is_some() as usize + (mip_level.is_some()) as usize; + if extra == 0 { + self.write_expr(module, coordinate, func_ctx)?; + } else { + let num_coords = match *func_ctx.resolve_type(coordinate, &module.types) { + crate::TypeInner::Scalar { .. } => 1, + crate::TypeInner::Vector { size, .. } => size as usize, + _ => unreachable!(), + }; + write!(self.out, "{}{}(", kind, num_coords + extra)?; + self.write_expr(module, coordinate, func_ctx)?; + if let Some(expr) = array_index { + write!(self.out, ", ")?; + self.write_expr(module, expr, func_ctx)?; + } + if let Some(expr) = mip_level { + write!(self.out, ", ")?; + self.write_expr(module, expr, func_ctx)?; + } + write!(self.out, ")")?; + } + Ok(()) + } + + pub(super) fn write_mat_cx2_typedef_and_functions( + &mut self, + WrappedMatCx2 { columns }: WrappedMatCx2, + ) -> BackendResult { + use crate::back::INDENT; + + // typedef + write!(self.out, "typedef struct {{ ")?; + for i in 0..columns as u8 { + write!(self.out, "float2 _{i}; ")?; + } + writeln!(self.out, "}} __mat{}x2;", columns as u8)?; + + // __get_col_of_mat + writeln!( + self.out, + "float2 __get_col_of_mat{}x2(__mat{}x2 mat, uint idx) {{", + columns as u8, columns as u8 + )?; + writeln!(self.out, "{INDENT}switch(idx) {{")?; + for i in 0..columns as u8 { + writeln!(self.out, "{INDENT}case {i}: {{ return mat._{i}; }}")?; + } + writeln!(self.out, "{INDENT}default: {{ return (float2)0; }}")?; + writeln!(self.out, "{INDENT}}}")?; + writeln!(self.out, "}}")?; + + // __set_col_of_mat + writeln!( + self.out, + "void __set_col_of_mat{}x2(__mat{}x2 mat, uint idx, float2 value) {{", + columns as u8, columns as u8 + )?; + writeln!(self.out, "{INDENT}switch(idx) {{")?; + for i in 0..columns as u8 { + writeln!(self.out, "{INDENT}case {i}: {{ mat._{i} = value; break; }}")?; + } + writeln!(self.out, "{INDENT}}}")?; + writeln!(self.out, "}}")?; + + // __set_el_of_mat + writeln!( + self.out, + "void __set_el_of_mat{}x2(__mat{}x2 mat, uint idx, uint vec_idx, float value) {{", + columns as u8, columns as u8 + )?; + writeln!(self.out, "{INDENT}switch(idx) {{")?; + for i in 0..columns as u8 { + writeln!( + self.out, + "{INDENT}case {i}: {{ mat._{i}[vec_idx] = value; break; }}" + )?; + } + writeln!(self.out, "{INDENT}}}")?; + writeln!(self.out, "}}")?; + + writeln!(self.out)?; + + Ok(()) + } + + pub(super) fn write_all_mat_cx2_typedefs_and_functions( + &mut self, + module: &crate::Module, + ) -> BackendResult { + for (handle, _) in module.global_variables.iter() { + let global = &module.global_variables[handle]; + + if global.space == crate::AddressSpace::Uniform { + if let Some(super::writer::MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = super::writer::get_inner_matrix_data(module, global.ty) + { + let entry = WrappedMatCx2 { columns }; + if self.wrapped.mat_cx2s.insert(entry) { + self.write_mat_cx2_typedef_and_functions(entry)?; + } + } + } + } + + for (_, ty) in module.types.iter() { + if let crate::TypeInner::Struct { ref members, .. } = ty.inner { + for member in members.iter() { + if let crate::TypeInner::Array { .. } = module.types[member.ty].inner { + if let Some(super::writer::MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = super::writer::get_inner_matrix_data(module, member.ty) + { + let entry = WrappedMatCx2 { columns }; + if self.wrapped.mat_cx2s.insert(entry) { + self.write_mat_cx2_typedef_and_functions(entry)?; + } + } + } + } + } + } + + Ok(()) + } +} diff --git a/naga/src/back/hlsl/keywords.rs b/naga/src/back/hlsl/keywords.rs new file mode 100644 index 0000000000..059e533ff7 --- /dev/null +++ b/naga/src/back/hlsl/keywords.rs @@ -0,0 +1,904 @@ +// When compiling with FXC without strict mode, these keywords are actually case insensitive. +// If you compile with strict mode and specify a different casing like "Pass" instead in an identifier, FXC will give this error: +// "error X3086: alternate cases for 'pass' are deprecated in strict mode" +// This behavior is not documented anywhere, but as far as I can tell this is the full list. +pub const RESERVED_CASE_INSENSITIVE: &[&str] = &[ + "asm", + "decl", + "pass", + "technique", + "Texture1D", + "Texture2D", + "Texture3D", + "TextureCube", +]; + +pub const RESERVED: &[&str] = &[ + // FXC keywords, from https://github.com/MicrosoftDocs/win32/blob/c885cb0c63b0e9be80c6a0e6512473ac6f4e771e/desktop-src/direct3dhlsl/dx-graphics-hlsl-appendix-keywords.md?plain=1#L99-L118 + "AppendStructuredBuffer", + "asm", + "asm_fragment", + "BlendState", + "bool", + "break", + "Buffer", + "ByteAddressBuffer", + "case", + "cbuffer", + "centroid", + "class", + "column_major", + "compile", + "compile_fragment", + "CompileShader", + "const", + "continue", + "ComputeShader", + "ConsumeStructuredBuffer", + "default", + "DepthStencilState", + "DepthStencilView", + "discard", + "do", + "double", + "DomainShader", + "dword", + "else", + "export", + "extern", + "false", + "float", + "for", + "fxgroup", + "GeometryShader", + "groupshared", + "half", + "Hullshader", + "if", + "in", + "inline", + "inout", + "InputPatch", + "int", + "interface", + "line", + "lineadj", + "linear", + "LineStream", + "matrix", + "min16float", + "min10float", + "min16int", + "min12int", + "min16uint", + "namespace", + "nointerpolation", + "noperspective", + "NULL", + "out", + "OutputPatch", + "packoffset", + "pass", + "pixelfragment", + "PixelShader", + "point", + "PointStream", + "precise", + "RasterizerState", + "RenderTargetView", + "return", + "register", + "row_major", + "RWBuffer", + "RWByteAddressBuffer", + "RWStructuredBuffer", + "RWTexture1D", + "RWTexture1DArray", + "RWTexture2D", + "RWTexture2DArray", + "RWTexture3D", + "sample", + "sampler", + "SamplerState", + "SamplerComparisonState", + "shared", + "snorm", + "stateblock", + "stateblock_state", + "static", + "string", + "struct", + "switch", + "StructuredBuffer", + "tbuffer", + "technique", + "technique10", + "technique11", + "texture", + "Texture1D", + "Texture1DArray", + "Texture2D", + "Texture2DArray", + "Texture2DMS", + "Texture2DMSArray", + "Texture3D", + "TextureCube", + "TextureCubeArray", + "true", + "typedef", + "triangle", + "triangleadj", + "TriangleStream", + "uint", + "uniform", + "unorm", + "unsigned", + "vector", + "vertexfragment", + "VertexShader", + "void", + "volatile", + "while", + // FXC reserved keywords, from https://github.com/MicrosoftDocs/win32/blob/c885cb0c63b0e9be80c6a0e6512473ac6f4e771e/desktop-src/direct3dhlsl/dx-graphics-hlsl-appendix-reserved-words.md?plain=1#L19-L38 + "auto", + "case", + "catch", + "char", + "class", + "const_cast", + "default", + "delete", + "dynamic_cast", + "enum", + "explicit", + "friend", + "goto", + "long", + "mutable", + "new", + "operator", + "private", + "protected", + "public", + "reinterpret_cast", + "short", + "signed", + "sizeof", + "static_cast", + "template", + "this", + "throw", + "try", + "typename", + "union", + "unsigned", + "using", + "virtual", + // FXC intrinsics, from https://github.com/MicrosoftDocs/win32/blob/1682b99e203708f6f5eda972d966e30f3c1588de/desktop-src/direct3dhlsl/dx-graphics-hlsl-intrinsic-functions.md?plain=1#L26-L165 + "abort", + "abs", + "acos", + "all", + "AllMemoryBarrier", + "AllMemoryBarrierWithGroupSync", + "any", + "asdouble", + "asfloat", + "asin", + "asint", + "asuint", + "atan", + "atan2", + "ceil", + "CheckAccessFullyMapped", + "clamp", + "clip", + "cos", + "cosh", + "countbits", + "cross", + "D3DCOLORtoUBYTE4", + "ddx", + "ddx_coarse", + "ddx_fine", + "ddy", + "ddy_coarse", + "ddy_fine", + "degrees", + "determinant", + "DeviceMemoryBarrier", + "DeviceMemoryBarrierWithGroupSync", + "distance", + "dot", + "dst", + "errorf", + "EvaluateAttributeCentroid", + "EvaluateAttributeAtSample", + "EvaluateAttributeSnapped", + "exp", + "exp2", + "f16tof32", + "f32tof16", + "faceforward", + "firstbithigh", + "firstbitlow", + "floor", + "fma", + "fmod", + "frac", + "frexp", + "fwidth", + "GetRenderTargetSampleCount", + "GetRenderTargetSamplePosition", + "GroupMemoryBarrier", + "GroupMemoryBarrierWithGroupSync", + "InterlockedAdd", + "InterlockedAnd", + "InterlockedCompareExchange", + "InterlockedCompareStore", + "InterlockedExchange", + "InterlockedMax", + "InterlockedMin", + "InterlockedOr", + "InterlockedXor", + "isfinite", + "isinf", + "isnan", + "ldexp", + "length", + "lerp", + "lit", + "log", + "log10", + "log2", + "mad", + "max", + "min", + "modf", + "msad4", + "mul", + "noise", + "normalize", + "pow", + "printf", + "Process2DQuadTessFactorsAvg", + "Process2DQuadTessFactorsMax", + "Process2DQuadTessFactorsMin", + "ProcessIsolineTessFactors", + "ProcessQuadTessFactorsAvg", + "ProcessQuadTessFactorsMax", + "ProcessQuadTessFactorsMin", + "ProcessTriTessFactorsAvg", + "ProcessTriTessFactorsMax", + "ProcessTriTessFactorsMin", + "radians", + "rcp", + "reflect", + "refract", + "reversebits", + "round", + "rsqrt", + "saturate", + "sign", + "sin", + "sincos", + "sinh", + "smoothstep", + "sqrt", + "step", + "tan", + "tanh", + "tex1D", + "tex1Dbias", + "tex1Dgrad", + "tex1Dlod", + "tex1Dproj", + "tex2D", + "tex2Dbias", + "tex2Dgrad", + "tex2Dlod", + "tex2Dproj", + "tex3D", + "tex3Dbias", + "tex3Dgrad", + "tex3Dlod", + "tex3Dproj", + "texCUBE", + "texCUBEbias", + "texCUBEgrad", + "texCUBElod", + "texCUBEproj", + "transpose", + "trunc", + // DXC (reserved) keywords, from https://github.com/microsoft/DirectXShaderCompiler/blob/d5d478470d3020a438d3cb810b8d3fe0992e6709/tools/clang/include/clang/Basic/TokenKinds.def#L222-L648 + // with the KEYALL, KEYCXX, BOOLSUPPORT, WCHARSUPPORT, KEYHLSL options enabled (see https://github.com/microsoft/DirectXShaderCompiler/blob/d5d478470d3020a438d3cb810b8d3fe0992e6709/tools/clang/lib/Frontend/CompilerInvocation.cpp#L1199) + "auto", + "break", + "case", + "char", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extern", + "float", + "for", + "goto", + "if", + "inline", + "int", + "long", + "register", + "return", + "short", + "signed", + "sizeof", + "static", + "struct", + "switch", + "typedef", + "union", + "unsigned", + "void", + "volatile", + "while", + "_Alignas", + "_Alignof", + "_Atomic", + "_Complex", + "_Generic", + "_Imaginary", + "_Noreturn", + "_Static_assert", + "_Thread_local", + "__func__", + "__objc_yes", + "__objc_no", + "asm", + "bool", + "catch", + "class", + "const_cast", + "delete", + "dynamic_cast", + "explicit", + "export", + "false", + "friend", + "mutable", + "namespace", + "new", + "operator", + "private", + "protected", + "public", + "reinterpret_cast", + "static_cast", + "template", + "this", + "throw", + "true", + "try", + "typename", + "typeid", + "using", + "virtual", + "wchar_t", + "_Decimal32", + "_Decimal64", + "_Decimal128", + "__null", + "__alignof", + "__attribute", + "__builtin_choose_expr", + "__builtin_offsetof", + "__builtin_va_arg", + "__extension__", + "__imag", + "__int128", + "__label__", + "__real", + "__thread", + "__FUNCTION__", + "__PRETTY_FUNCTION__", + "__is_nothrow_assignable", + "__is_constructible", + "__is_nothrow_constructible", + "__has_nothrow_assign", + "__has_nothrow_move_assign", + "__has_nothrow_copy", + "__has_nothrow_constructor", + "__has_trivial_assign", + "__has_trivial_move_assign", + "__has_trivial_copy", + "__has_trivial_constructor", + "__has_trivial_move_constructor", + "__has_trivial_destructor", + "__has_virtual_destructor", + "__is_abstract", + "__is_base_of", + "__is_class", + "__is_convertible_to", + "__is_empty", + "__is_enum", + "__is_final", + "__is_literal", + "__is_literal_type", + "__is_pod", + "__is_polymorphic", + "__is_trivial", + "__is_union", + "__is_trivially_constructible", + "__is_trivially_copyable", + "__is_trivially_assignable", + "__underlying_type", + "__is_lvalue_expr", + "__is_rvalue_expr", + "__is_arithmetic", + "__is_floating_point", + "__is_integral", + "__is_complete_type", + "__is_void", + "__is_array", + "__is_function", + "__is_reference", + "__is_lvalue_reference", + "__is_rvalue_reference", + "__is_fundamental", + "__is_object", + "__is_scalar", + "__is_compound", + "__is_pointer", + "__is_member_object_pointer", + "__is_member_function_pointer", + "__is_member_pointer", + "__is_const", + "__is_volatile", + "__is_standard_layout", + "__is_signed", + "__is_unsigned", + "__is_same", + "__is_convertible", + "__array_rank", + "__array_extent", + "__private_extern__", + "__module_private__", + "__declspec", + "__cdecl", + "__stdcall", + "__fastcall", + "__thiscall", + "__vectorcall", + "cbuffer", + "tbuffer", + "packoffset", + "linear", + "centroid", + "nointerpolation", + "noperspective", + "sample", + "column_major", + "row_major", + "in", + "out", + "inout", + "uniform", + "precise", + "center", + "shared", + "groupshared", + "discard", + "snorm", + "unorm", + "point", + "line", + "lineadj", + "triangle", + "triangleadj", + "globallycoherent", + "interface", + "sampler_state", + "technique", + "indices", + "vertices", + "primitives", + "payload", + "Technique", + "technique10", + "technique11", + "__builtin_omp_required_simd_align", + "__pascal", + "__fp16", + "__alignof__", + "__asm", + "__asm__", + "__attribute__", + "__complex", + "__complex__", + "__const", + "__const__", + "__decltype", + "__imag__", + "__inline", + "__inline__", + "__nullptr", + "__real__", + "__restrict", + "__restrict__", + "__signed", + "__signed__", + "__typeof", + "__typeof__", + "__volatile", + "__volatile__", + "_Nonnull", + "_Nullable", + "_Null_unspecified", + "__builtin_convertvector", + "__char16_t", + "__char32_t", + // DXC intrinsics, from https://github.com/microsoft/DirectXShaderCompiler/blob/18c9e114f9c314f93e68fbc72ce207d4ed2e65ae/utils/hct/gen_intrin_main.txt#L86-L376 + "D3DCOLORtoUBYTE4", + "GetRenderTargetSampleCount", + "GetRenderTargetSamplePosition", + "abort", + "abs", + "acos", + "all", + "AllMemoryBarrier", + "AllMemoryBarrierWithGroupSync", + "any", + "asdouble", + "asfloat", + "asfloat16", + "asint16", + "asin", + "asint", + "asuint", + "asuint16", + "atan", + "atan2", + "ceil", + "clamp", + "clip", + "cos", + "cosh", + "countbits", + "cross", + "ddx", + "ddx_coarse", + "ddx_fine", + "ddy", + "ddy_coarse", + "ddy_fine", + "degrees", + "determinant", + "DeviceMemoryBarrier", + "DeviceMemoryBarrierWithGroupSync", + "distance", + "dot", + "dst", + "EvaluateAttributeAtSample", + "EvaluateAttributeCentroid", + "EvaluateAttributeSnapped", + "GetAttributeAtVertex", + "exp", + "exp2", + "f16tof32", + "f32tof16", + "faceforward", + "firstbithigh", + "firstbitlow", + "floor", + "fma", + "fmod", + "frac", + "frexp", + "fwidth", + "GroupMemoryBarrier", + "GroupMemoryBarrierWithGroupSync", + "InterlockedAdd", + "InterlockedMin", + "InterlockedMax", + "InterlockedAnd", + "InterlockedOr", + "InterlockedXor", + "InterlockedCompareStore", + "InterlockedExchange", + "InterlockedCompareExchange", + "InterlockedCompareStoreFloatBitwise", + "InterlockedCompareExchangeFloatBitwise", + "isfinite", + "isinf", + "isnan", + "ldexp", + "length", + "lerp", + "lit", + "log", + "log10", + "log2", + "mad", + "max", + "min", + "modf", + "msad4", + "mul", + "normalize", + "pow", + "printf", + "Process2DQuadTessFactorsAvg", + "Process2DQuadTessFactorsMax", + "Process2DQuadTessFactorsMin", + "ProcessIsolineTessFactors", + "ProcessQuadTessFactorsAvg", + "ProcessQuadTessFactorsMax", + "ProcessQuadTessFactorsMin", + "ProcessTriTessFactorsAvg", + "ProcessTriTessFactorsMax", + "ProcessTriTessFactorsMin", + "radians", + "rcp", + "reflect", + "refract", + "reversebits", + "round", + "rsqrt", + "saturate", + "sign", + "sin", + "sincos", + "sinh", + "smoothstep", + "source_mark", + "sqrt", + "step", + "tan", + "tanh", + "tex1D", + "tex1Dbias", + "tex1Dgrad", + "tex1Dlod", + "tex1Dproj", + "tex2D", + "tex2Dbias", + "tex2Dgrad", + "tex2Dlod", + "tex2Dproj", + "tex3D", + "tex3Dbias", + "tex3Dgrad", + "tex3Dlod", + "tex3Dproj", + "texCUBE", + "texCUBEbias", + "texCUBEgrad", + "texCUBElod", + "texCUBEproj", + "transpose", + "trunc", + "CheckAccessFullyMapped", + "AddUint64", + "NonUniformResourceIndex", + "WaveIsFirstLane", + "WaveGetLaneIndex", + "WaveGetLaneCount", + "WaveActiveAnyTrue", + "WaveActiveAllTrue", + "WaveActiveAllEqual", + "WaveActiveBallot", + "WaveReadLaneAt", + "WaveReadLaneFirst", + "WaveActiveCountBits", + "WaveActiveSum", + "WaveActiveProduct", + "WaveActiveBitAnd", + "WaveActiveBitOr", + "WaveActiveBitXor", + "WaveActiveMin", + "WaveActiveMax", + "WavePrefixCountBits", + "WavePrefixSum", + "WavePrefixProduct", + "WaveMatch", + "WaveMultiPrefixBitAnd", + "WaveMultiPrefixBitOr", + "WaveMultiPrefixBitXor", + "WaveMultiPrefixCountBits", + "WaveMultiPrefixProduct", + "WaveMultiPrefixSum", + "QuadReadLaneAt", + "QuadReadAcrossX", + "QuadReadAcrossY", + "QuadReadAcrossDiagonal", + "QuadAny", + "QuadAll", + "TraceRay", + "ReportHit", + "CallShader", + "IgnoreHit", + "AcceptHitAndEndSearch", + "DispatchRaysIndex", + "DispatchRaysDimensions", + "WorldRayOrigin", + "WorldRayDirection", + "ObjectRayOrigin", + "ObjectRayDirection", + "RayTMin", + "RayTCurrent", + "PrimitiveIndex", + "InstanceID", + "InstanceIndex", + "GeometryIndex", + "HitKind", + "RayFlags", + "ObjectToWorld", + "WorldToObject", + "ObjectToWorld3x4", + "WorldToObject3x4", + "ObjectToWorld4x3", + "WorldToObject4x3", + "dot4add_u8packed", + "dot4add_i8packed", + "dot2add", + "unpack_s8s16", + "unpack_u8u16", + "unpack_s8s32", + "unpack_u8u32", + "pack_s8", + "pack_u8", + "pack_clamp_s8", + "pack_clamp_u8", + "SetMeshOutputCounts", + "DispatchMesh", + "IsHelperLane", + "AllocateRayQuery", + "CreateResourceFromHeap", + "and", + "or", + "select", + // DXC resource and other types, from https://github.com/microsoft/DirectXShaderCompiler/blob/18c9e114f9c314f93e68fbc72ce207d4ed2e65ae/tools/clang/lib/AST/HlslTypes.cpp#L441-#L572 + "InputPatch", + "OutputPatch", + "PointStream", + "LineStream", + "TriangleStream", + "Texture1D", + "RWTexture1D", + "Texture2D", + "RWTexture2D", + "Texture2DMS", + "RWTexture2DMS", + "Texture3D", + "RWTexture3D", + "TextureCube", + "RWTextureCube", + "Texture1DArray", + "RWTexture1DArray", + "Texture2DArray", + "RWTexture2DArray", + "Texture2DMSArray", + "RWTexture2DMSArray", + "TextureCubeArray", + "RWTextureCubeArray", + "FeedbackTexture2D", + "FeedbackTexture2DArray", + "RasterizerOrderedTexture1D", + "RasterizerOrderedTexture2D", + "RasterizerOrderedTexture3D", + "RasterizerOrderedTexture1DArray", + "RasterizerOrderedTexture2DArray", + "RasterizerOrderedBuffer", + "RasterizerOrderedByteAddressBuffer", + "RasterizerOrderedStructuredBuffer", + "ByteAddressBuffer", + "RWByteAddressBuffer", + "StructuredBuffer", + "RWStructuredBuffer", + "AppendStructuredBuffer", + "ConsumeStructuredBuffer", + "Buffer", + "RWBuffer", + "SamplerState", + "SamplerComparisonState", + "ConstantBuffer", + "TextureBuffer", + "RaytracingAccelerationStructure", + // DXC templated types, from https://github.com/microsoft/DirectXShaderCompiler/blob/18c9e114f9c314f93e68fbc72ce207d4ed2e65ae/tools/clang/lib/AST/ASTContextHLSL.cpp + // look for `BuiltinTypeDeclBuilder` + "matrix", + "vector", + "TextureBuffer", + "ConstantBuffer", + "RayQuery", + // Naga utilities + super::writer::MODF_FUNCTION, + super::writer::FREXP_FUNCTION, +]; + +// DXC scalar types, from https://github.com/microsoft/DirectXShaderCompiler/blob/18c9e114f9c314f93e68fbc72ce207d4ed2e65ae/tools/clang/lib/AST/ASTContextHLSL.cpp#L48-L254 +// + vector and matrix shorthands +pub const TYPES: &[&str] = &{ + const L: usize = 23 * (1 + 4 + 4 * 4); + let mut res = [""; L]; + let mut c = 0; + + /// For each scalar type, it will additionally generate vector and matrix shorthands + macro_rules! generate { + ([$($roots:literal),*], $x:tt) => { + $( + generate!(@inner push $roots); + generate!(@inner $roots, $x); + )* + }; + + (@inner $root:literal, [$($x:literal),*]) => { + generate!(@inner vector $root, $($x)*); + generate!(@inner matrix $root, $($x)*); + }; + + (@inner vector $root:literal, $($x:literal)*) => { + $( + generate!(@inner push concat!($root, $x)); + )* + }; + + (@inner matrix $root:literal, $($x:literal)*) => { + // Duplicate the list + generate!(@inner matrix $root, $($x)*; $($x)*); + }; + + // The head/tail recursion: pick the first element of the first list and recursively do it for the tail. + (@inner matrix $root:literal, $head:literal $($tail:literal)*; $($x:literal)*) => { + $( + generate!(@inner push concat!($root, $head, "x", $x)); + )* + generate!(@inner matrix $root, $($tail)*; $($x)*); + + }; + + // The end of iteration: we exhausted the list + (@inner matrix $root:literal, ; $($x:literal)*) => {}; + + (@inner push $v:expr) => { + res[c] = $v; + c += 1; + }; + } + + generate!( + [ + "bool", + "int", + "uint", + "dword", + "half", + "float", + "double", + "min10float", + "min16float", + "min12int", + "min16int", + "min16uint", + "int16_t", + "int32_t", + "int64_t", + "uint16_t", + "uint32_t", + "uint64_t", + "float16_t", + "float32_t", + "float64_t", + "int8_t4_packed", + "uint8_t4_packed" + ], + ["1", "2", "3", "4"] + ); + + debug_assert!(c == L); + + res +}; diff --git a/naga/src/back/hlsl/mod.rs b/naga/src/back/hlsl/mod.rs new file mode 100644 index 0000000000..37ddbd3d67 --- /dev/null +++ b/naga/src/back/hlsl/mod.rs @@ -0,0 +1,302 @@ +/*! +Backend for [HLSL][hlsl] (High-Level Shading Language). + +# Supported shader model versions: +- 5.0 +- 5.1 +- 6.0 + +# Layout of values in `uniform` buffers + +WGSL's ["Internal Layout of Values"][ilov] rules specify how each WGSL +type should be stored in `uniform` and `storage` buffers. The HLSL we +generate must access values in that form, even when it is not what +HLSL would use normally. + +The rules described here only apply to WGSL `uniform` variables. WGSL +`storage` buffers are translated as HLSL `ByteAddressBuffers`, for +which we generate `Load` and `Store` method calls with explicit byte +offsets. WGSL pipeline inputs must be scalars or vectors; they cannot +be matrices, which is where the interesting problems arise. + +## Row- and column-major ordering for matrices + +WGSL specifies that matrices in uniform buffers are stored in +column-major order. This matches HLSL's default, so one might expect +things to be straightforward. Unfortunately, WGSL and HLSL disagree on +what indexing a matrix means: in WGSL, `m[i]` retrieves the `i`'th +*column* of `m`, whereas in HLSL it retrieves the `i`'th *row*. We +want to avoid translating `m[i]` into some complicated reassembly of a +vector from individually fetched components, so this is a problem. + +However, with a bit of trickery, it is possible to use HLSL's `m[i]` +as the translation of WGSL's `m[i]`: + +- We declare all matrices in uniform buffers in HLSL with the + `row_major` qualifier, and transpose the row and column counts: a + WGSL `mat3x4`, say, becomes an HLSL `row_major float3x4`. (Note + that WGSL and HLSL type names put the row and column in reverse + order.) Since the HLSL type is the transpose of how WebGPU directs + the user to store the data, HLSL will load all matrices transposed. + +- Since matrices are transposed, an HLSL indexing expression retrieves + the "columns" of the intended WGSL value, as desired. + +- For vector-matrix multiplication, since `mul(transpose(m), v)` is + equivalent to `mul(v, m)` (note the reversal of the arguments), and + `mul(v, transpose(m))` is equivalent to `mul(m, v)`, we can + translate WGSL `m * v` and `v * m` to HLSL by simply reversing the + arguments to `mul`. + +## Padding in two-row matrices + +An HLSL `row_major floatKx2` matrix has padding between its rows that +the WGSL `matKx2` matrix it represents does not. HLSL stores all +matrix rows [aligned on 16-byte boundaries][16bb], whereas WGSL says +that the columns of a `matKx2` need only be [aligned as required +for `vec2`][ilov], which is [eight-byte alignment][8bb]. + +To compensate for this, any time a `matKx2` appears in a WGSL +`uniform` variable, whether directly as the variable's type or as part +of a struct/array, we actually emit `K` separate `float2` members, and +assemble/disassemble the matrix from its columns (in WGSL; rows in +HLSL) upon load and store. + +For example, the following WGSL struct type: + +```ignore +struct Baz { + m: mat3x2, +} +``` + +is rendered as the HLSL struct type: + +```ignore +struct Baz { + float2 m_0; float2 m_1; float2 m_2; +}; +``` + +The `wrapped_struct_matrix` functions in `help.rs` generate HLSL +helper functions to access such members, converting between the stored +form and the HLSL matrix types appropriately. For example, for reading +the member `m` of the `Baz` struct above, we emit: + +```ignore +float3x2 GetMatmOnBaz(Baz obj) { + return float3x2(obj.m_0, obj.m_1, obj.m_2); +} +``` + +We also emit an analogous `Set` function, as well as functions for +accessing individual columns by dynamic index. + +[hlsl]: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl +[ilov]: https://gpuweb.github.io/gpuweb/wgsl/#internal-value-layout +[16bb]: https://github.com/microsoft/DirectXShaderCompiler/wiki/Buffer-Packing#constant-buffer-packing +[8bb]: https://gpuweb.github.io/gpuweb/wgsl/#alignment-and-size +*/ + +mod conv; +mod help; +mod keywords; +mod storage; +mod writer; + +use std::fmt::Error as FmtError; +use thiserror::Error; + +use crate::{back, proc}; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct BindTarget { + pub space: u8, + pub register: u32, + /// If the binding is an unsized binding array, this overrides the size. + pub binding_array_size: Option, +} + +// Using `BTreeMap` instead of `HashMap` so that we can hash itself. +pub type BindingMap = std::collections::BTreeMap; + +/// A HLSL shader model version. +#[allow(non_snake_case, non_camel_case_types)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub enum ShaderModel { + V5_0, + V5_1, + V6_0, +} + +impl ShaderModel { + pub const fn to_str(self) -> &'static str { + match self { + Self::V5_0 => "5_0", + Self::V5_1 => "5_1", + Self::V6_0 => "6_0", + } + } +} + +impl crate::ShaderStage { + pub const fn to_hlsl_str(self) -> &'static str { + match self { + Self::Vertex => "vs", + Self::Fragment => "ps", + Self::Compute => "cs", + } + } +} + +impl crate::ImageDimension { + const fn to_hlsl_str(self) -> &'static str { + match self { + Self::D1 => "1D", + Self::D2 => "2D", + Self::D3 => "3D", + Self::Cube => "Cube", + } + } +} + +/// Shorthand result used internally by the backend +type BackendResult = Result<(), Error>; + +#[derive(Clone, Debug, PartialEq, thiserror::Error)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub enum EntryPointError { + #[error("mapping of {0:?} is missing")] + MissingBinding(crate::ResourceBinding), +} + +/// Configuration used in the [`Writer`]. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct Options { + /// The hlsl shader model to be used + pub shader_model: ShaderModel, + /// Map of resources association to binding locations. + pub binding_map: BindingMap, + /// Don't panic on missing bindings, instead generate any HLSL. + pub fake_missing_bindings: bool, + /// Add special constants to `SV_VertexIndex` and `SV_InstanceIndex`, + /// to make them work like in Vulkan/Metal, with help of the host. + pub special_constants_binding: Option, + /// Bind target of the push constant buffer + pub push_constants_target: Option, + /// Should workgroup variables be zero initialized (by polyfilling)? + pub zero_initialize_workgroup_memory: bool, +} + +impl Default for Options { + fn default() -> Self { + Options { + shader_model: ShaderModel::V5_1, + binding_map: BindingMap::default(), + fake_missing_bindings: true, + special_constants_binding: None, + push_constants_target: None, + zero_initialize_workgroup_memory: true, + } + } +} + +impl Options { + fn resolve_resource_binding( + &self, + res_binding: &crate::ResourceBinding, + ) -> Result { + match self.binding_map.get(res_binding) { + Some(target) => Ok(target.clone()), + None if self.fake_missing_bindings => Ok(BindTarget { + space: res_binding.group as u8, + register: res_binding.binding, + binding_array_size: None, + }), + None => Err(EntryPointError::MissingBinding(res_binding.clone())), + } + } +} + +/// Reflection info for entry point names. +#[derive(Default)] +pub struct ReflectionInfo { + /// Mapping of the entry point names. + /// + /// Each item in the array corresponds to an entry point index. The real entry point name may be different if one of the + /// reserved words are used. + /// + /// Note: Some entry points may fail translation because of missing bindings. + pub entry_point_names: Vec>, +} + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + IoError(#[from] FmtError), + #[error("A scalar with an unsupported width was requested: {0:?}")] + UnsupportedScalar(crate::Scalar), + #[error("{0}")] + Unimplemented(String), // TODO: Error used only during development + #[error("{0}")] + Custom(String), +} + +#[derive(Default)] +struct Wrapped { + array_lengths: crate::FastHashSet, + image_queries: crate::FastHashSet, + constructors: crate::FastHashSet, + struct_matrix_access: crate::FastHashSet, + mat_cx2s: crate::FastHashSet, +} + +impl Wrapped { + fn clear(&mut self) { + self.array_lengths.clear(); + self.image_queries.clear(); + self.constructors.clear(); + self.struct_matrix_access.clear(); + self.mat_cx2s.clear(); + } +} + +pub struct Writer<'a, W> { + out: W, + names: crate::FastHashMap, + namer: proc::Namer, + /// HLSL backend options + options: &'a Options, + /// Information about entry point arguments and result types. + entry_point_io: Vec, + /// Set of expressions that have associated temporary variables + named_expressions: crate::NamedExpressions, + wrapped: Wrapped, + + /// A reference to some part of a global variable, lowered to a series of + /// byte offset calculations. + /// + /// See the [`storage`] module for background on why we need this. + /// + /// Each [`SubAccess`] in the vector is a lowering of some [`Access`] or + /// [`AccessIndex`] expression to the level of byte strides and offsets. See + /// [`SubAccess`] for details. + /// + /// This field is a member of [`Writer`] solely to allow re-use of + /// the `Vec`'s dynamic allocation. The value is no longer needed + /// once HLSL for the access has been generated. + /// + /// [`Storage`]: crate::AddressSpace::Storage + /// [`SubAccess`]: storage::SubAccess + /// [`Access`]: crate::Expression::Access + /// [`AccessIndex`]: crate::Expression::AccessIndex + temp_access_chain: Vec, + need_bake_expressions: back::NeedBakeExpressions, +} diff --git a/naga/src/back/hlsl/storage.rs b/naga/src/back/hlsl/storage.rs new file mode 100644 index 0000000000..1b8a6ec12d --- /dev/null +++ b/naga/src/back/hlsl/storage.rs @@ -0,0 +1,494 @@ +/*! +Generating accesses to [`ByteAddressBuffer`] contents. + +Naga IR globals in the [`Storage`] address space are rendered as +[`ByteAddressBuffer`]s or [`RWByteAddressBuffer`]s in HLSL. These +buffers don't have HLSL types (structs, arrays, etc.); instead, they +are just raw blocks of bytes, with methods to load and store values of +specific types at particular byte offsets. This means that Naga must +translate chains of [`Access`] and [`AccessIndex`] expressions into +HLSL expressions that compute byte offsets into the buffer. + +To generate code for a [`Storage`] access: + +- Call [`Writer::fill_access_chain`] on the expression referring to + the value. This populates [`Writer::temp_access_chain`] with the + appropriate byte offset calculations, as a vector of [`SubAccess`] + values. + +- Call [`Writer::write_storage_address`] to emit an HLSL expression + for a given slice of [`SubAccess`] values. + +Naga IR expressions can operate on composite values of any type, but +[`ByteAddressBuffer`] and [`RWByteAddressBuffer`] have only a fixed +set of `Load` and `Store` methods, to access one through four +consecutive 32-bit values. To synthesize a Naga access, you can +initialize [`temp_access_chain`] to refer to the composite, and then +temporarily push and pop additional steps on +[`Writer::temp_access_chain`] to generate accesses to the individual +elements/members. + +The [`temp_access_chain`] field is a member of [`Writer`] solely to +allow re-use of the `Vec`'s dynamic allocation. Its value is no longer +needed once HLSL for the access has been generated. + +[`Storage`]: crate::AddressSpace::Storage +[`ByteAddressBuffer`]: https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-byteaddressbuffer +[`RWByteAddressBuffer`]: https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-object-rwbyteaddressbuffer +[`Access`]: crate::Expression::Access +[`AccessIndex`]: crate::Expression::AccessIndex +[`Writer::fill_access_chain`]: super::Writer::fill_access_chain +[`Writer::write_storage_address`]: super::Writer::write_storage_address +[`Writer::temp_access_chain`]: super::Writer::temp_access_chain +[`temp_access_chain`]: super::Writer::temp_access_chain +[`Writer`]: super::Writer +*/ + +use super::{super::FunctionCtx, BackendResult, Error}; +use crate::{ + proc::{Alignment, NameKey, TypeResolution}, + Handle, +}; + +use std::{fmt, mem}; + +const STORE_TEMP_NAME: &str = "_value"; + +/// One step in accessing a [`Storage`] global's component or element. +/// +/// [`Writer::temp_access_chain`] holds a series of these structures, +/// describing how to compute the byte offset of a particular element +/// or member of some global variable in the [`Storage`] address +/// space. +/// +/// [`Writer::temp_access_chain`]: super::Writer::temp_access_chain +/// [`Storage`]: crate::AddressSpace::Storage +#[derive(Debug)] +pub(super) enum SubAccess { + /// Add the given byte offset. This is used for struct members, or + /// known components of a vector or matrix. In all those cases, + /// the byte offset is a compile-time constant. + Offset(u32), + + /// Scale `value` by `stride`, and add that to the current byte + /// offset. This is used to compute the offset of an array element + /// whose index is computed at runtime. + Index { + value: Handle, + stride: u32, + }, +} + +pub(super) enum StoreValue { + Expression(Handle), + TempIndex { + depth: usize, + index: u32, + ty: TypeResolution, + }, + TempAccess { + depth: usize, + base: Handle, + member_index: u32, + }, +} + +impl super::Writer<'_, W> { + pub(super) fn write_storage_address( + &mut self, + module: &crate::Module, + chain: &[SubAccess], + func_ctx: &FunctionCtx, + ) -> BackendResult { + if chain.is_empty() { + write!(self.out, "0")?; + } + for (i, access) in chain.iter().enumerate() { + if i != 0 { + write!(self.out, "+")?; + } + match *access { + SubAccess::Offset(offset) => { + write!(self.out, "{offset}")?; + } + SubAccess::Index { value, stride } => { + self.write_expr(module, value, func_ctx)?; + write!(self.out, "*{stride}")?; + } + } + } + Ok(()) + } + + fn write_storage_load_sequence>( + &mut self, + module: &crate::Module, + var_handle: Handle, + sequence: I, + func_ctx: &FunctionCtx, + ) -> BackendResult { + for (i, (ty_resolution, offset)) in sequence.enumerate() { + // add the index temporarily + self.temp_access_chain.push(SubAccess::Offset(offset)); + if i != 0 { + write!(self.out, ", ")?; + }; + self.write_storage_load(module, var_handle, ty_resolution, func_ctx)?; + self.temp_access_chain.pop(); + } + Ok(()) + } + + /// Emit code to access a [`Storage`] global's component. + /// + /// Emit HLSL to access the component of `var_handle`, a global + /// variable in the [`Storage`] address space, whose type is + /// `result_ty` and whose location within the global is given by + /// [`self.temp_access_chain`]. See the [`storage`] module's + /// documentation for background. + /// + /// [`Storage`]: crate::AddressSpace::Storage + /// [`self.temp_access_chain`]: super::Writer::temp_access_chain + pub(super) fn write_storage_load( + &mut self, + module: &crate::Module, + var_handle: Handle, + result_ty: TypeResolution, + func_ctx: &FunctionCtx, + ) -> BackendResult { + match *result_ty.inner_with(&module.types) { + crate::TypeInner::Scalar(scalar) => { + // working around the borrow checker in `self.write_expr` + let chain = mem::take(&mut self.temp_access_chain); + let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; + let cast = scalar.kind.to_hlsl_cast(); + write!(self.out, "{cast}({var_name}.Load(")?; + self.write_storage_address(module, &chain, func_ctx)?; + write!(self.out, "))")?; + self.temp_access_chain = chain; + } + crate::TypeInner::Vector { size, scalar } => { + // working around the borrow checker in `self.write_expr` + let chain = mem::take(&mut self.temp_access_chain); + let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; + let cast = scalar.kind.to_hlsl_cast(); + write!(self.out, "{}({}.Load{}(", cast, var_name, size as u8)?; + self.write_storage_address(module, &chain, func_ctx)?; + write!(self.out, "))")?; + self.temp_access_chain = chain; + } + crate::TypeInner::Matrix { + columns, + rows, + scalar, + } => { + write!( + self.out, + "{}{}x{}(", + scalar.to_hlsl_str()?, + columns as u8, + rows as u8, + )?; + + // Note: Matrices containing vec3s, due to padding, act like they contain vec4s. + let row_stride = Alignment::from(rows) * scalar.width as u32; + let iter = (0..columns as u32).map(|i| { + let ty_inner = crate::TypeInner::Vector { size: rows, scalar }; + (TypeResolution::Value(ty_inner), i * row_stride) + }); + self.write_storage_load_sequence(module, var_handle, iter, func_ctx)?; + write!(self.out, ")")?; + } + crate::TypeInner::Array { + base, + size: crate::ArraySize::Constant(size), + stride, + } => { + let constructor = super::help::WrappedConstructor { + ty: result_ty.handle().unwrap(), + }; + self.write_wrapped_constructor_function_name(module, constructor)?; + write!(self.out, "(")?; + let iter = (0..size.get()).map(|i| (TypeResolution::Handle(base), stride * i)); + self.write_storage_load_sequence(module, var_handle, iter, func_ctx)?; + write!(self.out, ")")?; + } + crate::TypeInner::Struct { ref members, .. } => { + let constructor = super::help::WrappedConstructor { + ty: result_ty.handle().unwrap(), + }; + self.write_wrapped_constructor_function_name(module, constructor)?; + write!(self.out, "(")?; + let iter = members + .iter() + .map(|m| (TypeResolution::Handle(m.ty), m.offset)); + self.write_storage_load_sequence(module, var_handle, iter, func_ctx)?; + write!(self.out, ")")?; + } + _ => unreachable!(), + } + Ok(()) + } + + fn write_store_value( + &mut self, + module: &crate::Module, + value: &StoreValue, + func_ctx: &FunctionCtx, + ) -> BackendResult { + match *value { + StoreValue::Expression(expr) => self.write_expr(module, expr, func_ctx)?, + StoreValue::TempIndex { + depth, + index, + ty: _, + } => write!(self.out, "{STORE_TEMP_NAME}{depth}[{index}]")?, + StoreValue::TempAccess { + depth, + base, + member_index, + } => { + let name = &self.names[&NameKey::StructMember(base, member_index)]; + write!(self.out, "{STORE_TEMP_NAME}{depth}.{name}")? + } + } + Ok(()) + } + + /// Helper function to write down the Store operation on a `ByteAddressBuffer`. + pub(super) fn write_storage_store( + &mut self, + module: &crate::Module, + var_handle: Handle, + value: StoreValue, + func_ctx: &FunctionCtx, + level: crate::back::Level, + ) -> BackendResult { + let temp_resolution; + let ty_resolution = match value { + StoreValue::Expression(expr) => &func_ctx.info[expr].ty, + StoreValue::TempIndex { + depth: _, + index: _, + ref ty, + } => ty, + StoreValue::TempAccess { + depth: _, + base, + member_index, + } => { + let ty_handle = match module.types[base].inner { + crate::TypeInner::Struct { ref members, .. } => { + members[member_index as usize].ty + } + _ => unreachable!(), + }; + temp_resolution = TypeResolution::Handle(ty_handle); + &temp_resolution + } + }; + match *ty_resolution.inner_with(&module.types) { + crate::TypeInner::Scalar(_) => { + // working around the borrow checker in `self.write_expr` + let chain = mem::take(&mut self.temp_access_chain); + let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; + write!(self.out, "{level}{var_name}.Store(")?; + self.write_storage_address(module, &chain, func_ctx)?; + write!(self.out, ", asuint(")?; + self.write_store_value(module, &value, func_ctx)?; + writeln!(self.out, "));")?; + self.temp_access_chain = chain; + } + crate::TypeInner::Vector { size, .. } => { + // working around the borrow checker in `self.write_expr` + let chain = mem::take(&mut self.temp_access_chain); + let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; + write!(self.out, "{}{}.Store{}(", level, var_name, size as u8)?; + self.write_storage_address(module, &chain, func_ctx)?; + write!(self.out, ", asuint(")?; + self.write_store_value(module, &value, func_ctx)?; + writeln!(self.out, "));")?; + self.temp_access_chain = chain; + } + crate::TypeInner::Matrix { + columns, + rows, + scalar, + } => { + // first, assign the value to a temporary + writeln!(self.out, "{level}{{")?; + let depth = level.0 + 1; + write!( + self.out, + "{}{}{}x{} {}{} = ", + level.next(), + scalar.to_hlsl_str()?, + columns as u8, + rows as u8, + STORE_TEMP_NAME, + depth, + )?; + self.write_store_value(module, &value, func_ctx)?; + writeln!(self.out, ";")?; + + // Note: Matrices containing vec3s, due to padding, act like they contain vec4s. + let row_stride = Alignment::from(rows) * scalar.width as u32; + + // then iterate the stores + for i in 0..columns as u32 { + self.temp_access_chain + .push(SubAccess::Offset(i * row_stride)); + let ty_inner = crate::TypeInner::Vector { size: rows, scalar }; + let sv = StoreValue::TempIndex { + depth, + index: i, + ty: TypeResolution::Value(ty_inner), + }; + self.write_storage_store(module, var_handle, sv, func_ctx, level.next())?; + self.temp_access_chain.pop(); + } + // done + writeln!(self.out, "{level}}}")?; + } + crate::TypeInner::Array { + base, + size: crate::ArraySize::Constant(size), + stride, + } => { + // first, assign the value to a temporary + writeln!(self.out, "{level}{{")?; + write!(self.out, "{}", level.next())?; + self.write_value_type(module, &module.types[base].inner)?; + let depth = level.next().0; + write!(self.out, " {STORE_TEMP_NAME}{depth}")?; + self.write_array_size(module, base, crate::ArraySize::Constant(size))?; + write!(self.out, " = ")?; + self.write_store_value(module, &value, func_ctx)?; + writeln!(self.out, ";")?; + // then iterate the stores + for i in 0..size.get() { + self.temp_access_chain.push(SubAccess::Offset(i * stride)); + let sv = StoreValue::TempIndex { + depth, + index: i, + ty: TypeResolution::Handle(base), + }; + self.write_storage_store(module, var_handle, sv, func_ctx, level.next())?; + self.temp_access_chain.pop(); + } + // done + writeln!(self.out, "{level}}}")?; + } + crate::TypeInner::Struct { ref members, .. } => { + // first, assign the value to a temporary + writeln!(self.out, "{level}{{")?; + let depth = level.next().0; + let struct_ty = ty_resolution.handle().unwrap(); + let struct_name = &self.names[&NameKey::Type(struct_ty)]; + write!( + self.out, + "{}{} {}{} = ", + level.next(), + struct_name, + STORE_TEMP_NAME, + depth + )?; + self.write_store_value(module, &value, func_ctx)?; + writeln!(self.out, ";")?; + // then iterate the stores + for (i, member) in members.iter().enumerate() { + self.temp_access_chain + .push(SubAccess::Offset(member.offset)); + let sv = StoreValue::TempAccess { + depth, + base: struct_ty, + member_index: i as u32, + }; + self.write_storage_store(module, var_handle, sv, func_ctx, level.next())?; + self.temp_access_chain.pop(); + } + // done + writeln!(self.out, "{level}}}")?; + } + _ => unreachable!(), + } + Ok(()) + } + + /// Set [`temp_access_chain`] to compute the byte offset of `cur_expr`. + /// + /// The `cur_expr` expression must be a reference to a global + /// variable in the [`Storage`] address space, or a chain of + /// [`Access`] and [`AccessIndex`] expressions referring to some + /// component of such a global. + /// + /// [`temp_access_chain`]: super::Writer::temp_access_chain + /// [`Storage`]: crate::AddressSpace::Storage + /// [`Access`]: crate::Expression::Access + /// [`AccessIndex`]: crate::Expression::AccessIndex + pub(super) fn fill_access_chain( + &mut self, + module: &crate::Module, + mut cur_expr: Handle, + func_ctx: &FunctionCtx, + ) -> Result, Error> { + enum AccessIndex { + Expression(Handle), + Constant(u32), + } + enum Parent<'a> { + Array { stride: u32 }, + Struct(&'a [crate::StructMember]), + } + self.temp_access_chain.clear(); + + loop { + let (next_expr, access_index) = match func_ctx.expressions[cur_expr] { + crate::Expression::GlobalVariable(handle) => return Ok(handle), + crate::Expression::Access { base, index } => (base, AccessIndex::Expression(index)), + crate::Expression::AccessIndex { base, index } => { + (base, AccessIndex::Constant(index)) + } + ref other => { + return Err(Error::Unimplemented(format!("Pointer access of {other:?}"))) + } + }; + + let parent = match *func_ctx.resolve_type(next_expr, &module.types) { + crate::TypeInner::Pointer { base, .. } => match module.types[base].inner { + crate::TypeInner::Struct { ref members, .. } => Parent::Struct(members), + crate::TypeInner::Array { stride, .. } => Parent::Array { stride }, + crate::TypeInner::Vector { scalar, .. } => Parent::Array { + stride: scalar.width as u32, + }, + crate::TypeInner::Matrix { rows, scalar, .. } => Parent::Array { + // The stride between matrices is the count of rows as this is how + // long each column is. + stride: Alignment::from(rows) * scalar.width as u32, + }, + _ => unreachable!(), + }, + crate::TypeInner::ValuePointer { scalar, .. } => Parent::Array { + stride: scalar.width as u32, + }, + _ => unreachable!(), + }; + + let sub = match (parent, access_index) { + (Parent::Array { stride }, AccessIndex::Expression(value)) => { + SubAccess::Index { value, stride } + } + (Parent::Array { stride }, AccessIndex::Constant(index)) => { + SubAccess::Offset(stride * index) + } + (Parent::Struct(members), AccessIndex::Constant(index)) => { + SubAccess::Offset(members[index as usize].offset) + } + (Parent::Struct(_), AccessIndex::Expression(_)) => unreachable!(), + }; + + self.temp_access_chain.push(sub); + cur_expr = next_expr; + } + } +} diff --git a/naga/src/back/hlsl/writer.rs b/naga/src/back/hlsl/writer.rs new file mode 100644 index 0000000000..0dd60c6ad7 --- /dev/null +++ b/naga/src/back/hlsl/writer.rs @@ -0,0 +1,3366 @@ +use super::{ + help::{WrappedArrayLength, WrappedConstructor, WrappedImageQuery, WrappedStructMatrixAccess}, + storage::StoreValue, + BackendResult, Error, Options, +}; +use crate::{ + back, + proc::{self, NameKey}, + valid, Handle, Module, ScalarKind, ShaderStage, TypeInner, +}; +use std::{fmt, mem}; + +const LOCATION_SEMANTIC: &str = "LOC"; +const SPECIAL_CBUF_TYPE: &str = "NagaConstants"; +const SPECIAL_CBUF_VAR: &str = "_NagaConstants"; +const SPECIAL_FIRST_VERTEX: &str = "first_vertex"; +const SPECIAL_FIRST_INSTANCE: &str = "first_instance"; +const SPECIAL_OTHER: &str = "other"; + +pub(crate) const MODF_FUNCTION: &str = "naga_modf"; +pub(crate) const FREXP_FUNCTION: &str = "naga_frexp"; + +struct EpStructMember { + name: String, + ty: Handle, + // technically, this should always be `Some` + binding: Option, + index: u32, +} + +/// Structure contains information required for generating +/// wrapped structure of all entry points arguments +struct EntryPointBinding { + /// Name of the fake EP argument that contains the struct + /// with all the flattened input data. + arg_name: String, + /// Generated structure name + ty_name: String, + /// Members of generated structure + members: Vec, +} + +pub(super) struct EntryPointInterface { + /// If `Some`, the input of an entry point is gathered in a special + /// struct with members sorted by binding. + /// The `EntryPointBinding::members` array is sorted by index, + /// so that we can walk it in `write_ep_arguments_initialization`. + input: Option, + /// If `Some`, the output of an entry point is flattened. + /// The `EntryPointBinding::members` array is sorted by binding, + /// So that we can walk it in `Statement::Return` handler. + output: Option, +} + +#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)] +enum InterfaceKey { + Location(u32), + BuiltIn(crate::BuiltIn), + Other, +} + +impl InterfaceKey { + const fn new(binding: Option<&crate::Binding>) -> Self { + match binding { + Some(&crate::Binding::Location { location, .. }) => Self::Location(location), + Some(&crate::Binding::BuiltIn(built_in)) => Self::BuiltIn(built_in), + None => Self::Other, + } + } +} + +#[derive(Copy, Clone, PartialEq)] +enum Io { + Input, + Output, +} + +impl<'a, W: fmt::Write> super::Writer<'a, W> { + pub fn new(out: W, options: &'a Options) -> Self { + Self { + out, + names: crate::FastHashMap::default(), + namer: proc::Namer::default(), + options, + entry_point_io: Vec::new(), + named_expressions: crate::NamedExpressions::default(), + wrapped: super::Wrapped::default(), + temp_access_chain: Vec::new(), + need_bake_expressions: Default::default(), + } + } + + fn reset(&mut self, module: &Module) { + self.names.clear(); + self.namer.reset( + module, + super::keywords::RESERVED, + super::keywords::TYPES, + super::keywords::RESERVED_CASE_INSENSITIVE, + &[], + &mut self.names, + ); + self.entry_point_io.clear(); + self.named_expressions.clear(); + self.wrapped.clear(); + self.need_bake_expressions.clear(); + } + + /// Helper method used to find which expressions of a given function require baking + /// + /// # Notes + /// Clears `need_bake_expressions` set before adding to it + fn update_expressions_to_bake( + &mut self, + module: &Module, + func: &crate::Function, + info: &valid::FunctionInfo, + ) { + use crate::Expression; + self.need_bake_expressions.clear(); + for (fun_handle, expr) in func.expressions.iter() { + let expr_info = &info[fun_handle]; + let min_ref_count = func.expressions[fun_handle].bake_ref_count(); + if min_ref_count <= expr_info.ref_count { + self.need_bake_expressions.insert(fun_handle); + } + + if let Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + } = *expr + { + match fun { + crate::MathFunction::Asinh + | crate::MathFunction::Acosh + | crate::MathFunction::Atanh + | crate::MathFunction::Unpack2x16float + | crate::MathFunction::Unpack2x16snorm + | crate::MathFunction::Unpack2x16unorm + | crate::MathFunction::Unpack4x8snorm + | crate::MathFunction::Unpack4x8unorm + | crate::MathFunction::Pack2x16float + | crate::MathFunction::Pack2x16snorm + | crate::MathFunction::Pack2x16unorm + | crate::MathFunction::Pack4x8snorm + | crate::MathFunction::Pack4x8unorm => { + self.need_bake_expressions.insert(arg); + } + crate::MathFunction::ExtractBits => { + self.need_bake_expressions.insert(arg); + self.need_bake_expressions.insert(arg1.unwrap()); + self.need_bake_expressions.insert(arg2.unwrap()); + } + crate::MathFunction::InsertBits => { + self.need_bake_expressions.insert(arg); + self.need_bake_expressions.insert(arg1.unwrap()); + self.need_bake_expressions.insert(arg2.unwrap()); + self.need_bake_expressions.insert(arg3.unwrap()); + } + crate::MathFunction::CountLeadingZeros => { + let inner = info[fun_handle].ty.inner_with(&module.types); + if let Some(crate::ScalarKind::Sint) = inner.scalar_kind() { + self.need_bake_expressions.insert(arg); + } + } + _ => {} + } + } + + if let Expression::Derivative { axis, ctrl, expr } = *expr { + use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; + if axis == Axis::Width && (ctrl == Ctrl::Coarse || ctrl == Ctrl::Fine) { + self.need_bake_expressions.insert(expr); + } + } + } + } + + pub fn write( + &mut self, + module: &Module, + module_info: &valid::ModuleInfo, + ) -> Result { + self.reset(module); + + // Write special constants, if needed + if let Some(ref bt) = self.options.special_constants_binding { + writeln!(self.out, "struct {SPECIAL_CBUF_TYPE} {{")?; + writeln!(self.out, "{}int {};", back::INDENT, SPECIAL_FIRST_VERTEX)?; + writeln!(self.out, "{}int {};", back::INDENT, SPECIAL_FIRST_INSTANCE)?; + writeln!(self.out, "{}uint {};", back::INDENT, SPECIAL_OTHER)?; + writeln!(self.out, "}};")?; + write!( + self.out, + "ConstantBuffer<{}> {}: register(b{}", + SPECIAL_CBUF_TYPE, SPECIAL_CBUF_VAR, bt.register + )?; + if bt.space != 0 { + write!(self.out, ", space{}", bt.space)?; + } + writeln!(self.out, ");")?; + + // Extra newline for readability + writeln!(self.out)?; + } + + // Save all entry point output types + let ep_results = module + .entry_points + .iter() + .map(|ep| (ep.stage, ep.function.result.clone())) + .collect::)>>(); + + self.write_all_mat_cx2_typedefs_and_functions(module)?; + + // Write all structs + for (handle, ty) in module.types.iter() { + if let TypeInner::Struct { ref members, span } = ty.inner { + if module.types[members.last().unwrap().ty] + .inner + .is_dynamically_sized(&module.types) + { + // unsized arrays can only be in storage buffers, + // for which we use `ByteAddressBuffer` anyway. + continue; + } + + let ep_result = ep_results.iter().find(|e| { + if let Some(ref result) = e.1 { + result.ty == handle + } else { + false + } + }); + + self.write_struct( + module, + handle, + members, + span, + ep_result.map(|r| (r.0, Io::Output)), + )?; + writeln!(self.out)?; + } + } + + self.write_special_functions(module)?; + + self.write_wrapped_compose_functions(module, &module.const_expressions)?; + + // Write all named constants + let mut constants = module + .constants + .iter() + .filter(|&(_, c)| c.name.is_some()) + .peekable(); + while let Some((handle, _)) = constants.next() { + self.write_global_constant(module, handle)?; + // Add extra newline for readability on last iteration + if constants.peek().is_none() { + writeln!(self.out)?; + } + } + + // Write all globals + for (ty, _) in module.global_variables.iter() { + self.write_global(module, ty)?; + } + + if !module.global_variables.is_empty() { + // Add extra newline for readability + writeln!(self.out)?; + } + + // Write all entry points wrapped structs + for (index, ep) in module.entry_points.iter().enumerate() { + let ep_name = self.names[&NameKey::EntryPoint(index as u16)].clone(); + let ep_io = self.write_ep_interface(module, &ep.function, ep.stage, &ep_name)?; + self.entry_point_io.push(ep_io); + } + + // Write all regular functions + for (handle, function) in module.functions.iter() { + let info = &module_info[handle]; + + // Check if all of the globals are accessible + if !self.options.fake_missing_bindings { + if let Some((var_handle, _)) = + module + .global_variables + .iter() + .find(|&(var_handle, var)| match var.binding { + Some(ref binding) if !info[var_handle].is_empty() => { + self.options.resolve_resource_binding(binding).is_err() + } + _ => false, + }) + { + log::info!( + "Skipping function {:?} (name {:?}) because global {:?} is inaccessible", + handle, + function.name, + var_handle + ); + continue; + } + } + + let ctx = back::FunctionCtx { + ty: back::FunctionType::Function(handle), + info, + expressions: &function.expressions, + named_expressions: &function.named_expressions, + }; + let name = self.names[&NameKey::Function(handle)].clone(); + + self.write_wrapped_functions(module, &ctx)?; + + self.write_function(module, name.as_str(), function, &ctx, info)?; + + writeln!(self.out)?; + } + + let mut entry_point_names = Vec::with_capacity(module.entry_points.len()); + + // Write all entry points + for (index, ep) in module.entry_points.iter().enumerate() { + let info = module_info.get_entry_point(index); + + if !self.options.fake_missing_bindings { + let mut ep_error = None; + for (var_handle, var) in module.global_variables.iter() { + match var.binding { + Some(ref binding) if !info[var_handle].is_empty() => { + if let Err(err) = self.options.resolve_resource_binding(binding) { + ep_error = Some(err); + break; + } + } + _ => {} + } + } + if let Some(err) = ep_error { + entry_point_names.push(Err(err)); + continue; + } + } + + let ctx = back::FunctionCtx { + ty: back::FunctionType::EntryPoint(index as u16), + info, + expressions: &ep.function.expressions, + named_expressions: &ep.function.named_expressions, + }; + + self.write_wrapped_functions(module, &ctx)?; + + if ep.stage == ShaderStage::Compute { + // HLSL is calling workgroup size "num threads" + let num_threads = ep.workgroup_size; + writeln!( + self.out, + "[numthreads({}, {}, {})]", + num_threads[0], num_threads[1], num_threads[2] + )?; + } + + let name = self.names[&NameKey::EntryPoint(index as u16)].clone(); + self.write_function(module, &name, &ep.function, &ctx, info)?; + + if index < module.entry_points.len() - 1 { + writeln!(self.out)?; + } + + entry_point_names.push(Ok(name)); + } + + Ok(super::ReflectionInfo { entry_point_names }) + } + + fn write_modifier(&mut self, binding: &crate::Binding) -> BackendResult { + match *binding { + crate::Binding::BuiltIn(crate::BuiltIn::Position { invariant: true }) => { + write!(self.out, "precise ")?; + } + crate::Binding::Location { + interpolation, + sampling, + .. + } => { + if let Some(interpolation) = interpolation { + if let Some(string) = interpolation.to_hlsl_str() { + write!(self.out, "{string} ")? + } + } + + if let Some(sampling) = sampling { + if let Some(string) = sampling.to_hlsl_str() { + write!(self.out, "{string} ")? + } + } + } + crate::Binding::BuiltIn(_) => {} + } + + Ok(()) + } + + //TODO: we could force fragment outputs to always go through `entry_point_io.output` path + // if they are struct, so that the `stage` argument here could be omitted. + fn write_semantic( + &mut self, + binding: &crate::Binding, + stage: Option<(ShaderStage, Io)>, + ) -> BackendResult { + match *binding { + crate::Binding::BuiltIn(builtin) => { + let builtin_str = builtin.to_hlsl_str()?; + write!(self.out, " : {builtin_str}")?; + } + crate::Binding::Location { + second_blend_source: true, + .. + } => { + write!(self.out, " : SV_Target1")?; + } + crate::Binding::Location { + location, + second_blend_source: false, + .. + } => { + if stage == Some((crate::ShaderStage::Fragment, Io::Output)) { + write!(self.out, " : SV_Target{location}")?; + } else { + write!(self.out, " : {LOCATION_SEMANTIC}{location}")?; + } + } + } + + Ok(()) + } + + fn write_interface_struct( + &mut self, + module: &Module, + shader_stage: (ShaderStage, Io), + struct_name: String, + mut members: Vec, + ) -> Result { + // Sort the members so that first come the user-defined varyings + // in ascending locations, and then built-ins. This allows VS and FS + // interfaces to match with regards to order. + members.sort_by_key(|m| InterfaceKey::new(m.binding.as_ref())); + + write!(self.out, "struct {struct_name}")?; + writeln!(self.out, " {{")?; + for m in members.iter() { + write!(self.out, "{}", back::INDENT)?; + if let Some(ref binding) = m.binding { + self.write_modifier(binding)?; + } + self.write_type(module, m.ty)?; + write!(self.out, " {}", &m.name)?; + if let Some(ref binding) = m.binding { + self.write_semantic(binding, Some(shader_stage))?; + } + writeln!(self.out, ";")?; + } + writeln!(self.out, "}};")?; + writeln!(self.out)?; + + match shader_stage.1 { + Io::Input => { + // bring back the original order + members.sort_by_key(|m| m.index); + } + Io::Output => { + // keep it sorted by binding + } + } + + Ok(EntryPointBinding { + arg_name: self.namer.call(struct_name.to_lowercase().as_str()), + ty_name: struct_name, + members, + }) + } + + /// Flatten all entry point arguments into a single struct. + /// This is needed since we need to re-order them: first placing user locations, + /// then built-ins. + fn write_ep_input_struct( + &mut self, + module: &Module, + func: &crate::Function, + stage: ShaderStage, + entry_point_name: &str, + ) -> Result { + let struct_name = format!("{stage:?}Input_{entry_point_name}"); + + let mut fake_members = Vec::new(); + for arg in func.arguments.iter() { + match module.types[arg.ty].inner { + TypeInner::Struct { ref members, .. } => { + for member in members.iter() { + let name = self.namer.call_or(&member.name, "member"); + let index = fake_members.len() as u32; + fake_members.push(EpStructMember { + name, + ty: member.ty, + binding: member.binding.clone(), + index, + }); + } + } + _ => { + let member_name = self.namer.call_or(&arg.name, "member"); + let index = fake_members.len() as u32; + fake_members.push(EpStructMember { + name: member_name, + ty: arg.ty, + binding: arg.binding.clone(), + index, + }); + } + } + } + + self.write_interface_struct(module, (stage, Io::Input), struct_name, fake_members) + } + + /// Flatten all entry point results into a single struct. + /// This is needed since we need to re-order them: first placing user locations, + /// then built-ins. + fn write_ep_output_struct( + &mut self, + module: &Module, + result: &crate::FunctionResult, + stage: ShaderStage, + entry_point_name: &str, + ) -> Result { + let struct_name = format!("{stage:?}Output_{entry_point_name}"); + + let mut fake_members = Vec::new(); + let empty = []; + let members = match module.types[result.ty].inner { + TypeInner::Struct { ref members, .. } => members, + ref other => { + log::error!("Unexpected {:?} output type without a binding", other); + &empty[..] + } + }; + + for member in members.iter() { + let member_name = self.namer.call_or(&member.name, "member"); + let index = fake_members.len() as u32; + fake_members.push(EpStructMember { + name: member_name, + ty: member.ty, + binding: member.binding.clone(), + index, + }); + } + + self.write_interface_struct(module, (stage, Io::Output), struct_name, fake_members) + } + + /// Writes special interface structures for an entry point. The special structures have + /// all the fields flattened into them and sorted by binding. They are only needed for + /// VS outputs and FS inputs, so that these interfaces match. + fn write_ep_interface( + &mut self, + module: &Module, + func: &crate::Function, + stage: ShaderStage, + ep_name: &str, + ) -> Result { + Ok(EntryPointInterface { + input: if !func.arguments.is_empty() && stage == ShaderStage::Fragment { + Some(self.write_ep_input_struct(module, func, stage, ep_name)?) + } else { + None + }, + output: match func.result { + Some(ref fr) if fr.binding.is_none() && stage == ShaderStage::Vertex => { + Some(self.write_ep_output_struct(module, fr, stage, ep_name)?) + } + _ => None, + }, + }) + } + + /// Write an entry point preface that initializes the arguments as specified in IR. + fn write_ep_arguments_initialization( + &mut self, + module: &Module, + func: &crate::Function, + ep_index: u16, + ) -> BackendResult { + let ep_input = match self.entry_point_io[ep_index as usize].input.take() { + Some(ep_input) => ep_input, + None => return Ok(()), + }; + let mut fake_iter = ep_input.members.iter(); + for (arg_index, arg) in func.arguments.iter().enumerate() { + write!(self.out, "{}", back::INDENT)?; + self.write_type(module, arg.ty)?; + let arg_name = &self.names[&NameKey::EntryPointArgument(ep_index, arg_index as u32)]; + write!(self.out, " {arg_name}")?; + match module.types[arg.ty].inner { + TypeInner::Array { base, size, .. } => { + self.write_array_size(module, base, size)?; + let fake_member = fake_iter.next().unwrap(); + writeln!(self.out, " = {}.{};", ep_input.arg_name, fake_member.name)?; + } + TypeInner::Struct { ref members, .. } => { + write!(self.out, " = {{ ")?; + for index in 0..members.len() { + if index != 0 { + write!(self.out, ", ")?; + } + let fake_member = fake_iter.next().unwrap(); + write!(self.out, "{}.{}", ep_input.arg_name, fake_member.name)?; + } + writeln!(self.out, " }};")?; + } + _ => { + let fake_member = fake_iter.next().unwrap(); + writeln!(self.out, " = {}.{};", ep_input.arg_name, fake_member.name)?; + } + } + } + assert!(fake_iter.next().is_none()); + Ok(()) + } + + /// Helper method used to write global variables + /// # Notes + /// Always adds a newline + fn write_global( + &mut self, + module: &Module, + handle: Handle, + ) -> BackendResult { + let global = &module.global_variables[handle]; + let inner = &module.types[global.ty].inner; + + if let Some(ref binding) = global.binding { + if let Err(err) = self.options.resolve_resource_binding(binding) { + log::info!( + "Skipping global {:?} (name {:?}) for being inaccessible: {}", + handle, + global.name, + err, + ); + return Ok(()); + } + } + + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-variable-register + let register_ty = match global.space { + crate::AddressSpace::Function => unreachable!("Function address space"), + crate::AddressSpace::Private => { + write!(self.out, "static ")?; + self.write_type(module, global.ty)?; + "" + } + crate::AddressSpace::WorkGroup => { + write!(self.out, "groupshared ")?; + self.write_type(module, global.ty)?; + "" + } + crate::AddressSpace::Uniform => { + // constant buffer declarations are expected to be inlined, e.g. + // `cbuffer foo: register(b0) { field1: type1; }` + write!(self.out, "cbuffer")?; + "b" + } + crate::AddressSpace::Storage { access } => { + let (prefix, register) = if access.contains(crate::StorageAccess::STORE) { + ("RW", "u") + } else { + ("", "t") + }; + write!(self.out, "{prefix}ByteAddressBuffer")?; + register + } + crate::AddressSpace::Handle => { + let handle_ty = match *inner { + TypeInner::BindingArray { ref base, .. } => &module.types[*base].inner, + _ => inner, + }; + + let register = match *handle_ty { + TypeInner::Sampler { .. } => "s", + // all storage textures are UAV, unconditionally + TypeInner::Image { + class: crate::ImageClass::Storage { .. }, + .. + } => "u", + _ => "t", + }; + self.write_type(module, global.ty)?; + register + } + crate::AddressSpace::PushConstant => { + // The type of the push constants will be wrapped in `ConstantBuffer` + write!(self.out, "ConstantBuffer<")?; + "b" + } + }; + + // If the global is a push constant write the type now because it will be a + // generic argument to `ConstantBuffer` + if global.space == crate::AddressSpace::PushConstant { + self.write_global_type(module, global.ty)?; + + // need to write the array size if the type was emitted with `write_type` + if let TypeInner::Array { base, size, .. } = module.types[global.ty].inner { + self.write_array_size(module, base, size)?; + } + + // Close the angled brackets for the generic argument + write!(self.out, ">")?; + } + + let name = &self.names[&NameKey::GlobalVariable(handle)]; + write!(self.out, " {name}")?; + + // Push constants need to be assigned a binding explicitly by the consumer + // since naga has no way to know the binding from the shader alone + if global.space == crate::AddressSpace::PushConstant { + let target = self + .options + .push_constants_target + .as_ref() + .expect("No bind target was defined for the push constants block"); + write!(self.out, ": register(b{}", target.register)?; + if target.space != 0 { + write!(self.out, ", space{}", target.space)?; + } + write!(self.out, ")")?; + } + + if let Some(ref binding) = global.binding { + // this was already resolved earlier when we started evaluating an entry point. + let bt = self.options.resolve_resource_binding(binding).unwrap(); + + // need to write the binding array size if the type was emitted with `write_type` + if let TypeInner::BindingArray { base, size, .. } = module.types[global.ty].inner { + if let Some(overridden_size) = bt.binding_array_size { + write!(self.out, "[{overridden_size}]")?; + } else { + self.write_array_size(module, base, size)?; + } + } + + write!(self.out, " : register({}{}", register_ty, bt.register)?; + if bt.space != 0 { + write!(self.out, ", space{}", bt.space)?; + } + write!(self.out, ")")?; + } else { + // need to write the array size if the type was emitted with `write_type` + if let TypeInner::Array { base, size, .. } = module.types[global.ty].inner { + self.write_array_size(module, base, size)?; + } + if global.space == crate::AddressSpace::Private { + write!(self.out, " = ")?; + if let Some(init) = global.init { + self.write_const_expression(module, init)?; + } else { + self.write_default_init(module, global.ty)?; + } + } + } + + if global.space == crate::AddressSpace::Uniform { + write!(self.out, " {{ ")?; + + self.write_global_type(module, global.ty)?; + + write!( + self.out, + " {}", + &self.names[&NameKey::GlobalVariable(handle)] + )?; + + // need to write the array size if the type was emitted with `write_type` + if let TypeInner::Array { base, size, .. } = module.types[global.ty].inner { + self.write_array_size(module, base, size)?; + } + + writeln!(self.out, "; }}")?; + } else { + writeln!(self.out, ";")?; + } + + Ok(()) + } + + /// Helper method used to write global constants + /// + /// # Notes + /// Ends in a newline + fn write_global_constant( + &mut self, + module: &Module, + handle: Handle, + ) -> BackendResult { + write!(self.out, "static const ")?; + let constant = &module.constants[handle]; + self.write_type(module, constant.ty)?; + let name = &self.names[&NameKey::Constant(handle)]; + write!(self.out, " {}", name)?; + // Write size for array type + if let TypeInner::Array { base, size, .. } = module.types[constant.ty].inner { + self.write_array_size(module, base, size)?; + } + write!(self.out, " = ")?; + self.write_const_expression(module, constant.init)?; + writeln!(self.out, ";")?; + Ok(()) + } + + pub(super) fn write_array_size( + &mut self, + module: &Module, + base: Handle, + size: crate::ArraySize, + ) -> BackendResult { + write!(self.out, "[")?; + + match size { + crate::ArraySize::Constant(size) => { + write!(self.out, "{size}")?; + } + crate::ArraySize::Dynamic => unreachable!(), + } + + write!(self.out, "]")?; + + if let TypeInner::Array { + base: next_base, + size: next_size, + .. + } = module.types[base].inner + { + self.write_array_size(module, next_base, next_size)?; + } + + Ok(()) + } + + /// Helper method used to write structs + /// + /// # Notes + /// Ends in a newline + fn write_struct( + &mut self, + module: &Module, + handle: Handle, + members: &[crate::StructMember], + span: u32, + shader_stage: Option<(ShaderStage, Io)>, + ) -> BackendResult { + // Write struct name + let struct_name = &self.names[&NameKey::Type(handle)]; + writeln!(self.out, "struct {struct_name} {{")?; + + let mut last_offset = 0; + for (index, member) in members.iter().enumerate() { + if member.binding.is_none() && member.offset > last_offset { + // using int as padding should work as long as the backend + // doesn't support a type that's less than 4 bytes in size + // (Error::UnsupportedScalar catches this) + let padding = (member.offset - last_offset) / 4; + for i in 0..padding { + writeln!(self.out, "{}int _pad{}_{};", back::INDENT, index, i)?; + } + } + let ty_inner = &module.types[member.ty].inner; + last_offset = member.offset + ty_inner.size_hlsl(module.to_ctx()); + + // The indentation is only for readability + write!(self.out, "{}", back::INDENT)?; + + match module.types[member.ty].inner { + TypeInner::Array { base, size, .. } => { + // HLSL arrays are written as `type name[size]` + + self.write_global_type(module, member.ty)?; + + // Write `name` + write!( + self.out, + " {}", + &self.names[&NameKey::StructMember(handle, index as u32)] + )?; + // Write [size] + self.write_array_size(module, base, size)?; + } + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. + // See the module-level block comment in mod.rs for details. + TypeInner::Matrix { + rows, + columns, + scalar, + } if member.binding.is_none() && rows == crate::VectorSize::Bi => { + let vec_ty = crate::TypeInner::Vector { size: rows, scalar }; + let field_name_key = NameKey::StructMember(handle, index as u32); + + for i in 0..columns as u8 { + if i != 0 { + write!(self.out, "; ")?; + } + self.write_value_type(module, &vec_ty)?; + write!(self.out, " {}_{}", &self.names[&field_name_key], i)?; + } + } + _ => { + // Write modifier before type + if let Some(ref binding) = member.binding { + self.write_modifier(binding)?; + } + + // Even though Naga IR matrices are column-major, we must describe + // matrices passed from the CPU as being in row-major order. + // See the module-level block comment in mod.rs for details. + if let TypeInner::Matrix { .. } = module.types[member.ty].inner { + write!(self.out, "row_major ")?; + } + + // Write the member type and name + self.write_type(module, member.ty)?; + write!( + self.out, + " {}", + &self.names[&NameKey::StructMember(handle, index as u32)] + )?; + } + } + + if let Some(ref binding) = member.binding { + self.write_semantic(binding, shader_stage)?; + }; + writeln!(self.out, ";")?; + } + + // add padding at the end since sizes of types don't get rounded up to their alignment in HLSL + if members.last().unwrap().binding.is_none() && span > last_offset { + let padding = (span - last_offset) / 4; + for i in 0..padding { + writeln!(self.out, "{}int _end_pad_{};", back::INDENT, i)?; + } + } + + writeln!(self.out, "}};")?; + Ok(()) + } + + /// Helper method used to write global/structs non image/sampler types + /// + /// # Notes + /// Adds no trailing or leading whitespace + pub(super) fn write_global_type( + &mut self, + module: &Module, + ty: Handle, + ) -> BackendResult { + let matrix_data = get_inner_matrix_data(module, ty); + + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. + // See the module-level block comment in mod.rs for details. + if let Some(MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = matrix_data + { + write!(self.out, "__mat{}x2", columns as u8)?; + } else { + // Even though Naga IR matrices are column-major, we must describe + // matrices passed from the CPU as being in row-major order. + // See the module-level block comment in mod.rs for details. + if matrix_data.is_some() { + write!(self.out, "row_major ")?; + } + + self.write_type(module, ty)?; + } + + Ok(()) + } + + /// Helper method used to write non image/sampler types + /// + /// # Notes + /// Adds no trailing or leading whitespace + pub(super) fn write_type(&mut self, module: &Module, ty: Handle) -> BackendResult { + let inner = &module.types[ty].inner; + match *inner { + TypeInner::Struct { .. } => write!(self.out, "{}", self.names[&NameKey::Type(ty)])?, + // hlsl array has the size separated from the base type + TypeInner::Array { base, .. } | TypeInner::BindingArray { base, .. } => { + self.write_type(module, base)? + } + ref other => self.write_value_type(module, other)?, + } + + Ok(()) + } + + /// Helper method used to write value types + /// + /// # Notes + /// Adds no trailing or leading whitespace + pub(super) fn write_value_type(&mut self, module: &Module, inner: &TypeInner) -> BackendResult { + match *inner { + TypeInner::Scalar(scalar) | TypeInner::Atomic(scalar) => { + write!(self.out, "{}", scalar.to_hlsl_str()?)?; + } + TypeInner::Vector { size, scalar } => { + write!( + self.out, + "{}{}", + scalar.to_hlsl_str()?, + back::vector_size_str(size) + )?; + } + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + // The IR supports only float matrix + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-matrix + + // Because of the implicit transpose all matrices have in HLSL, we need to transpose the size as well. + write!( + self.out, + "{}{}x{}", + scalar.to_hlsl_str()?, + back::vector_size_str(columns), + back::vector_size_str(rows), + )?; + } + TypeInner::Image { + dim, + arrayed, + class, + } => { + self.write_image_type(dim, arrayed, class)?; + } + TypeInner::Sampler { comparison } => { + let sampler = if comparison { + "SamplerComparisonState" + } else { + "SamplerState" + }; + write!(self.out, "{sampler}")?; + } + // HLSL arrays are written as `type name[size]` + // Current code is written arrays only as `[size]` + // Base `type` and `name` should be written outside + TypeInner::Array { base, size, .. } | TypeInner::BindingArray { base, size } => { + self.write_array_size(module, base, size)?; + } + _ => return Err(Error::Unimplemented(format!("write_value_type {inner:?}"))), + } + + Ok(()) + } + + /// Helper method used to write functions + /// # Notes + /// Ends in a newline + fn write_function( + &mut self, + module: &Module, + name: &str, + func: &crate::Function, + func_ctx: &back::FunctionCtx<'_>, + info: &valid::FunctionInfo, + ) -> BackendResult { + // Function Declaration Syntax - https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-function-syntax + + self.update_expressions_to_bake(module, func, info); + + // Write modifier + if let Some(crate::FunctionResult { + binding: + Some( + ref binding @ crate::Binding::BuiltIn(crate::BuiltIn::Position { + invariant: true, + }), + ), + .. + }) = func.result + { + self.write_modifier(binding)?; + } + + // Write return type + if let Some(ref result) = func.result { + match func_ctx.ty { + back::FunctionType::Function(_) => { + self.write_type(module, result.ty)?; + } + back::FunctionType::EntryPoint(index) => { + if let Some(ref ep_output) = self.entry_point_io[index as usize].output { + write!(self.out, "{}", ep_output.ty_name)?; + } else { + self.write_type(module, result.ty)?; + } + } + } + } else { + write!(self.out, "void")?; + } + + // Write function name + write!(self.out, " {name}(")?; + + let need_workgroup_variables_initialization = + self.need_workgroup_variables_initialization(func_ctx, module); + + // Write function arguments for non entry point functions + match func_ctx.ty { + back::FunctionType::Function(handle) => { + for (index, arg) in func.arguments.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + // Write argument type + let arg_ty = match module.types[arg.ty].inner { + // pointers in function arguments are expected and resolve to `inout` + TypeInner::Pointer { base, .. } => { + //TODO: can we narrow this down to just `in` when possible? + write!(self.out, "inout ")?; + base + } + _ => arg.ty, + }; + self.write_type(module, arg_ty)?; + + let argument_name = + &self.names[&NameKey::FunctionArgument(handle, index as u32)]; + + // Write argument name. Space is important. + write!(self.out, " {argument_name}")?; + if let TypeInner::Array { base, size, .. } = module.types[arg_ty].inner { + self.write_array_size(module, base, size)?; + } + } + } + back::FunctionType::EntryPoint(ep_index) => { + if let Some(ref ep_input) = self.entry_point_io[ep_index as usize].input { + write!(self.out, "{} {}", ep_input.ty_name, ep_input.arg_name,)?; + } else { + let stage = module.entry_points[ep_index as usize].stage; + for (index, arg) in func.arguments.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + self.write_type(module, arg.ty)?; + + let argument_name = + &self.names[&NameKey::EntryPointArgument(ep_index, index as u32)]; + + write!(self.out, " {argument_name}")?; + if let TypeInner::Array { base, size, .. } = module.types[arg.ty].inner { + self.write_array_size(module, base, size)?; + } + + if let Some(ref binding) = arg.binding { + self.write_semantic(binding, Some((stage, Io::Input)))?; + } + } + + if need_workgroup_variables_initialization { + if !func.arguments.is_empty() { + write!(self.out, ", ")?; + } + write!(self.out, "uint3 __local_invocation_id : SV_GroupThreadID")?; + } + } + } + } + // Ends of arguments + write!(self.out, ")")?; + + // Write semantic if it present + if let back::FunctionType::EntryPoint(index) = func_ctx.ty { + let stage = module.entry_points[index as usize].stage; + if let Some(crate::FunctionResult { + binding: Some(ref binding), + .. + }) = func.result + { + self.write_semantic(binding, Some((stage, Io::Output)))?; + } + } + + // Function body start + writeln!(self.out)?; + writeln!(self.out, "{{")?; + + if need_workgroup_variables_initialization { + self.write_workgroup_variables_initialization(func_ctx, module)?; + } + + if let back::FunctionType::EntryPoint(index) = func_ctx.ty { + self.write_ep_arguments_initialization(module, func, index)?; + } + + // Write function local variables + for (handle, local) in func.local_variables.iter() { + // Write indentation (only for readability) + write!(self.out, "{}", back::INDENT)?; + + // Write the local name + // The leading space is important + self.write_type(module, local.ty)?; + write!(self.out, " {}", self.names[&func_ctx.name_key(handle)])?; + // Write size for array type + if let TypeInner::Array { base, size, .. } = module.types[local.ty].inner { + self.write_array_size(module, base, size)?; + } + + write!(self.out, " = ")?; + // Write the local initializer if needed + if let Some(init) = local.init { + self.write_expr(module, init, func_ctx)?; + } else { + // Zero initialize local variables + self.write_default_init(module, local.ty)?; + } + + // Finish the local with `;` and add a newline (only for readability) + writeln!(self.out, ";")? + } + + if !func.local_variables.is_empty() { + writeln!(self.out)?; + } + + // Write the function body (statement list) + for sta in func.body.iter() { + // The indentation should always be 1 when writing the function body + self.write_stmt(module, sta, func_ctx, back::Level(1))?; + } + + writeln!(self.out, "}}")?; + + self.named_expressions.clear(); + + Ok(()) + } + + fn need_workgroup_variables_initialization( + &mut self, + func_ctx: &back::FunctionCtx, + module: &Module, + ) -> bool { + self.options.zero_initialize_workgroup_memory + && func_ctx.ty.is_compute_entry_point(module) + && module.global_variables.iter().any(|(handle, var)| { + !func_ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup + }) + } + + fn write_workgroup_variables_initialization( + &mut self, + func_ctx: &back::FunctionCtx, + module: &Module, + ) -> BackendResult { + let level = back::Level(1); + + writeln!( + self.out, + "{level}if (all(__local_invocation_id == uint3(0u, 0u, 0u))) {{" + )?; + + let vars = module.global_variables.iter().filter(|&(handle, var)| { + !func_ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup + }); + + for (handle, var) in vars { + let name = &self.names[&NameKey::GlobalVariable(handle)]; + write!(self.out, "{}{} = ", level.next(), name)?; + self.write_default_init(module, var.ty)?; + writeln!(self.out, ";")?; + } + + writeln!(self.out, "{level}}}")?; + self.write_barrier(crate::Barrier::WORK_GROUP, level) + } + + /// Helper method used to write statements + /// + /// # Notes + /// Always adds a newline + fn write_stmt( + &mut self, + module: &Module, + stmt: &crate::Statement, + func_ctx: &back::FunctionCtx<'_>, + level: back::Level, + ) -> BackendResult { + use crate::Statement; + + match *stmt { + Statement::Emit(ref range) => { + for handle in range.clone() { + let ptr_class = func_ctx.resolve_type(handle, &module.types).pointer_space(); + let expr_name = if ptr_class.is_some() { + // HLSL can't save a pointer-valued expression in a variable, + // but we shouldn't ever need to: they should never be named expressions, + // and none of the expression types flagged by bake_ref_count can be pointer-valued. + None + } else if let Some(name) = func_ctx.named_expressions.get(&handle) { + // Front end provides names for all variables at the start of writing. + // But we write them to step by step. We need to recache them + // Otherwise, we could accidentally write variable name instead of full expression. + // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords. + Some(self.namer.call(name)) + } else if self.need_bake_expressions.contains(&handle) { + Some(format!("_expr{}", handle.index())) + } else { + None + }; + + if let Some(name) = expr_name { + write!(self.out, "{level}")?; + self.write_named_expr(module, handle, name, handle, func_ctx)?; + } + } + } + // TODO: copy-paste from glsl-out + Statement::Block(ref block) => { + write!(self.out, "{level}")?; + writeln!(self.out, "{{")?; + for sta in block.iter() { + // Increase the indentation to help with readability + self.write_stmt(module, sta, func_ctx, level.next())? + } + writeln!(self.out, "{level}}}")? + } + // TODO: copy-paste from glsl-out + Statement::If { + condition, + ref accept, + ref reject, + } => { + write!(self.out, "{level}")?; + write!(self.out, "if (")?; + self.write_expr(module, condition, func_ctx)?; + writeln!(self.out, ") {{")?; + + let l2 = level.next(); + for sta in accept { + // Increase indentation to help with readability + self.write_stmt(module, sta, func_ctx, l2)?; + } + + // If there are no statements in the reject block we skip writing it + // This is only for readability + if !reject.is_empty() { + writeln!(self.out, "{level}}} else {{")?; + + for sta in reject { + // Increase indentation to help with readability + self.write_stmt(module, sta, func_ctx, l2)?; + } + } + + writeln!(self.out, "{level}}}")? + } + // TODO: copy-paste from glsl-out + Statement::Kill => writeln!(self.out, "{level}discard;")?, + Statement::Return { value: None } => { + writeln!(self.out, "{level}return;")?; + } + Statement::Return { value: Some(expr) } => { + let base_ty_res = &func_ctx.info[expr].ty; + let mut resolved = base_ty_res.inner_with(&module.types); + if let TypeInner::Pointer { base, space: _ } = *resolved { + resolved = &module.types[base].inner; + } + + if let TypeInner::Struct { .. } = *resolved { + // We can safely unwrap here, since we now we working with struct + let ty = base_ty_res.handle().unwrap(); + let struct_name = &self.names[&NameKey::Type(ty)]; + let variable_name = self.namer.call(&struct_name.to_lowercase()); + write!(self.out, "{level}const {struct_name} {variable_name} = ",)?; + self.write_expr(module, expr, func_ctx)?; + writeln!(self.out, ";")?; + + // for entry point returns, we may need to reshuffle the outputs into a different struct + let ep_output = match func_ctx.ty { + back::FunctionType::Function(_) => None, + back::FunctionType::EntryPoint(index) => { + self.entry_point_io[index as usize].output.as_ref() + } + }; + let final_name = match ep_output { + Some(ep_output) => { + let final_name = self.namer.call(&variable_name); + write!( + self.out, + "{}const {} {} = {{ ", + level, ep_output.ty_name, final_name, + )?; + for (index, m) in ep_output.members.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + let member_name = &self.names[&NameKey::StructMember(ty, m.index)]; + write!(self.out, "{variable_name}.{member_name}")?; + } + writeln!(self.out, " }};")?; + final_name + } + None => variable_name, + }; + writeln!(self.out, "{level}return {final_name};")?; + } else { + write!(self.out, "{level}return ")?; + self.write_expr(module, expr, func_ctx)?; + writeln!(self.out, ";")? + } + } + Statement::Store { pointer, value } => { + let ty_inner = func_ctx.resolve_type(pointer, &module.types); + if let Some(crate::AddressSpace::Storage { .. }) = ty_inner.pointer_space() { + let var_handle = self.fill_access_chain(module, pointer, func_ctx)?; + self.write_storage_store( + module, + var_handle, + StoreValue::Expression(value), + func_ctx, + level, + )?; + } else { + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. + // See the module-level block comment in mod.rs for details. + // + // We handle matrix Stores here directly (including sub accesses for Vectors and Scalars). + // Loads are handled by `Expression::AccessIndex` (since sub accesses work fine for Loads). + struct MatrixAccess { + base: Handle, + index: u32, + } + enum Index { + Expression(Handle), + Static(u32), + } + + let get_members = |expr: Handle| { + let resolved = func_ctx.resolve_type(expr, &module.types); + match *resolved { + TypeInner::Pointer { base, .. } => match module.types[base].inner { + TypeInner::Struct { ref members, .. } => Some(members), + _ => None, + }, + _ => None, + } + }; + + let mut matrix = None; + let mut vector = None; + let mut scalar = None; + + let mut current_expr = pointer; + for _ in 0..3 { + let resolved = func_ctx.resolve_type(current_expr, &module.types); + + match (resolved, &func_ctx.expressions[current_expr]) { + ( + &TypeInner::Pointer { base: ty, .. }, + &crate::Expression::AccessIndex { base, index }, + ) if matches!( + module.types[ty].inner, + TypeInner::Matrix { + rows: crate::VectorSize::Bi, + .. + } + ) && get_members(base) + .map(|members| members[index as usize].binding.is_none()) + == Some(true) => + { + matrix = Some(MatrixAccess { base, index }); + break; + } + ( + &TypeInner::ValuePointer { + size: Some(crate::VectorSize::Bi), + .. + }, + &crate::Expression::Access { base, index }, + ) => { + vector = Some(Index::Expression(index)); + current_expr = base; + } + ( + &TypeInner::ValuePointer { + size: Some(crate::VectorSize::Bi), + .. + }, + &crate::Expression::AccessIndex { base, index }, + ) => { + vector = Some(Index::Static(index)); + current_expr = base; + } + ( + &TypeInner::ValuePointer { size: None, .. }, + &crate::Expression::Access { base, index }, + ) => { + scalar = Some(Index::Expression(index)); + current_expr = base; + } + ( + &TypeInner::ValuePointer { size: None, .. }, + &crate::Expression::AccessIndex { base, index }, + ) => { + scalar = Some(Index::Static(index)); + current_expr = base; + } + _ => break, + } + } + + write!(self.out, "{level}")?; + + if let Some(MatrixAccess { index, base }) = matrix { + let base_ty_res = &func_ctx.info[base].ty; + let resolved = base_ty_res.inner_with(&module.types); + let ty = match *resolved { + TypeInner::Pointer { base, .. } => base, + _ => base_ty_res.handle().unwrap(), + }; + + if let Some(Index::Static(vec_index)) = vector { + self.write_expr(module, base, func_ctx)?; + write!( + self.out, + ".{}_{}", + &self.names[&NameKey::StructMember(ty, index)], + vec_index + )?; + + if let Some(scalar_index) = scalar { + write!(self.out, "[")?; + match scalar_index { + Index::Static(index) => { + write!(self.out, "{index}")?; + } + Index::Expression(index) => { + self.write_expr(module, index, func_ctx)?; + } + } + write!(self.out, "]")?; + } + + write!(self.out, " = ")?; + self.write_expr(module, value, func_ctx)?; + writeln!(self.out, ";")?; + } else { + let access = WrappedStructMatrixAccess { ty, index }; + match (&vector, &scalar) { + (&Some(_), &Some(_)) => { + self.write_wrapped_struct_matrix_set_scalar_function_name( + access, + )?; + } + (&Some(_), &None) => { + self.write_wrapped_struct_matrix_set_vec_function_name(access)?; + } + (&None, _) => { + self.write_wrapped_struct_matrix_set_function_name(access)?; + } + } + + write!(self.out, "(")?; + self.write_expr(module, base, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, value, func_ctx)?; + + if let Some(Index::Expression(vec_index)) = vector { + write!(self.out, ", ")?; + self.write_expr(module, vec_index, func_ctx)?; + + if let Some(scalar_index) = scalar { + write!(self.out, ", ")?; + match scalar_index { + Index::Static(index) => { + write!(self.out, "{index}")?; + } + Index::Expression(index) => { + self.write_expr(module, index, func_ctx)?; + } + } + } + } + writeln!(self.out, ");")?; + } + } else { + // We handle `Store`s to __matCx2 column vectors and scalar elements via + // the previously injected functions __set_col_of_matCx2 / __set_el_of_matCx2. + struct MatrixData { + columns: crate::VectorSize, + base: Handle, + } + + enum Index { + Expression(Handle), + Static(u32), + } + + let mut matrix = None; + let mut vector = None; + let mut scalar = None; + + let mut current_expr = pointer; + for _ in 0..3 { + let resolved = func_ctx.resolve_type(current_expr, &module.types); + match (resolved, &func_ctx.expressions[current_expr]) { + ( + &TypeInner::ValuePointer { + size: Some(crate::VectorSize::Bi), + .. + }, + &crate::Expression::Access { base, index }, + ) => { + vector = Some(index); + current_expr = base; + } + ( + &TypeInner::ValuePointer { size: None, .. }, + &crate::Expression::Access { base, index }, + ) => { + scalar = Some(Index::Expression(index)); + current_expr = base; + } + ( + &TypeInner::ValuePointer { size: None, .. }, + &crate::Expression::AccessIndex { base, index }, + ) => { + scalar = Some(Index::Static(index)); + current_expr = base; + } + _ => { + if let Some(MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = get_inner_matrix_of_struct_array_member( + module, + current_expr, + func_ctx, + true, + ) { + matrix = Some(MatrixData { + columns, + base: current_expr, + }); + } + + break; + } + } + } + + if let (Some(MatrixData { columns, base }), Some(vec_index)) = + (matrix, vector) + { + if scalar.is_some() { + write!(self.out, "__set_el_of_mat{}x2", columns as u8)?; + } else { + write!(self.out, "__set_col_of_mat{}x2", columns as u8)?; + } + write!(self.out, "(")?; + self.write_expr(module, base, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, vec_index, func_ctx)?; + + if let Some(scalar_index) = scalar { + write!(self.out, ", ")?; + match scalar_index { + Index::Static(index) => { + write!(self.out, "{index}")?; + } + Index::Expression(index) => { + self.write_expr(module, index, func_ctx)?; + } + } + } + + write!(self.out, ", ")?; + self.write_expr(module, value, func_ctx)?; + + writeln!(self.out, ");")?; + } else { + self.write_expr(module, pointer, func_ctx)?; + write!(self.out, " = ")?; + + // We cast the RHS of this store in cases where the LHS + // is a struct member with type: + // - matCx2 or + // - a (possibly nested) array of matCx2's + if let Some(MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = get_inner_matrix_of_struct_array_member( + module, pointer, func_ctx, false, + ) { + let mut resolved = func_ctx.resolve_type(pointer, &module.types); + if let TypeInner::Pointer { base, .. } = *resolved { + resolved = &module.types[base].inner; + } + + write!(self.out, "(__mat{}x2", columns as u8)?; + if let TypeInner::Array { base, size, .. } = *resolved { + self.write_array_size(module, base, size)?; + } + write!(self.out, ")")?; + } + + self.write_expr(module, value, func_ctx)?; + writeln!(self.out, ";")? + } + } + } + } + Statement::Loop { + ref body, + ref continuing, + break_if, + } => { + let l2 = level.next(); + if !continuing.is_empty() || break_if.is_some() { + let gate_name = self.namer.call("loop_init"); + writeln!(self.out, "{level}bool {gate_name} = true;")?; + writeln!(self.out, "{level}while(true) {{")?; + writeln!(self.out, "{l2}if (!{gate_name}) {{")?; + let l3 = l2.next(); + for sta in continuing.iter() { + self.write_stmt(module, sta, func_ctx, l3)?; + } + if let Some(condition) = break_if { + write!(self.out, "{l3}if (")?; + self.write_expr(module, condition, func_ctx)?; + writeln!(self.out, ") {{")?; + writeln!(self.out, "{}break;", l3.next())?; + writeln!(self.out, "{l3}}}")?; + } + writeln!(self.out, "{l2}}}")?; + writeln!(self.out, "{l2}{gate_name} = false;")?; + } else { + writeln!(self.out, "{level}while(true) {{")?; + } + + for sta in body.iter() { + self.write_stmt(module, sta, func_ctx, l2)?; + } + writeln!(self.out, "{level}}}")? + } + Statement::Break => writeln!(self.out, "{level}break;")?, + Statement::Continue => writeln!(self.out, "{level}continue;")?, + Statement::Barrier(barrier) => { + self.write_barrier(barrier, level)?; + } + Statement::ImageStore { + image, + coordinate, + array_index, + value, + } => { + write!(self.out, "{level}")?; + self.write_expr(module, image, func_ctx)?; + + write!(self.out, "[")?; + if let Some(index) = array_index { + // Array index accepted only for texture_storage_2d_array, so we can safety use int3(coordinate, array_index) here + write!(self.out, "int3(")?; + self.write_expr(module, coordinate, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, index, func_ctx)?; + write!(self.out, ")")?; + } else { + self.write_expr(module, coordinate, func_ctx)?; + } + write!(self.out, "]")?; + + write!(self.out, " = ")?; + self.write_expr(module, value, func_ctx)?; + writeln!(self.out, ";")?; + } + Statement::Call { + function, + ref arguments, + result, + } => { + write!(self.out, "{level}")?; + if let Some(expr) = result { + write!(self.out, "const ")?; + let name = format!("{}{}", back::BAKE_PREFIX, expr.index()); + let expr_ty = &func_ctx.info[expr].ty; + match *expr_ty { + proc::TypeResolution::Handle(handle) => self.write_type(module, handle)?, + proc::TypeResolution::Value(ref value) => { + self.write_value_type(module, value)? + } + }; + write!(self.out, " {name} = ")?; + self.named_expressions.insert(expr, name); + } + let func_name = &self.names[&NameKey::Function(function)]; + write!(self.out, "{func_name}(")?; + for (index, argument) in arguments.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + self.write_expr(module, *argument, func_ctx)?; + } + writeln!(self.out, ");")? + } + Statement::Atomic { + pointer, + ref fun, + value, + result, + } => { + write!(self.out, "{level}")?; + let res_name = format!("{}{}", back::BAKE_PREFIX, result.index()); + match func_ctx.info[result].ty { + proc::TypeResolution::Handle(handle) => self.write_type(module, handle)?, + proc::TypeResolution::Value(ref value) => { + self.write_value_type(module, value)? + } + }; + + // Validation ensures that `pointer` has a `Pointer` type. + let pointer_space = func_ctx + .resolve_type(pointer, &module.types) + .pointer_space() + .unwrap(); + + let fun_str = fun.to_hlsl_suffix(); + write!(self.out, " {res_name}; ")?; + match pointer_space { + crate::AddressSpace::WorkGroup => { + write!(self.out, "Interlocked{fun_str}(")?; + self.write_expr(module, pointer, func_ctx)?; + } + crate::AddressSpace::Storage { .. } => { + let var_handle = self.fill_access_chain(module, pointer, func_ctx)?; + // The call to `self.write_storage_address` wants + // mutable access to all of `self`, so temporarily take + // ownership of our reusable access chain buffer. + let chain = mem::take(&mut self.temp_access_chain); + let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; + write!(self.out, "{var_name}.Interlocked{fun_str}(")?; + self.write_storage_address(module, &chain, func_ctx)?; + self.temp_access_chain = chain; + } + ref other => { + return Err(Error::Custom(format!( + "invalid address space {other:?} for atomic statement" + ))) + } + } + write!(self.out, ", ")?; + // handle the special cases + match *fun { + crate::AtomicFunction::Subtract => { + // we just wrote `InterlockedAdd`, so negate the argument + write!(self.out, "-")?; + } + crate::AtomicFunction::Exchange { compare: Some(_) } => { + return Err(Error::Unimplemented("atomic CompareExchange".to_string())); + } + _ => {} + } + self.write_expr(module, value, func_ctx)?; + writeln!(self.out, ", {res_name});")?; + self.named_expressions.insert(result, res_name); + } + Statement::WorkGroupUniformLoad { pointer, result } => { + self.write_barrier(crate::Barrier::WORK_GROUP, level)?; + write!(self.out, "{level}")?; + let name = format!("_expr{}", result.index()); + self.write_named_expr(module, pointer, name, result, func_ctx)?; + + self.write_barrier(crate::Barrier::WORK_GROUP, level)?; + } + Statement::Switch { + selector, + ref cases, + } => { + // Start the switch + write!(self.out, "{level}")?; + write!(self.out, "switch(")?; + self.write_expr(module, selector, func_ctx)?; + writeln!(self.out, ") {{")?; + + // Write all cases + let indent_level_1 = level.next(); + let indent_level_2 = indent_level_1.next(); + + for (i, case) in cases.iter().enumerate() { + match case.value { + crate::SwitchValue::I32(value) => { + write!(self.out, "{indent_level_1}case {value}:")? + } + crate::SwitchValue::U32(value) => { + write!(self.out, "{indent_level_1}case {value}u:")? + } + crate::SwitchValue::Default => { + write!(self.out, "{indent_level_1}default:")? + } + } + + // The new block is not only stylistic, it plays a role here: + // We might end up having to write the same case body + // multiple times due to FXC not supporting fallthrough. + // Therefore, some `Expression`s written by `Statement::Emit` + // will end up having the same name (`_expr`). + // So we need to put each case in its own scope. + let write_block_braces = !(case.fall_through && case.body.is_empty()); + if write_block_braces { + writeln!(self.out, " {{")?; + } else { + writeln!(self.out)?; + } + + // Although FXC does support a series of case clauses before + // a block[^yes], it does not support fallthrough from a + // non-empty case block to the next[^no]. If this case has a + // non-empty body with a fallthrough, emulate that by + // duplicating the bodies of all the cases it would fall + // into as extensions of this case's own body. This makes + // the HLSL output potentially quadratic in the size of the + // Naga IR. + // + // [^yes]: ```hlsl + // case 1: + // case 2: do_stuff() + // ``` + // [^no]: ```hlsl + // case 1: do_this(); + // case 2: do_that(); + // ``` + if case.fall_through && !case.body.is_empty() { + let curr_len = i + 1; + let end_case_idx = curr_len + + cases + .iter() + .skip(curr_len) + .position(|case| !case.fall_through) + .unwrap(); + let indent_level_3 = indent_level_2.next(); + for case in &cases[i..=end_case_idx] { + writeln!(self.out, "{indent_level_2}{{")?; + let prev_len = self.named_expressions.len(); + for sta in case.body.iter() { + self.write_stmt(module, sta, func_ctx, indent_level_3)?; + } + // Clear all named expressions that were previously inserted by the statements in the block + self.named_expressions.truncate(prev_len); + writeln!(self.out, "{indent_level_2}}}")?; + } + + let last_case = &cases[end_case_idx]; + if last_case.body.last().map_or(true, |s| !s.is_terminator()) { + writeln!(self.out, "{indent_level_2}break;")?; + } + } else { + for sta in case.body.iter() { + self.write_stmt(module, sta, func_ctx, indent_level_2)?; + } + if !case.fall_through + && case.body.last().map_or(true, |s| !s.is_terminator()) + { + writeln!(self.out, "{indent_level_2}break;")?; + } + } + + if write_block_braces { + writeln!(self.out, "{indent_level_1}}}")?; + } + } + + writeln!(self.out, "{level}}}")? + } + Statement::RayQuery { .. } => unreachable!(), + } + + Ok(()) + } + + fn write_const_expression( + &mut self, + module: &Module, + expr: Handle, + ) -> BackendResult { + self.write_possibly_const_expression( + module, + expr, + &module.const_expressions, + |writer, expr| writer.write_const_expression(module, expr), + ) + } + + fn write_possibly_const_expression( + &mut self, + module: &Module, + expr: Handle, + expressions: &crate::Arena, + write_expression: E, + ) -> BackendResult + where + E: Fn(&mut Self, Handle) -> BackendResult, + { + use crate::Expression; + + match expressions[expr] { + Expression::Literal(literal) => match literal { + // Floats are written using `Debug` instead of `Display` because it always appends the + // decimal part even it's zero + crate::Literal::F64(value) => write!(self.out, "{value:?}L")?, + crate::Literal::F32(value) => write!(self.out, "{value:?}")?, + crate::Literal::U32(value) => write!(self.out, "{}u", value)?, + crate::Literal::I32(value) => write!(self.out, "{}", value)?, + crate::Literal::I64(value) => write!(self.out, "{}L", value)?, + crate::Literal::Bool(value) => write!(self.out, "{}", value)?, + crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { + return Err(Error::Custom( + "Abstract types should not appear in IR presented to backends".into(), + )); + } + }, + Expression::Constant(handle) => { + let constant = &module.constants[handle]; + if constant.name.is_some() { + write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; + } else { + self.write_const_expression(module, constant.init)?; + } + } + Expression::ZeroValue(ty) => self.write_default_init(module, ty)?, + Expression::Compose { ty, ref components } => { + match module.types[ty].inner { + TypeInner::Struct { .. } | TypeInner::Array { .. } => { + self.write_wrapped_constructor_function_name( + module, + WrappedConstructor { ty }, + )?; + } + _ => { + self.write_type(module, ty)?; + } + }; + write!(self.out, "(")?; + for (index, component) in components.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + write_expression(self, *component)?; + } + write!(self.out, ")")?; + } + Expression::Splat { size, value } => { + // hlsl is not supported one value constructor + // if we write, for example, int4(0), dxc returns error: + // error: too few elements in vector initialization (expected 4 elements, have 1) + let number_of_components = match size { + crate::VectorSize::Bi => "xx", + crate::VectorSize::Tri => "xxx", + crate::VectorSize::Quad => "xxxx", + }; + write!(self.out, "(")?; + write_expression(self, value)?; + write!(self.out, ").{number_of_components}")? + } + _ => unreachable!(), + } + + Ok(()) + } + + /// Helper method to write expressions + /// + /// # Notes + /// Doesn't add any newlines or leading/trailing spaces + pub(super) fn write_expr( + &mut self, + module: &Module, + expr: Handle, + func_ctx: &back::FunctionCtx<'_>, + ) -> BackendResult { + use crate::Expression; + + // Handle the special semantics of vertex_index/instance_index + let ff_input = if self.options.special_constants_binding.is_some() { + func_ctx.is_fixed_function_input(expr, module) + } else { + None + }; + let closing_bracket = match ff_input { + Some(crate::BuiltIn::VertexIndex) => { + write!(self.out, "({SPECIAL_CBUF_VAR}.{SPECIAL_FIRST_VERTEX} + ")?; + ")" + } + Some(crate::BuiltIn::InstanceIndex) => { + write!(self.out, "({SPECIAL_CBUF_VAR}.{SPECIAL_FIRST_INSTANCE} + ",)?; + ")" + } + Some(crate::BuiltIn::NumWorkGroups) => { + // Note: despite their names (`FIRST_VERTEX` and `FIRST_INSTANCE`), + // in compute shaders the special constants contain the number + // of workgroups, which we are using here. + write!( + self.out, + "uint3({SPECIAL_CBUF_VAR}.{SPECIAL_FIRST_VERTEX}, {SPECIAL_CBUF_VAR}.{SPECIAL_FIRST_INSTANCE}, {SPECIAL_CBUF_VAR}.{SPECIAL_OTHER})", + )?; + return Ok(()); + } + _ => "", + }; + + if let Some(name) = self.named_expressions.get(&expr) { + write!(self.out, "{name}{closing_bracket}")?; + return Ok(()); + } + + let expression = &func_ctx.expressions[expr]; + + match *expression { + Expression::Literal(_) + | Expression::Constant(_) + | Expression::ZeroValue(_) + | Expression::Compose { .. } + | Expression::Splat { .. } => { + self.write_possibly_const_expression( + module, + expr, + func_ctx.expressions, + |writer, expr| writer.write_expr(module, expr, func_ctx), + )?; + } + // All of the multiplication can be expressed as `mul`, + // except vector * vector, which needs to use the "*" operator. + Expression::Binary { + op: crate::BinaryOperator::Multiply, + left, + right, + } if func_ctx.resolve_type(left, &module.types).is_matrix() + || func_ctx.resolve_type(right, &module.types).is_matrix() => + { + // We intentionally flip the order of multiplication as our matrices are implicitly transposed. + write!(self.out, "mul(")?; + self.write_expr(module, right, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, left, func_ctx)?; + write!(self.out, ")")?; + } + + // TODO: handle undefined behavior of BinaryOperator::Modulo + // + // sint: + // if right == 0 return 0 + // if left == min(type_of(left)) && right == -1 return 0 + // if sign(left) != sign(right) return result as defined by WGSL + // + // uint: + // if right == 0 return 0 + // + // float: + // if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798 + + // While HLSL supports float operands with the % operator it is only + // defined in cases where both sides are either positive or negative. + Expression::Binary { + op: crate::BinaryOperator::Modulo, + left, + right, + } if func_ctx.resolve_type(left, &module.types).scalar_kind() + == Some(crate::ScalarKind::Float) => + { + write!(self.out, "fmod(")?; + self.write_expr(module, left, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, right, func_ctx)?; + write!(self.out, ")")?; + } + Expression::Binary { op, left, right } => { + write!(self.out, "(")?; + self.write_expr(module, left, func_ctx)?; + write!(self.out, " {} ", crate::back::binary_operation_str(op))?; + self.write_expr(module, right, func_ctx)?; + write!(self.out, ")")?; + } + Expression::Access { base, index } => { + if let Some(crate::AddressSpace::Storage { .. }) = + func_ctx.resolve_type(expr, &module.types).pointer_space() + { + // do nothing, the chain is written on `Load`/`Store` + } else { + // We use the function __get_col_of_matCx2 here in cases + // where `base`s type resolves to a matCx2 and is part of a + // struct member with type of (possibly nested) array of matCx2's. + // + // Note that this only works for `Load`s and we handle + // `Store`s differently in `Statement::Store`. + if let Some(MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = get_inner_matrix_of_struct_array_member(module, base, func_ctx, true) + { + write!(self.out, "__get_col_of_mat{}x2(", columns as u8)?; + self.write_expr(module, base, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, index, func_ctx)?; + write!(self.out, ")")?; + return Ok(()); + } + + let resolved = func_ctx.resolve_type(base, &module.types); + + let non_uniform_qualifier = match *resolved { + TypeInner::BindingArray { .. } => { + let uniformity = &func_ctx.info[index].uniformity; + + uniformity.non_uniform_result.is_some() + } + _ => false, + }; + + self.write_expr(module, base, func_ctx)?; + write!(self.out, "[")?; + if non_uniform_qualifier { + write!(self.out, "NonUniformResourceIndex(")?; + } + self.write_expr(module, index, func_ctx)?; + if non_uniform_qualifier { + write!(self.out, ")")?; + } + write!(self.out, "]")?; + } + } + Expression::AccessIndex { base, index } => { + if let Some(crate::AddressSpace::Storage { .. }) = + func_ctx.resolve_type(expr, &module.types).pointer_space() + { + // do nothing, the chain is written on `Load`/`Store` + } else { + fn write_access( + writer: &mut super::Writer<'_, W>, + resolved: &TypeInner, + base_ty_handle: Option>, + index: u32, + ) -> BackendResult { + match *resolved { + // We specifcally lift the ValuePointer to this case. While `[0]` is valid + // HLSL for any vector behind a value pointer, FXC completely miscompiles + // it and generates completely nonsensical DXBC. + // + // See https://github.com/gfx-rs/naga/issues/2095 for more details. + TypeInner::Vector { .. } | TypeInner::ValuePointer { .. } => { + // Write vector access as a swizzle + write!(writer.out, ".{}", back::COMPONENTS[index as usize])? + } + TypeInner::Matrix { .. } + | TypeInner::Array { .. } + | TypeInner::BindingArray { .. } => write!(writer.out, "[{index}]")?, + TypeInner::Struct { .. } => { + // This will never panic in case the type is a `Struct`, this is not true + // for other types so we can only check while inside this match arm + let ty = base_ty_handle.unwrap(); + + write!( + writer.out, + ".{}", + &writer.names[&NameKey::StructMember(ty, index)] + )? + } + ref other => { + return Err(Error::Custom(format!("Cannot index {other:?}"))) + } + } + Ok(()) + } + + // We write the matrix column access in a special way since + // the type of `base` is our special __matCx2 struct. + if let Some(MatrixType { + rows: crate::VectorSize::Bi, + width: 4, + .. + }) = get_inner_matrix_of_struct_array_member(module, base, func_ctx, true) + { + self.write_expr(module, base, func_ctx)?; + write!(self.out, "._{index}")?; + return Ok(()); + } + + let base_ty_res = &func_ctx.info[base].ty; + let mut resolved = base_ty_res.inner_with(&module.types); + let base_ty_handle = match *resolved { + TypeInner::Pointer { base, .. } => { + resolved = &module.types[base].inner; + Some(base) + } + _ => base_ty_res.handle(), + }; + + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. + // See the module-level block comment in mod.rs for details. + // + // We handle matrix reconstruction here for Loads. + // Stores are handled directly by `Statement::Store`. + if let TypeInner::Struct { ref members, .. } = *resolved { + let member = &members[index as usize]; + + match module.types[member.ty].inner { + TypeInner::Matrix { + rows: crate::VectorSize::Bi, + .. + } if member.binding.is_none() => { + let ty = base_ty_handle.unwrap(); + self.write_wrapped_struct_matrix_get_function_name( + WrappedStructMatrixAccess { ty, index }, + )?; + write!(self.out, "(")?; + self.write_expr(module, base, func_ctx)?; + write!(self.out, ")")?; + return Ok(()); + } + _ => {} + } + } + + self.write_expr(module, base, func_ctx)?; + write_access(self, resolved, base_ty_handle, index)?; + } + } + Expression::FunctionArgument(pos) => { + let key = func_ctx.argument_key(pos); + let name = &self.names[&key]; + write!(self.out, "{name}")?; + } + Expression::ImageSample { + image, + sampler, + gather, + coordinate, + array_index, + offset, + level, + depth_ref, + } => { + use crate::SampleLevel as Sl; + const COMPONENTS: [&str; 4] = ["", "Green", "Blue", "Alpha"]; + + let (base_str, component_str) = match gather { + Some(component) => ("Gather", COMPONENTS[component as usize]), + None => ("Sample", ""), + }; + let cmp_str = match depth_ref { + Some(_) => "Cmp", + None => "", + }; + let level_str = match level { + Sl::Zero if gather.is_none() => "LevelZero", + Sl::Auto | Sl::Zero => "", + Sl::Exact(_) => "Level", + Sl::Bias(_) => "Bias", + Sl::Gradient { .. } => "Grad", + }; + + self.write_expr(module, image, func_ctx)?; + write!(self.out, ".{base_str}{cmp_str}{component_str}{level_str}(")?; + self.write_expr(module, sampler, func_ctx)?; + write!(self.out, ", ")?; + self.write_texture_coordinates( + "float", + coordinate, + array_index, + None, + module, + func_ctx, + )?; + + if let Some(depth_ref) = depth_ref { + write!(self.out, ", ")?; + self.write_expr(module, depth_ref, func_ctx)?; + } + + match level { + Sl::Auto | Sl::Zero => {} + Sl::Exact(expr) => { + write!(self.out, ", ")?; + self.write_expr(module, expr, func_ctx)?; + } + Sl::Bias(expr) => { + write!(self.out, ", ")?; + self.write_expr(module, expr, func_ctx)?; + } + Sl::Gradient { x, y } => { + write!(self.out, ", ")?; + self.write_expr(module, x, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, y, func_ctx)?; + } + } + + if let Some(offset) = offset { + write!(self.out, ", ")?; + write!(self.out, "int2(")?; // work around https://github.com/microsoft/DirectXShaderCompiler/issues/5082#issuecomment-1540147807 + self.write_const_expression(module, offset)?; + write!(self.out, ")")?; + } + + write!(self.out, ")")?; + } + Expression::ImageQuery { image, query } => { + // use wrapped image query function + if let TypeInner::Image { + dim, + arrayed, + class, + } = *func_ctx.resolve_type(image, &module.types) + { + let wrapped_image_query = WrappedImageQuery { + dim, + arrayed, + class, + query: query.into(), + }; + + self.write_wrapped_image_query_function_name(wrapped_image_query)?; + write!(self.out, "(")?; + // Image always first param + self.write_expr(module, image, func_ctx)?; + if let crate::ImageQuery::Size { level: Some(level) } = query { + write!(self.out, ", ")?; + self.write_expr(module, level, func_ctx)?; + } + write!(self.out, ")")?; + } + } + Expression::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => { + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-to-load + self.write_expr(module, image, func_ctx)?; + write!(self.out, ".Load(")?; + + self.write_texture_coordinates( + "int", + coordinate, + array_index, + level, + module, + func_ctx, + )?; + + if let Some(sample) = sample { + write!(self.out, ", ")?; + self.write_expr(module, sample, func_ctx)?; + } + + // close bracket for Load function + write!(self.out, ")")?; + + // return x component if return type is scalar + if let TypeInner::Scalar(_) = *func_ctx.resolve_type(expr, &module.types) { + write!(self.out, ".x")?; + } + } + Expression::GlobalVariable(handle) => match module.global_variables[handle].space { + crate::AddressSpace::Storage { .. } => {} + _ => { + let name = &self.names[&NameKey::GlobalVariable(handle)]; + write!(self.out, "{name}")?; + } + }, + Expression::LocalVariable(handle) => { + write!(self.out, "{}", self.names[&func_ctx.name_key(handle)])? + } + Expression::Load { pointer } => { + match func_ctx + .resolve_type(pointer, &module.types) + .pointer_space() + { + Some(crate::AddressSpace::Storage { .. }) => { + let var_handle = self.fill_access_chain(module, pointer, func_ctx)?; + let result_ty = func_ctx.info[expr].ty.clone(); + self.write_storage_load(module, var_handle, result_ty, func_ctx)?; + } + _ => { + let mut close_paren = false; + + // We cast the value loaded to a native HLSL floatCx2 + // in cases where it is of type: + // - __matCx2 or + // - a (possibly nested) array of __matCx2's + if let Some(MatrixType { + rows: crate::VectorSize::Bi, + width: 4, + .. + }) = get_inner_matrix_of_struct_array_member( + module, pointer, func_ctx, false, + ) + .or_else(|| get_inner_matrix_of_global_uniform(module, pointer, func_ctx)) + { + let mut resolved = func_ctx.resolve_type(pointer, &module.types); + if let TypeInner::Pointer { base, .. } = *resolved { + resolved = &module.types[base].inner; + } + + write!(self.out, "((")?; + if let TypeInner::Array { base, size, .. } = *resolved { + self.write_type(module, base)?; + self.write_array_size(module, base, size)?; + } else { + self.write_value_type(module, resolved)?; + } + write!(self.out, ")")?; + close_paren = true; + } + + self.write_expr(module, pointer, func_ctx)?; + + if close_paren { + write!(self.out, ")")?; + } + } + } + } + Expression::Unary { op, expr } => { + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-operators#unary-operators + let op_str = match op { + crate::UnaryOperator::Negate => "-", + crate::UnaryOperator::LogicalNot => "!", + crate::UnaryOperator::BitwiseNot => "~", + }; + write!(self.out, "{op_str}(")?; + self.write_expr(module, expr, func_ctx)?; + write!(self.out, ")")?; + } + Expression::As { + expr, + kind, + convert, + } => { + let inner = func_ctx.resolve_type(expr, &module.types); + match convert { + Some(dst_width) => { + let scalar = crate::Scalar { + kind, + width: dst_width, + }; + match *inner { + TypeInner::Vector { size, .. } => { + write!( + self.out, + "{}{}(", + scalar.to_hlsl_str()?, + back::vector_size_str(size) + )?; + } + TypeInner::Scalar(_) => { + write!(self.out, "{}(", scalar.to_hlsl_str()?,)?; + } + TypeInner::Matrix { columns, rows, .. } => { + write!( + self.out, + "{}{}x{}(", + scalar.to_hlsl_str()?, + back::vector_size_str(columns), + back::vector_size_str(rows) + )?; + } + _ => { + return Err(Error::Unimplemented(format!( + "write_expr expression::as {inner:?}" + ))); + } + }; + } + None => { + write!(self.out, "{}(", kind.to_hlsl_cast(),)?; + } + } + self.write_expr(module, expr, func_ctx)?; + write!(self.out, ")")?; + } + Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + } => { + use crate::MathFunction as Mf; + + enum Function { + Asincosh { is_sin: bool }, + Atanh, + ExtractBits, + InsertBits, + Pack2x16float, + Pack2x16snorm, + Pack2x16unorm, + Pack4x8snorm, + Pack4x8unorm, + Unpack2x16float, + Unpack2x16snorm, + Unpack2x16unorm, + Unpack4x8snorm, + Unpack4x8unorm, + Regular(&'static str), + MissingIntOverload(&'static str), + MissingIntReturnType(&'static str), + CountTrailingZeros, + CountLeadingZeros, + } + + let fun = match fun { + // comparison + Mf::Abs => Function::Regular("abs"), + Mf::Min => Function::Regular("min"), + Mf::Max => Function::Regular("max"), + Mf::Clamp => Function::Regular("clamp"), + Mf::Saturate => Function::Regular("saturate"), + // trigonometry + Mf::Cos => Function::Regular("cos"), + Mf::Cosh => Function::Regular("cosh"), + Mf::Sin => Function::Regular("sin"), + Mf::Sinh => Function::Regular("sinh"), + Mf::Tan => Function::Regular("tan"), + Mf::Tanh => Function::Regular("tanh"), + Mf::Acos => Function::Regular("acos"), + Mf::Asin => Function::Regular("asin"), + Mf::Atan => Function::Regular("atan"), + Mf::Atan2 => Function::Regular("atan2"), + Mf::Asinh => Function::Asincosh { is_sin: true }, + Mf::Acosh => Function::Asincosh { is_sin: false }, + Mf::Atanh => Function::Atanh, + Mf::Radians => Function::Regular("radians"), + Mf::Degrees => Function::Regular("degrees"), + // decomposition + Mf::Ceil => Function::Regular("ceil"), + Mf::Floor => Function::Regular("floor"), + Mf::Round => Function::Regular("round"), + Mf::Fract => Function::Regular("frac"), + Mf::Trunc => Function::Regular("trunc"), + Mf::Modf => Function::Regular(MODF_FUNCTION), + Mf::Frexp => Function::Regular(FREXP_FUNCTION), + Mf::Ldexp => Function::Regular("ldexp"), + // exponent + Mf::Exp => Function::Regular("exp"), + Mf::Exp2 => Function::Regular("exp2"), + Mf::Log => Function::Regular("log"), + Mf::Log2 => Function::Regular("log2"), + Mf::Pow => Function::Regular("pow"), + // geometry + Mf::Dot => Function::Regular("dot"), + //Mf::Outer => , + Mf::Cross => Function::Regular("cross"), + Mf::Distance => Function::Regular("distance"), + Mf::Length => Function::Regular("length"), + Mf::Normalize => Function::Regular("normalize"), + Mf::FaceForward => Function::Regular("faceforward"), + Mf::Reflect => Function::Regular("reflect"), + Mf::Refract => Function::Regular("refract"), + // computational + Mf::Sign => Function::Regular("sign"), + Mf::Fma => Function::Regular("mad"), + Mf::Mix => Function::Regular("lerp"), + Mf::Step => Function::Regular("step"), + Mf::SmoothStep => Function::Regular("smoothstep"), + Mf::Sqrt => Function::Regular("sqrt"), + Mf::InverseSqrt => Function::Regular("rsqrt"), + //Mf::Inverse =>, + Mf::Transpose => Function::Regular("transpose"), + Mf::Determinant => Function::Regular("determinant"), + // bits + Mf::CountTrailingZeros => Function::CountTrailingZeros, + Mf::CountLeadingZeros => Function::CountLeadingZeros, + Mf::CountOneBits => Function::MissingIntOverload("countbits"), + Mf::ReverseBits => Function::MissingIntOverload("reversebits"), + Mf::FindLsb => Function::MissingIntReturnType("firstbitlow"), + Mf::FindMsb => Function::MissingIntReturnType("firstbithigh"), + Mf::ExtractBits => Function::ExtractBits, + Mf::InsertBits => Function::InsertBits, + // Data Packing + Mf::Pack2x16float => Function::Pack2x16float, + Mf::Pack2x16snorm => Function::Pack2x16snorm, + Mf::Pack2x16unorm => Function::Pack2x16unorm, + Mf::Pack4x8snorm => Function::Pack4x8snorm, + Mf::Pack4x8unorm => Function::Pack4x8unorm, + // Data Unpacking + Mf::Unpack2x16float => Function::Unpack2x16float, + Mf::Unpack2x16snorm => Function::Unpack2x16snorm, + Mf::Unpack2x16unorm => Function::Unpack2x16unorm, + Mf::Unpack4x8snorm => Function::Unpack4x8snorm, + Mf::Unpack4x8unorm => Function::Unpack4x8unorm, + _ => return Err(Error::Unimplemented(format!("write_expr_math {fun:?}"))), + }; + + match fun { + Function::Asincosh { is_sin } => { + write!(self.out, "log(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " + sqrt(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " * ")?; + self.write_expr(module, arg, func_ctx)?; + match is_sin { + true => write!(self.out, " + 1.0))")?, + false => write!(self.out, " - 1.0))")?, + } + } + Function::Atanh => { + write!(self.out, "0.5 * log((1.0 + ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ") / (1.0 - ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "))")?; + } + Function::ExtractBits => { + // e: T, + // offset: u32, + // count: u32 + // T is u32 or i32 or vecN or vecN + if let (Some(offset), Some(count)) = (arg1, arg2) { + let scalar_width: u8 = 32; + // Works for signed and unsigned + // (count == 0 ? 0 : (e << (32 - count - offset)) >> (32 - count)) + write!(self.out, "(")?; + self.write_expr(module, count, func_ctx)?; + write!(self.out, " == 0 ? 0 : (")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " << ({scalar_width} - ")?; + self.write_expr(module, count, func_ctx)?; + write!(self.out, " - ")?; + self.write_expr(module, offset, func_ctx)?; + write!(self.out, ")) >> ({scalar_width} - ")?; + self.write_expr(module, count, func_ctx)?; + write!(self.out, "))")?; + } + } + Function::InsertBits => { + // e: T, + // newbits: T, + // offset: u32, + // count: u32 + // returns T + // T is i32, u32, vecN, or vecN + if let (Some(newbits), Some(offset), Some(count)) = (arg1, arg2, arg3) { + let scalar_width: u8 = 32; + let scalar_max: u32 = 0xFFFFFFFF; + // mask = ((0xFFFFFFFFu >> (32 - count)) << offset) + // (count == 0 ? e : ((e & ~mask) | ((newbits << offset) & mask))) + write!(self.out, "(")?; + self.write_expr(module, count, func_ctx)?; + write!(self.out, " == 0 ? ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " : ")?; + write!(self.out, "(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " & ~")?; + // mask + write!(self.out, "(({scalar_max}u >> ({scalar_width}u - ")?; + self.write_expr(module, count, func_ctx)?; + write!(self.out, ")) << ")?; + self.write_expr(module, offset, func_ctx)?; + write!(self.out, ")")?; + // end mask + write!(self.out, ") | ((")?; + self.write_expr(module, newbits, func_ctx)?; + write!(self.out, " << ")?; + self.write_expr(module, offset, func_ctx)?; + write!(self.out, ") & ")?; + // // mask + write!(self.out, "(({scalar_max}u >> ({scalar_width}u - ")?; + self.write_expr(module, count, func_ctx)?; + write!(self.out, ")) << ")?; + self.write_expr(module, offset, func_ctx)?; + write!(self.out, ")")?; + // // end mask + write!(self.out, "))")?; + } + } + Function::Pack2x16float => { + write!(self.out, "(f32tof16(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "[0]) | f32tof16(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "[1]) << 16)")?; + } + Function::Pack2x16snorm => { + let scale = 32767; + + write!(self.out, "uint((int(round(clamp(")?; + self.write_expr(module, arg, func_ctx)?; + write!( + self.out, + "[0], -1.0, 1.0) * {scale}.0)) & 0xFFFF) | ((int(round(clamp(" + )?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "[1], -1.0, 1.0) * {scale}.0)) & 0xFFFF) << 16))",)?; + } + Function::Pack2x16unorm => { + let scale = 65535; + + write!(self.out, "(uint(round(clamp(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "[0], 0.0, 1.0) * {scale}.0)) | uint(round(clamp(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "[1], 0.0, 1.0) * {scale}.0)) << 16)")?; + } + Function::Pack4x8snorm => { + let scale = 127; + + write!(self.out, "uint((int(round(clamp(")?; + self.write_expr(module, arg, func_ctx)?; + write!( + self.out, + "[0], -1.0, 1.0) * {scale}.0)) & 0xFF) | ((int(round(clamp(" + )?; + self.write_expr(module, arg, func_ctx)?; + write!( + self.out, + "[1], -1.0, 1.0) * {scale}.0)) & 0xFF) << 8) | ((int(round(clamp(" + )?; + self.write_expr(module, arg, func_ctx)?; + write!( + self.out, + "[2], -1.0, 1.0) * {scale}.0)) & 0xFF) << 16) | ((int(round(clamp(" + )?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "[3], -1.0, 1.0) * {scale}.0)) & 0xFF) << 24))",)?; + } + Function::Pack4x8unorm => { + let scale = 255; + + write!(self.out, "(uint(round(clamp(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "[0], 0.0, 1.0) * {scale}.0)) | uint(round(clamp(")?; + self.write_expr(module, arg, func_ctx)?; + write!( + self.out, + "[1], 0.0, 1.0) * {scale}.0)) << 8 | uint(round(clamp(" + )?; + self.write_expr(module, arg, func_ctx)?; + write!( + self.out, + "[2], 0.0, 1.0) * {scale}.0)) << 16 | uint(round(clamp(" + )?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "[3], 0.0, 1.0) * {scale}.0)) << 24)")?; + } + + Function::Unpack2x16float => { + write!(self.out, "float2(f16tof32(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "), f16tof32((")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ") >> 16))")?; + } + Function::Unpack2x16snorm => { + let scale = 32767; + + write!(self.out, "(float2(int2(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " << 16, ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ") >> 16) / {scale}.0)")?; + } + Function::Unpack2x16unorm => { + let scale = 65535; + + write!(self.out, "(float2(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " & 0xFFFF, ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " >> 16) / {scale}.0)")?; + } + Function::Unpack4x8snorm => { + let scale = 127; + + write!(self.out, "(float4(int4(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " << 24, ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " << 16, ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " << 8, ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ") >> 24) / {scale}.0)")?; + } + Function::Unpack4x8unorm => { + let scale = 255; + + write!(self.out, "(float4(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " & 0xFF, ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " >> 8 & 0xFF, ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " >> 16 & 0xFF, ")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " >> 24) / {scale}.0)")?; + } + Function::Regular(fun_name) => { + write!(self.out, "{fun_name}(")?; + self.write_expr(module, arg, func_ctx)?; + if let Some(arg) = arg1 { + write!(self.out, ", ")?; + self.write_expr(module, arg, func_ctx)?; + } + if let Some(arg) = arg2 { + write!(self.out, ", ")?; + self.write_expr(module, arg, func_ctx)?; + } + if let Some(arg) = arg3 { + write!(self.out, ", ")?; + self.write_expr(module, arg, func_ctx)?; + } + write!(self.out, ")")? + } + Function::MissingIntOverload(fun_name) => { + let scalar_kind = func_ctx.resolve_type(arg, &module.types).scalar_kind(); + if let Some(ScalarKind::Sint) = scalar_kind { + write!(self.out, "asint({fun_name}(asuint(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ")))")?; + } else { + write!(self.out, "{fun_name}(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ")")?; + } + } + Function::MissingIntReturnType(fun_name) => { + let scalar_kind = func_ctx.resolve_type(arg, &module.types).scalar_kind(); + if let Some(ScalarKind::Sint) = scalar_kind { + write!(self.out, "asint({fun_name}(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "))")?; + } else { + write!(self.out, "{fun_name}(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ")")?; + } + } + Function::CountTrailingZeros => { + match *func_ctx.resolve_type(arg, &module.types) { + TypeInner::Vector { size, scalar } => { + let s = match size { + crate::VectorSize::Bi => ".xx", + crate::VectorSize::Tri => ".xxx", + crate::VectorSize::Quad => ".xxxx", + }; + + if let ScalarKind::Uint = scalar.kind { + write!(self.out, "min((32u){s}, firstbitlow(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "))")?; + } else { + write!(self.out, "asint(min((32u){s}, firstbitlow(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ")))")?; + } + } + TypeInner::Scalar(scalar) => { + if let ScalarKind::Uint = scalar.kind { + write!(self.out, "min(32u, firstbitlow(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "))")?; + } else { + write!(self.out, "asint(min(32u, firstbitlow(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ")))")?; + } + } + _ => unreachable!(), + } + + return Ok(()); + } + Function::CountLeadingZeros => { + match *func_ctx.resolve_type(arg, &module.types) { + TypeInner::Vector { size, scalar } => { + let s = match size { + crate::VectorSize::Bi => ".xx", + crate::VectorSize::Tri => ".xxx", + crate::VectorSize::Quad => ".xxxx", + }; + + if let ScalarKind::Uint = scalar.kind { + write!(self.out, "((31u){s} - firstbithigh(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "))")?; + } else { + write!(self.out, "(")?; + self.write_expr(module, arg, func_ctx)?; + write!( + self.out, + " < (0){s} ? (0){s} : (31){s} - asint(firstbithigh(" + )?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ")))")?; + } + } + TypeInner::Scalar(scalar) => { + if let ScalarKind::Uint = scalar.kind { + write!(self.out, "(31u - firstbithigh(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, "))")?; + } else { + write!(self.out, "(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, " < 0 ? 0 : 31 - asint(firstbithigh(")?; + self.write_expr(module, arg, func_ctx)?; + write!(self.out, ")))")?; + } + } + _ => unreachable!(), + } + + return Ok(()); + } + } + } + Expression::Swizzle { + size, + vector, + pattern, + } => { + self.write_expr(module, vector, func_ctx)?; + write!(self.out, ".")?; + for &sc in pattern[..size as usize].iter() { + self.out.write_char(back::COMPONENTS[sc as usize])?; + } + } + Expression::ArrayLength(expr) => { + let var_handle = match func_ctx.expressions[expr] { + Expression::AccessIndex { base, index: _ } => { + match func_ctx.expressions[base] { + Expression::GlobalVariable(handle) => handle, + _ => unreachable!(), + } + } + Expression::GlobalVariable(handle) => handle, + _ => unreachable!(), + }; + + let var = &module.global_variables[var_handle]; + let (offset, stride) = match module.types[var.ty].inner { + TypeInner::Array { stride, .. } => (0, stride), + TypeInner::Struct { ref members, .. } => { + let last = members.last().unwrap(); + let stride = match module.types[last.ty].inner { + TypeInner::Array { stride, .. } => stride, + _ => unreachable!(), + }; + (last.offset, stride) + } + _ => unreachable!(), + }; + + let storage_access = match var.space { + crate::AddressSpace::Storage { access } => access, + _ => crate::StorageAccess::default(), + }; + let wrapped_array_length = WrappedArrayLength { + writable: storage_access.contains(crate::StorageAccess::STORE), + }; + + write!(self.out, "((")?; + self.write_wrapped_array_length_function_name(wrapped_array_length)?; + let var_name = &self.names[&NameKey::GlobalVariable(var_handle)]; + write!(self.out, "({var_name}) - {offset}) / {stride})")? + } + Expression::Derivative { axis, ctrl, expr } => { + use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; + if axis == Axis::Width && (ctrl == Ctrl::Coarse || ctrl == Ctrl::Fine) { + let tail = match ctrl { + Ctrl::Coarse => "coarse", + Ctrl::Fine => "fine", + Ctrl::None => unreachable!(), + }; + write!(self.out, "abs(ddx_{tail}(")?; + self.write_expr(module, expr, func_ctx)?; + write!(self.out, ")) + abs(ddy_{tail}(")?; + self.write_expr(module, expr, func_ctx)?; + write!(self.out, "))")? + } else { + let fun_str = match (axis, ctrl) { + (Axis::X, Ctrl::Coarse) => "ddx_coarse", + (Axis::X, Ctrl::Fine) => "ddx_fine", + (Axis::X, Ctrl::None) => "ddx", + (Axis::Y, Ctrl::Coarse) => "ddy_coarse", + (Axis::Y, Ctrl::Fine) => "ddy_fine", + (Axis::Y, Ctrl::None) => "ddy", + (Axis::Width, Ctrl::Coarse | Ctrl::Fine) => unreachable!(), + (Axis::Width, Ctrl::None) => "fwidth", + }; + write!(self.out, "{fun_str}(")?; + self.write_expr(module, expr, func_ctx)?; + write!(self.out, ")")? + } + } + Expression::Relational { fun, argument } => { + use crate::RelationalFunction as Rf; + + let fun_str = match fun { + Rf::All => "all", + Rf::Any => "any", + Rf::IsNan => "isnan", + Rf::IsInf => "isinf", + }; + write!(self.out, "{fun_str}(")?; + self.write_expr(module, argument, func_ctx)?; + write!(self.out, ")")? + } + Expression::Select { + condition, + accept, + reject, + } => { + write!(self.out, "(")?; + self.write_expr(module, condition, func_ctx)?; + write!(self.out, " ? ")?; + self.write_expr(module, accept, func_ctx)?; + write!(self.out, " : ")?; + self.write_expr(module, reject, func_ctx)?; + write!(self.out, ")")? + } + // Not supported yet + Expression::RayQueryGetIntersection { .. } => unreachable!(), + // Nothing to do here, since call expression already cached + Expression::CallResult(_) + | Expression::AtomicResult { .. } + | Expression::WorkGroupUniformLoadResult { .. } + | Expression::RayQueryProceedResult => {} + } + + if !closing_bracket.is_empty() { + write!(self.out, "{closing_bracket}")?; + } + Ok(()) + } + + fn write_named_expr( + &mut self, + module: &Module, + handle: Handle, + name: String, + // The expression which is being named. + // Generally, this is the same as handle, except in WorkGroupUniformLoad + named: Handle, + ctx: &back::FunctionCtx, + ) -> BackendResult { + match ctx.info[named].ty { + proc::TypeResolution::Handle(ty_handle) => match module.types[ty_handle].inner { + TypeInner::Struct { .. } => { + let ty_name = &self.names[&NameKey::Type(ty_handle)]; + write!(self.out, "{ty_name}")?; + } + _ => { + self.write_type(module, ty_handle)?; + } + }, + proc::TypeResolution::Value(ref inner) => { + self.write_value_type(module, inner)?; + } + } + + let resolved = ctx.resolve_type(named, &module.types); + + write!(self.out, " {name}")?; + // If rhs is a array type, we should write array size + if let TypeInner::Array { base, size, .. } = *resolved { + self.write_array_size(module, base, size)?; + } + write!(self.out, " = ")?; + self.write_expr(module, handle, ctx)?; + writeln!(self.out, ";")?; + self.named_expressions.insert(named, name); + + Ok(()) + } + + /// Helper function that write default zero initialization + fn write_default_init(&mut self, module: &Module, ty: Handle) -> BackendResult { + write!(self.out, "(")?; + self.write_type(module, ty)?; + if let TypeInner::Array { base, size, .. } = module.types[ty].inner { + self.write_array_size(module, base, size)?; + } + write!(self.out, ")0")?; + Ok(()) + } + + fn write_barrier(&mut self, barrier: crate::Barrier, level: back::Level) -> BackendResult { + if barrier.contains(crate::Barrier::STORAGE) { + writeln!(self.out, "{level}DeviceMemoryBarrierWithGroupSync();")?; + } + if barrier.contains(crate::Barrier::WORK_GROUP) { + writeln!(self.out, "{level}GroupMemoryBarrierWithGroupSync();")?; + } + Ok(()) + } +} + +pub(super) struct MatrixType { + pub(super) columns: crate::VectorSize, + pub(super) rows: crate::VectorSize, + pub(super) width: crate::Bytes, +} + +pub(super) fn get_inner_matrix_data( + module: &Module, + handle: Handle, +) -> Option { + match module.types[handle].inner { + TypeInner::Matrix { + columns, + rows, + scalar, + } => Some(MatrixType { + columns, + rows, + width: scalar.width, + }), + TypeInner::Array { base, .. } => get_inner_matrix_data(module, base), + _ => None, + } +} + +/// Returns the matrix data if the access chain starting at `base`: +/// - starts with an expression with resolved type of [`TypeInner::Matrix`] if `direct = true` +/// - contains one or more expressions with resolved type of [`TypeInner::Array`] of [`TypeInner::Matrix`] +/// - ends at an expression with resolved type of [`TypeInner::Struct`] +pub(super) fn get_inner_matrix_of_struct_array_member( + module: &Module, + base: Handle, + func_ctx: &back::FunctionCtx<'_>, + direct: bool, +) -> Option { + let mut mat_data = None; + let mut array_base = None; + + let mut current_base = base; + loop { + let mut resolved = func_ctx.resolve_type(current_base, &module.types); + if let TypeInner::Pointer { base, .. } = *resolved { + resolved = &module.types[base].inner; + }; + + match *resolved { + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + mat_data = Some(MatrixType { + columns, + rows, + width: scalar.width, + }) + } + TypeInner::Array { base, .. } => { + array_base = Some(base); + } + TypeInner::Struct { .. } => { + if let Some(array_base) = array_base { + if direct { + return mat_data; + } else { + return get_inner_matrix_data(module, array_base); + } + } + + break; + } + _ => break, + } + + current_base = match func_ctx.expressions[current_base] { + crate::Expression::Access { base, .. } => base, + crate::Expression::AccessIndex { base, .. } => base, + _ => break, + }; + } + None +} + +/// Returns the matrix data if the access chain starting at `base`: +/// - starts with an expression with resolved type of [`TypeInner::Matrix`] +/// - contains zero or more expressions with resolved type of [`TypeInner::Array`] of [`TypeInner::Matrix`] +/// - ends with an [`Expression::GlobalVariable`](crate::Expression::GlobalVariable) in [`AddressSpace::Uniform`](crate::AddressSpace::Uniform) +fn get_inner_matrix_of_global_uniform( + module: &Module, + base: Handle, + func_ctx: &back::FunctionCtx<'_>, +) -> Option { + let mut mat_data = None; + let mut array_base = None; + + let mut current_base = base; + loop { + let mut resolved = func_ctx.resolve_type(current_base, &module.types); + if let TypeInner::Pointer { base, .. } = *resolved { + resolved = &module.types[base].inner; + }; + + match *resolved { + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + mat_data = Some(MatrixType { + columns, + rows, + width: scalar.width, + }) + } + TypeInner::Array { base, .. } => { + array_base = Some(base); + } + _ => break, + } + + current_base = match func_ctx.expressions[current_base] { + crate::Expression::Access { base, .. } => base, + crate::Expression::AccessIndex { base, .. } => base, + crate::Expression::GlobalVariable(handle) + if module.global_variables[handle].space == crate::AddressSpace::Uniform => + { + return mat_data.or_else(|| { + array_base.and_then(|array_base| get_inner_matrix_data(module, array_base)) + }) + } + _ => break, + }; + } + None +} diff --git a/naga/src/back/mod.rs b/naga/src/back/mod.rs new file mode 100644 index 0000000000..8100b930e9 --- /dev/null +++ b/naga/src/back/mod.rs @@ -0,0 +1,273 @@ +/*! +Backend functions that export shader [`Module`](super::Module)s into binary and text formats. +*/ +#![allow(dead_code)] // can be dead if none of the enabled backends need it + +#[cfg(feature = "dot-out")] +pub mod dot; +#[cfg(feature = "glsl-out")] +pub mod glsl; +#[cfg(feature = "hlsl-out")] +pub mod hlsl; +#[cfg(feature = "msl-out")] +pub mod msl; +#[cfg(feature = "spv-out")] +pub mod spv; +#[cfg(feature = "wgsl-out")] +pub mod wgsl; + +const COMPONENTS: &[char] = &['x', 'y', 'z', 'w']; +const INDENT: &str = " "; +const BAKE_PREFIX: &str = "_e"; + +type NeedBakeExpressions = crate::FastHashSet>; + +#[derive(Clone, Copy)] +struct Level(usize); + +impl Level { + const fn next(&self) -> Self { + Level(self.0 + 1) + } +} + +impl std::fmt::Display for Level { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + (0..self.0).try_for_each(|_| formatter.write_str(INDENT)) + } +} + +/// Whether we're generating an entry point or a regular function. +/// +/// Backend languages often require different code for a [`Function`] +/// depending on whether it represents an [`EntryPoint`] or not. +/// Backends can pass common code one of these values to select the +/// right behavior. +/// +/// These values also carry enough information to find the `Function` +/// in the [`Module`]: the `Handle` for a regular function, or the +/// index into [`Module::entry_points`] for an entry point. +/// +/// [`Function`]: crate::Function +/// [`EntryPoint`]: crate::EntryPoint +/// [`Module`]: crate::Module +/// [`Module::entry_points`]: crate::Module::entry_points +enum FunctionType { + /// A regular function. + Function(crate::Handle), + /// An [`EntryPoint`], and its index in [`Module::entry_points`]. + /// + /// [`EntryPoint`]: crate::EntryPoint + /// [`Module::entry_points`]: crate::Module::entry_points + EntryPoint(crate::proc::EntryPointIndex), +} + +impl FunctionType { + fn is_compute_entry_point(&self, module: &crate::Module) -> bool { + match *self { + FunctionType::EntryPoint(index) => { + module.entry_points[index as usize].stage == crate::ShaderStage::Compute + } + FunctionType::Function(_) => false, + } + } +} + +/// Helper structure that stores data needed when writing the function +struct FunctionCtx<'a> { + /// The current function being written + ty: FunctionType, + /// Analysis about the function + info: &'a crate::valid::FunctionInfo, + /// The expression arena of the current function being written + expressions: &'a crate::Arena, + /// Map of expressions that have associated variable names + named_expressions: &'a crate::NamedExpressions, +} + +impl FunctionCtx<'_> { + fn resolve_type<'a>( + &'a self, + handle: crate::Handle, + types: &'a crate::UniqueArena, + ) -> &'a crate::TypeInner { + self.info[handle].ty.inner_with(types) + } + + /// Helper method that generates a [`NameKey`](crate::proc::NameKey) for a local in the current function + const fn name_key(&self, local: crate::Handle) -> crate::proc::NameKey { + match self.ty { + FunctionType::Function(handle) => crate::proc::NameKey::FunctionLocal(handle, local), + FunctionType::EntryPoint(idx) => crate::proc::NameKey::EntryPointLocal(idx, local), + } + } + + /// Helper method that generates a [`NameKey`](crate::proc::NameKey) for a function argument. + /// + /// # Panics + /// - If the function arguments are less or equal to `arg` + const fn argument_key(&self, arg: u32) -> crate::proc::NameKey { + match self.ty { + FunctionType::Function(handle) => crate::proc::NameKey::FunctionArgument(handle, arg), + FunctionType::EntryPoint(ep_index) => { + crate::proc::NameKey::EntryPointArgument(ep_index, arg) + } + } + } + + // Returns true if the given expression points to a fixed-function pipeline input. + fn is_fixed_function_input( + &self, + mut expression: crate::Handle, + module: &crate::Module, + ) -> Option { + let ep_function = match self.ty { + FunctionType::Function(_) => return None, + FunctionType::EntryPoint(ep_index) => &module.entry_points[ep_index as usize].function, + }; + let mut built_in = None; + loop { + match self.expressions[expression] { + crate::Expression::FunctionArgument(arg_index) => { + return match ep_function.arguments[arg_index as usize].binding { + Some(crate::Binding::BuiltIn(bi)) => Some(bi), + _ => built_in, + }; + } + crate::Expression::AccessIndex { base, index } => { + match *self.resolve_type(base, &module.types) { + crate::TypeInner::Struct { ref members, .. } => { + if let Some(crate::Binding::BuiltIn(bi)) = + members[index as usize].binding + { + built_in = Some(bi); + } + } + _ => return None, + } + expression = base; + } + _ => return None, + } + } + } +} + +impl crate::Expression { + /// Returns the ref count, upon reaching which this expression + /// should be considered for baking. + /// + /// Note: we have to cache any expressions that depend on the control flow, + /// or otherwise they may be moved into a non-uniform control flow, accidentally. + /// See the [module-level documentation][emit] for details. + /// + /// [emit]: index.html#expression-evaluation-time + const fn bake_ref_count(&self) -> usize { + match *self { + // accesses are never cached, only loads are + crate::Expression::Access { .. } | crate::Expression::AccessIndex { .. } => usize::MAX, + // sampling may use the control flow, and image ops look better by themselves + crate::Expression::ImageSample { .. } | crate::Expression::ImageLoad { .. } => 1, + // derivatives use the control flow + crate::Expression::Derivative { .. } => 1, + // TODO: We need a better fix for named `Load` expressions + // More info - https://github.com/gfx-rs/naga/pull/914 + // And https://github.com/gfx-rs/naga/issues/910 + crate::Expression::Load { .. } => 1, + // cache expressions that are referenced multiple times + _ => 2, + } + } +} + +/// Helper function that returns the string corresponding to the [`BinaryOperator`](crate::BinaryOperator) +/// # Notes +/// Used by `glsl-out`, `msl-out`, `wgsl-out`, `hlsl-out`. +const fn binary_operation_str(op: crate::BinaryOperator) -> &'static str { + use crate::BinaryOperator as Bo; + match op { + Bo::Add => "+", + Bo::Subtract => "-", + Bo::Multiply => "*", + Bo::Divide => "/", + Bo::Modulo => "%", + Bo::Equal => "==", + Bo::NotEqual => "!=", + Bo::Less => "<", + Bo::LessEqual => "<=", + Bo::Greater => ">", + Bo::GreaterEqual => ">=", + Bo::And => "&", + Bo::ExclusiveOr => "^", + Bo::InclusiveOr => "|", + Bo::LogicalAnd => "&&", + Bo::LogicalOr => "||", + Bo::ShiftLeft => "<<", + Bo::ShiftRight => ">>", + } +} + +/// Helper function that returns the string corresponding to the [`VectorSize`](crate::VectorSize) +/// # Notes +/// Used by `msl-out`, `wgsl-out`, `hlsl-out`. +const fn vector_size_str(size: crate::VectorSize) -> &'static str { + match size { + crate::VectorSize::Bi => "2", + crate::VectorSize::Tri => "3", + crate::VectorSize::Quad => "4", + } +} + +impl crate::TypeInner { + const fn is_handle(&self) -> bool { + match *self { + crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } => true, + _ => false, + } + } +} + +impl crate::Statement { + /// Returns true if the statement directly terminates the current block. + /// + /// Used to decide whether case blocks require a explicit `break`. + pub const fn is_terminator(&self) -> bool { + match *self { + crate::Statement::Break + | crate::Statement::Continue + | crate::Statement::Return { .. } + | crate::Statement::Kill => true, + _ => false, + } + } +} + +bitflags::bitflags! { + /// Ray flags, for a [`RayDesc`]'s `flags` field. + /// + /// Note that these exactly correspond to the SPIR-V "Ray Flags" mask, and + /// the SPIR-V backend passes them directly through to the + /// `OpRayQueryInitializeKHR` instruction. (We have to choose something, so + /// we might as well make one back end's life easier.) + /// + /// [`RayDesc`]: crate::Module::generate_ray_desc_type + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct RayFlag: u32 { + const OPAQUE = 0x01; + const NO_OPAQUE = 0x02; + const TERMINATE_ON_FIRST_HIT = 0x04; + const SKIP_CLOSEST_HIT_SHADER = 0x08; + const CULL_BACK_FACING = 0x10; + const CULL_FRONT_FACING = 0x20; + const CULL_OPAQUE = 0x40; + const CULL_NO_OPAQUE = 0x80; + const SKIP_TRIANGLES = 0x100; + const SKIP_AABBS = 0x200; + } +} + +#[repr(u32)] +enum RayIntersectionType { + Triangle = 1, + BoundingBox = 4, +} diff --git a/naga/src/back/msl/keywords.rs b/naga/src/back/msl/keywords.rs new file mode 100644 index 0000000000..f0025bf239 --- /dev/null +++ b/naga/src/back/msl/keywords.rs @@ -0,0 +1,342 @@ +// MSLS - Metal Shading Language Specification: +// https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf +// +// C++ - Standard for Programming Language C++ (N4431) +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4431.pdf +pub const RESERVED: &[&str] = &[ + // Standard for Programming Language C++ (N4431): 2.5 Alternative tokens + "and", + "bitor", + "or", + "xor", + "compl", + "bitand", + "and_eq", + "or_eq", + "xor_eq", + "not", + "not_eq", + // Standard for Programming Language C++ (N4431): 2.11 Keywords + "alignas", + "alignof", + "asm", + "auto", + "bool", + "break", + "case", + "catch", + "char", + "char16_t", + "char32_t", + "class", + "const", + "constexpr", + "const_cast", + "continue", + "decltype", + "default", + "delete", + "do", + "double", + "dynamic_cast", + "else", + "enum", + "explicit", + "export", + "extern", + "false", + "float", + "for", + "friend", + "goto", + "if", + "inline", + "int", + "long", + "mutable", + "namespace", + "new", + "noexcept", + "nullptr", + "operator", + "private", + "protected", + "public", + "register", + "reinterpret_cast", + "return", + "short", + "signed", + "sizeof", + "static", + "static_assert", + "static_cast", + "struct", + "switch", + "template", + "this", + "thread_local", + "throw", + "true", + "try", + "typedef", + "typeid", + "typename", + "union", + "unsigned", + "using", + "virtual", + "void", + "volatile", + "wchar_t", + "while", + // Metal Shading Language Specification: 1.4.4 Restrictions + "main", + // Metal Shading Language Specification: 2.1 Scalar Data Types + "int8_t", + "uchar", + "uint8_t", + "int16_t", + "ushort", + "uint16_t", + "int32_t", + "uint", + "uint32_t", + "int64_t", + "uint64_t", + "half", + "bfloat", + "size_t", + "ptrdiff_t", + // Metal Shading Language Specification: 2.2 Vector Data Types + "bool2", + "bool3", + "bool4", + "char2", + "char3", + "char4", + "short2", + "short3", + "short4", + "int2", + "int3", + "int4", + "long2", + "long3", + "long4", + "uchar2", + "uchar3", + "uchar4", + "ushort2", + "ushort3", + "ushort4", + "uint2", + "uint3", + "uint4", + "ulong2", + "ulong3", + "ulong4", + "half2", + "half3", + "half4", + "bfloat2", + "bfloat3", + "bfloat4", + "float2", + "float3", + "float4", + "vec", + // Metal Shading Language Specification: 2.2.3 Packed Vector Types + "packed_bool2", + "packed_bool3", + "packed_bool4", + "packed_char2", + "packed_char3", + "packed_char4", + "packed_short2", + "packed_short3", + "packed_short4", + "packed_int2", + "packed_int3", + "packed_int4", + "packed_uchar2", + "packed_uchar3", + "packed_uchar4", + "packed_ushort2", + "packed_ushort3", + "packed_ushort4", + "packed_uint2", + "packed_uint3", + "packed_uint4", + "packed_half2", + "packed_half3", + "packed_half4", + "packed_bfloat2", + "packed_bfloat3", + "packed_bfloat4", + "packed_float2", + "packed_float3", + "packed_float4", + "packed_long2", + "packed_long3", + "packed_long4", + "packed_vec", + // Metal Shading Language Specification: 2.3 Matrix Data Types + "half2x2", + "half2x3", + "half2x4", + "half3x2", + "half3x3", + "half3x4", + "half4x2", + "half4x3", + "half4x4", + "float2x2", + "float2x3", + "float2x4", + "float3x2", + "float3x3", + "float3x4", + "float4x2", + "float4x3", + "float4x4", + "matrix", + // Metal Shading Language Specification: 2.6 Atomic Data Types + "atomic", + "atomic_int", + "atomic_uint", + "atomic_bool", + "atomic_ulong", + "atomic_float", + // Metal Shading Language Specification: 2.20 Type Conversions and Re-interpreting Data + "as_type", + // Metal Shading Language Specification: 4 Address Spaces + "device", + "constant", + "thread", + "threadgroup", + "threadgroup_imageblock", + "ray_data", + "object_data", + // Metal Shading Language Specification: 5.1 Functions + "vertex", + "fragment", + "kernel", + // Metal Shading Language Specification: 6.1 Namespace and Header Files + "metal", + // C99 / C++ extension: + "restrict", + // Metal reserved types in : + "llong", + "ullong", + "quad", + "complex", + "imaginary", + // Constants in : + "CHAR_BIT", + "SCHAR_MAX", + "SCHAR_MIN", + "UCHAR_MAX", + "CHAR_MAX", + "CHAR_MIN", + "USHRT_MAX", + "SHRT_MAX", + "SHRT_MIN", + "UINT_MAX", + "INT_MAX", + "INT_MIN", + "ULONG_MAX", + "LONG_MAX", + "LONG_MIN", + "ULLONG_MAX", + "LLONG_MAX", + "LLONG_MIN", + "FLT_DIG", + "FLT_MANT_DIG", + "FLT_MAX_10_EXP", + "FLT_MAX_EXP", + "FLT_MIN_10_EXP", + "FLT_MIN_EXP", + "FLT_RADIX", + "FLT_MAX", + "FLT_MIN", + "FLT_EPSILON", + "FLT_DECIMAL_DIG", + "FP_ILOGB0", + "FP_ILOGB0", + "FP_ILOGBNAN", + "FP_ILOGBNAN", + "MAXFLOAT", + "HUGE_VALF", + "INFINITY", + "NAN", + "M_E_F", + "M_LOG2E_F", + "M_LOG10E_F", + "M_LN2_F", + "M_LN10_F", + "M_PI_F", + "M_PI_2_F", + "M_PI_4_F", + "M_1_PI_F", + "M_2_PI_F", + "M_2_SQRTPI_F", + "M_SQRT2_F", + "M_SQRT1_2_F", + "HALF_DIG", + "HALF_MANT_DIG", + "HALF_MAX_10_EXP", + "HALF_MAX_EXP", + "HALF_MIN_10_EXP", + "HALF_MIN_EXP", + "HALF_RADIX", + "HALF_MAX", + "HALF_MIN", + "HALF_EPSILON", + "HALF_DECIMAL_DIG", + "MAXHALF", + "HUGE_VALH", + "M_E_H", + "M_LOG2E_H", + "M_LOG10E_H", + "M_LN2_H", + "M_LN10_H", + "M_PI_H", + "M_PI_2_H", + "M_PI_4_H", + "M_1_PI_H", + "M_2_PI_H", + "M_2_SQRTPI_H", + "M_SQRT2_H", + "M_SQRT1_2_H", + "DBL_DIG", + "DBL_MANT_DIG", + "DBL_MAX_10_EXP", + "DBL_MAX_EXP", + "DBL_MIN_10_EXP", + "DBL_MIN_EXP", + "DBL_RADIX", + "DBL_MAX", + "DBL_MIN", + "DBL_EPSILON", + "DBL_DECIMAL_DIG", + "MAXDOUBLE", + "HUGE_VAL", + "M_E", + "M_LOG2E", + "M_LOG10E", + "M_LN2", + "M_LN10", + "M_PI", + "M_PI_2", + "M_PI_4", + "M_1_PI", + "M_2_PI", + "M_2_SQRTPI", + "M_SQRT2", + "M_SQRT1_2", + // Naga utilities + "DefaultConstructible", + super::writer::FREXP_FUNCTION, + super::writer::MODF_FUNCTION, +]; diff --git a/naga/src/back/msl/mod.rs b/naga/src/back/msl/mod.rs new file mode 100644 index 0000000000..5ef18730c9 --- /dev/null +++ b/naga/src/back/msl/mod.rs @@ -0,0 +1,541 @@ +/*! +Backend for [MSL][msl] (Metal Shading Language). + +## Binding model + +Metal's bindings are flat per resource. Since there isn't an obvious mapping +from SPIR-V's descriptor sets, we require a separate mapping provided in the options. +This mapping may have one or more resource end points for each descriptor set + index +pair. + +## Entry points + +Even though MSL and our IR appear to be similar in that the entry points in both can +accept arguments and return values, the restrictions are different. +MSL allows the varyings to be either in separate arguments, or inside a single +`[[stage_in]]` struct. We gather input varyings and form this artificial structure. +We also add all the (non-Private) globals into the arguments. + +At the beginning of the entry point, we assign the local constants and re-compose +the arguments as they are declared on IR side, so that the rest of the logic can +pretend that MSL doesn't have all the restrictions it has. + +For the result type, if it's a structure, we re-compose it with a temporary value +holding the result. + +[msl]: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf +*/ + +use crate::{arena::Handle, proc::index, valid::ModuleInfo}; +use std::fmt::{Error as FmtError, Write}; + +mod keywords; +pub mod sampler; +mod writer; + +pub use writer::Writer; + +pub type Slot = u8; +pub type InlineSamplerIndex = u8; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub enum BindSamplerTarget { + Resource(Slot), + Inline(InlineSamplerIndex), +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))] +pub struct BindTarget { + pub buffer: Option, + pub texture: Option, + pub sampler: Option, + /// If the binding is an unsized binding array, this overrides the size. + pub binding_array_size: Option, + pub mutable: bool, +} + +// Using `BTreeMap` instead of `HashMap` so that we can hash itself. +pub type BindingMap = std::collections::BTreeMap; + +#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))] +pub struct EntryPointResources { + pub resources: BindingMap, + + pub push_constant_buffer: Option, + + /// The slot of a buffer that contains an array of `u32`, + /// one for the size of each bound buffer that contains a runtime array, + /// in order of [`crate::GlobalVariable`] declarations. + pub sizes_buffer: Option, +} + +pub type EntryPointResourceMap = std::collections::BTreeMap; + +enum ResolvedBinding { + BuiltIn(crate::BuiltIn), + Attribute(u32), + Color { + location: u32, + second_blend_source: bool, + }, + User { + prefix: &'static str, + index: u32, + interpolation: Option, + }, + Resource(BindTarget), +} + +#[derive(Copy, Clone)] +enum ResolvedInterpolation { + CenterPerspective, + CenterNoPerspective, + CentroidPerspective, + CentroidNoPerspective, + SamplePerspective, + SampleNoPerspective, + Flat, +} + +// Note: some of these should be removed in favor of proper IR validation. + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Format(#[from] FmtError), + #[error("bind target {0:?} is empty")] + UnimplementedBindTarget(BindTarget), + #[error("composing of {0:?} is not implemented yet")] + UnsupportedCompose(Handle), + #[error("operation {0:?} is not implemented yet")] + UnsupportedBinaryOp(crate::BinaryOperator), + #[error("standard function '{0}' is not implemented yet")] + UnsupportedCall(String), + #[error("feature '{0}' is not implemented yet")] + FeatureNotImplemented(String), + #[error("module is not valid")] + Validation, + #[error("BuiltIn {0:?} is not supported")] + UnsupportedBuiltIn(crate::BuiltIn), + #[error("capability {0:?} is not supported")] + CapabilityNotSupported(crate::valid::Capabilities), + #[error("attribute '{0}' is not supported for target MSL version")] + UnsupportedAttribute(String), + #[error("function '{0}' is not supported for target MSL version")] + UnsupportedFunction(String), + #[error("can not use writeable storage buffers in fragment stage prior to MSL 1.2")] + UnsupportedWriteableStorageBuffer, + #[error("can not use writeable storage textures in {0:?} stage prior to MSL 1.2")] + UnsupportedWriteableStorageTexture(crate::ShaderStage), + #[error("can not use read-write storage textures prior to MSL 1.2")] + UnsupportedRWStorageTexture, + #[error("array of '{0}' is not supported for target MSL version")] + UnsupportedArrayOf(String), + #[error("array of type '{0:?}' is not supported")] + UnsupportedArrayOfType(Handle), + #[error("ray tracing is not supported prior to MSL 2.3")] + UnsupportedRayTracing, +} + +#[derive(Clone, Debug, PartialEq, thiserror::Error)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub enum EntryPointError { + #[error("global '{0}' doesn't have a binding")] + MissingBinding(String), + #[error("mapping of {0:?} is missing")] + MissingBindTarget(crate::ResourceBinding), + #[error("mapping for push constants is missing")] + MissingPushConstants, + #[error("mapping for sizes buffer is missing")] + MissingSizesBuffer, +} + +/// Points in the MSL code where we might emit a pipeline input or output. +/// +/// Note that, even though vertex shaders' outputs are always fragment +/// shaders' inputs, we still need to distinguish `VertexOutput` and +/// `FragmentInput`, since there are certain differences in the way +/// [`ResolvedBinding`s] are represented on either side. +/// +/// [`ResolvedBinding`s]: ResolvedBinding +#[derive(Clone, Copy, Debug)] +enum LocationMode { + /// Input to the vertex shader. + VertexInput, + + /// Output from the vertex shader. + VertexOutput, + + /// Input to the fragment shader. + FragmentInput, + + /// Output from the fragment shader. + FragmentOutput, + + /// Compute shader input or output. + Uniform, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct Options { + /// (Major, Minor) target version of the Metal Shading Language. + pub lang_version: (u8, u8), + /// Map of entry-point resources, indexed by entry point function name, to slots. + pub per_entry_point_map: EntryPointResourceMap, + /// Samplers to be inlined into the code. + pub inline_samplers: Vec, + /// Make it possible to link different stages via SPIRV-Cross. + pub spirv_cross_compatibility: bool, + /// Don't panic on missing bindings, instead generate invalid MSL. + pub fake_missing_bindings: bool, + /// Bounds checking policies. + #[cfg_attr(feature = "deserialize", serde(default))] + pub bounds_check_policies: index::BoundsCheckPolicies, + /// Should workgroup variables be zero initialized (by polyfilling)? + pub zero_initialize_workgroup_memory: bool, +} + +impl Default for Options { + fn default() -> Self { + Options { + lang_version: (1, 0), + per_entry_point_map: EntryPointResourceMap::default(), + inline_samplers: Vec::new(), + spirv_cross_compatibility: false, + fake_missing_bindings: true, + bounds_check_policies: index::BoundsCheckPolicies::default(), + zero_initialize_workgroup_memory: true, + } + } +} + +/// A subset of options that are meant to be changed per pipeline. +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct PipelineOptions { + /// Allow `BuiltIn::PointSize` and inject it if doesn't exist. + /// + /// Metal doesn't like this for non-point primitive topologies and requires it for + /// point primitive topologies. + /// + /// Enable this for vertex shaders with point primitive topologies. + pub allow_and_force_point_size: bool, +} + +impl Options { + fn resolve_local_binding( + &self, + binding: &crate::Binding, + mode: LocationMode, + ) -> Result { + match *binding { + crate::Binding::BuiltIn(mut built_in) => { + match built_in { + crate::BuiltIn::Position { ref mut invariant } => { + if *invariant && self.lang_version < (2, 1) { + return Err(Error::UnsupportedAttribute("invariant".to_string())); + } + + // The 'invariant' attribute may only appear on vertex + // shader outputs, not fragment shader inputs. + if !matches!(mode, LocationMode::VertexOutput) { + *invariant = false; + } + } + crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => { + return Err(Error::UnsupportedAttribute("base_instance".to_string())); + } + crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => { + return Err(Error::UnsupportedAttribute("instance_id".to_string())); + } + // macOS: Since Metal 2.2 + // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/naga/issues/2164) + crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 2) => { + return Err(Error::UnsupportedAttribute("primitive_id".to_string())); + } + _ => {} + } + + Ok(ResolvedBinding::BuiltIn(built_in)) + } + crate::Binding::Location { + location, + interpolation, + sampling, + second_blend_source, + } => match mode { + LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)), + LocationMode::FragmentOutput => { + if second_blend_source && self.lang_version < (1, 2) { + return Err(Error::UnsupportedAttribute( + "second_blend_source".to_string(), + )); + } + Ok(ResolvedBinding::Color { + location, + second_blend_source, + }) + } + LocationMode::VertexOutput | LocationMode::FragmentInput => { + Ok(ResolvedBinding::User { + prefix: if self.spirv_cross_compatibility { + "locn" + } else { + "loc" + }, + index: location, + interpolation: { + // unwrap: The verifier ensures that vertex shader outputs and fragment + // shader inputs always have fully specified interpolation, and that + // sampling is `None` only for Flat interpolation. + let interpolation = interpolation.unwrap(); + let sampling = sampling.unwrap_or(crate::Sampling::Center); + Some(ResolvedInterpolation::from_binding(interpolation, sampling)) + }, + }) + } + LocationMode::Uniform => { + log::error!( + "Unexpected Binding::Location({}) for the Uniform mode", + location + ); + Err(Error::Validation) + } + }, + } + } + + fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> { + self.per_entry_point_map.get(&ep.name) + } + + fn get_resource_binding_target( + &self, + ep: &crate::EntryPoint, + res_binding: &crate::ResourceBinding, + ) -> Option<&BindTarget> { + self.get_entry_point_resources(ep) + .and_then(|res| res.resources.get(res_binding)) + } + + fn resolve_resource_binding( + &self, + ep: &crate::EntryPoint, + res_binding: &crate::ResourceBinding, + ) -> Result { + let target = self.get_resource_binding_target(ep, res_binding); + match target { + Some(target) => Ok(ResolvedBinding::Resource(target.clone())), + None if self.fake_missing_bindings => Ok(ResolvedBinding::User { + prefix: "fake", + index: 0, + interpolation: None, + }), + None => Err(EntryPointError::MissingBindTarget(res_binding.clone())), + } + } + + fn resolve_push_constants( + &self, + ep: &crate::EntryPoint, + ) -> Result { + let slot = self + .get_entry_point_resources(ep) + .and_then(|res| res.push_constant_buffer); + match slot { + Some(slot) => Ok(ResolvedBinding::Resource(BindTarget { + buffer: Some(slot), + ..Default::default() + })), + None if self.fake_missing_bindings => Ok(ResolvedBinding::User { + prefix: "fake", + index: 0, + interpolation: None, + }), + None => Err(EntryPointError::MissingPushConstants), + } + } + + fn resolve_sizes_buffer( + &self, + ep: &crate::EntryPoint, + ) -> Result { + let slot = self + .get_entry_point_resources(ep) + .and_then(|res| res.sizes_buffer); + match slot { + Some(slot) => Ok(ResolvedBinding::Resource(BindTarget { + buffer: Some(slot), + ..Default::default() + })), + None if self.fake_missing_bindings => Ok(ResolvedBinding::User { + prefix: "fake", + index: 0, + interpolation: None, + }), + None => Err(EntryPointError::MissingSizesBuffer), + } + } +} + +impl ResolvedBinding { + fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> { + match *self { + Self::Resource(BindTarget { + sampler: Some(BindSamplerTarget::Inline(index)), + .. + }) => Some(&options.inline_samplers[index as usize]), + _ => None, + } + } + + const fn as_bind_target(&self) -> Option<&BindTarget> { + match *self { + Self::Resource(ref target) => Some(target), + _ => None, + } + } + + fn try_fmt(&self, out: &mut W) -> Result<(), Error> { + write!(out, " [[")?; + match *self { + Self::BuiltIn(built_in) => { + use crate::BuiltIn as Bi; + let name = match built_in { + Bi::Position { invariant: false } => "position", + Bi::Position { invariant: true } => "position, invariant", + // vertex + Bi::BaseInstance => "base_instance", + Bi::BaseVertex => "base_vertex", + Bi::ClipDistance => "clip_distance", + Bi::InstanceIndex => "instance_id", + Bi::PointSize => "point_size", + Bi::VertexIndex => "vertex_id", + // fragment + Bi::FragDepth => "depth(any)", + Bi::PointCoord => "point_coord", + Bi::FrontFacing => "front_facing", + Bi::PrimitiveIndex => "primitive_id", + Bi::SampleIndex => "sample_id", + Bi::SampleMask => "sample_mask", + // compute + Bi::GlobalInvocationId => "thread_position_in_grid", + Bi::LocalInvocationId => "thread_position_in_threadgroup", + Bi::LocalInvocationIndex => "thread_index_in_threadgroup", + Bi::WorkGroupId => "threadgroup_position_in_grid", + Bi::WorkGroupSize => "dispatch_threads_per_threadgroup", + Bi::NumWorkGroups => "threadgroups_per_grid", + Bi::CullDistance | Bi::ViewIndex => { + return Err(Error::UnsupportedBuiltIn(built_in)) + } + }; + write!(out, "{name}")?; + } + Self::Attribute(index) => write!(out, "attribute({index})")?, + Self::Color { + location, + second_blend_source, + } => { + if second_blend_source { + write!(out, "color({location}) index(1)")? + } else { + write!(out, "color({location})")? + } + } + Self::User { + prefix, + index, + interpolation, + } => { + write!(out, "user({prefix}{index})")?; + if let Some(interpolation) = interpolation { + write!(out, ", ")?; + interpolation.try_fmt(out)?; + } + } + Self::Resource(ref target) => { + if let Some(id) = target.buffer { + write!(out, "buffer({id})")?; + } else if let Some(id) = target.texture { + write!(out, "texture({id})")?; + } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler { + write!(out, "sampler({id})")?; + } else { + return Err(Error::UnimplementedBindTarget(target.clone())); + } + } + } + write!(out, "]]")?; + Ok(()) + } +} + +impl ResolvedInterpolation { + const fn from_binding(interpolation: crate::Interpolation, sampling: crate::Sampling) -> Self { + use crate::Interpolation as I; + use crate::Sampling as S; + + match (interpolation, sampling) { + (I::Perspective, S::Center) => Self::CenterPerspective, + (I::Perspective, S::Centroid) => Self::CentroidPerspective, + (I::Perspective, S::Sample) => Self::SamplePerspective, + (I::Linear, S::Center) => Self::CenterNoPerspective, + (I::Linear, S::Centroid) => Self::CentroidNoPerspective, + (I::Linear, S::Sample) => Self::SampleNoPerspective, + (I::Flat, _) => Self::Flat, + } + } + + fn try_fmt(self, out: &mut W) -> Result<(), Error> { + let identifier = match self { + Self::CenterPerspective => "center_perspective", + Self::CenterNoPerspective => "center_no_perspective", + Self::CentroidPerspective => "centroid_perspective", + Self::CentroidNoPerspective => "centroid_no_perspective", + Self::SamplePerspective => "sample_perspective", + Self::SampleNoPerspective => "sample_no_perspective", + Self::Flat => "flat", + }; + out.write_str(identifier)?; + Ok(()) + } +} + +/// Information about a translated module that is required +/// for the use of the result. +pub struct TranslationInfo { + /// Mapping of the entry point names. Each item in the array + /// corresponds to an entry point index. + /// + ///Note: Some entry points may fail translation because of missing bindings. + pub entry_point_names: Vec>, +} + +pub fn write_string( + module: &crate::Module, + info: &ModuleInfo, + options: &Options, + pipeline_options: &PipelineOptions, +) -> Result<(String, TranslationInfo), Error> { + let mut w = writer::Writer::new(String::new()); + let info = w.write(module, info, options, pipeline_options)?; + Ok((w.finish(), info)) +} + +#[test] +fn test_error_size() { + use std::mem::size_of; + assert_eq!(size_of::(), 32); +} diff --git a/naga/src/back/msl/sampler.rs b/naga/src/back/msl/sampler.rs new file mode 100644 index 0000000000..0bf987076d --- /dev/null +++ b/naga/src/back/msl/sampler.rs @@ -0,0 +1,176 @@ +#[cfg(feature = "deserialize")] +use serde::Deserialize; +#[cfg(feature = "serialize")] +use serde::Serialize; +use std::{num::NonZeroU32, ops::Range}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +pub enum Coord { + Normalized, + Pixel, +} + +impl Default for Coord { + fn default() -> Self { + Self::Normalized + } +} + +impl Coord { + pub const fn as_str(&self) -> &'static str { + match *self { + Self::Normalized => "normalized", + Self::Pixel => "pixel", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +pub enum Address { + Repeat, + MirroredRepeat, + ClampToEdge, + ClampToZero, + ClampToBorder, +} + +impl Default for Address { + fn default() -> Self { + Self::ClampToEdge + } +} + +impl Address { + pub const fn as_str(&self) -> &'static str { + match *self { + Self::Repeat => "repeat", + Self::MirroredRepeat => "mirrored_repeat", + Self::ClampToEdge => "clamp_to_edge", + Self::ClampToZero => "clamp_to_zero", + Self::ClampToBorder => "clamp_to_border", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +pub enum BorderColor { + TransparentBlack, + OpaqueBlack, + OpaqueWhite, +} + +impl Default for BorderColor { + fn default() -> Self { + Self::TransparentBlack + } +} + +impl BorderColor { + pub const fn as_str(&self) -> &'static str { + match *self { + Self::TransparentBlack => "transparent_black", + Self::OpaqueBlack => "opaque_black", + Self::OpaqueWhite => "opaque_white", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +pub enum Filter { + Nearest, + Linear, +} + +impl Filter { + pub const fn as_str(&self) -> &'static str { + match *self { + Self::Nearest => "nearest", + Self::Linear => "linear", + } + } +} + +impl Default for Filter { + fn default() -> Self { + Self::Nearest + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +pub enum CompareFunc { + Never, + Less, + LessEqual, + Greater, + GreaterEqual, + Equal, + NotEqual, + Always, +} + +impl Default for CompareFunc { + fn default() -> Self { + Self::Never + } +} + +impl CompareFunc { + pub const fn as_str(&self) -> &'static str { + match *self { + Self::Never => "never", + Self::Less => "less", + Self::LessEqual => "less_equal", + Self::Greater => "greater", + Self::GreaterEqual => "greater_equal", + Self::Equal => "equal", + Self::NotEqual => "not_equal", + Self::Always => "always", + } + } +} + +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +pub struct InlineSampler { + pub coord: Coord, + pub address: [Address; 3], + pub border_color: BorderColor, + pub mag_filter: Filter, + pub min_filter: Filter, + pub mip_filter: Option, + pub lod_clamp: Option>, + pub max_anisotropy: Option, + pub compare_func: CompareFunc, +} + +impl Eq for InlineSampler {} + +#[allow(renamed_and_removed_lints)] +#[allow(clippy::derive_hash_xor_eq)] +impl std::hash::Hash for InlineSampler { + fn hash(&self, hasher: &mut H) { + self.coord.hash(hasher); + self.address.hash(hasher); + self.border_color.hash(hasher); + self.mag_filter.hash(hasher); + self.min_filter.hash(hasher); + self.mip_filter.hash(hasher); + self.lod_clamp + .as_ref() + .map(|range| (range.start.to_bits(), range.end.to_bits())) + .hash(hasher); + self.max_anisotropy.hash(hasher); + self.compare_func.hash(hasher); + } +} diff --git a/naga/src/back/msl/writer.rs b/naga/src/back/msl/writer.rs new file mode 100644 index 0000000000..f900add71e --- /dev/null +++ b/naga/src/back/msl/writer.rs @@ -0,0 +1,4659 @@ +use super::{sampler as sm, Error, LocationMode, Options, PipelineOptions, TranslationInfo}; +use crate::{ + arena::Handle, + back, + proc::index, + proc::{self, NameKey, TypeResolution}, + valid, FastHashMap, FastHashSet, +}; +use bit_set::BitSet; +use std::{ + fmt::{Display, Error as FmtError, Formatter, Write}, + iter, +}; + +/// Shorthand result used internally by the backend +type BackendResult = Result<(), Error>; + +const NAMESPACE: &str = "metal"; +// The name of the array member of the Metal struct types we generate to +// represent Naga `Array` types. See the comments in `Writer::write_type_defs` +// for details. +const WRAPPED_ARRAY_FIELD: &str = "inner"; +// This is a hack: we need to pass a pointer to an atomic, +// but generally the backend isn't putting "&" in front of every pointer. +// Some more general handling of pointers is needed to be implemented here. +const ATOMIC_REFERENCE: &str = "&"; + +const RT_NAMESPACE: &str = "metal::raytracing"; +const RAY_QUERY_TYPE: &str = "_RayQuery"; +const RAY_QUERY_FIELD_INTERSECTOR: &str = "intersector"; +const RAY_QUERY_FIELD_INTERSECTION: &str = "intersection"; +const RAY_QUERY_FIELD_READY: &str = "ready"; +const RAY_QUERY_FUN_MAP_INTERSECTION: &str = "_map_intersection_type"; + +pub(crate) const MODF_FUNCTION: &str = "naga_modf"; +pub(crate) const FREXP_FUNCTION: &str = "naga_frexp"; + +/// Write the Metal name for a Naga numeric type: scalar, vector, or matrix. +/// +/// The `sizes` slice determines whether this function writes a +/// scalar, vector, or matrix type: +/// +/// - An empty slice produces a scalar type. +/// - A one-element slice produces a vector type. +/// - A two element slice `[ROWS COLUMNS]` produces a matrix of the given size. +fn put_numeric_type( + out: &mut impl Write, + scalar: crate::Scalar, + sizes: &[crate::VectorSize], +) -> Result<(), FmtError> { + match (scalar, sizes) { + (scalar, &[]) => { + write!(out, "{}", scalar.to_msl_name()) + } + (scalar, &[rows]) => { + write!( + out, + "{}::{}{}", + NAMESPACE, + scalar.to_msl_name(), + back::vector_size_str(rows) + ) + } + (scalar, &[rows, columns]) => { + write!( + out, + "{}::{}{}x{}", + NAMESPACE, + scalar.to_msl_name(), + back::vector_size_str(columns), + back::vector_size_str(rows) + ) + } + (_, _) => Ok(()), // not meaningful + } +} + +/// Prefix for cached clamped level-of-detail values for `ImageLoad` expressions. +const CLAMPED_LOD_LOAD_PREFIX: &str = "clamped_lod_e"; + +struct TypeContext<'a> { + handle: Handle, + gctx: proc::GlobalCtx<'a>, + names: &'a FastHashMap, + access: crate::StorageAccess, + binding: Option<&'a super::ResolvedBinding>, + first_time: bool, +} + +impl<'a> Display for TypeContext<'a> { + fn fmt(&self, out: &mut Formatter<'_>) -> Result<(), FmtError> { + let ty = &self.gctx.types[self.handle]; + if ty.needs_alias() && !self.first_time { + let name = &self.names[&NameKey::Type(self.handle)]; + return write!(out, "{name}"); + } + + match ty.inner { + crate::TypeInner::Scalar(scalar) => put_numeric_type(out, scalar, &[]), + crate::TypeInner::Atomic(scalar) => { + write!(out, "{}::atomic_{}", NAMESPACE, scalar.to_msl_name()) + } + crate::TypeInner::Vector { size, scalar } => put_numeric_type(out, scalar, &[size]), + crate::TypeInner::Matrix { columns, rows, .. } => { + put_numeric_type(out, crate::Scalar::F32, &[rows, columns]) + } + crate::TypeInner::Pointer { base, space } => { + let sub = Self { + handle: base, + first_time: false, + ..*self + }; + let space_name = match space.to_msl_name() { + Some(name) => name, + None => return Ok(()), + }; + write!(out, "{space_name} {sub}&") + } + crate::TypeInner::ValuePointer { + size, + scalar, + space, + } => { + match space.to_msl_name() { + Some(name) => write!(out, "{name} ")?, + None => return Ok(()), + }; + match size { + Some(rows) => put_numeric_type(out, scalar, &[rows])?, + None => put_numeric_type(out, scalar, &[])?, + }; + + write!(out, "&") + } + crate::TypeInner::Array { base, .. } => { + let sub = Self { + handle: base, + first_time: false, + ..*self + }; + // Array lengths go at the end of the type definition, + // so just print the element type here. + write!(out, "{sub}") + } + crate::TypeInner::Struct { .. } => unreachable!(), + crate::TypeInner::Image { + dim, + arrayed, + class, + } => { + let dim_str = match dim { + crate::ImageDimension::D1 => "1d", + crate::ImageDimension::D2 => "2d", + crate::ImageDimension::D3 => "3d", + crate::ImageDimension::Cube => "cube", + }; + let (texture_str, msaa_str, kind, access) = match class { + crate::ImageClass::Sampled { kind, multi } => { + let (msaa_str, access) = if multi { + ("_ms", "read") + } else { + ("", "sample") + }; + ("texture", msaa_str, kind, access) + } + crate::ImageClass::Depth { multi } => { + let (msaa_str, access) = if multi { + ("_ms", "read") + } else { + ("", "sample") + }; + ("depth", msaa_str, crate::ScalarKind::Float, access) + } + crate::ImageClass::Storage { format, .. } => { + let access = if self + .access + .contains(crate::StorageAccess::LOAD | crate::StorageAccess::STORE) + { + "read_write" + } else if self.access.contains(crate::StorageAccess::STORE) { + "write" + } else if self.access.contains(crate::StorageAccess::LOAD) { + "read" + } else { + log::warn!( + "Storage access for {:?} (name '{}'): {:?}", + self.handle, + ty.name.as_deref().unwrap_or_default(), + self.access + ); + unreachable!("module is not valid"); + }; + ("texture", "", format.into(), access) + } + }; + let base_name = crate::Scalar { kind, width: 4 }.to_msl_name(); + let array_str = if arrayed { "_array" } else { "" }; + write!( + out, + "{NAMESPACE}::{texture_str}{dim_str}{msaa_str}{array_str}<{base_name}, {NAMESPACE}::access::{access}>", + ) + } + crate::TypeInner::Sampler { comparison: _ } => { + write!(out, "{NAMESPACE}::sampler") + } + crate::TypeInner::AccelerationStructure => { + write!(out, "{RT_NAMESPACE}::instance_acceleration_structure") + } + crate::TypeInner::RayQuery => { + write!(out, "{RAY_QUERY_TYPE}") + } + crate::TypeInner::BindingArray { base, size } => { + let base_tyname = Self { + handle: base, + first_time: false, + ..*self + }; + + if let Some(&super::ResolvedBinding::Resource(super::BindTarget { + binding_array_size: Some(override_size), + .. + })) = self.binding + { + write!(out, "{NAMESPACE}::array<{base_tyname}, {override_size}>") + } else if let crate::ArraySize::Constant(size) = size { + write!(out, "{NAMESPACE}::array<{base_tyname}, {size}>") + } else { + unreachable!("metal requires all arrays be constant sized"); + } + } + } + } +} + +struct TypedGlobalVariable<'a> { + module: &'a crate::Module, + names: &'a FastHashMap, + handle: Handle, + usage: valid::GlobalUse, + binding: Option<&'a super::ResolvedBinding>, + reference: bool, +} + +impl<'a> TypedGlobalVariable<'a> { + fn try_fmt(&self, out: &mut W) -> BackendResult { + let var = &self.module.global_variables[self.handle]; + let name = &self.names[&NameKey::GlobalVariable(self.handle)]; + + let storage_access = match var.space { + crate::AddressSpace::Storage { access } => access, + _ => match self.module.types[var.ty].inner { + crate::TypeInner::Image { + class: crate::ImageClass::Storage { access, .. }, + .. + } => access, + crate::TypeInner::BindingArray { base, .. } => { + match self.module.types[base].inner { + crate::TypeInner::Image { + class: crate::ImageClass::Storage { access, .. }, + .. + } => access, + _ => crate::StorageAccess::default(), + } + } + _ => crate::StorageAccess::default(), + }, + }; + let ty_name = TypeContext { + handle: var.ty, + gctx: self.module.to_ctx(), + names: self.names, + access: storage_access, + binding: self.binding, + first_time: false, + }; + + let (space, access, reference) = match var.space.to_msl_name() { + Some(space) if self.reference => { + let access = if var.space.needs_access_qualifier() + && !self.usage.contains(valid::GlobalUse::WRITE) + { + "const" + } else { + "" + }; + (space, access, "&") + } + _ => ("", "", ""), + }; + + Ok(write!( + out, + "{}{}{}{}{}{} {}", + space, + if space.is_empty() { "" } else { " " }, + ty_name, + if access.is_empty() { "" } else { " " }, + access, + reference, + name, + )?) + } +} + +pub struct Writer { + out: W, + names: FastHashMap, + named_expressions: crate::NamedExpressions, + /// Set of expressions that need to be baked to avoid unnecessary repetition in output + need_bake_expressions: back::NeedBakeExpressions, + namer: proc::Namer, + #[cfg(test)] + put_expression_stack_pointers: FastHashSet<*const ()>, + #[cfg(test)] + put_block_stack_pointers: FastHashSet<*const ()>, + /// Set of (struct type, struct field index) denoting which fields require + /// padding inserted **before** them (i.e. between fields at index - 1 and index) + struct_member_pads: FastHashSet<(Handle, u32)>, +} + +impl crate::Scalar { + const fn to_msl_name(self) -> &'static str { + use crate::ScalarKind as Sk; + match self { + Self { + kind: Sk::Float, + width: _, + } => "float", + Self { + kind: Sk::Sint, + width: _, + } => "int", + Self { + kind: Sk::Uint, + width: _, + } => "uint", + Self { + kind: Sk::Bool, + width: _, + } => "bool", + Self { + kind: Sk::AbstractInt | Sk::AbstractFloat, + width: _, + } => unreachable!(), + } + } +} + +const fn separate(need_separator: bool) -> &'static str { + if need_separator { + "," + } else { + "" + } +} + +fn should_pack_struct_member( + members: &[crate::StructMember], + span: u32, + index: usize, + module: &crate::Module, +) -> Option { + let member = &members[index]; + + let ty_inner = &module.types[member.ty].inner; + let last_offset = member.offset + ty_inner.size(module.to_ctx()); + let next_offset = match members.get(index + 1) { + Some(next) => next.offset, + None => span, + }; + let is_tight = next_offset == last_offset; + + match *ty_inner { + crate::TypeInner::Vector { + size: crate::VectorSize::Tri, + scalar: scalar @ crate::Scalar { width: 4, .. }, + } if is_tight => Some(scalar), + _ => None, + } +} + +fn needs_array_length(ty: Handle, arena: &crate::UniqueArena) -> bool { + match arena[ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + if let Some(member) = members.last() { + if let crate::TypeInner::Array { + size: crate::ArraySize::Dynamic, + .. + } = arena[member.ty].inner + { + return true; + } + } + false + } + crate::TypeInner::Array { + size: crate::ArraySize::Dynamic, + .. + } => true, + _ => false, + } +} + +impl crate::AddressSpace { + /// Returns true if global variables in this address space are + /// passed in function arguments. These arguments need to be + /// passed through any functions called from the entry point. + const fn needs_pass_through(&self) -> bool { + match *self { + Self::Uniform + | Self::Storage { .. } + | Self::Private + | Self::WorkGroup + | Self::PushConstant + | Self::Handle => true, + Self::Function => false, + } + } + + /// Returns true if the address space may need a "const" qualifier. + const fn needs_access_qualifier(&self) -> bool { + match *self { + //Note: we are ignoring the storage access here, and instead + // rely on the actual use of a global by functions. This means we + // may end up with "const" even if the binding is read-write, + // and that should be OK. + Self::Storage { .. } => true, + // These should always be read-write. + Self::Private | Self::WorkGroup => false, + // These translate to `constant` address space, no need for qualifiers. + Self::Uniform | Self::PushConstant => false, + // Not applicable. + Self::Handle | Self::Function => false, + } + } + + const fn to_msl_name(self) -> Option<&'static str> { + match self { + Self::Handle => None, + Self::Uniform | Self::PushConstant => Some("constant"), + Self::Storage { .. } => Some("device"), + Self::Private | Self::Function => Some("thread"), + Self::WorkGroup => Some("threadgroup"), + } + } +} + +impl crate::Type { + // Returns `true` if we need to emit an alias for this type. + const fn needs_alias(&self) -> bool { + use crate::TypeInner as Ti; + + match self.inner { + // value types are concise enough, we only alias them if they are named + Ti::Scalar(_) + | Ti::Vector { .. } + | Ti::Matrix { .. } + | Ti::Atomic(_) + | Ti::Pointer { .. } + | Ti::ValuePointer { .. } => self.name.is_some(), + // composite types are better to be aliased, regardless of the name + Ti::Struct { .. } | Ti::Array { .. } => true, + // handle types may be different, depending on the global var access, so we always inline them + Ti::Image { .. } + | Ti::Sampler { .. } + | Ti::AccelerationStructure + | Ti::RayQuery + | Ti::BindingArray { .. } => false, + } + } +} + +enum FunctionOrigin { + Handle(Handle), + EntryPoint(proc::EntryPointIndex), +} + +/// A level of detail argument. +/// +/// When [`BoundsCheckPolicy::Restrict`] applies to an [`ImageLoad`] access, we +/// save the clamped level of detail in a temporary variable whose name is based +/// on the handle of the `ImageLoad` expression. But for other policies, we just +/// use the expression directly. +/// +/// [`BoundsCheckPolicy::Restrict`]: index::BoundsCheckPolicy::Restrict +/// [`ImageLoad`]: crate::Expression::ImageLoad +#[derive(Clone, Copy)] +enum LevelOfDetail { + Direct(Handle), + Restricted(Handle), +} + +/// Values needed to select a particular texel for [`ImageLoad`] and [`ImageStore`]. +/// +/// When this is used in code paths unconcerned with the `Restrict` bounds check +/// policy, the `LevelOfDetail` enum introduces an unneeded match, since `level` +/// will always be either `None` or `Some(Direct(_))`. But this turns out not to +/// be too awkward. If that changes, we can revisit. +/// +/// [`ImageLoad`]: crate::Expression::ImageLoad +/// [`ImageStore`]: crate::Statement::ImageStore +struct TexelAddress { + coordinate: Handle, + array_index: Option>, + sample: Option>, + level: Option, +} + +struct ExpressionContext<'a> { + function: &'a crate::Function, + origin: FunctionOrigin, + info: &'a valid::FunctionInfo, + module: &'a crate::Module, + mod_info: &'a valid::ModuleInfo, + pipeline_options: &'a PipelineOptions, + lang_version: (u8, u8), + policies: index::BoundsCheckPolicies, + + /// A bitset containing the `Expression` handle indexes of expressions used + /// as indices in `ReadZeroSkipWrite`-policy accesses. These may need to be + /// cached in temporary variables. See `index::find_checked_indexes` for + /// details. + guarded_indices: BitSet, +} + +impl<'a> ExpressionContext<'a> { + fn resolve_type(&self, handle: Handle) -> &'a crate::TypeInner { + self.info[handle].ty.inner_with(&self.module.types) + } + + /// Return true if calls to `image`'s `read` and `write` methods should supply a level of detail. + /// + /// Only mipmapped images need to specify a level of detail. Since 1D + /// textures cannot have mipmaps, MSL requires that the level argument to + /// texture1d queries and accesses must be a constexpr 0. It's easiest + /// just to omit the level entirely for 1D textures. + fn image_needs_lod(&self, image: Handle) -> bool { + let image_ty = self.resolve_type(image); + if let crate::TypeInner::Image { dim, class, .. } = *image_ty { + class.is_mipmapped() && dim != crate::ImageDimension::D1 + } else { + false + } + } + + fn choose_bounds_check_policy( + &self, + pointer: Handle, + ) -> index::BoundsCheckPolicy { + self.policies + .choose_policy(pointer, &self.module.types, self.info) + } + + fn access_needs_check( + &self, + base: Handle, + index: index::GuardedIndex, + ) -> Option { + index::access_needs_check(base, index, self.module, self.function, self.info) + } + + fn get_packed_vec_kind(&self, expr_handle: Handle) -> Option { + match self.function.expressions[expr_handle] { + crate::Expression::AccessIndex { base, index } => { + let ty = match *self.resolve_type(base) { + crate::TypeInner::Pointer { base, .. } => &self.module.types[base].inner, + ref ty => ty, + }; + match *ty { + crate::TypeInner::Struct { + ref members, span, .. + } => should_pack_struct_member(members, span, index as usize, self.module), + _ => None, + } + } + _ => None, + } + } +} + +struct StatementContext<'a> { + expression: ExpressionContext<'a>, + result_struct: Option<&'a str>, +} + +impl Writer { + /// Creates a new `Writer` instance. + pub fn new(out: W) -> Self { + Writer { + out, + names: FastHashMap::default(), + named_expressions: Default::default(), + need_bake_expressions: Default::default(), + namer: proc::Namer::default(), + #[cfg(test)] + put_expression_stack_pointers: Default::default(), + #[cfg(test)] + put_block_stack_pointers: Default::default(), + struct_member_pads: FastHashSet::default(), + } + } + + /// Finishes writing and returns the output. + // See https://github.com/rust-lang/rust-clippy/issues/4979. + #[allow(clippy::missing_const_for_fn)] + pub fn finish(self) -> W { + self.out + } + + fn put_call_parameters( + &mut self, + parameters: impl Iterator>, + context: &ExpressionContext, + ) -> BackendResult { + self.put_call_parameters_impl(parameters, context, |writer, context, expr| { + writer.put_expression(expr, context, true) + }) + } + + fn put_call_parameters_impl( + &mut self, + parameters: impl Iterator>, + ctx: &C, + put_expression: E, + ) -> BackendResult + where + E: Fn(&mut Self, &C, Handle) -> BackendResult, + { + write!(self.out, "(")?; + for (i, handle) in parameters.enumerate() { + if i != 0 { + write!(self.out, ", ")?; + } + put_expression(self, ctx, handle)?; + } + write!(self.out, ")")?; + Ok(()) + } + + fn put_level_of_detail( + &mut self, + level: LevelOfDetail, + context: &ExpressionContext, + ) -> BackendResult { + match level { + LevelOfDetail::Direct(expr) => self.put_expression(expr, context, true)?, + LevelOfDetail::Restricted(load) => { + write!(self.out, "{}{}", CLAMPED_LOD_LOAD_PREFIX, load.index())? + } + } + Ok(()) + } + + fn put_image_query( + &mut self, + image: Handle, + query: &str, + level: Option, + context: &ExpressionContext, + ) -> BackendResult { + self.put_expression(image, context, false)?; + write!(self.out, ".get_{query}(")?; + if let Some(level) = level { + self.put_level_of_detail(level, context)?; + } + write!(self.out, ")")?; + Ok(()) + } + + fn put_image_size_query( + &mut self, + image: Handle, + level: Option, + kind: crate::ScalarKind, + context: &ExpressionContext, + ) -> BackendResult { + //Note: MSL only has separate width/height/depth queries, + // so compose the result of them. + let dim = match *context.resolve_type(image) { + crate::TypeInner::Image { dim, .. } => dim, + ref other => unreachable!("Unexpected type {:?}", other), + }; + let scalar = crate::Scalar { kind, width: 4 }; + let coordinate_type = scalar.to_msl_name(); + match dim { + crate::ImageDimension::D1 => { + // Since 1D textures never have mipmaps, MSL requires that the + // `level` argument be a constexpr 0. It's simplest for us just + // to pass `None` and omit the level entirely. + if kind == crate::ScalarKind::Uint { + // No need to construct a vector. No cast needed. + self.put_image_query(image, "width", None, context)?; + } else { + // There's no definition for `int` in the `metal` namespace. + write!(self.out, "int(")?; + self.put_image_query(image, "width", None, context)?; + write!(self.out, ")")?; + } + } + crate::ImageDimension::D2 => { + write!(self.out, "{NAMESPACE}::{coordinate_type}2(")?; + self.put_image_query(image, "width", level, context)?; + write!(self.out, ", ")?; + self.put_image_query(image, "height", level, context)?; + write!(self.out, ")")?; + } + crate::ImageDimension::D3 => { + write!(self.out, "{NAMESPACE}::{coordinate_type}3(")?; + self.put_image_query(image, "width", level, context)?; + write!(self.out, ", ")?; + self.put_image_query(image, "height", level, context)?; + write!(self.out, ", ")?; + self.put_image_query(image, "depth", level, context)?; + write!(self.out, ")")?; + } + crate::ImageDimension::Cube => { + write!(self.out, "{NAMESPACE}::{coordinate_type}2(")?; + self.put_image_query(image, "width", level, context)?; + write!(self.out, ")")?; + } + } + Ok(()) + } + + fn put_cast_to_uint_scalar_or_vector( + &mut self, + expr: Handle, + context: &ExpressionContext, + ) -> BackendResult { + // coordinates in IR are int, but Metal expects uint + match *context.resolve_type(expr) { + crate::TypeInner::Scalar(_) => { + put_numeric_type(&mut self.out, crate::Scalar::U32, &[])? + } + crate::TypeInner::Vector { size, .. } => { + put_numeric_type(&mut self.out, crate::Scalar::U32, &[size])? + } + _ => return Err(Error::Validation), + }; + + write!(self.out, "(")?; + self.put_expression(expr, context, true)?; + write!(self.out, ")")?; + Ok(()) + } + + fn put_image_sample_level( + &mut self, + image: Handle, + level: crate::SampleLevel, + context: &ExpressionContext, + ) -> BackendResult { + let has_levels = context.image_needs_lod(image); + match level { + crate::SampleLevel::Auto => {} + crate::SampleLevel::Zero => { + //TODO: do we support Zero on `Sampled` image classes? + } + _ if !has_levels => { + log::warn!("1D image can't be sampled with level {:?}", level); + } + crate::SampleLevel::Exact(h) => { + write!(self.out, ", {NAMESPACE}::level(")?; + self.put_expression(h, context, true)?; + write!(self.out, ")")?; + } + crate::SampleLevel::Bias(h) => { + write!(self.out, ", {NAMESPACE}::bias(")?; + self.put_expression(h, context, true)?; + write!(self.out, ")")?; + } + crate::SampleLevel::Gradient { x, y } => { + write!(self.out, ", {NAMESPACE}::gradient2d(")?; + self.put_expression(x, context, true)?; + write!(self.out, ", ")?; + self.put_expression(y, context, true)?; + write!(self.out, ")")?; + } + } + Ok(()) + } + + fn put_image_coordinate_limits( + &mut self, + image: Handle, + level: Option, + context: &ExpressionContext, + ) -> BackendResult { + self.put_image_size_query(image, level, crate::ScalarKind::Uint, context)?; + write!(self.out, " - 1")?; + Ok(()) + } + + /// General function for writing restricted image indexes. + /// + /// This is used to produce restricted mip levels, array indices, and sample + /// indices for [`ImageLoad`] and [`ImageStore`] accesses under the + /// [`Restrict`] bounds check policy. + /// + /// This function writes an expression of the form: + /// + /// ```ignore + /// + /// metal::min(uint(INDEX), IMAGE.LIMIT_METHOD() - 1) + /// + /// ``` + /// + /// [`ImageLoad`]: crate::Expression::ImageLoad + /// [`ImageStore`]: crate::Statement::ImageStore + /// [`Restrict`]: index::BoundsCheckPolicy::Restrict + fn put_restricted_scalar_image_index( + &mut self, + image: Handle, + index: Handle, + limit_method: &str, + context: &ExpressionContext, + ) -> BackendResult { + write!(self.out, "{NAMESPACE}::min(uint(")?; + self.put_expression(index, context, true)?; + write!(self.out, "), ")?; + self.put_expression(image, context, false)?; + write!(self.out, ".{limit_method}() - 1)")?; + Ok(()) + } + + fn put_restricted_texel_address( + &mut self, + image: Handle, + address: &TexelAddress, + context: &ExpressionContext, + ) -> BackendResult { + // Write the coordinate. + write!(self.out, "{NAMESPACE}::min(")?; + self.put_cast_to_uint_scalar_or_vector(address.coordinate, context)?; + write!(self.out, ", ")?; + self.put_image_coordinate_limits(image, address.level, context)?; + write!(self.out, ")")?; + + // Write the array index, if present. + if let Some(array_index) = address.array_index { + write!(self.out, ", ")?; + self.put_restricted_scalar_image_index(image, array_index, "get_array_size", context)?; + } + + // Write the sample index, if present. + if let Some(sample) = address.sample { + write!(self.out, ", ")?; + self.put_restricted_scalar_image_index(image, sample, "get_num_samples", context)?; + } + + // The level of detail should be clamped and cached by + // `put_cache_restricted_level`, so we don't need to clamp it here. + if let Some(level) = address.level { + write!(self.out, ", ")?; + self.put_level_of_detail(level, context)?; + } + + Ok(()) + } + + /// Write an expression that is true if the given image access is in bounds. + fn put_image_access_bounds_check( + &mut self, + image: Handle, + address: &TexelAddress, + context: &ExpressionContext, + ) -> BackendResult { + let mut conjunction = ""; + + // First, check the level of detail. Only if that is in bounds can we + // use it to find the appropriate bounds for the coordinates. + let level = if let Some(level) = address.level { + write!(self.out, "uint(")?; + self.put_level_of_detail(level, context)?; + write!(self.out, ") < ")?; + self.put_expression(image, context, true)?; + write!(self.out, ".get_num_mip_levels()")?; + conjunction = " && "; + Some(level) + } else { + None + }; + + // Check sample index, if present. + if let Some(sample) = address.sample { + write!(self.out, "uint(")?; + self.put_expression(sample, context, true)?; + write!(self.out, ") < ")?; + self.put_expression(image, context, true)?; + write!(self.out, ".get_num_samples()")?; + conjunction = " && "; + } + + // Check array index, if present. + if let Some(array_index) = address.array_index { + write!(self.out, "{conjunction}uint(")?; + self.put_expression(array_index, context, true)?; + write!(self.out, ") < ")?; + self.put_expression(image, context, true)?; + write!(self.out, ".get_array_size()")?; + conjunction = " && "; + } + + // Finally, check if the coordinates are within bounds. + let coord_is_vector = match *context.resolve_type(address.coordinate) { + crate::TypeInner::Vector { .. } => true, + _ => false, + }; + write!(self.out, "{conjunction}")?; + if coord_is_vector { + write!(self.out, "{NAMESPACE}::all(")?; + } + self.put_cast_to_uint_scalar_or_vector(address.coordinate, context)?; + write!(self.out, " < ")?; + self.put_image_size_query(image, level, crate::ScalarKind::Uint, context)?; + if coord_is_vector { + write!(self.out, ")")?; + } + + Ok(()) + } + + fn put_image_load( + &mut self, + load: Handle, + image: Handle, + mut address: TexelAddress, + context: &ExpressionContext, + ) -> BackendResult { + match context.policies.image_load { + proc::BoundsCheckPolicy::Restrict => { + // Use the cached restricted level of detail, if any. Omit the + // level altogether for 1D textures. + if address.level.is_some() { + address.level = if context.image_needs_lod(image) { + Some(LevelOfDetail::Restricted(load)) + } else { + None + } + } + + self.put_expression(image, context, false)?; + write!(self.out, ".read(")?; + self.put_restricted_texel_address(image, &address, context)?; + write!(self.out, ")")?; + } + proc::BoundsCheckPolicy::ReadZeroSkipWrite => { + write!(self.out, "(")?; + self.put_image_access_bounds_check(image, &address, context)?; + write!(self.out, " ? ")?; + self.put_unchecked_image_load(image, &address, context)?; + write!(self.out, ": DefaultConstructible())")?; + } + proc::BoundsCheckPolicy::Unchecked => { + self.put_unchecked_image_load(image, &address, context)?; + } + } + + Ok(()) + } + + fn put_unchecked_image_load( + &mut self, + image: Handle, + address: &TexelAddress, + context: &ExpressionContext, + ) -> BackendResult { + self.put_expression(image, context, false)?; + write!(self.out, ".read(")?; + // coordinates in IR are int, but Metal expects uint + self.put_cast_to_uint_scalar_or_vector(address.coordinate, context)?; + if let Some(expr) = address.array_index { + write!(self.out, ", ")?; + self.put_expression(expr, context, true)?; + } + if let Some(sample) = address.sample { + write!(self.out, ", ")?; + self.put_expression(sample, context, true)?; + } + if let Some(level) = address.level { + if context.image_needs_lod(image) { + write!(self.out, ", ")?; + self.put_level_of_detail(level, context)?; + } + } + write!(self.out, ")")?; + + Ok(()) + } + + fn put_image_store( + &mut self, + level: back::Level, + image: Handle, + address: &TexelAddress, + value: Handle, + context: &StatementContext, + ) -> BackendResult { + match context.expression.policies.image_store { + proc::BoundsCheckPolicy::Restrict => { + // We don't have a restricted level value, because we don't + // support writes to mipmapped textures. + debug_assert!(address.level.is_none()); + + write!(self.out, "{level}")?; + self.put_expression(image, &context.expression, false)?; + write!(self.out, ".write(")?; + self.put_expression(value, &context.expression, true)?; + write!(self.out, ", ")?; + self.put_restricted_texel_address(image, address, &context.expression)?; + writeln!(self.out, ");")?; + } + proc::BoundsCheckPolicy::ReadZeroSkipWrite => { + write!(self.out, "{level}if (")?; + self.put_image_access_bounds_check(image, address, &context.expression)?; + writeln!(self.out, ") {{")?; + self.put_unchecked_image_store(level.next(), image, address, value, context)?; + writeln!(self.out, "{level}}}")?; + } + proc::BoundsCheckPolicy::Unchecked => { + self.put_unchecked_image_store(level, image, address, value, context)?; + } + } + + Ok(()) + } + + fn put_unchecked_image_store( + &mut self, + level: back::Level, + image: Handle, + address: &TexelAddress, + value: Handle, + context: &StatementContext, + ) -> BackendResult { + write!(self.out, "{level}")?; + self.put_expression(image, &context.expression, false)?; + write!(self.out, ".write(")?; + self.put_expression(value, &context.expression, true)?; + write!(self.out, ", ")?; + // coordinates in IR are int, but Metal expects uint + self.put_cast_to_uint_scalar_or_vector(address.coordinate, &context.expression)?; + if let Some(expr) = address.array_index { + write!(self.out, ", ")?; + self.put_expression(expr, &context.expression, true)?; + } + writeln!(self.out, ");")?; + + Ok(()) + } + + /// Write the maximum valid index of the dynamically sized array at the end of `handle`. + /// + /// The 'maximum valid index' is simply one less than the array's length. + /// + /// This emits an expression of the form `a / b`, so the caller must + /// parenthesize its output if it will be applying operators of higher + /// precedence. + /// + /// `handle` must be the handle of a global variable whose final member is a + /// dynamically sized array. + fn put_dynamic_array_max_index( + &mut self, + handle: Handle, + context: &ExpressionContext, + ) -> BackendResult { + let global = &context.module.global_variables[handle]; + let (offset, array_ty) = match context.module.types[global.ty].inner { + crate::TypeInner::Struct { ref members, .. } => match members.last() { + Some(&crate::StructMember { offset, ty, .. }) => (offset, ty), + None => return Err(Error::Validation), + }, + crate::TypeInner::Array { + size: crate::ArraySize::Dynamic, + .. + } => (0, global.ty), + _ => return Err(Error::Validation), + }; + + let (size, stride) = match context.module.types[array_ty].inner { + crate::TypeInner::Array { base, stride, .. } => ( + context.module.types[base] + .inner + .size(context.module.to_ctx()), + stride, + ), + _ => return Err(Error::Validation), + }; + + // When the stride length is larger than the size, the final element's stride of + // bytes would have padding following the value. But the buffer size in + // `buffer_sizes.sizeN` may not include this padding - it only needs to be large + // enough to hold the actual values' bytes. + // + // So subtract off the size to get a byte size that falls at the start or within + // the final element. Then divide by the stride size, to get one less than the + // length, and then add one. This works even if the buffer size does include the + // stride padding, since division rounds towards zero (MSL 2.4 §6.1). It will fail + // if there are zero elements in the array, but the WebGPU `validating shader binding` + // rules, together with draw-time validation when `minBindingSize` is zero, + // prevent that. + write!( + self.out, + "(_buffer_sizes.size{idx} - {offset} - {size}) / {stride}", + idx = handle.index(), + offset = offset, + size = size, + stride = stride, + )?; + Ok(()) + } + + fn put_atomic_fetch( + &mut self, + pointer: Handle, + key: &str, + value: Handle, + context: &ExpressionContext, + ) -> BackendResult { + self.put_atomic_operation(pointer, "fetch_", key, value, context) + } + + fn put_atomic_operation( + &mut self, + pointer: Handle, + key1: &str, + key2: &str, + value: Handle, + context: &ExpressionContext, + ) -> BackendResult { + // If the pointer we're passing to the atomic operation needs to be conditional + // for `ReadZeroSkipWrite`, the condition needs to *surround* the atomic op, and + // the pointer operand should be unchecked. + let policy = context.choose_bounds_check_policy(pointer); + let checked = policy == index::BoundsCheckPolicy::ReadZeroSkipWrite + && self.put_bounds_checks(pointer, context, back::Level(0), "")?; + + // If requested and successfully put bounds checks, continue the ternary expression. + if checked { + write!(self.out, " ? ")?; + } + + write!( + self.out, + "{NAMESPACE}::atomic_{key1}{key2}_explicit({ATOMIC_REFERENCE}" + )?; + self.put_access_chain(pointer, policy, context)?; + write!(self.out, ", ")?; + self.put_expression(value, context, true)?; + write!(self.out, ", {NAMESPACE}::memory_order_relaxed)")?; + + // Finish the ternary expression. + if checked { + write!(self.out, " : DefaultConstructible()")?; + } + + Ok(()) + } + + /// Emit code for the arithmetic expression of the dot product. + /// + fn put_dot_product( + &mut self, + arg: Handle, + arg1: Handle, + size: usize, + context: &ExpressionContext, + ) -> BackendResult { + // Write parantheses around the dot product expression to prevent operators + // with different precedences from applying earlier. + write!(self.out, "(")?; + + // Cycle trough all the components of the vector + for index in 0..size { + let component = back::COMPONENTS[index]; + // Write the addition to the previous product + // This will print an extra '+' at the beginning but that is fine in msl + write!(self.out, " + ")?; + // Write the first vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.put_expression(arg, context, true)?; + // Access the current component on the first vector + write!(self.out, ".{component} * ")?; + // Write the second vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.put_expression(arg1, context, true)?; + // Access the current component on the second vector + write!(self.out, ".{component}")?; + } + + write!(self.out, ")")?; + Ok(()) + } + + /// Emit code for the sign(i32) expression. + /// + fn put_isign( + &mut self, + arg: Handle, + context: &ExpressionContext, + ) -> BackendResult { + write!(self.out, "{NAMESPACE}::select({NAMESPACE}::select(")?; + match context.resolve_type(arg) { + &crate::TypeInner::Vector { size, .. } => { + let size = back::vector_size_str(size); + write!(self.out, "int{size}(-1), int{size}(1)")?; + } + _ => { + write!(self.out, "-1, 1")?; + } + } + write!(self.out, ", (")?; + self.put_expression(arg, context, true)?; + write!(self.out, " > 0)), 0, (")?; + self.put_expression(arg, context, true)?; + write!(self.out, " == 0))")?; + Ok(()) + } + + fn put_const_expression( + &mut self, + expr_handle: Handle, + module: &crate::Module, + mod_info: &valid::ModuleInfo, + ) -> BackendResult { + self.put_possibly_const_expression( + expr_handle, + &module.const_expressions, + module, + mod_info, + &(module, mod_info), + |&(_, mod_info), expr| &mod_info[expr], + |writer, &(module, _), expr| writer.put_const_expression(expr, module, mod_info), + ) + } + + #[allow(clippy::too_many_arguments)] + fn put_possibly_const_expression( + &mut self, + expr_handle: Handle, + expressions: &crate::Arena, + module: &crate::Module, + mod_info: &valid::ModuleInfo, + ctx: &C, + get_expr_ty: I, + put_expression: E, + ) -> BackendResult + where + I: Fn(&C, Handle) -> &TypeResolution, + E: Fn(&mut Self, &C, Handle) -> BackendResult, + { + match expressions[expr_handle] { + crate::Expression::Literal(literal) => match literal { + crate::Literal::F64(_) => { + return Err(Error::CapabilityNotSupported(valid::Capabilities::FLOAT64)) + } + crate::Literal::F32(value) => { + if value.is_infinite() { + let sign = if value.is_sign_negative() { "-" } else { "" }; + write!(self.out, "{sign}INFINITY")?; + } else if value.is_nan() { + write!(self.out, "NAN")?; + } else { + let suffix = if value.fract() == 0.0 { ".0" } else { "" }; + write!(self.out, "{value}{suffix}")?; + } + } + crate::Literal::U32(value) => { + write!(self.out, "{value}u")?; + } + crate::Literal::I32(value) => { + write!(self.out, "{value}")?; + } + crate::Literal::I64(value) => { + write!(self.out, "{value}L")?; + } + crate::Literal::Bool(value) => { + write!(self.out, "{value}")?; + } + crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { + return Err(Error::Validation); + } + }, + crate::Expression::Constant(handle) => { + let constant = &module.constants[handle]; + if constant.name.is_some() { + write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; + } else { + self.put_const_expression(constant.init, module, mod_info)?; + } + } + crate::Expression::ZeroValue(ty) => { + let ty_name = TypeContext { + handle: ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + write!(self.out, "{ty_name} {{}}")?; + } + crate::Expression::Compose { ty, ref components } => { + let ty_name = TypeContext { + handle: ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + write!(self.out, "{ty_name}")?; + match module.types[ty].inner { + crate::TypeInner::Scalar(_) + | crate::TypeInner::Vector { .. } + | crate::TypeInner::Matrix { .. } => { + self.put_call_parameters_impl( + components.iter().copied(), + ctx, + put_expression, + )?; + } + crate::TypeInner::Array { .. } | crate::TypeInner::Struct { .. } => { + write!(self.out, " {{")?; + for (index, &component) in components.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + // insert padding initialization, if needed + if self.struct_member_pads.contains(&(ty, index as u32)) { + write!(self.out, "{{}}, ")?; + } + put_expression(self, ctx, component)?; + } + write!(self.out, "}}")?; + } + _ => return Err(Error::UnsupportedCompose(ty)), + } + } + crate::Expression::Splat { size, value } => { + let scalar = match *get_expr_ty(ctx, value).inner_with(&module.types) { + crate::TypeInner::Scalar(scalar) => scalar, + _ => return Err(Error::Validation), + }; + put_numeric_type(&mut self.out, scalar, &[size])?; + write!(self.out, "(")?; + put_expression(self, ctx, value)?; + write!(self.out, ")")?; + } + _ => unreachable!(), + } + + Ok(()) + } + + /// Emit code for the expression `expr_handle`. + /// + /// The `is_scoped` argument is true if the surrounding operators have the + /// precedence of the comma operator, or lower. So, for example: + /// + /// - Pass `true` for `is_scoped` when writing function arguments, an + /// expression statement, an initializer expression, or anything already + /// wrapped in parenthesis. + /// + /// - Pass `false` if it is an operand of a `?:` operator, a `[]`, or really + /// almost anything else. + fn put_expression( + &mut self, + expr_handle: Handle, + context: &ExpressionContext, + is_scoped: bool, + ) -> BackendResult { + // Add to the set in order to track the stack size. + #[cfg(test)] + #[allow(trivial_casts)] + self.put_expression_stack_pointers + .insert(&expr_handle as *const _ as *const ()); + + if let Some(name) = self.named_expressions.get(&expr_handle) { + write!(self.out, "{name}")?; + return Ok(()); + } + + let expression = &context.function.expressions[expr_handle]; + log::trace!("expression {:?} = {:?}", expr_handle, expression); + match *expression { + crate::Expression::Literal(_) + | crate::Expression::Constant(_) + | crate::Expression::ZeroValue(_) + | crate::Expression::Compose { .. } + | crate::Expression::Splat { .. } => { + self.put_possibly_const_expression( + expr_handle, + &context.function.expressions, + context.module, + context.mod_info, + context, + |context, expr: Handle| &context.info[expr].ty, + |writer, context, expr| writer.put_expression(expr, context, true), + )?; + } + crate::Expression::Access { base, .. } + | crate::Expression::AccessIndex { base, .. } => { + // This is an acceptable place to generate a `ReadZeroSkipWrite` check. + // Since `put_bounds_checks` and `put_access_chain` handle an entire + // access chain at a time, recursing back through `put_expression` only + // for index expressions and the base object, we will never see intermediate + // `Access` or `AccessIndex` expressions here. + let policy = context.choose_bounds_check_policy(base); + if policy == index::BoundsCheckPolicy::ReadZeroSkipWrite + && self.put_bounds_checks( + expr_handle, + context, + back::Level(0), + if is_scoped { "" } else { "(" }, + )? + { + write!(self.out, " ? ")?; + self.put_access_chain(expr_handle, policy, context)?; + write!(self.out, " : DefaultConstructible()")?; + + if !is_scoped { + write!(self.out, ")")?; + } + } else { + self.put_access_chain(expr_handle, policy, context)?; + } + } + crate::Expression::Swizzle { + size, + vector, + pattern, + } => { + self.put_wrapped_expression_for_packed_vec3_access(vector, context, false)?; + write!(self.out, ".")?; + for &sc in pattern[..size as usize].iter() { + write!(self.out, "{}", back::COMPONENTS[sc as usize])?; + } + } + crate::Expression::FunctionArgument(index) => { + let name_key = match context.origin { + FunctionOrigin::Handle(handle) => NameKey::FunctionArgument(handle, index), + FunctionOrigin::EntryPoint(ep_index) => { + NameKey::EntryPointArgument(ep_index, index) + } + }; + let name = &self.names[&name_key]; + write!(self.out, "{name}")?; + } + crate::Expression::GlobalVariable(handle) => { + let name = &self.names[&NameKey::GlobalVariable(handle)]; + write!(self.out, "{name}")?; + } + crate::Expression::LocalVariable(handle) => { + let name_key = match context.origin { + FunctionOrigin::Handle(fun_handle) => { + NameKey::FunctionLocal(fun_handle, handle) + } + FunctionOrigin::EntryPoint(ep_index) => { + NameKey::EntryPointLocal(ep_index, handle) + } + }; + let name = &self.names[&name_key]; + write!(self.out, "{name}")?; + } + crate::Expression::Load { pointer } => self.put_load(pointer, context, is_scoped)?, + crate::Expression::ImageSample { + image, + sampler, + gather, + coordinate, + array_index, + offset, + level, + depth_ref, + } => { + let main_op = match gather { + Some(_) => "gather", + None => "sample", + }; + let comparison_op = match depth_ref { + Some(_) => "_compare", + None => "", + }; + self.put_expression(image, context, false)?; + write!(self.out, ".{main_op}{comparison_op}(")?; + self.put_expression(sampler, context, true)?; + write!(self.out, ", ")?; + self.put_expression(coordinate, context, true)?; + if let Some(expr) = array_index { + write!(self.out, ", ")?; + self.put_expression(expr, context, true)?; + } + if let Some(dref) = depth_ref { + write!(self.out, ", ")?; + self.put_expression(dref, context, true)?; + } + + self.put_image_sample_level(image, level, context)?; + + if let Some(offset) = offset { + write!(self.out, ", ")?; + self.put_const_expression(offset, context.module, context.mod_info)?; + } + + match gather { + None | Some(crate::SwizzleComponent::X) => {} + Some(component) => { + let is_cube_map = match *context.resolve_type(image) { + crate::TypeInner::Image { + dim: crate::ImageDimension::Cube, + .. + } => true, + _ => false, + }; + // Offset always comes before the gather, except + // in cube maps where it's not applicable + if offset.is_none() && !is_cube_map { + write!(self.out, ", {NAMESPACE}::int2(0)")?; + } + let letter = back::COMPONENTS[component as usize]; + write!(self.out, ", {NAMESPACE}::component::{letter}")?; + } + } + write!(self.out, ")")?; + } + crate::Expression::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => { + let address = TexelAddress { + coordinate, + array_index, + sample, + level: level.map(LevelOfDetail::Direct), + }; + self.put_image_load(expr_handle, image, address, context)?; + } + //Note: for all the queries, the signed integers are expected, + // so a conversion is needed. + crate::Expression::ImageQuery { image, query } => match query { + crate::ImageQuery::Size { level } => { + self.put_image_size_query( + image, + level.map(LevelOfDetail::Direct), + crate::ScalarKind::Uint, + context, + )?; + } + crate::ImageQuery::NumLevels => { + self.put_expression(image, context, false)?; + write!(self.out, ".get_num_mip_levels()")?; + } + crate::ImageQuery::NumLayers => { + self.put_expression(image, context, false)?; + write!(self.out, ".get_array_size()")?; + } + crate::ImageQuery::NumSamples => { + self.put_expression(image, context, false)?; + write!(self.out, ".get_num_samples()")?; + } + }, + crate::Expression::Unary { op, expr } => { + let op_str = match op { + crate::UnaryOperator::Negate => "-", + crate::UnaryOperator::LogicalNot => "!", + crate::UnaryOperator::BitwiseNot => "~", + }; + write!(self.out, "{op_str}(")?; + self.put_expression(expr, context, false)?; + write!(self.out, ")")?; + } + crate::Expression::Binary { op, left, right } => { + let op_str = crate::back::binary_operation_str(op); + let kind = context + .resolve_type(left) + .scalar_kind() + .ok_or(Error::UnsupportedBinaryOp(op))?; + + // TODO: handle undefined behavior of BinaryOperator::Modulo + // + // sint: + // if right == 0 return 0 + // if left == min(type_of(left)) && right == -1 return 0 + // if sign(left) == -1 || sign(right) == -1 return result as defined by WGSL + // + // uint: + // if right == 0 return 0 + // + // float: + // if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798 + + if op == crate::BinaryOperator::Modulo && kind == crate::ScalarKind::Float { + write!(self.out, "{NAMESPACE}::fmod(")?; + self.put_expression(left, context, true)?; + write!(self.out, ", ")?; + self.put_expression(right, context, true)?; + write!(self.out, ")")?; + } else { + if !is_scoped { + write!(self.out, "(")?; + } + + // Cast packed vector if necessary + // Packed vector - matrix multiplications are not supported in MSL + if op == crate::BinaryOperator::Multiply + && matches!( + context.resolve_type(right), + &crate::TypeInner::Matrix { .. } + ) + { + self.put_wrapped_expression_for_packed_vec3_access(left, context, false)?; + } else { + self.put_expression(left, context, false)?; + } + + write!(self.out, " {op_str} ")?; + + // See comment above + if op == crate::BinaryOperator::Multiply + && matches!(context.resolve_type(left), &crate::TypeInner::Matrix { .. }) + { + self.put_wrapped_expression_for_packed_vec3_access(right, context, false)?; + } else { + self.put_expression(right, context, false)?; + } + + if !is_scoped { + write!(self.out, ")")?; + } + } + } + crate::Expression::Select { + condition, + accept, + reject, + } => match *context.resolve_type(condition) { + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Bool, + .. + }) => { + if !is_scoped { + write!(self.out, "(")?; + } + self.put_expression(condition, context, false)?; + write!(self.out, " ? ")?; + self.put_expression(accept, context, false)?; + write!(self.out, " : ")?; + self.put_expression(reject, context, false)?; + if !is_scoped { + write!(self.out, ")")?; + } + } + crate::TypeInner::Vector { + scalar: + crate::Scalar { + kind: crate::ScalarKind::Bool, + .. + }, + .. + } => { + write!(self.out, "{NAMESPACE}::select(")?; + self.put_expression(reject, context, true)?; + write!(self.out, ", ")?; + self.put_expression(accept, context, true)?; + write!(self.out, ", ")?; + self.put_expression(condition, context, true)?; + write!(self.out, ")")?; + } + _ => return Err(Error::Validation), + }, + crate::Expression::Derivative { axis, expr, .. } => { + use crate::DerivativeAxis as Axis; + let op = match axis { + Axis::X => "dfdx", + Axis::Y => "dfdy", + Axis::Width => "fwidth", + }; + write!(self.out, "{NAMESPACE}::{op}")?; + self.put_call_parameters(iter::once(expr), context)?; + } + crate::Expression::Relational { fun, argument } => { + let op = match fun { + crate::RelationalFunction::Any => "any", + crate::RelationalFunction::All => "all", + crate::RelationalFunction::IsNan => "isnan", + crate::RelationalFunction::IsInf => "isinf", + }; + write!(self.out, "{NAMESPACE}::{op}")?; + self.put_call_parameters(iter::once(argument), context)?; + } + crate::Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + } => { + use crate::MathFunction as Mf; + + let arg_type = context.resolve_type(arg); + let scalar_argument = match arg_type { + &crate::TypeInner::Scalar(_) => true, + _ => false, + }; + + let fun_name = match fun { + // comparison + Mf::Abs => "abs", + Mf::Min => "min", + Mf::Max => "max", + Mf::Clamp => "clamp", + Mf::Saturate => "saturate", + // trigonometry + Mf::Cos => "cos", + Mf::Cosh => "cosh", + Mf::Sin => "sin", + Mf::Sinh => "sinh", + Mf::Tan => "tan", + Mf::Tanh => "tanh", + Mf::Acos => "acos", + Mf::Asin => "asin", + Mf::Atan => "atan", + Mf::Atan2 => "atan2", + Mf::Asinh => "asinh", + Mf::Acosh => "acosh", + Mf::Atanh => "atanh", + Mf::Radians => "", + Mf::Degrees => "", + // decomposition + Mf::Ceil => "ceil", + Mf::Floor => "floor", + Mf::Round => "rint", + Mf::Fract => "fract", + Mf::Trunc => "trunc", + Mf::Modf => MODF_FUNCTION, + Mf::Frexp => FREXP_FUNCTION, + Mf::Ldexp => "ldexp", + // exponent + Mf::Exp => "exp", + Mf::Exp2 => "exp2", + Mf::Log => "log", + Mf::Log2 => "log2", + Mf::Pow => "pow", + // geometry + Mf::Dot => match *context.resolve_type(arg) { + crate::TypeInner::Vector { + scalar: + crate::Scalar { + kind: crate::ScalarKind::Float, + .. + }, + .. + } => "dot", + crate::TypeInner::Vector { size, .. } => { + return self.put_dot_product(arg, arg1.unwrap(), size as usize, context) + } + _ => unreachable!( + "Correct TypeInner for dot product should be already validated" + ), + }, + Mf::Outer => return Err(Error::UnsupportedCall(format!("{fun:?}"))), + Mf::Cross => "cross", + Mf::Distance => "distance", + Mf::Length if scalar_argument => "abs", + Mf::Length => "length", + Mf::Normalize => "normalize", + Mf::FaceForward => "faceforward", + Mf::Reflect => "reflect", + Mf::Refract => "refract", + // computational + Mf::Sign => match arg_type.scalar_kind() { + Some(crate::ScalarKind::Sint) => { + return self.put_isign(arg, context); + } + _ => "sign", + }, + Mf::Fma => "fma", + Mf::Mix => "mix", + Mf::Step => "step", + Mf::SmoothStep => "smoothstep", + Mf::Sqrt => "sqrt", + Mf::InverseSqrt => "rsqrt", + Mf::Inverse => return Err(Error::UnsupportedCall(format!("{fun:?}"))), + Mf::Transpose => "transpose", + Mf::Determinant => "determinant", + // bits + Mf::CountTrailingZeros => "ctz", + Mf::CountLeadingZeros => "clz", + Mf::CountOneBits => "popcount", + Mf::ReverseBits => "reverse_bits", + Mf::ExtractBits => "extract_bits", + Mf::InsertBits => "insert_bits", + Mf::FindLsb => "", + Mf::FindMsb => "", + // data packing + Mf::Pack4x8snorm => "pack_float_to_snorm4x8", + Mf::Pack4x8unorm => "pack_float_to_unorm4x8", + Mf::Pack2x16snorm => "pack_float_to_snorm2x16", + Mf::Pack2x16unorm => "pack_float_to_unorm2x16", + Mf::Pack2x16float => "", + // data unpacking + Mf::Unpack4x8snorm => "unpack_snorm4x8_to_float", + Mf::Unpack4x8unorm => "unpack_unorm4x8_to_float", + Mf::Unpack2x16snorm => "unpack_snorm2x16_to_float", + Mf::Unpack2x16unorm => "unpack_unorm2x16_to_float", + Mf::Unpack2x16float => "", + }; + + match fun { + Mf::ReverseBits | Mf::ExtractBits | Mf::InsertBits => { + // reverse_bits is listed as requiring MSL 2.1 but that + // is a copy/paste error. Looking at previous snapshots + // on web.archive.org it's present in MSL 1.2. + // + // https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/WhatsNewiniOS10tvOS10andOSX1012/WhatsNewiniOS10tvOS10andOSX1012.html + // also talks about MSL 1.2 adding "New integer + // functions to extract, insert, and reverse bits, as + // described in Integer Functions." + if context.lang_version < (1, 2) { + return Err(Error::UnsupportedFunction(fun_name.to_string())); + } + } + _ => {} + } + + if fun == Mf::Distance && scalar_argument { + write!(self.out, "{NAMESPACE}::abs(")?; + self.put_expression(arg, context, false)?; + write!(self.out, " - ")?; + self.put_expression(arg1.unwrap(), context, false)?; + write!(self.out, ")")?; + } else if fun == Mf::FindLsb { + write!(self.out, "((({NAMESPACE}::ctz(")?; + self.put_expression(arg, context, true)?; + write!(self.out, ") + 1) % 33) - 1)")?; + } else if fun == Mf::FindMsb { + let inner = context.resolve_type(arg); + + write!(self.out, "{NAMESPACE}::select(31 - {NAMESPACE}::clz(")?; + + if let Some(crate::ScalarKind::Sint) = inner.scalar_kind() { + write!(self.out, "{NAMESPACE}::select(")?; + self.put_expression(arg, context, true)?; + write!(self.out, ", ~")?; + self.put_expression(arg, context, true)?; + write!(self.out, ", ")?; + self.put_expression(arg, context, true)?; + write!(self.out, " < 0)")?; + } else { + self.put_expression(arg, context, true)?; + } + + write!(self.out, "), ")?; + + // or metal will complain that select is ambiguous + match *inner { + crate::TypeInner::Vector { size, scalar } => { + let size = back::vector_size_str(size); + if let crate::ScalarKind::Sint = scalar.kind { + write!(self.out, "int{size}")?; + } else { + write!(self.out, "uint{size}")?; + } + } + crate::TypeInner::Scalar(scalar) => { + if let crate::ScalarKind::Sint = scalar.kind { + write!(self.out, "int")?; + } else { + write!(self.out, "uint")?; + } + } + _ => (), + } + + write!(self.out, "(-1), ")?; + self.put_expression(arg, context, true)?; + write!(self.out, " == 0 || ")?; + self.put_expression(arg, context, true)?; + write!(self.out, " == -1)")?; + } else if fun == Mf::Unpack2x16float { + write!(self.out, "float2(as_type(")?; + self.put_expression(arg, context, false)?; + write!(self.out, "))")?; + } else if fun == Mf::Pack2x16float { + write!(self.out, "as_type(half2(")?; + self.put_expression(arg, context, false)?; + write!(self.out, "))")?; + } else if fun == Mf::Radians { + write!(self.out, "((")?; + self.put_expression(arg, context, false)?; + write!(self.out, ") * 0.017453292519943295474)")?; + } else if fun == Mf::Degrees { + write!(self.out, "((")?; + self.put_expression(arg, context, false)?; + write!(self.out, ") * 57.295779513082322865)")?; + } else if fun == Mf::Modf || fun == Mf::Frexp { + write!(self.out, "{fun_name}")?; + self.put_call_parameters(iter::once(arg), context)?; + } else { + write!(self.out, "{NAMESPACE}::{fun_name}")?; + self.put_call_parameters( + iter::once(arg).chain(arg1).chain(arg2).chain(arg3), + context, + )?; + } + } + crate::Expression::As { + expr, + kind, + convert, + } => match *context.resolve_type(expr) { + crate::TypeInner::Scalar(src) | crate::TypeInner::Vector { scalar: src, .. } => { + let target_scalar = crate::Scalar { + kind, + width: convert.unwrap_or(src.width), + }; + let is_bool_cast = + kind == crate::ScalarKind::Bool || src.kind == crate::ScalarKind::Bool; + let op = match convert { + Some(w) if w == src.width || is_bool_cast => "static_cast", + Some(8) if kind == crate::ScalarKind::Float => { + return Err(Error::CapabilityNotSupported(valid::Capabilities::FLOAT64)) + } + Some(_) => return Err(Error::Validation), + None => "as_type", + }; + write!(self.out, "{op}<")?; + match *context.resolve_type(expr) { + crate::TypeInner::Vector { size, .. } => { + put_numeric_type(&mut self.out, target_scalar, &[size])? + } + _ => put_numeric_type(&mut self.out, target_scalar, &[])?, + }; + write!(self.out, ">(")?; + self.put_expression(expr, context, true)?; + write!(self.out, ")")?; + } + crate::TypeInner::Matrix { + columns, + rows, + scalar, + } => { + let target_scalar = crate::Scalar { + kind, + width: convert.unwrap_or(scalar.width), + }; + put_numeric_type(&mut self.out, target_scalar, &[rows, columns])?; + write!(self.out, "(")?; + self.put_expression(expr, context, true)?; + write!(self.out, ")")?; + } + _ => return Err(Error::Validation), + }, + // has to be a named expression + crate::Expression::CallResult(_) + | crate::Expression::AtomicResult { .. } + | crate::Expression::WorkGroupUniformLoadResult { .. } + | crate::Expression::RayQueryProceedResult => { + unreachable!() + } + crate::Expression::ArrayLength(expr) => { + // Find the global to which the array belongs. + let global = match context.function.expressions[expr] { + crate::Expression::AccessIndex { base, .. } => { + match context.function.expressions[base] { + crate::Expression::GlobalVariable(handle) => handle, + _ => return Err(Error::Validation), + } + } + crate::Expression::GlobalVariable(handle) => handle, + _ => return Err(Error::Validation), + }; + + if !is_scoped { + write!(self.out, "(")?; + } + write!(self.out, "1 + ")?; + self.put_dynamic_array_max_index(global, context)?; + if !is_scoped { + write!(self.out, ")")?; + } + } + crate::Expression::RayQueryGetIntersection { query, committed } => { + if context.lang_version < (2, 4) { + return Err(Error::UnsupportedRayTracing); + } + + if !committed { + unimplemented!() + } + let ty = context.module.special_types.ray_intersection.unwrap(); + let type_name = &self.names[&NameKey::Type(ty)]; + write!(self.out, "{type_name} {{{RAY_QUERY_FUN_MAP_INTERSECTION}(")?; + self.put_expression(query, context, true)?; + write!(self.out, ".{RAY_QUERY_FIELD_INTERSECTION}.type)")?; + let fields = [ + "distance", + "user_instance_id", // req Metal 2.4 + "instance_id", + "", // SBT offset + "geometry_id", + "primitive_id", + "triangle_barycentric_coord", + "triangle_front_facing", + "", // padding + "object_to_world_transform", // req Metal 2.4 + "world_to_object_transform", // req Metal 2.4 + ]; + for field in fields { + write!(self.out, ", ")?; + if field.is_empty() { + write!(self.out, "{{}}")?; + } else { + self.put_expression(query, context, true)?; + write!(self.out, ".{RAY_QUERY_FIELD_INTERSECTION}.{field}")?; + } + } + write!(self.out, "}}")?; + } + } + Ok(()) + } + + /// Used by expressions like Swizzle and Binary since they need packed_vec3's to be casted to a vec3 + fn put_wrapped_expression_for_packed_vec3_access( + &mut self, + expr_handle: Handle, + context: &ExpressionContext, + is_scoped: bool, + ) -> BackendResult { + if let Some(scalar) = context.get_packed_vec_kind(expr_handle) { + write!(self.out, "{}::{}3(", NAMESPACE, scalar.to_msl_name())?; + self.put_expression(expr_handle, context, is_scoped)?; + write!(self.out, ")")?; + } else { + self.put_expression(expr_handle, context, is_scoped)?; + } + Ok(()) + } + + /// Write a `GuardedIndex` as a Metal expression. + fn put_index( + &mut self, + index: index::GuardedIndex, + context: &ExpressionContext, + is_scoped: bool, + ) -> BackendResult { + match index { + index::GuardedIndex::Expression(expr) => { + self.put_expression(expr, context, is_scoped)? + } + index::GuardedIndex::Known(value) => write!(self.out, "{value}")?, + } + Ok(()) + } + + /// Emit an index bounds check condition for `chain`, if required. + /// + /// `chain` is a subtree of `Access` and `AccessIndex` expressions, + /// operating either on a pointer to a value, or on a value directly. If we cannot + /// statically determine that all indexing operations in `chain` are within + /// bounds, then write a conditional expression to check them dynamically, + /// and return true. All accesses in the chain are checked by the generated + /// expression. + /// + /// This assumes that the [`BoundsCheckPolicy`] for `chain` is [`ReadZeroSkipWrite`]. + /// + /// The text written is of the form: + /// + /// ```ignore + /// {level}{prefix}uint(i) < 4 && uint(j) < 10 + /// ``` + /// + /// where `{level}` and `{prefix}` are the arguments to this function. For [`Store`] + /// statements, presumably these arguments start an indented `if` statement; for + /// [`Load`] expressions, the caller is probably building up a ternary `?:` + /// expression. In either case, what is written is not a complete syntactic structure + /// in its own right, and the caller will have to finish it off if we return `true`. + /// + /// If no expression is written, return false. + /// + /// [`BoundsCheckPolicy`]: index::BoundsCheckPolicy + /// [`ReadZeroSkipWrite`]: index::BoundsCheckPolicy::ReadZeroSkipWrite + /// [`Store`]: crate::Statement::Store + /// [`Load`]: crate::Expression::Load + #[allow(unused_variables)] + fn put_bounds_checks( + &mut self, + mut chain: Handle, + context: &ExpressionContext, + level: back::Level, + prefix: &'static str, + ) -> Result { + let mut check_written = false; + + // Iterate over the access chain, handling each expression. + loop { + // Produce a `GuardedIndex`, so we can shared code between the + // `Access` and `AccessIndex` cases. + let (base, guarded_index) = match context.function.expressions[chain] { + crate::Expression::Access { base, index } => { + (base, Some(index::GuardedIndex::Expression(index))) + } + crate::Expression::AccessIndex { base, index } => { + // Don't try to check indices into structs. Validation already took + // care of them, and index::needs_guard doesn't handle that case. + let mut base_inner = context.resolve_type(base); + if let crate::TypeInner::Pointer { base, .. } = *base_inner { + base_inner = &context.module.types[base].inner; + } + match *base_inner { + crate::TypeInner::Struct { .. } => (base, None), + _ => (base, Some(index::GuardedIndex::Known(index))), + } + } + _ => break, + }; + + if let Some(index) = guarded_index { + if let Some(length) = context.access_needs_check(base, index) { + if check_written { + write!(self.out, " && ")?; + } else { + write!(self.out, "{level}{prefix}")?; + check_written = true; + } + + // Check that the index falls within bounds. Do this with a single + // comparison, by casting the index to `uint` first, so that negative + // indices become large positive values. + write!(self.out, "uint(")?; + self.put_index(index, context, true)?; + self.out.write_str(") < ")?; + match length { + index::IndexableLength::Known(value) => write!(self.out, "{value}")?, + index::IndexableLength::Dynamic => { + let global = context + .function + .originating_global(base) + .ok_or(Error::Validation)?; + write!(self.out, "1 + ")?; + self.put_dynamic_array_max_index(global, context)? + } + } + } + } + + chain = base + } + + Ok(check_written) + } + + /// Write the access chain `chain`. + /// + /// `chain` is a subtree of [`Access`] and [`AccessIndex`] expressions, + /// operating either on a pointer to a value, or on a value directly. + /// + /// Generate bounds checks code only if `policy` is [`Restrict`]. The + /// [`ReadZeroSkipWrite`] policy requires checks before any accesses take place, so + /// that must be handled in the caller. + /// + /// Handle the entire chain, recursing back into `put_expression` only for index + /// expressions and the base expression that originates the pointer or composite value + /// being accessed. This allows `put_expression` to assume that any `Access` or + /// `AccessIndex` expressions it sees are the top of a chain, so it can emit + /// `ReadZeroSkipWrite` checks. + /// + /// [`Access`]: crate::Expression::Access + /// [`AccessIndex`]: crate::Expression::AccessIndex + /// [`Restrict`]: crate::proc::index::BoundsCheckPolicy::Restrict + /// [`ReadZeroSkipWrite`]: crate::proc::index::BoundsCheckPolicy::ReadZeroSkipWrite + fn put_access_chain( + &mut self, + chain: Handle, + policy: index::BoundsCheckPolicy, + context: &ExpressionContext, + ) -> BackendResult { + match context.function.expressions[chain] { + crate::Expression::Access { base, index } => { + let mut base_ty = context.resolve_type(base); + + // Look through any pointers to see what we're really indexing. + if let crate::TypeInner::Pointer { base, space: _ } = *base_ty { + base_ty = &context.module.types[base].inner; + } + + self.put_subscripted_access_chain( + base, + base_ty, + index::GuardedIndex::Expression(index), + policy, + context, + )?; + } + crate::Expression::AccessIndex { base, index } => { + let base_resolution = &context.info[base].ty; + let mut base_ty = base_resolution.inner_with(&context.module.types); + let mut base_ty_handle = base_resolution.handle(); + + // Look through any pointers to see what we're really indexing. + if let crate::TypeInner::Pointer { base, space: _ } = *base_ty { + base_ty = &context.module.types[base].inner; + base_ty_handle = Some(base); + } + + // Handle structs and anything else that can use `.x` syntax here, so + // `put_subscripted_access_chain` won't have to handle the absurd case of + // indexing a struct with an expression. + match *base_ty { + crate::TypeInner::Struct { .. } => { + let base_ty = base_ty_handle.unwrap(); + self.put_access_chain(base, policy, context)?; + let name = &self.names[&NameKey::StructMember(base_ty, index)]; + write!(self.out, ".{name}")?; + } + crate::TypeInner::ValuePointer { .. } | crate::TypeInner::Vector { .. } => { + self.put_access_chain(base, policy, context)?; + // Prior to Metal v2.1 component access for packed vectors wasn't available + // however array indexing is + if context.get_packed_vec_kind(base).is_some() { + write!(self.out, "[{index}]")?; + } else { + write!(self.out, ".{}", back::COMPONENTS[index as usize])?; + } + } + _ => { + self.put_subscripted_access_chain( + base, + base_ty, + index::GuardedIndex::Known(index), + policy, + context, + )?; + } + } + } + _ => self.put_expression(chain, context, false)?, + } + + Ok(()) + } + + /// Write a `[]`-style access of `base` by `index`. + /// + /// If `policy` is [`Restrict`], then generate code as needed to force all index + /// values within bounds. + /// + /// The `base_ty` argument must be the type we are actually indexing, like [`Array`] or + /// [`Vector`]. In other words, it's `base`'s type with any surrounding [`Pointer`] + /// removed. Our callers often already have this handy. + /// + /// This only emits `[]` expressions; it doesn't handle struct member accesses or + /// referencing vector components by name. + /// + /// [`Restrict`]: crate::proc::index::BoundsCheckPolicy::Restrict + /// [`Array`]: crate::TypeInner::Array + /// [`Vector`]: crate::TypeInner::Vector + /// [`Pointer`]: crate::TypeInner::Pointer + fn put_subscripted_access_chain( + &mut self, + base: Handle, + base_ty: &crate::TypeInner, + index: index::GuardedIndex, + policy: index::BoundsCheckPolicy, + context: &ExpressionContext, + ) -> BackendResult { + let accessing_wrapped_array = match *base_ty { + crate::TypeInner::Array { + size: crate::ArraySize::Constant(_), + .. + } => true, + _ => false, + }; + + self.put_access_chain(base, policy, context)?; + if accessing_wrapped_array { + write!(self.out, ".{WRAPPED_ARRAY_FIELD}")?; + } + write!(self.out, "[")?; + + // Decide whether this index needs to be clamped to fall within range. + let restriction_needed = if policy == index::BoundsCheckPolicy::Restrict { + context.access_needs_check(base, index) + } else { + None + }; + if let Some(limit) = restriction_needed { + write!(self.out, "{NAMESPACE}::min(unsigned(")?; + self.put_index(index, context, true)?; + write!(self.out, "), ")?; + match limit { + index::IndexableLength::Known(limit) => { + write!(self.out, "{}u", limit - 1)?; + } + index::IndexableLength::Dynamic => { + let global = context + .function + .originating_global(base) + .ok_or(Error::Validation)?; + self.put_dynamic_array_max_index(global, context)?; + } + } + write!(self.out, ")")?; + } else { + self.put_index(index, context, true)?; + } + + write!(self.out, "]")?; + + Ok(()) + } + + fn put_load( + &mut self, + pointer: Handle, + context: &ExpressionContext, + is_scoped: bool, + ) -> BackendResult { + // Since access chains never cross between address spaces, we can just + // check the index bounds check policy once at the top. + let policy = context.choose_bounds_check_policy(pointer); + if policy == index::BoundsCheckPolicy::ReadZeroSkipWrite + && self.put_bounds_checks( + pointer, + context, + back::Level(0), + if is_scoped { "" } else { "(" }, + )? + { + write!(self.out, " ? ")?; + self.put_unchecked_load(pointer, policy, context)?; + write!(self.out, " : DefaultConstructible()")?; + + if !is_scoped { + write!(self.out, ")")?; + } + } else { + self.put_unchecked_load(pointer, policy, context)?; + } + + Ok(()) + } + + fn put_unchecked_load( + &mut self, + pointer: Handle, + policy: index::BoundsCheckPolicy, + context: &ExpressionContext, + ) -> BackendResult { + let is_atomic_pointer = context + .resolve_type(pointer) + .is_atomic_pointer(&context.module.types); + + if is_atomic_pointer { + write!( + self.out, + "{NAMESPACE}::atomic_load_explicit({ATOMIC_REFERENCE}" + )?; + self.put_access_chain(pointer, policy, context)?; + write!(self.out, ", {NAMESPACE}::memory_order_relaxed)")?; + } else { + // We don't do any dereferencing with `*` here as pointer arguments to functions + // are done by `&` references and not `*` pointers. These do not need to be + // dereferenced. + self.put_access_chain(pointer, policy, context)?; + } + + Ok(()) + } + + fn put_return_value( + &mut self, + level: back::Level, + expr_handle: Handle, + result_struct: Option<&str>, + context: &ExpressionContext, + ) -> BackendResult { + match result_struct { + Some(struct_name) => { + let mut has_point_size = false; + let result_ty = context.function.result.as_ref().unwrap().ty; + match context.module.types[result_ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + let tmp = "_tmp"; + write!(self.out, "{level}const auto {tmp} = ")?; + self.put_expression(expr_handle, context, true)?; + writeln!(self.out, ";")?; + write!(self.out, "{level}return {struct_name} {{")?; + + let mut is_first = true; + + for (index, member) in members.iter().enumerate() { + if let Some(crate::Binding::BuiltIn(crate::BuiltIn::PointSize)) = + member.binding + { + has_point_size = true; + if !context.pipeline_options.allow_and_force_point_size { + continue; + } + } + + let comma = if is_first { "" } else { "," }; + is_first = false; + let name = &self.names[&NameKey::StructMember(result_ty, index as u32)]; + // HACK: we are forcefully deduplicating the expression here + // to convert from a wrapped struct to a raw array, e.g. + // `float gl_ClipDistance1 [[clip_distance]] [1];`. + if let crate::TypeInner::Array { + size: crate::ArraySize::Constant(size), + .. + } = context.module.types[member.ty].inner + { + write!(self.out, "{comma} {{")?; + for j in 0..size.get() { + if j != 0 { + write!(self.out, ",")?; + } + write!(self.out, "{tmp}.{name}.{WRAPPED_ARRAY_FIELD}[{j}]")?; + } + write!(self.out, "}}")?; + } else { + write!(self.out, "{comma} {tmp}.{name}")?; + } + } + } + _ => { + write!(self.out, "{level}return {struct_name} {{ ")?; + self.put_expression(expr_handle, context, true)?; + } + } + + if let FunctionOrigin::EntryPoint(ep_index) = context.origin { + let stage = context.module.entry_points[ep_index as usize].stage; + if context.pipeline_options.allow_and_force_point_size + && stage == crate::ShaderStage::Vertex + && !has_point_size + { + // point size was injected and comes last + write!(self.out, ", 1.0")?; + } + } + write!(self.out, " }}")?; + } + None => { + write!(self.out, "{level}return ")?; + self.put_expression(expr_handle, context, true)?; + } + } + writeln!(self.out, ";")?; + Ok(()) + } + + /// Helper method used to find which expressions of a given function require baking + /// + /// # Notes + /// This function overwrites the contents of `self.need_bake_expressions` + fn update_expressions_to_bake( + &mut self, + func: &crate::Function, + info: &valid::FunctionInfo, + context: &ExpressionContext, + ) { + use crate::Expression; + self.need_bake_expressions.clear(); + + for (expr_handle, expr) in func.expressions.iter() { + // Expressions whose reference count is above the + // threshold should always be stored in temporaries. + let expr_info = &info[expr_handle]; + let min_ref_count = func.expressions[expr_handle].bake_ref_count(); + if min_ref_count <= expr_info.ref_count { + self.need_bake_expressions.insert(expr_handle); + } else { + match expr_info.ty { + // force ray desc to be baked: it's used multiple times internally + TypeResolution::Handle(h) + if Some(h) == context.module.special_types.ray_desc => + { + self.need_bake_expressions.insert(expr_handle); + } + _ => {} + } + } + + if let Expression::Math { fun, arg, arg1, .. } = *expr { + match fun { + crate::MathFunction::Dot => { + // WGSL's `dot` function works on any `vecN` type, but Metal's only + // works on floating-point vectors, so we emit inline code for + // integer vector `dot` calls. But that code uses each argument `N` + // times, once for each component (see `put_dot_product`), so to + // avoid duplicated evaluation, we must bake integer operands. + + // check what kind of product this is depending + // on the resolve type of the Dot function itself + let inner = context.resolve_type(expr_handle); + if let crate::TypeInner::Scalar(scalar) = *inner { + match scalar.kind { + crate::ScalarKind::Sint | crate::ScalarKind::Uint => { + self.need_bake_expressions.insert(arg); + self.need_bake_expressions.insert(arg1.unwrap()); + } + _ => {} + } + } + } + crate::MathFunction::FindMsb => { + self.need_bake_expressions.insert(arg); + } + crate::MathFunction::Sign => { + // WGSL's `sign` function works also on signed ints, but Metal's only + // works on floating points, so we emit inline code for integer `sign` + // calls. But that code uses each argument 2 times (see `put_isign`), + // so to avoid duplicated evaluation, we must bake the argument. + let inner = context.resolve_type(expr_handle); + if inner.scalar_kind() == Some(crate::ScalarKind::Sint) { + self.need_bake_expressions.insert(arg); + } + } + _ => {} + } + } + } + } + + fn start_baking_expression( + &mut self, + handle: Handle, + context: &ExpressionContext, + name: &str, + ) -> BackendResult { + match context.info[handle].ty { + TypeResolution::Handle(ty_handle) => { + let ty_name = TypeContext { + handle: ty_handle, + gctx: context.module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + write!(self.out, "{ty_name}")?; + } + TypeResolution::Value(crate::TypeInner::Scalar(scalar)) => { + put_numeric_type(&mut self.out, scalar, &[])?; + } + TypeResolution::Value(crate::TypeInner::Vector { size, scalar }) => { + put_numeric_type(&mut self.out, scalar, &[size])?; + } + TypeResolution::Value(crate::TypeInner::Matrix { + columns, + rows, + scalar, + }) => { + put_numeric_type(&mut self.out, scalar, &[rows, columns])?; + } + TypeResolution::Value(ref other) => { + log::warn!("Type {:?} isn't a known local", other); //TEMP! + return Err(Error::FeatureNotImplemented("weird local type".to_string())); + } + } + + //TODO: figure out the naming scheme that wouldn't collide with user names. + write!(self.out, " {name} = ")?; + + Ok(()) + } + + /// Cache a clamped level of detail value, if necessary. + /// + /// [`ImageLoad`] accesses covered by [`BoundsCheckPolicy::Restrict`] use a + /// properly clamped level of detail value both in the access itself, and + /// for fetching the size of the requested MIP level, needed to clamp the + /// coordinates. To avoid recomputing this clamped level of detail, we cache + /// it in a temporary variable, as part of the [`Emit`] statement covering + /// the [`ImageLoad`] expression. + /// + /// [`ImageLoad`]: crate::Expression::ImageLoad + /// [`BoundsCheckPolicy::Restrict`]: index::BoundsCheckPolicy::Restrict + /// [`Emit`]: crate::Statement::Emit + fn put_cache_restricted_level( + &mut self, + load: Handle, + image: Handle, + mip_level: Option>, + indent: back::Level, + context: &StatementContext, + ) -> BackendResult { + // Does this image access actually require (or even permit) a + // level-of-detail, and does the policy require us to restrict it? + let level_of_detail = match mip_level { + Some(level) => level, + None => return Ok(()), + }; + + if context.expression.policies.image_load != index::BoundsCheckPolicy::Restrict + || !context.expression.image_needs_lod(image) + { + return Ok(()); + } + + write!( + self.out, + "{}uint {}{} = ", + indent, + CLAMPED_LOD_LOAD_PREFIX, + load.index(), + )?; + self.put_restricted_scalar_image_index( + image, + level_of_detail, + "get_num_mip_levels", + &context.expression, + )?; + writeln!(self.out, ";")?; + + Ok(()) + } + + fn put_block( + &mut self, + level: back::Level, + statements: &[crate::Statement], + context: &StatementContext, + ) -> BackendResult { + // Add to the set in order to track the stack size. + #[cfg(test)] + #[allow(trivial_casts)] + self.put_block_stack_pointers + .insert(&level as *const _ as *const ()); + + for statement in statements { + log::trace!("statement[{}] {:?}", level.0, statement); + match *statement { + crate::Statement::Emit(ref range) => { + for handle in range.clone() { + // `ImageLoad` expressions covered by the `Restrict` bounds check policy + // may need to cache a clamped version of their level-of-detail argument. + if let crate::Expression::ImageLoad { + image, + level: mip_level, + .. + } = context.expression.function.expressions[handle] + { + self.put_cache_restricted_level( + handle, image, mip_level, level, context, + )?; + } + + let ptr_class = context.expression.resolve_type(handle).pointer_space(); + let expr_name = if ptr_class.is_some() { + None // don't bake pointer expressions (just yet) + } else if let Some(name) = + context.expression.function.named_expressions.get(&handle) + { + // The `crate::Function::named_expressions` table holds + // expressions that should be saved in temporaries once they + // are `Emit`ted. We only add them to `self.named_expressions` + // when we reach the `Emit` that covers them, so that we don't + // try to use their names before we've actually initialized + // the temporary that holds them. + // + // Don't assume the names in `named_expressions` are unique, + // or even valid. Use the `Namer`. + Some(self.namer.call(name)) + } else { + // If this expression is an index that we're going to first compare + // against a limit, and then actually use as an index, then we may + // want to cache it in a temporary, to avoid evaluating it twice. + let bake = + if context.expression.guarded_indices.contains(handle.index()) { + true + } else { + self.need_bake_expressions.contains(&handle) + }; + + if bake { + Some(format!("{}{}", back::BAKE_PREFIX, handle.index())) + } else { + None + } + }; + + if let Some(name) = expr_name { + write!(self.out, "{level}")?; + self.start_baking_expression(handle, &context.expression, &name)?; + self.put_expression(handle, &context.expression, true)?; + self.named_expressions.insert(handle, name); + writeln!(self.out, ";")?; + } + } + } + crate::Statement::Block(ref block) => { + if !block.is_empty() { + writeln!(self.out, "{level}{{")?; + self.put_block(level.next(), block, context)?; + writeln!(self.out, "{level}}}")?; + } + } + crate::Statement::If { + condition, + ref accept, + ref reject, + } => { + write!(self.out, "{level}if (")?; + self.put_expression(condition, &context.expression, true)?; + writeln!(self.out, ") {{")?; + self.put_block(level.next(), accept, context)?; + if !reject.is_empty() { + writeln!(self.out, "{level}}} else {{")?; + self.put_block(level.next(), reject, context)?; + } + writeln!(self.out, "{level}}}")?; + } + crate::Statement::Switch { + selector, + ref cases, + } => { + write!(self.out, "{level}switch(")?; + self.put_expression(selector, &context.expression, true)?; + writeln!(self.out, ") {{")?; + let lcase = level.next(); + for case in cases.iter() { + match case.value { + crate::SwitchValue::I32(value) => { + write!(self.out, "{lcase}case {value}:")?; + } + crate::SwitchValue::U32(value) => { + write!(self.out, "{lcase}case {value}u:")?; + } + crate::SwitchValue::Default => { + write!(self.out, "{lcase}default:")?; + } + } + + let write_block_braces = !(case.fall_through && case.body.is_empty()); + if write_block_braces { + writeln!(self.out, " {{")?; + } else { + writeln!(self.out)?; + } + + self.put_block(lcase.next(), &case.body, context)?; + if !case.fall_through + && case.body.last().map_or(true, |s| !s.is_terminator()) + { + writeln!(self.out, "{}break;", lcase.next())?; + } + + if write_block_braces { + writeln!(self.out, "{lcase}}}")?; + } + } + writeln!(self.out, "{level}}}")?; + } + crate::Statement::Loop { + ref body, + ref continuing, + break_if, + } => { + if !continuing.is_empty() || break_if.is_some() { + let gate_name = self.namer.call("loop_init"); + writeln!(self.out, "{level}bool {gate_name} = true;")?; + writeln!(self.out, "{level}while(true) {{")?; + let lif = level.next(); + let lcontinuing = lif.next(); + writeln!(self.out, "{lif}if (!{gate_name}) {{")?; + self.put_block(lcontinuing, continuing, context)?; + if let Some(condition) = break_if { + write!(self.out, "{lcontinuing}if (")?; + self.put_expression(condition, &context.expression, true)?; + writeln!(self.out, ") {{")?; + writeln!(self.out, "{}break;", lcontinuing.next())?; + writeln!(self.out, "{lcontinuing}}}")?; + } + writeln!(self.out, "{lif}}}")?; + writeln!(self.out, "{lif}{gate_name} = false;")?; + } else { + writeln!(self.out, "{level}while(true) {{")?; + } + self.put_block(level.next(), body, context)?; + writeln!(self.out, "{level}}}")?; + } + crate::Statement::Break => { + writeln!(self.out, "{level}break;")?; + } + crate::Statement::Continue => { + writeln!(self.out, "{level}continue;")?; + } + crate::Statement::Return { + value: Some(expr_handle), + } => { + self.put_return_value( + level, + expr_handle, + context.result_struct, + &context.expression, + )?; + } + crate::Statement::Return { value: None } => { + writeln!(self.out, "{level}return;")?; + } + crate::Statement::Kill => { + writeln!(self.out, "{level}{NAMESPACE}::discard_fragment();")?; + } + crate::Statement::Barrier(flags) => { + self.write_barrier(flags, level)?; + } + crate::Statement::Store { pointer, value } => { + self.put_store(pointer, value, level, context)? + } + crate::Statement::ImageStore { + image, + coordinate, + array_index, + value, + } => { + let address = TexelAddress { + coordinate, + array_index, + sample: None, + level: None, + }; + self.put_image_store(level, image, &address, value, context)? + } + crate::Statement::Call { + function, + ref arguments, + result, + } => { + write!(self.out, "{level}")?; + if let Some(expr) = result { + let name = format!("{}{}", back::BAKE_PREFIX, expr.index()); + self.start_baking_expression(expr, &context.expression, &name)?; + self.named_expressions.insert(expr, name); + } + let fun_name = &self.names[&NameKey::Function(function)]; + write!(self.out, "{fun_name}(")?; + // first, write down the actual arguments + for (i, &handle) in arguments.iter().enumerate() { + if i != 0 { + write!(self.out, ", ")?; + } + self.put_expression(handle, &context.expression, true)?; + } + // follow-up with any global resources used + let mut separate = !arguments.is_empty(); + let fun_info = &context.expression.mod_info[function]; + let mut supports_array_length = false; + for (handle, var) in context.expression.module.global_variables.iter() { + if fun_info[handle].is_empty() { + continue; + } + if var.space.needs_pass_through() { + let name = &self.names[&NameKey::GlobalVariable(handle)]; + if separate { + write!(self.out, ", ")?; + } else { + separate = true; + } + write!(self.out, "{name}")?; + } + supports_array_length |= + needs_array_length(var.ty, &context.expression.module.types); + } + if supports_array_length { + if separate { + write!(self.out, ", ")?; + } + write!(self.out, "_buffer_sizes")?; + } + + // done + writeln!(self.out, ");")?; + } + crate::Statement::Atomic { + pointer, + ref fun, + value, + result, + } => { + write!(self.out, "{level}")?; + let res_name = format!("{}{}", back::BAKE_PREFIX, result.index()); + self.start_baking_expression(result, &context.expression, &res_name)?; + self.named_expressions.insert(result, res_name); + match *fun { + crate::AtomicFunction::Add => { + self.put_atomic_fetch(pointer, "add", value, &context.expression)?; + } + crate::AtomicFunction::Subtract => { + self.put_atomic_fetch(pointer, "sub", value, &context.expression)?; + } + crate::AtomicFunction::And => { + self.put_atomic_fetch(pointer, "and", value, &context.expression)?; + } + crate::AtomicFunction::InclusiveOr => { + self.put_atomic_fetch(pointer, "or", value, &context.expression)?; + } + crate::AtomicFunction::ExclusiveOr => { + self.put_atomic_fetch(pointer, "xor", value, &context.expression)?; + } + crate::AtomicFunction::Min => { + self.put_atomic_fetch(pointer, "min", value, &context.expression)?; + } + crate::AtomicFunction::Max => { + self.put_atomic_fetch(pointer, "max", value, &context.expression)?; + } + crate::AtomicFunction::Exchange { compare: None } => { + self.put_atomic_operation( + pointer, + "exchange", + "", + value, + &context.expression, + )?; + } + crate::AtomicFunction::Exchange { .. } => { + return Err(Error::FeatureNotImplemented( + "atomic CompareExchange".to_string(), + )); + } + } + // done + writeln!(self.out, ";")?; + } + crate::Statement::WorkGroupUniformLoad { pointer, result } => { + self.write_barrier(crate::Barrier::WORK_GROUP, level)?; + + write!(self.out, "{level}")?; + let name = self.namer.call(""); + self.start_baking_expression(result, &context.expression, &name)?; + self.put_load(pointer, &context.expression, true)?; + self.named_expressions.insert(result, name); + + writeln!(self.out, ";")?; + self.write_barrier(crate::Barrier::WORK_GROUP, level)?; + } + crate::Statement::RayQuery { query, ref fun } => { + if context.expression.lang_version < (2, 4) { + return Err(Error::UnsupportedRayTracing); + } + + match *fun { + crate::RayQueryFunction::Initialize { + acceleration_structure, + descriptor, + } => { + //TODO: how to deal with winding? + write!(self.out, "{level}")?; + self.put_expression(query, &context.expression, true)?; + writeln!(self.out, ".{RAY_QUERY_FIELD_INTERSECTOR}.assume_geometry_type({RT_NAMESPACE}::geometry_type::triangle);")?; + { + let f_opaque = back::RayFlag::CULL_OPAQUE.bits(); + let f_no_opaque = back::RayFlag::CULL_NO_OPAQUE.bits(); + write!(self.out, "{level}")?; + self.put_expression(query, &context.expression, true)?; + write!( + self.out, + ".{RAY_QUERY_FIELD_INTERSECTOR}.set_opacity_cull_mode((" + )?; + self.put_expression(descriptor, &context.expression, true)?; + write!(self.out, ".flags & {f_opaque}) != 0 ? {RT_NAMESPACE}::opacity_cull_mode::opaque : (")?; + self.put_expression(descriptor, &context.expression, true)?; + write!(self.out, ".flags & {f_no_opaque}) != 0 ? {RT_NAMESPACE}::opacity_cull_mode::non_opaque : ")?; + writeln!(self.out, "{RT_NAMESPACE}::opacity_cull_mode::none);")?; + } + { + let f_opaque = back::RayFlag::OPAQUE.bits(); + let f_no_opaque = back::RayFlag::NO_OPAQUE.bits(); + write!(self.out, "{level}")?; + self.put_expression(query, &context.expression, true)?; + write!(self.out, ".{RAY_QUERY_FIELD_INTERSECTOR}.force_opacity((")?; + self.put_expression(descriptor, &context.expression, true)?; + write!(self.out, ".flags & {f_opaque}) != 0 ? {RT_NAMESPACE}::forced_opacity::opaque : (")?; + self.put_expression(descriptor, &context.expression, true)?; + write!(self.out, ".flags & {f_no_opaque}) != 0 ? {RT_NAMESPACE}::forced_opacity::non_opaque : ")?; + writeln!(self.out, "{RT_NAMESPACE}::forced_opacity::none);")?; + } + { + let flag = back::RayFlag::TERMINATE_ON_FIRST_HIT.bits(); + write!(self.out, "{level}")?; + self.put_expression(query, &context.expression, true)?; + write!( + self.out, + ".{RAY_QUERY_FIELD_INTERSECTOR}.accept_any_intersection((" + )?; + self.put_expression(descriptor, &context.expression, true)?; + writeln!(self.out, ".flags & {flag}) != 0);")?; + } + + write!(self.out, "{level}")?; + self.put_expression(query, &context.expression, true)?; + write!(self.out, ".{RAY_QUERY_FIELD_INTERSECTION} = ")?; + self.put_expression(query, &context.expression, true)?; + write!( + self.out, + ".{RAY_QUERY_FIELD_INTERSECTOR}.intersect({RT_NAMESPACE}::ray(" + )?; + self.put_expression(descriptor, &context.expression, true)?; + write!(self.out, ".origin, ")?; + self.put_expression(descriptor, &context.expression, true)?; + write!(self.out, ".dir, ")?; + self.put_expression(descriptor, &context.expression, true)?; + write!(self.out, ".tmin, ")?; + self.put_expression(descriptor, &context.expression, true)?; + write!(self.out, ".tmax), ")?; + self.put_expression(acceleration_structure, &context.expression, true)?; + write!(self.out, ", ")?; + self.put_expression(descriptor, &context.expression, true)?; + write!(self.out, ".cull_mask);")?; + + write!(self.out, "{level}")?; + self.put_expression(query, &context.expression, true)?; + writeln!(self.out, ".{RAY_QUERY_FIELD_READY} = true;")?; + } + crate::RayQueryFunction::Proceed { result } => { + write!(self.out, "{level}")?; + let name = format!("{}{}", back::BAKE_PREFIX, result.index()); + self.start_baking_expression(result, &context.expression, &name)?; + self.named_expressions.insert(result, name); + self.put_expression(query, &context.expression, true)?; + writeln!(self.out, ".{RAY_QUERY_FIELD_READY};")?; + //TODO: actually proceed? + + write!(self.out, "{level}")?; + self.put_expression(query, &context.expression, true)?; + writeln!(self.out, ".{RAY_QUERY_FIELD_READY} = false;")?; + } + crate::RayQueryFunction::Terminate => { + write!(self.out, "{level}")?; + self.put_expression(query, &context.expression, true)?; + writeln!(self.out, ".{RAY_QUERY_FIELD_INTERSECTION}.abort();")?; + } + } + } + } + } + + // un-emit expressions + //TODO: take care of loop/continuing? + for statement in statements { + if let crate::Statement::Emit(ref range) = *statement { + for handle in range.clone() { + self.named_expressions.remove(&handle); + } + } + } + Ok(()) + } + + fn put_store( + &mut self, + pointer: Handle, + value: Handle, + level: back::Level, + context: &StatementContext, + ) -> BackendResult { + let policy = context.expression.choose_bounds_check_policy(pointer); + if policy == index::BoundsCheckPolicy::ReadZeroSkipWrite + && self.put_bounds_checks(pointer, &context.expression, level, "if (")? + { + writeln!(self.out, ") {{")?; + self.put_unchecked_store(pointer, value, policy, level.next(), context)?; + writeln!(self.out, "{level}}}")?; + } else { + self.put_unchecked_store(pointer, value, policy, level, context)?; + } + + Ok(()) + } + + fn put_unchecked_store( + &mut self, + pointer: Handle, + value: Handle, + policy: index::BoundsCheckPolicy, + level: back::Level, + context: &StatementContext, + ) -> BackendResult { + let is_atomic_pointer = context + .expression + .resolve_type(pointer) + .is_atomic_pointer(&context.expression.module.types); + + if is_atomic_pointer { + write!( + self.out, + "{level}{NAMESPACE}::atomic_store_explicit({ATOMIC_REFERENCE}" + )?; + self.put_access_chain(pointer, policy, &context.expression)?; + write!(self.out, ", ")?; + self.put_expression(value, &context.expression, true)?; + writeln!(self.out, ", {NAMESPACE}::memory_order_relaxed);")?; + } else { + write!(self.out, "{level}")?; + self.put_access_chain(pointer, policy, &context.expression)?; + write!(self.out, " = ")?; + self.put_expression(value, &context.expression, true)?; + writeln!(self.out, ";")?; + } + + Ok(()) + } + + pub fn write( + &mut self, + module: &crate::Module, + info: &valid::ModuleInfo, + options: &Options, + pipeline_options: &PipelineOptions, + ) -> Result { + self.names.clear(); + self.namer.reset( + module, + super::keywords::RESERVED, + &[], + &[], + &[CLAMPED_LOD_LOAD_PREFIX], + &mut self.names, + ); + self.struct_member_pads.clear(); + + writeln!( + self.out, + "// language: metal{}.{}", + options.lang_version.0, options.lang_version.1 + )?; + writeln!(self.out, "#include ")?; + writeln!(self.out, "#include ")?; + writeln!(self.out)?; + // Work around Metal bug where `uint` is not available by default + writeln!(self.out, "using {NAMESPACE}::uint;")?; + + let mut uses_ray_query = false; + for (_, ty) in module.types.iter() { + match ty.inner { + crate::TypeInner::AccelerationStructure => { + if options.lang_version < (2, 4) { + return Err(Error::UnsupportedRayTracing); + } + } + crate::TypeInner::RayQuery => { + if options.lang_version < (2, 4) { + return Err(Error::UnsupportedRayTracing); + } + uses_ray_query = true; + } + _ => (), + } + } + + if module.special_types.ray_desc.is_some() + || module.special_types.ray_intersection.is_some() + { + if options.lang_version < (2, 4) { + return Err(Error::UnsupportedRayTracing); + } + } + + if uses_ray_query { + self.put_ray_query_type()?; + } + + if options + .bounds_check_policies + .contains(index::BoundsCheckPolicy::ReadZeroSkipWrite) + { + self.put_default_constructible()?; + } + writeln!(self.out)?; + + { + let mut indices = vec![]; + for (handle, var) in module.global_variables.iter() { + if needs_array_length(var.ty, &module.types) { + let idx = handle.index(); + indices.push(idx); + } + } + + if !indices.is_empty() { + writeln!(self.out, "struct _mslBufferSizes {{")?; + + for idx in indices { + writeln!(self.out, "{}uint size{};", back::INDENT, idx)?; + } + + writeln!(self.out, "}};")?; + writeln!(self.out)?; + } + }; + + self.write_type_defs(module)?; + self.write_global_constants(module, info)?; + self.write_functions(module, info, options, pipeline_options) + } + + /// Write the definition for the `DefaultConstructible` class. + /// + /// The [`ReadZeroSkipWrite`] bounds check policy requires us to be able to + /// produce 'zero' values for any type, including structs, arrays, and so + /// on. We could do this by emitting default constructor applications, but + /// that would entail printing the name of the type, which is more trouble + /// than you'd think. Instead, we just construct this magic C++14 class that + /// can be converted to any type that can be default constructed, using + /// template parameter inference to detect which type is needed, so we don't + /// have to figure out the name. + /// + /// [`ReadZeroSkipWrite`]: index::BoundsCheckPolicy::ReadZeroSkipWrite + fn put_default_constructible(&mut self) -> BackendResult { + let tab = back::INDENT; + writeln!(self.out, "struct DefaultConstructible {{")?; + writeln!(self.out, "{tab}template")?; + writeln!(self.out, "{tab}operator T() && {{")?; + writeln!(self.out, "{tab}{tab}return T {{}};")?; + writeln!(self.out, "{tab}}}")?; + writeln!(self.out, "}};")?; + Ok(()) + } + + fn put_ray_query_type(&mut self) -> BackendResult { + let tab = back::INDENT; + writeln!(self.out, "struct {RAY_QUERY_TYPE} {{")?; + let full_type = format!("{RT_NAMESPACE}::intersector<{RT_NAMESPACE}::instancing, {RT_NAMESPACE}::triangle_data, {RT_NAMESPACE}::world_space_data>"); + writeln!(self.out, "{tab}{full_type} {RAY_QUERY_FIELD_INTERSECTOR};")?; + writeln!( + self.out, + "{tab}{full_type}::result_type {RAY_QUERY_FIELD_INTERSECTION};" + )?; + writeln!(self.out, "{tab}bool {RAY_QUERY_FIELD_READY} = false;")?; + writeln!(self.out, "}};")?; + writeln!(self.out, "constexpr {NAMESPACE}::uint {RAY_QUERY_FUN_MAP_INTERSECTION}(const {RT_NAMESPACE}::intersection_type ty) {{")?; + let v_triangle = back::RayIntersectionType::Triangle as u32; + let v_bbox = back::RayIntersectionType::BoundingBox as u32; + writeln!( + self.out, + "{tab}return ty=={RT_NAMESPACE}::intersection_type::triangle ? {v_triangle} : " + )?; + writeln!( + self.out, + "{tab}{tab}ty=={RT_NAMESPACE}::intersection_type::bounding_box ? {v_bbox} : 0;" + )?; + writeln!(self.out, "}}")?; + Ok(()) + } + + fn write_type_defs(&mut self, module: &crate::Module) -> BackendResult { + for (handle, ty) in module.types.iter() { + if !ty.needs_alias() { + continue; + } + let name = &self.names[&NameKey::Type(handle)]; + match ty.inner { + // Naga IR can pass around arrays by value, but Metal, following + // C++, performs an array-to-pointer conversion (C++ [conv.array]) + // on expressions of array type, so assigning the array by value + // isn't possible. However, Metal *does* assign structs by + // value. So in our Metal output, we wrap all array types in + // synthetic struct types: + // + // struct type1 { + // float inner[10] + // }; + // + // Then we carefully include `.inner` (`WRAPPED_ARRAY_FIELD`) in + // any expression that actually wants access to the array. + crate::TypeInner::Array { + base, + size, + stride: _, + } => { + let base_name = TypeContext { + handle: base, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + + match size { + crate::ArraySize::Constant(size) => { + writeln!(self.out, "struct {name} {{")?; + writeln!( + self.out, + "{}{} {}[{}];", + back::INDENT, + base_name, + WRAPPED_ARRAY_FIELD, + size + )?; + writeln!(self.out, "}};")?; + } + crate::ArraySize::Dynamic => { + writeln!(self.out, "typedef {base_name} {name}[1];")?; + } + } + } + crate::TypeInner::Struct { + ref members, span, .. + } => { + writeln!(self.out, "struct {name} {{")?; + let mut last_offset = 0; + for (index, member) in members.iter().enumerate() { + if member.offset > last_offset { + self.struct_member_pads.insert((handle, index as u32)); + let pad = member.offset - last_offset; + writeln!(self.out, "{}char _pad{}[{}];", back::INDENT, index, pad)?; + } + let ty_inner = &module.types[member.ty].inner; + last_offset = member.offset + ty_inner.size(module.to_ctx()); + + let member_name = &self.names[&NameKey::StructMember(handle, index as u32)]; + + // If the member should be packed (as is the case for a misaligned vec3) issue a packed vector + match should_pack_struct_member(members, span, index, module) { + Some(scalar) => { + writeln!( + self.out, + "{}{}::packed_{}3 {};", + back::INDENT, + NAMESPACE, + scalar.to_msl_name(), + member_name + )?; + } + None => { + let base_name = TypeContext { + handle: member.ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + writeln!( + self.out, + "{}{} {};", + back::INDENT, + base_name, + member_name + )?; + + // for 3-component vectors, add one component + if let crate::TypeInner::Vector { + size: crate::VectorSize::Tri, + scalar, + } = *ty_inner + { + last_offset += scalar.width as u32; + } + } + } + } + writeln!(self.out, "}};")?; + } + _ => { + let ty_name = TypeContext { + handle, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: true, + }; + writeln!(self.out, "typedef {ty_name} {name};")?; + } + } + } + + // Write functions to create special types. + for (type_key, struct_ty) in module.special_types.predeclared_types.iter() { + match type_key { + &crate::PredeclaredType::ModfResult { size, width } + | &crate::PredeclaredType::FrexpResult { size, width } => { + let arg_type_name_owner; + let arg_type_name = if let Some(size) = size { + arg_type_name_owner = format!( + "{NAMESPACE}::{}{}", + if width == 8 { "double" } else { "float" }, + size as u8 + ); + &arg_type_name_owner + } else if width == 8 { + "double" + } else { + "float" + }; + + let other_type_name_owner; + let (defined_func_name, called_func_name, other_type_name) = + if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) { + (MODF_FUNCTION, "modf", arg_type_name) + } else { + let other_type_name = if let Some(size) = size { + other_type_name_owner = format!("int{}", size as u8); + &other_type_name_owner + } else { + "int" + }; + (FREXP_FUNCTION, "frexp", other_type_name) + }; + + let struct_name = &self.names[&NameKey::Type(*struct_ty)]; + + writeln!(self.out)?; + writeln!( + self.out, + "{} {defined_func_name}({arg_type_name} arg) {{ + {other_type_name} other; + {arg_type_name} fract = {NAMESPACE}::{called_func_name}(arg, other); + return {}{{ fract, other }}; +}}", + struct_name, struct_name + )?; + } + &crate::PredeclaredType::AtomicCompareExchangeWeakResult { .. } => {} + } + } + + Ok(()) + } + + /// Writes all named constants + fn write_global_constants( + &mut self, + module: &crate::Module, + mod_info: &valid::ModuleInfo, + ) -> BackendResult { + let constants = module.constants.iter().filter(|&(_, c)| c.name.is_some()); + + for (handle, constant) in constants { + let ty_name = TypeContext { + handle: constant.ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + let name = &self.names[&NameKey::Constant(handle)]; + write!(self.out, "constant {ty_name} {name} = ")?; + self.put_const_expression(constant.init, module, mod_info)?; + writeln!(self.out, ";")?; + } + + Ok(()) + } + + fn put_inline_sampler_properties( + &mut self, + level: back::Level, + sampler: &sm::InlineSampler, + ) -> BackendResult { + for (&letter, address) in ['s', 't', 'r'].iter().zip(sampler.address.iter()) { + writeln!( + self.out, + "{}{}::{}_address::{},", + level, + NAMESPACE, + letter, + address.as_str(), + )?; + } + writeln!( + self.out, + "{}{}::mag_filter::{},", + level, + NAMESPACE, + sampler.mag_filter.as_str(), + )?; + writeln!( + self.out, + "{}{}::min_filter::{},", + level, + NAMESPACE, + sampler.min_filter.as_str(), + )?; + if let Some(filter) = sampler.mip_filter { + writeln!( + self.out, + "{}{}::mip_filter::{},", + level, + NAMESPACE, + filter.as_str(), + )?; + } + // avoid setting it on platforms that don't support it + if sampler.border_color != sm::BorderColor::TransparentBlack { + writeln!( + self.out, + "{}{}::border_color::{},", + level, + NAMESPACE, + sampler.border_color.as_str(), + )?; + } + //TODO: I'm not able to feed this in a way that MSL likes: + //>error: use of undeclared identifier 'lod_clamp' + //>error: no member named 'max_anisotropy' in namespace 'metal' + if false { + if let Some(ref lod) = sampler.lod_clamp { + writeln!(self.out, "{}lod_clamp({},{}),", level, lod.start, lod.end,)?; + } + if let Some(aniso) = sampler.max_anisotropy { + writeln!(self.out, "{}max_anisotropy({}),", level, aniso.get(),)?; + } + } + if sampler.compare_func != sm::CompareFunc::Never { + writeln!( + self.out, + "{}{}::compare_func::{},", + level, + NAMESPACE, + sampler.compare_func.as_str(), + )?; + } + writeln!( + self.out, + "{}{}::coord::{}", + level, + NAMESPACE, + sampler.coord.as_str() + )?; + Ok(()) + } + + // Returns the array of mapped entry point names. + fn write_functions( + &mut self, + module: &crate::Module, + mod_info: &valid::ModuleInfo, + options: &Options, + pipeline_options: &PipelineOptions, + ) -> Result { + let mut pass_through_globals = Vec::new(); + for (fun_handle, fun) in module.functions.iter() { + log::trace!( + "function {:?}, handle {:?}", + fun.name.as_deref().unwrap_or("(anonymous)"), + fun_handle + ); + + let fun_info = &mod_info[fun_handle]; + pass_through_globals.clear(); + let mut supports_array_length = false; + for (handle, var) in module.global_variables.iter() { + if !fun_info[handle].is_empty() { + if var.space.needs_pass_through() { + pass_through_globals.push(handle); + } + supports_array_length |= needs_array_length(var.ty, &module.types); + } + } + + writeln!(self.out)?; + let fun_name = &self.names[&NameKey::Function(fun_handle)]; + match fun.result { + Some(ref result) => { + let ty_name = TypeContext { + handle: result.ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + write!(self.out, "{ty_name}")?; + } + None => { + write!(self.out, "void")?; + } + } + writeln!(self.out, " {fun_name}(")?; + + for (index, arg) in fun.arguments.iter().enumerate() { + let name = &self.names[&NameKey::FunctionArgument(fun_handle, index as u32)]; + let param_type_name = TypeContext { + handle: arg.ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + let separator = separate( + !pass_through_globals.is_empty() + || index + 1 != fun.arguments.len() + || supports_array_length, + ); + writeln!( + self.out, + "{}{} {}{}", + back::INDENT, + param_type_name, + name, + separator + )?; + } + for (index, &handle) in pass_through_globals.iter().enumerate() { + let tyvar = TypedGlobalVariable { + module, + names: &self.names, + handle, + usage: fun_info[handle], + binding: None, + reference: true, + }; + let separator = + separate(index + 1 != pass_through_globals.len() || supports_array_length); + write!(self.out, "{}", back::INDENT)?; + tyvar.try_fmt(&mut self.out)?; + writeln!(self.out, "{separator}")?; + } + + if supports_array_length { + writeln!( + self.out, + "{}constant _mslBufferSizes& _buffer_sizes", + back::INDENT + )?; + } + + writeln!(self.out, ") {{")?; + + let guarded_indices = + index::find_checked_indexes(module, fun, fun_info, options.bounds_check_policies); + + let context = StatementContext { + expression: ExpressionContext { + function: fun, + origin: FunctionOrigin::Handle(fun_handle), + info: fun_info, + lang_version: options.lang_version, + policies: options.bounds_check_policies, + guarded_indices, + module, + mod_info, + pipeline_options, + }, + result_struct: None, + }; + + for (local_handle, local) in fun.local_variables.iter() { + let ty_name = TypeContext { + handle: local.ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + let local_name = &self.names[&NameKey::FunctionLocal(fun_handle, local_handle)]; + write!(self.out, "{}{} {}", back::INDENT, ty_name, local_name)?; + match local.init { + Some(value) => { + write!(self.out, " = ")?; + self.put_expression(value, &context.expression, true)?; + } + None => { + write!(self.out, " = {{}}")?; + } + }; + writeln!(self.out, ";")?; + } + + self.update_expressions_to_bake(fun, fun_info, &context.expression); + self.put_block(back::Level(1), &fun.body, &context)?; + writeln!(self.out, "}}")?; + self.named_expressions.clear(); + } + + let mut info = TranslationInfo { + entry_point_names: Vec::with_capacity(module.entry_points.len()), + }; + for (ep_index, ep) in module.entry_points.iter().enumerate() { + let fun = &ep.function; + let fun_info = mod_info.get_entry_point(ep_index); + let mut ep_error = None; + + log::trace!( + "entry point {:?}, index {:?}", + fun.name.as_deref().unwrap_or("(anonymous)"), + ep_index + ); + + // Is any global variable used by this entry point dynamically sized? + let supports_array_length = module + .global_variables + .iter() + .filter(|&(handle, _)| !fun_info[handle].is_empty()) + .any(|(_, var)| needs_array_length(var.ty, &module.types)); + + // skip this entry point if any global bindings are missing, + // or their types are incompatible. + if !options.fake_missing_bindings { + for (var_handle, var) in module.global_variables.iter() { + if fun_info[var_handle].is_empty() { + continue; + } + match var.space { + crate::AddressSpace::Uniform + | crate::AddressSpace::Storage { .. } + | crate::AddressSpace::Handle => { + let br = match var.binding { + Some(ref br) => br, + None => { + let var_name = var.name.clone().unwrap_or_default(); + ep_error = + Some(super::EntryPointError::MissingBinding(var_name)); + break; + } + }; + let target = options.get_resource_binding_target(ep, br); + let good = match target { + Some(target) => { + let binding_ty = match module.types[var.ty].inner { + crate::TypeInner::BindingArray { base, .. } => { + &module.types[base].inner + } + ref ty => ty, + }; + match *binding_ty { + crate::TypeInner::Image { .. } => target.texture.is_some(), + crate::TypeInner::Sampler { .. } => { + target.sampler.is_some() + } + _ => target.buffer.is_some(), + } + } + None => false, + }; + if !good { + ep_error = + Some(super::EntryPointError::MissingBindTarget(br.clone())); + break; + } + } + crate::AddressSpace::PushConstant => { + if let Err(e) = options.resolve_push_constants(ep) { + ep_error = Some(e); + break; + } + } + crate::AddressSpace::Function + | crate::AddressSpace::Private + | crate::AddressSpace::WorkGroup => {} + } + } + if supports_array_length { + if let Err(err) = options.resolve_sizes_buffer(ep) { + ep_error = Some(err); + } + } + } + + if let Some(err) = ep_error { + info.entry_point_names.push(Err(err)); + continue; + } + let fun_name = &self.names[&NameKey::EntryPoint(ep_index as _)]; + info.entry_point_names.push(Ok(fun_name.clone())); + + writeln!(self.out)?; + + let (em_str, in_mode, out_mode) = match ep.stage { + crate::ShaderStage::Vertex => ( + "vertex", + LocationMode::VertexInput, + LocationMode::VertexOutput, + ), + crate::ShaderStage::Fragment { .. } => ( + "fragment", + LocationMode::FragmentInput, + LocationMode::FragmentOutput, + ), + crate::ShaderStage::Compute { .. } => { + ("kernel", LocationMode::Uniform, LocationMode::Uniform) + } + }; + + // Since `Namer.reset` wasn't expecting struct members to be + // suddenly injected into another namespace like this, + // `self.names` doesn't keep them distinct from other variables. + // Generate fresh names for these arguments, and remember the + // mapping. + let mut flattened_member_names = FastHashMap::default(); + // Varyings' members get their own namespace + let mut varyings_namer = crate::proc::Namer::default(); + + // List all the Naga `EntryPoint`'s `Function`'s arguments, + // flattening structs into their members. In Metal, we will pass + // each of these values to the entry point as a separate argument— + // except for the varyings, handled next. + let mut flattened_arguments = Vec::new(); + for (arg_index, arg) in fun.arguments.iter().enumerate() { + match module.types[arg.ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + for (member_index, member) in members.iter().enumerate() { + let member_index = member_index as u32; + flattened_arguments.push(( + NameKey::StructMember(arg.ty, member_index), + member.ty, + member.binding.as_ref(), + )); + let name_key = NameKey::StructMember(arg.ty, member_index); + let name = match member.binding { + Some(crate::Binding::Location { .. }) => { + varyings_namer.call(&self.names[&name_key]) + } + _ => self.namer.call(&self.names[&name_key]), + }; + flattened_member_names.insert(name_key, name); + } + } + _ => flattened_arguments.push(( + NameKey::EntryPointArgument(ep_index as _, arg_index as u32), + arg.ty, + arg.binding.as_ref(), + )), + } + } + + // Identify the varyings among the argument values, and emit a + // struct type named `Input` to hold them. + let stage_in_name = format!("{fun_name}Input"); + let varyings_member_name = self.namer.call("varyings"); + let mut has_varyings = false; + if !flattened_arguments.is_empty() { + writeln!(self.out, "struct {stage_in_name} {{")?; + for &(ref name_key, ty, binding) in flattened_arguments.iter() { + let binding = match binding { + Some(ref binding @ &crate::Binding::Location { .. }) => binding, + _ => continue, + }; + has_varyings = true; + let name = match *name_key { + NameKey::StructMember(..) => &flattened_member_names[name_key], + _ => &self.names[name_key], + }; + let ty_name = TypeContext { + handle: ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + let resolved = options.resolve_local_binding(binding, in_mode)?; + write!(self.out, "{}{} {}", back::INDENT, ty_name, name)?; + resolved.try_fmt(&mut self.out)?; + writeln!(self.out, ";")?; + } + writeln!(self.out, "}};")?; + } + + // Define a struct type named for the return value, if any, named + // `Output`. + let stage_out_name = format!("{fun_name}Output"); + let result_member_name = self.namer.call("member"); + let result_type_name = match fun.result { + Some(ref result) => { + let mut result_members = Vec::new(); + if let crate::TypeInner::Struct { ref members, .. } = + module.types[result.ty].inner + { + for (member_index, member) in members.iter().enumerate() { + result_members.push(( + &self.names[&NameKey::StructMember(result.ty, member_index as u32)], + member.ty, + member.binding.as_ref(), + )); + } + } else { + result_members.push(( + &result_member_name, + result.ty, + result.binding.as_ref(), + )); + } + + writeln!(self.out, "struct {stage_out_name} {{")?; + let mut has_point_size = false; + for (name, ty, binding) in result_members { + let ty_name = TypeContext { + handle: ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: true, + }; + let binding = binding.ok_or(Error::Validation)?; + + if let crate::Binding::BuiltIn(crate::BuiltIn::PointSize) = *binding { + has_point_size = true; + if !pipeline_options.allow_and_force_point_size { + continue; + } + } + + let array_len = match module.types[ty].inner { + crate::TypeInner::Array { + size: crate::ArraySize::Constant(size), + .. + } => Some(size), + _ => None, + }; + let resolved = options.resolve_local_binding(binding, out_mode)?; + write!(self.out, "{}{} {}", back::INDENT, ty_name, name)?; + if let Some(array_len) = array_len { + write!(self.out, " [{array_len}]")?; + } + resolved.try_fmt(&mut self.out)?; + writeln!(self.out, ";")?; + } + + if pipeline_options.allow_and_force_point_size + && ep.stage == crate::ShaderStage::Vertex + && !has_point_size + { + // inject the point size output last + writeln!( + self.out, + "{}float _point_size [[point_size]];", + back::INDENT + )?; + } + writeln!(self.out, "}};")?; + &stage_out_name + } + None => "void", + }; + + // Write the entry point function's name, and begin its argument list. + writeln!(self.out, "{em_str} {result_type_name} {fun_name}(")?; + let mut is_first_argument = true; + + // If we have produced a struct holding the `EntryPoint`'s + // `Function`'s arguments' varyings, pass that struct first. + if has_varyings { + writeln!( + self.out, + " {stage_in_name} {varyings_member_name} [[stage_in]]" + )?; + is_first_argument = false; + } + + let mut local_invocation_id = None; + + // Then pass the remaining arguments not included in the varyings + // struct. + for &(ref name_key, ty, binding) in flattened_arguments.iter() { + let binding = match binding { + Some(binding @ &crate::Binding::BuiltIn { .. }) => binding, + _ => continue, + }; + let name = match *name_key { + NameKey::StructMember(..) => &flattened_member_names[name_key], + _ => &self.names[name_key], + }; + + if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { + local_invocation_id = Some(name_key); + } + + let ty_name = TypeContext { + handle: ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + let resolved = options.resolve_local_binding(binding, in_mode)?; + let separator = if is_first_argument { + is_first_argument = false; + ' ' + } else { + ',' + }; + write!(self.out, "{separator} {ty_name} {name}")?; + resolved.try_fmt(&mut self.out)?; + writeln!(self.out)?; + } + + let need_workgroup_variables_initialization = + self.need_workgroup_variables_initialization(options, ep, module, fun_info); + + if need_workgroup_variables_initialization && local_invocation_id.is_none() { + let separator = if is_first_argument { + is_first_argument = false; + ' ' + } else { + ',' + }; + writeln!( + self.out, + "{separator} {NAMESPACE}::uint3 __local_invocation_id [[thread_position_in_threadgroup]]" + )?; + } + + // Those global variables used by this entry point and its callees + // get passed as arguments. `Private` globals are an exception, they + // don't outlive this invocation, so we declare them below as locals + // within the entry point. + for (handle, var) in module.global_variables.iter() { + let usage = fun_info[handle]; + if usage.is_empty() || var.space == crate::AddressSpace::Private { + continue; + } + + if options.lang_version < (1, 2) { + match var.space { + // This restriction is not documented in the MSL spec + // but validation will fail if it is not upheld. + // + // We infer the required version from the "Function + // Buffer Read-Writes" section of [what's new], where + // the feature sets listed correspond with the ones + // supporting MSL 1.2. + // + // [what's new]: https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/WhatsNewiniOS10tvOS10andOSX1012/WhatsNewiniOS10tvOS10andOSX1012.html + crate::AddressSpace::Storage { access } + if access.contains(crate::StorageAccess::STORE) + && ep.stage == crate::ShaderStage::Fragment => + { + return Err(Error::UnsupportedWriteableStorageBuffer) + } + crate::AddressSpace::Handle => { + match module.types[var.ty].inner { + crate::TypeInner::Image { + class: crate::ImageClass::Storage { access, .. }, + .. + } => { + // This restriction is not documented in the MSL spec + // but validation will fail if it is not upheld. + // + // We infer the required version from the "Function + // Texture Read-Writes" section of [what's new], where + // the feature sets listed correspond with the ones + // supporting MSL 1.2. + // + // [what's new]: https://developer.apple.com/library/archive/documentation/Miscellaneous/Conceptual/MetalProgrammingGuide/WhatsNewiniOS10tvOS10andOSX1012/WhatsNewiniOS10tvOS10andOSX1012.html + if access.contains(crate::StorageAccess::STORE) + && (ep.stage == crate::ShaderStage::Vertex + || ep.stage == crate::ShaderStage::Fragment) + { + return Err(Error::UnsupportedWriteableStorageTexture( + ep.stage, + )); + } + + if access.contains( + crate::StorageAccess::LOAD | crate::StorageAccess::STORE, + ) { + return Err(Error::UnsupportedRWStorageTexture); + } + } + _ => {} + } + } + _ => {} + } + } + + // Check min MSL version for binding arrays + match var.space { + crate::AddressSpace::Handle => match module.types[var.ty].inner { + crate::TypeInner::BindingArray { base, .. } => { + match module.types[base].inner { + crate::TypeInner::Sampler { .. } => { + if options.lang_version < (2, 0) { + return Err(Error::UnsupportedArrayOf( + "samplers".to_string(), + )); + } + } + crate::TypeInner::Image { class, .. } => match class { + crate::ImageClass::Sampled { .. } + | crate::ImageClass::Depth { .. } + | crate::ImageClass::Storage { + access: crate::StorageAccess::LOAD, + .. + } => { + // Array of textures since: + // - iOS: Metal 1.2 (check depends on https://github.com/gfx-rs/naga/issues/2164) + // - macOS: Metal 2 + + if options.lang_version < (2, 0) { + return Err(Error::UnsupportedArrayOf( + "textures".to_string(), + )); + } + } + crate::ImageClass::Storage { + access: crate::StorageAccess::STORE, + .. + } => { + // Array of write-only textures since: + // - iOS: Metal 2.2 (check depends on https://github.com/gfx-rs/naga/issues/2164) + // - macOS: Metal 2 + + if options.lang_version < (2, 0) { + return Err(Error::UnsupportedArrayOf( + "write-only textures".to_string(), + )); + } + } + crate::ImageClass::Storage { .. } => { + return Err(Error::UnsupportedArrayOf( + "read-write textures".to_string(), + )); + } + }, + _ => { + return Err(Error::UnsupportedArrayOfType(base)); + } + } + } + _ => {} + }, + _ => {} + } + + // the resolves have already been checked for `!fake_missing_bindings` case + let resolved = match var.space { + crate::AddressSpace::PushConstant => options.resolve_push_constants(ep).ok(), + crate::AddressSpace::WorkGroup => None, + _ => options + .resolve_resource_binding(ep, var.binding.as_ref().unwrap()) + .ok(), + }; + if let Some(ref resolved) = resolved { + // Inline samplers are be defined in the EP body + if resolved.as_inline_sampler(options).is_some() { + continue; + } + } + + let tyvar = TypedGlobalVariable { + module, + names: &self.names, + handle, + usage, + binding: resolved.as_ref(), + reference: true, + }; + let separator = if is_first_argument { + is_first_argument = false; + ' ' + } else { + ',' + }; + write!(self.out, "{separator} ")?; + tyvar.try_fmt(&mut self.out)?; + if let Some(resolved) = resolved { + resolved.try_fmt(&mut self.out)?; + } + if let Some(value) = var.init { + write!(self.out, " = ")?; + self.put_const_expression(value, module, mod_info)?; + } + writeln!(self.out)?; + } + + // If this entry uses any variable-length arrays, their sizes are + // passed as a final struct-typed argument. + if supports_array_length { + // this is checked earlier + let resolved = options.resolve_sizes_buffer(ep).unwrap(); + let separator = if module.global_variables.is_empty() { + ' ' + } else { + ',' + }; + write!( + self.out, + "{separator} constant _mslBufferSizes& _buffer_sizes", + )?; + resolved.try_fmt(&mut self.out)?; + writeln!(self.out)?; + } + + // end of the entry point argument list + writeln!(self.out, ") {{")?; + + if need_workgroup_variables_initialization { + self.write_workgroup_variables_initialization( + module, + mod_info, + fun_info, + local_invocation_id, + )?; + } + + // Metal doesn't support private mutable variables outside of functions, + // so we put them here, just like the locals. + for (handle, var) in module.global_variables.iter() { + let usage = fun_info[handle]; + if usage.is_empty() { + continue; + } + if var.space == crate::AddressSpace::Private { + let tyvar = TypedGlobalVariable { + module, + names: &self.names, + handle, + usage, + binding: None, + reference: false, + }; + write!(self.out, "{}", back::INDENT)?; + tyvar.try_fmt(&mut self.out)?; + match var.init { + Some(value) => { + write!(self.out, " = ")?; + self.put_const_expression(value, module, mod_info)?; + writeln!(self.out, ";")?; + } + None => { + writeln!(self.out, " = {{}};")?; + } + }; + } else if let Some(ref binding) = var.binding { + // write an inline sampler + let resolved = options.resolve_resource_binding(ep, binding).unwrap(); + if let Some(sampler) = resolved.as_inline_sampler(options) { + let name = &self.names[&NameKey::GlobalVariable(handle)]; + writeln!( + self.out, + "{}constexpr {}::sampler {}(", + back::INDENT, + NAMESPACE, + name + )?; + self.put_inline_sampler_properties(back::Level(2), sampler)?; + writeln!(self.out, "{});", back::INDENT)?; + } + } + } + + // Now take the arguments that we gathered into structs, and the + // structs that we flattened into arguments, and emit local + // variables with initializers that put everything back the way the + // body code expects. + // + // If we had to generate fresh names for struct members passed as + // arguments, be sure to use those names when rebuilding the struct. + // + // "Each day, I change some zeros to ones, and some ones to zeros. + // The rest, I leave alone." + for (arg_index, arg) in fun.arguments.iter().enumerate() { + let arg_name = + &self.names[&NameKey::EntryPointArgument(ep_index as _, arg_index as u32)]; + match module.types[arg.ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + let struct_name = &self.names[&NameKey::Type(arg.ty)]; + write!( + self.out, + "{}const {} {} = {{ ", + back::INDENT, + struct_name, + arg_name + )?; + for (member_index, member) in members.iter().enumerate() { + let key = NameKey::StructMember(arg.ty, member_index as u32); + let name = &flattened_member_names[&key]; + if member_index != 0 { + write!(self.out, ", ")?; + } + // insert padding initialization, if needed + if self + .struct_member_pads + .contains(&(arg.ty, member_index as u32)) + { + write!(self.out, "{{}}, ")?; + } + if let Some(crate::Binding::Location { .. }) = member.binding { + write!(self.out, "{varyings_member_name}.")?; + } + write!(self.out, "{name}")?; + } + writeln!(self.out, " }};")?; + } + _ => { + if let Some(crate::Binding::Location { .. }) = arg.binding { + writeln!( + self.out, + "{}const auto {} = {}.{};", + back::INDENT, + arg_name, + varyings_member_name, + arg_name + )?; + } + } + } + } + + let guarded_indices = + index::find_checked_indexes(module, fun, fun_info, options.bounds_check_policies); + + let context = StatementContext { + expression: ExpressionContext { + function: fun, + origin: FunctionOrigin::EntryPoint(ep_index as _), + info: fun_info, + lang_version: options.lang_version, + policies: options.bounds_check_policies, + guarded_indices, + module, + mod_info, + pipeline_options, + }, + result_struct: Some(&stage_out_name), + }; + + // Finally, declare all the local variables that we need + //TODO: we can postpone this till the relevant expressions are emitted + for (local_handle, local) in fun.local_variables.iter() { + let name = &self.names[&NameKey::EntryPointLocal(ep_index as _, local_handle)]; + let ty_name = TypeContext { + handle: local.ty, + gctx: module.to_ctx(), + names: &self.names, + access: crate::StorageAccess::empty(), + binding: None, + first_time: false, + }; + write!(self.out, "{}{} {}", back::INDENT, ty_name, name)?; + match local.init { + Some(value) => { + write!(self.out, " = ")?; + self.put_expression(value, &context.expression, true)?; + } + None => { + write!(self.out, " = {{}}")?; + } + }; + writeln!(self.out, ";")?; + } + + self.update_expressions_to_bake(fun, fun_info, &context.expression); + self.put_block(back::Level(1), &fun.body, &context)?; + writeln!(self.out, "}}")?; + if ep_index + 1 != module.entry_points.len() { + writeln!(self.out)?; + } + self.named_expressions.clear(); + } + + Ok(info) + } + + fn write_barrier(&mut self, flags: crate::Barrier, level: back::Level) -> BackendResult { + // Note: OR-ring bitflags requires `__HAVE_MEMFLAG_OPERATORS__`, + // so we try to avoid it here. + if flags.is_empty() { + writeln!( + self.out, + "{level}{NAMESPACE}::threadgroup_barrier({NAMESPACE}::mem_flags::mem_none);", + )?; + } + if flags.contains(crate::Barrier::STORAGE) { + writeln!( + self.out, + "{level}{NAMESPACE}::threadgroup_barrier({NAMESPACE}::mem_flags::mem_device);", + )?; + } + if flags.contains(crate::Barrier::WORK_GROUP) { + writeln!( + self.out, + "{level}{NAMESPACE}::threadgroup_barrier({NAMESPACE}::mem_flags::mem_threadgroup);", + )?; + } + Ok(()) + } +} + +/// Initializing workgroup variables is more tricky for Metal because we have to deal +/// with atomics at the type-level (which don't have a copy constructor). +mod workgroup_mem_init { + use crate::EntryPoint; + + use super::*; + + enum Access { + GlobalVariable(Handle), + StructMember(Handle, u32), + Array(usize), + } + + impl Access { + fn write( + &self, + writer: &mut W, + names: &FastHashMap, + ) -> Result<(), core::fmt::Error> { + match *self { + Access::GlobalVariable(handle) => { + write!(writer, "{}", &names[&NameKey::GlobalVariable(handle)]) + } + Access::StructMember(handle, index) => { + write!(writer, ".{}", &names[&NameKey::StructMember(handle, index)]) + } + Access::Array(depth) => write!(writer, ".{WRAPPED_ARRAY_FIELD}[__i{depth}]"), + } + } + } + + struct AccessStack { + stack: Vec, + array_depth: usize, + } + + impl AccessStack { + const fn new() -> Self { + Self { + stack: Vec::new(), + array_depth: 0, + } + } + + fn enter_array(&mut self, cb: impl FnOnce(&mut Self, usize) -> R) -> R { + let array_depth = self.array_depth; + self.stack.push(Access::Array(array_depth)); + self.array_depth += 1; + let res = cb(self, array_depth); + self.stack.pop(); + self.array_depth -= 1; + res + } + + fn enter(&mut self, new: Access, cb: impl FnOnce(&mut Self) -> R) -> R { + self.stack.push(new); + let res = cb(self); + self.stack.pop(); + res + } + + fn write( + &self, + writer: &mut W, + names: &FastHashMap, + ) -> Result<(), core::fmt::Error> { + for next in self.stack.iter() { + next.write(writer, names)?; + } + Ok(()) + } + } + + impl Writer { + pub(super) fn need_workgroup_variables_initialization( + &mut self, + options: &Options, + ep: &EntryPoint, + module: &crate::Module, + fun_info: &valid::FunctionInfo, + ) -> bool { + options.zero_initialize_workgroup_memory + && ep.stage == crate::ShaderStage::Compute + && module.global_variables.iter().any(|(handle, var)| { + !fun_info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup + }) + } + + pub(super) fn write_workgroup_variables_initialization( + &mut self, + module: &crate::Module, + module_info: &valid::ModuleInfo, + fun_info: &valid::FunctionInfo, + local_invocation_id: Option<&NameKey>, + ) -> BackendResult { + let level = back::Level(1); + + writeln!( + self.out, + "{}if ({}::all({} == {}::uint3(0u))) {{", + level, + NAMESPACE, + local_invocation_id + .map(|name_key| self.names[name_key].as_str()) + .unwrap_or("__local_invocation_id"), + NAMESPACE, + )?; + + let mut access_stack = AccessStack::new(); + + let vars = module.global_variables.iter().filter(|&(handle, var)| { + !fun_info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup + }); + + for (handle, var) in vars { + access_stack.enter(Access::GlobalVariable(handle), |access_stack| { + self.write_workgroup_variable_initialization( + module, + module_info, + var.ty, + access_stack, + level.next(), + ) + })?; + } + + writeln!(self.out, "{level}}}")?; + self.write_barrier(crate::Barrier::WORK_GROUP, level) + } + + fn write_workgroup_variable_initialization( + &mut self, + module: &crate::Module, + module_info: &valid::ModuleInfo, + ty: Handle, + access_stack: &mut AccessStack, + level: back::Level, + ) -> BackendResult { + if module_info[ty].contains(valid::TypeFlags::CONSTRUCTIBLE) { + write!(self.out, "{level}")?; + access_stack.write(&mut self.out, &self.names)?; + writeln!(self.out, " = {{}};")?; + } else { + match module.types[ty].inner { + crate::TypeInner::Atomic { .. } => { + write!( + self.out, + "{level}{NAMESPACE}::atomic_store_explicit({ATOMIC_REFERENCE}" + )?; + access_stack.write(&mut self.out, &self.names)?; + writeln!(self.out, ", 0, {NAMESPACE}::memory_order_relaxed);")?; + } + crate::TypeInner::Array { base, size, .. } => { + let count = match size.to_indexable_length(module).expect("Bad array size") + { + proc::IndexableLength::Known(count) => count, + proc::IndexableLength::Dynamic => unreachable!(), + }; + + access_stack.enter_array(|access_stack, array_depth| { + writeln!( + self.out, + "{level}for (int __i{array_depth} = 0; __i{array_depth} < {count}; __i{array_depth}++) {{" + )?; + self.write_workgroup_variable_initialization( + module, + module_info, + base, + access_stack, + level.next(), + )?; + writeln!(self.out, "{level}}}")?; + BackendResult::Ok(()) + })?; + } + crate::TypeInner::Struct { ref members, .. } => { + for (index, member) in members.iter().enumerate() { + access_stack.enter( + Access::StructMember(ty, index as u32), + |access_stack| { + self.write_workgroup_variable_initialization( + module, + module_info, + member.ty, + access_stack, + level, + ) + }, + )?; + } + } + _ => unreachable!(), + } + } + + Ok(()) + } + } +} + +#[test] +fn test_stack_size() { + use crate::valid::{Capabilities, ValidationFlags}; + // create a module with at least one expression nested + let mut module = crate::Module::default(); + let mut fun = crate::Function::default(); + let const_expr = fun.expressions.append( + crate::Expression::Literal(crate::Literal::F32(1.0)), + Default::default(), + ); + let nested_expr = fun.expressions.append( + crate::Expression::Unary { + op: crate::UnaryOperator::Negate, + expr: const_expr, + }, + Default::default(), + ); + fun.body.push( + crate::Statement::Emit(fun.expressions.range_from(1)), + Default::default(), + ); + fun.body.push( + crate::Statement::If { + condition: nested_expr, + accept: crate::Block::new(), + reject: crate::Block::new(), + }, + Default::default(), + ); + let _ = module.functions.append(fun, Default::default()); + // analyse the module + let info = crate::valid::Validator::new(ValidationFlags::empty(), Capabilities::empty()) + .validate(&module) + .unwrap(); + // process the module + let mut writer = Writer::new(String::new()); + writer + .write(&module, &info, &Default::default(), &Default::default()) + .unwrap(); + + { + // check expression stack + let mut addresses_start = usize::MAX; + let mut addresses_end = 0usize; + for pointer in writer.put_expression_stack_pointers { + addresses_start = addresses_start.min(pointer as usize); + addresses_end = addresses_end.max(pointer as usize); + } + let stack_size = addresses_end - addresses_start; + // check the size (in debug only) + // last observed macOS value: 20528 (CI) + if !(11000..=25000).contains(&stack_size) { + panic!("`put_expression` stack size {stack_size} has changed!"); + } + } + + { + // check block stack + let mut addresses_start = usize::MAX; + let mut addresses_end = 0usize; + for pointer in writer.put_block_stack_pointers { + addresses_start = addresses_start.min(pointer as usize); + addresses_end = addresses_end.max(pointer as usize); + } + let stack_size = addresses_end - addresses_start; + // check the size (in debug only) + // last observed macOS value: 19152 (CI) + if !(9000..=20000).contains(&stack_size) { + panic!("`put_block` stack size {stack_size} has changed!"); + } + } +} diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs new file mode 100644 index 0000000000..6c96fa09e3 --- /dev/null +++ b/naga/src/back/spv/block.rs @@ -0,0 +1,2368 @@ +/*! +Implementations for `BlockContext` methods. +*/ + +use super::{ + helpers, index::BoundsCheckResult, make_local, selection::Selection, Block, BlockContext, + Dimension, Error, Instruction, LocalType, LookupType, LoopContext, ResultMember, Writer, + WriterFlags, +}; +use crate::{arena::Handle, proc::TypeResolution, Statement}; +use spirv::Word; + +fn get_dimension(type_inner: &crate::TypeInner) -> Dimension { + match *type_inner { + crate::TypeInner::Scalar(_) => Dimension::Scalar, + crate::TypeInner::Vector { .. } => Dimension::Vector, + crate::TypeInner::Matrix { .. } => Dimension::Matrix, + _ => unreachable!(), + } +} + +/// The results of emitting code for a left-hand-side expression. +/// +/// On success, `write_expression_pointer` returns one of these. +enum ExpressionPointer { + /// The pointer to the expression's value is available, as the value of the + /// expression with the given id. + Ready { pointer_id: Word }, + + /// The access expression must be conditional on the value of `condition`, a boolean + /// expression that is true if all indices are in bounds. If `condition` is true, then + /// `access` is an `OpAccessChain` instruction that will compute a pointer to the + /// expression's value. If `condition` is false, then executing `access` would be + /// undefined behavior. + Conditional { + condition: Word, + access: Instruction, + }, +} + +/// The termination statement to be added to the end of the block +pub enum BlockExit { + /// Generates an OpReturn (void return) + Return, + /// Generates an OpBranch to the specified block + Branch { + /// The branch target block + target: Word, + }, + /// Translates a loop `break if` into an `OpBranchConditional` to the + /// merge block if true (the merge block is passed through [`LoopContext::break_id`] + /// or else to the loop header (passed through [`preamble_id`]) + /// + /// [`preamble_id`]: Self::BreakIf::preamble_id + BreakIf { + /// The condition of the `break if` + condition: Handle, + /// The loop header block id + preamble_id: Word, + }, +} + +#[derive(Debug)] +pub(crate) struct DebugInfoInner<'a> { + pub source_code: &'a str, + pub source_file_id: Word, +} + +impl Writer { + // Flip Y coordinate to adjust for coordinate space difference + // between SPIR-V and our IR. + // The `position_id` argument is a pointer to a `vecN`, + // whose `y` component we will negate. + fn write_epilogue_position_y_flip( + &mut self, + position_id: Word, + body: &mut Vec, + ) -> Result<(), Error> { + let float_ptr_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::F32, + pointer_space: Some(spirv::StorageClass::Output), + })); + let index_y_id = self.get_index_constant(1); + let access_id = self.id_gen.next(); + body.push(Instruction::access_chain( + float_ptr_type_id, + access_id, + position_id, + &[index_y_id], + )); + + let float_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::F32, + pointer_space: None, + })); + let load_id = self.id_gen.next(); + body.push(Instruction::load(float_type_id, load_id, access_id, None)); + + let neg_id = self.id_gen.next(); + body.push(Instruction::unary( + spirv::Op::FNegate, + float_type_id, + neg_id, + load_id, + )); + + body.push(Instruction::store(access_id, neg_id, None)); + Ok(()) + } + + // Clamp fragment depth between 0 and 1. + fn write_epilogue_frag_depth_clamp( + &mut self, + frag_depth_id: Word, + body: &mut Vec, + ) -> Result<(), Error> { + let float_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::F32, + pointer_space: None, + })); + let zero_scalar_id = self.get_constant_scalar(crate::Literal::F32(0.0)); + let one_scalar_id = self.get_constant_scalar(crate::Literal::F32(1.0)); + + let original_id = self.id_gen.next(); + body.push(Instruction::load( + float_type_id, + original_id, + frag_depth_id, + None, + )); + + let clamp_id = self.id_gen.next(); + body.push(Instruction::ext_inst( + self.gl450_ext_inst_id, + spirv::GLOp::FClamp, + float_type_id, + clamp_id, + &[original_id, zero_scalar_id, one_scalar_id], + )); + + body.push(Instruction::store(frag_depth_id, clamp_id, None)); + Ok(()) + } + + fn write_entry_point_return( + &mut self, + value_id: Word, + ir_result: &crate::FunctionResult, + result_members: &[ResultMember], + body: &mut Vec, + ) -> Result<(), Error> { + for (index, res_member) in result_members.iter().enumerate() { + let member_value_id = match ir_result.binding { + Some(_) => value_id, + None => { + let member_value_id = self.id_gen.next(); + body.push(Instruction::composite_extract( + res_member.type_id, + member_value_id, + value_id, + &[index as u32], + )); + member_value_id + } + }; + + body.push(Instruction::store(res_member.id, member_value_id, None)); + + match res_member.built_in { + Some(crate::BuiltIn::Position { .. }) + if self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE) => + { + self.write_epilogue_position_y_flip(res_member.id, body)?; + } + Some(crate::BuiltIn::FragDepth) + if self.flags.contains(WriterFlags::CLAMP_FRAG_DEPTH) => + { + self.write_epilogue_frag_depth_clamp(res_member.id, body)?; + } + _ => {} + } + } + Ok(()) + } +} + +impl<'w> BlockContext<'w> { + /// Decide whether to put off emitting instructions for `expr_handle`. + /// + /// We would like to gather together chains of `Access` and `AccessIndex` + /// Naga expressions into a single `OpAccessChain` SPIR-V instruction. To do + /// this, we don't generate instructions for these exprs when we first + /// encounter them. Their ids in `self.writer.cached.ids` are left as zero. Then, + /// once we encounter a `Load` or `Store` expression that actually needs the + /// chain's value, we call `write_expression_pointer` to handle the whole + /// thing in one fell swoop. + fn is_intermediate(&self, expr_handle: Handle) -> bool { + match self.ir_function.expressions[expr_handle] { + crate::Expression::GlobalVariable(handle) => { + match self.ir_module.global_variables[handle].space { + crate::AddressSpace::Handle => false, + _ => true, + } + } + crate::Expression::LocalVariable(_) => true, + crate::Expression::FunctionArgument(index) => { + let arg = &self.ir_function.arguments[index as usize]; + self.ir_module.types[arg.ty].inner.pointer_space().is_some() + } + + // The chain rule: if this `Access...`'s `base` operand was + // previously omitted, then omit this one, too. + _ => self.cached.ids[expr_handle.index()] == 0, + } + } + + /// Cache an expression for a value. + pub(super) fn cache_expression_value( + &mut self, + expr_handle: Handle, + block: &mut Block, + ) -> Result<(), Error> { + let is_named_expression = self + .ir_function + .named_expressions + .contains_key(&expr_handle); + + if self.fun_info[expr_handle].ref_count == 0 && !is_named_expression { + return Ok(()); + } + + let result_type_id = self.get_expression_type_id(&self.fun_info[expr_handle].ty); + let id = match self.ir_function.expressions[expr_handle] { + crate::Expression::Literal(literal) => self.writer.get_constant_scalar(literal), + crate::Expression::Constant(handle) => { + let init = self.ir_module.constants[handle].init; + self.writer.constant_ids[init.index()] + } + crate::Expression::ZeroValue(_) => self.writer.get_constant_null(result_type_id), + crate::Expression::Compose { ty, ref components } => { + self.temp_list.clear(); + if self.expression_constness.is_const(expr_handle) { + self.temp_list.extend( + crate::proc::flatten_compose( + ty, + components, + &self.ir_function.expressions, + &self.ir_module.types, + ) + .map(|component| self.cached[component]), + ); + self.writer + .get_constant_composite(LookupType::Handle(ty), &self.temp_list) + } else { + self.temp_list + .extend(components.iter().map(|&component| self.cached[component])); + + let id = self.gen_id(); + block.body.push(Instruction::composite_construct( + result_type_id, + id, + &self.temp_list, + )); + id + } + } + crate::Expression::Splat { size, value } => { + let value_id = self.cached[value]; + let components = &[value_id; 4][..size as usize]; + + if self.expression_constness.is_const(expr_handle) { + let ty = self + .writer + .get_expression_lookup_type(&self.fun_info[expr_handle].ty); + self.writer.get_constant_composite(ty, components) + } else { + let id = self.gen_id(); + block.body.push(Instruction::composite_construct( + result_type_id, + id, + components, + )); + id + } + } + crate::Expression::Access { base, index: _ } if self.is_intermediate(base) => { + // See `is_intermediate`; we'll handle this later in + // `write_expression_pointer`. + 0 + } + crate::Expression::Access { base, index } => { + let base_ty_inner = self.fun_info[base].ty.inner_with(&self.ir_module.types); + match *base_ty_inner { + crate::TypeInner::Vector { .. } => { + self.write_vector_access(expr_handle, base, index, block)? + } + // Only binding arrays in the Handle address space will take this path (due to `is_intermediate`) + crate::TypeInner::BindingArray { + base: binding_type, .. + } => { + let space = match self.ir_function.expressions[base] { + crate::Expression::GlobalVariable(gvar) => { + self.ir_module.global_variables[gvar].space + } + _ => unreachable!(), + }; + let binding_array_false_pointer = LookupType::Local(LocalType::Pointer { + base: binding_type, + class: helpers::map_storage_class(space), + }); + + let result_id = match self.write_expression_pointer( + expr_handle, + block, + Some(binding_array_false_pointer), + )? { + ExpressionPointer::Ready { pointer_id } => pointer_id, + ExpressionPointer::Conditional { .. } => { + return Err(Error::FeatureNotImplemented( + "Texture array out-of-bounds handling", + )); + } + }; + + let binding_type_id = self.get_type_id(LookupType::Handle(binding_type)); + + let load_id = self.gen_id(); + block.body.push(Instruction::load( + binding_type_id, + load_id, + result_id, + None, + )); + + // Subsequent image operations require the image/sampler to be decorated as NonUniform + // if the image/sampler binding array was accessed with a non-uniform index + // see VUID-RuntimeSpirv-NonUniform-06274 + if self.fun_info[index].uniformity.non_uniform_result.is_some() { + self.writer + .decorate_non_uniform_binding_array_access(load_id)?; + } + + load_id + } + ref other => { + log::error!( + "Unable to access base {:?} of type {:?}", + self.ir_function.expressions[base], + other + ); + return Err(Error::Validation( + "only vectors may be dynamically indexed by value", + )); + } + } + } + crate::Expression::AccessIndex { base, index: _ } if self.is_intermediate(base) => { + // See `is_intermediate`; we'll handle this later in + // `write_expression_pointer`. + 0 + } + crate::Expression::AccessIndex { base, index } => { + match *self.fun_info[base].ty.inner_with(&self.ir_module.types) { + crate::TypeInner::Vector { .. } + | crate::TypeInner::Matrix { .. } + | crate::TypeInner::Array { .. } + | crate::TypeInner::Struct { .. } => { + // We never need bounds checks here: dynamically sized arrays can + // only appear behind pointers, and are thus handled by the + // `is_intermediate` case above. Everything else's size is + // statically known and checked in validation. + let id = self.gen_id(); + let base_id = self.cached[base]; + block.body.push(Instruction::composite_extract( + result_type_id, + id, + base_id, + &[index], + )); + id + } + // Only binding arrays in the Handle address space will take this path (due to `is_intermediate`) + crate::TypeInner::BindingArray { + base: binding_type, .. + } => { + let space = match self.ir_function.expressions[base] { + crate::Expression::GlobalVariable(gvar) => { + self.ir_module.global_variables[gvar].space + } + _ => unreachable!(), + }; + let binding_array_false_pointer = LookupType::Local(LocalType::Pointer { + base: binding_type, + class: helpers::map_storage_class(space), + }); + + let result_id = match self.write_expression_pointer( + expr_handle, + block, + Some(binding_array_false_pointer), + )? { + ExpressionPointer::Ready { pointer_id } => pointer_id, + ExpressionPointer::Conditional { .. } => { + return Err(Error::FeatureNotImplemented( + "Texture array out-of-bounds handling", + )); + } + }; + + let binding_type_id = self.get_type_id(LookupType::Handle(binding_type)); + + let load_id = self.gen_id(); + block.body.push(Instruction::load( + binding_type_id, + load_id, + result_id, + None, + )); + + load_id + } + ref other => { + log::error!("Unable to access index of {:?}", other); + return Err(Error::FeatureNotImplemented("access index for type")); + } + } + } + crate::Expression::GlobalVariable(handle) => { + self.writer.global_variables[handle.index()].access_id + } + crate::Expression::Swizzle { + size, + vector, + pattern, + } => { + let vector_id = self.cached[vector]; + self.temp_list.clear(); + for &sc in pattern[..size as usize].iter() { + self.temp_list.push(sc as Word); + } + let id = self.gen_id(); + block.body.push(Instruction::vector_shuffle( + result_type_id, + id, + vector_id, + vector_id, + &self.temp_list, + )); + id + } + crate::Expression::Unary { op, expr } => { + let id = self.gen_id(); + let expr_id = self.cached[expr]; + let expr_ty_inner = self.fun_info[expr].ty.inner_with(&self.ir_module.types); + + let spirv_op = match op { + crate::UnaryOperator::Negate => match expr_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Float) => spirv::Op::FNegate, + Some(crate::ScalarKind::Sint) => spirv::Op::SNegate, + _ => return Err(Error::Validation("Unexpected kind for negation")), + }, + crate::UnaryOperator::LogicalNot => spirv::Op::LogicalNot, + crate::UnaryOperator::BitwiseNot => spirv::Op::Not, + }; + + block + .body + .push(Instruction::unary(spirv_op, result_type_id, id, expr_id)); + id + } + crate::Expression::Binary { op, left, right } => { + let id = self.gen_id(); + let left_id = self.cached[left]; + let right_id = self.cached[right]; + + let left_ty_inner = self.fun_info[left].ty.inner_with(&self.ir_module.types); + let right_ty_inner = self.fun_info[right].ty.inner_with(&self.ir_module.types); + + let left_dimension = get_dimension(left_ty_inner); + let right_dimension = get_dimension(right_ty_inner); + + let mut reverse_operands = false; + + let spirv_op = match op { + crate::BinaryOperator::Add => match *left_ty_inner { + crate::TypeInner::Scalar(scalar) + | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { + crate::ScalarKind::Float => spirv::Op::FAdd, + _ => spirv::Op::IAdd, + }, + crate::TypeInner::Matrix { + columns, + rows, + scalar, + } => { + self.write_matrix_matrix_column_op( + block, + id, + result_type_id, + left_id, + right_id, + columns, + rows, + scalar.width, + spirv::Op::FAdd, + ); + + self.cached[expr_handle] = id; + return Ok(()); + } + _ => unimplemented!(), + }, + crate::BinaryOperator::Subtract => match *left_ty_inner { + crate::TypeInner::Scalar(scalar) + | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { + crate::ScalarKind::Float => spirv::Op::FSub, + _ => spirv::Op::ISub, + }, + crate::TypeInner::Matrix { + columns, + rows, + scalar, + } => { + self.write_matrix_matrix_column_op( + block, + id, + result_type_id, + left_id, + right_id, + columns, + rows, + scalar.width, + spirv::Op::FSub, + ); + + self.cached[expr_handle] = id; + return Ok(()); + } + _ => unimplemented!(), + }, + crate::BinaryOperator::Multiply => match (left_dimension, right_dimension) { + (Dimension::Scalar, Dimension::Vector) => { + self.write_vector_scalar_mult( + block, + id, + result_type_id, + right_id, + left_id, + right_ty_inner, + ); + + self.cached[expr_handle] = id; + return Ok(()); + } + (Dimension::Vector, Dimension::Scalar) => { + self.write_vector_scalar_mult( + block, + id, + result_type_id, + left_id, + right_id, + left_ty_inner, + ); + + self.cached[expr_handle] = id; + return Ok(()); + } + (Dimension::Vector, Dimension::Matrix) => spirv::Op::VectorTimesMatrix, + (Dimension::Matrix, Dimension::Scalar) => spirv::Op::MatrixTimesScalar, + (Dimension::Scalar, Dimension::Matrix) => { + reverse_operands = true; + spirv::Op::MatrixTimesScalar + } + (Dimension::Matrix, Dimension::Vector) => spirv::Op::MatrixTimesVector, + (Dimension::Matrix, Dimension::Matrix) => spirv::Op::MatrixTimesMatrix, + (Dimension::Vector, Dimension::Vector) + | (Dimension::Scalar, Dimension::Scalar) + if left_ty_inner.scalar_kind() == Some(crate::ScalarKind::Float) => + { + spirv::Op::FMul + } + (Dimension::Vector, Dimension::Vector) + | (Dimension::Scalar, Dimension::Scalar) => spirv::Op::IMul, + }, + crate::BinaryOperator::Divide => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Sint) => spirv::Op::SDiv, + Some(crate::ScalarKind::Uint) => spirv::Op::UDiv, + Some(crate::ScalarKind::Float) => spirv::Op::FDiv, + _ => unimplemented!(), + }, + crate::BinaryOperator::Modulo => match left_ty_inner.scalar_kind() { + // TODO: handle undefined behavior + // if right == 0 return 0 + // if left == min(type_of(left)) && right == -1 return 0 + Some(crate::ScalarKind::Sint) => spirv::Op::SRem, + // TODO: handle undefined behavior + // if right == 0 return 0 + Some(crate::ScalarKind::Uint) => spirv::Op::UMod, + // TODO: handle undefined behavior + // if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798 + Some(crate::ScalarKind::Float) => spirv::Op::FRem, + _ => unimplemented!(), + }, + crate::BinaryOperator::Equal => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => { + spirv::Op::IEqual + } + Some(crate::ScalarKind::Float) => spirv::Op::FOrdEqual, + Some(crate::ScalarKind::Bool) => spirv::Op::LogicalEqual, + _ => unimplemented!(), + }, + crate::BinaryOperator::NotEqual => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => { + spirv::Op::INotEqual + } + Some(crate::ScalarKind::Float) => spirv::Op::FOrdNotEqual, + Some(crate::ScalarKind::Bool) => spirv::Op::LogicalNotEqual, + _ => unimplemented!(), + }, + crate::BinaryOperator::Less => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Sint) => spirv::Op::SLessThan, + Some(crate::ScalarKind::Uint) => spirv::Op::ULessThan, + Some(crate::ScalarKind::Float) => spirv::Op::FOrdLessThan, + _ => unimplemented!(), + }, + crate::BinaryOperator::LessEqual => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Sint) => spirv::Op::SLessThanEqual, + Some(crate::ScalarKind::Uint) => spirv::Op::ULessThanEqual, + Some(crate::ScalarKind::Float) => spirv::Op::FOrdLessThanEqual, + _ => unimplemented!(), + }, + crate::BinaryOperator::Greater => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Sint) => spirv::Op::SGreaterThan, + Some(crate::ScalarKind::Uint) => spirv::Op::UGreaterThan, + Some(crate::ScalarKind::Float) => spirv::Op::FOrdGreaterThan, + _ => unimplemented!(), + }, + crate::BinaryOperator::GreaterEqual => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Sint) => spirv::Op::SGreaterThanEqual, + Some(crate::ScalarKind::Uint) => spirv::Op::UGreaterThanEqual, + Some(crate::ScalarKind::Float) => spirv::Op::FOrdGreaterThanEqual, + _ => unimplemented!(), + }, + crate::BinaryOperator::And => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Bool) => spirv::Op::LogicalAnd, + _ => spirv::Op::BitwiseAnd, + }, + crate::BinaryOperator::ExclusiveOr => spirv::Op::BitwiseXor, + crate::BinaryOperator::InclusiveOr => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Bool) => spirv::Op::LogicalOr, + _ => spirv::Op::BitwiseOr, + }, + crate::BinaryOperator::LogicalAnd => spirv::Op::LogicalAnd, + crate::BinaryOperator::LogicalOr => spirv::Op::LogicalOr, + crate::BinaryOperator::ShiftLeft => spirv::Op::ShiftLeftLogical, + crate::BinaryOperator::ShiftRight => match left_ty_inner.scalar_kind() { + Some(crate::ScalarKind::Sint) => spirv::Op::ShiftRightArithmetic, + Some(crate::ScalarKind::Uint) => spirv::Op::ShiftRightLogical, + _ => unimplemented!(), + }, + }; + + block.body.push(Instruction::binary( + spirv_op, + result_type_id, + id, + if reverse_operands { right_id } else { left_id }, + if reverse_operands { left_id } else { right_id }, + )); + id + } + crate::Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + } => { + use crate::MathFunction as Mf; + enum MathOp { + Ext(spirv::GLOp), + Custom(Instruction), + } + + let arg0_id = self.cached[arg]; + let arg_ty = self.fun_info[arg].ty.inner_with(&self.ir_module.types); + let arg_scalar_kind = arg_ty.scalar_kind(); + let arg1_id = match arg1 { + Some(handle) => self.cached[handle], + None => 0, + }; + let arg2_id = match arg2 { + Some(handle) => self.cached[handle], + None => 0, + }; + let arg3_id = match arg3 { + Some(handle) => self.cached[handle], + None => 0, + }; + + let id = self.gen_id(); + let math_op = match fun { + // comparison + Mf::Abs => { + match arg_scalar_kind { + Some(crate::ScalarKind::Float) => MathOp::Ext(spirv::GLOp::FAbs), + Some(crate::ScalarKind::Sint) => MathOp::Ext(spirv::GLOp::SAbs), + Some(crate::ScalarKind::Uint) => { + MathOp::Custom(Instruction::unary( + spirv::Op::CopyObject, // do nothing + result_type_id, + id, + arg0_id, + )) + } + other => unimplemented!("Unexpected abs({:?})", other), + } + } + Mf::Min => MathOp::Ext(match arg_scalar_kind { + Some(crate::ScalarKind::Float) => spirv::GLOp::FMin, + Some(crate::ScalarKind::Sint) => spirv::GLOp::SMin, + Some(crate::ScalarKind::Uint) => spirv::GLOp::UMin, + other => unimplemented!("Unexpected min({:?})", other), + }), + Mf::Max => MathOp::Ext(match arg_scalar_kind { + Some(crate::ScalarKind::Float) => spirv::GLOp::FMax, + Some(crate::ScalarKind::Sint) => spirv::GLOp::SMax, + Some(crate::ScalarKind::Uint) => spirv::GLOp::UMax, + other => unimplemented!("Unexpected max({:?})", other), + }), + Mf::Clamp => MathOp::Ext(match arg_scalar_kind { + Some(crate::ScalarKind::Float) => spirv::GLOp::FClamp, + Some(crate::ScalarKind::Sint) => spirv::GLOp::SClamp, + Some(crate::ScalarKind::Uint) => spirv::GLOp::UClamp, + other => unimplemented!("Unexpected max({:?})", other), + }), + Mf::Saturate => { + let (maybe_size, scalar) = match *arg_ty { + crate::TypeInner::Vector { size, scalar } => (Some(size), scalar), + crate::TypeInner::Scalar(scalar) => (None, scalar), + ref other => unimplemented!("Unexpected saturate({:?})", other), + }; + let scalar = crate::Scalar::float(scalar.width); + let mut arg1_id = self.writer.get_constant_scalar_with(0, scalar)?; + let mut arg2_id = self.writer.get_constant_scalar_with(1, scalar)?; + + if let Some(size) = maybe_size { + let ty = LocalType::Value { + vector_size: Some(size), + scalar, + pointer_space: None, + } + .into(); + + self.temp_list.clear(); + self.temp_list.resize(size as _, arg1_id); + + arg1_id = self.writer.get_constant_composite(ty, &self.temp_list); + + self.temp_list.fill(arg2_id); + + arg2_id = self.writer.get_constant_composite(ty, &self.temp_list); + } + + MathOp::Custom(Instruction::ext_inst( + self.writer.gl450_ext_inst_id, + spirv::GLOp::FClamp, + result_type_id, + id, + &[arg0_id, arg1_id, arg2_id], + )) + } + // trigonometry + Mf::Sin => MathOp::Ext(spirv::GLOp::Sin), + Mf::Sinh => MathOp::Ext(spirv::GLOp::Sinh), + Mf::Asin => MathOp::Ext(spirv::GLOp::Asin), + Mf::Cos => MathOp::Ext(spirv::GLOp::Cos), + Mf::Cosh => MathOp::Ext(spirv::GLOp::Cosh), + Mf::Acos => MathOp::Ext(spirv::GLOp::Acos), + Mf::Tan => MathOp::Ext(spirv::GLOp::Tan), + Mf::Tanh => MathOp::Ext(spirv::GLOp::Tanh), + Mf::Atan => MathOp::Ext(spirv::GLOp::Atan), + Mf::Atan2 => MathOp::Ext(spirv::GLOp::Atan2), + Mf::Asinh => MathOp::Ext(spirv::GLOp::Asinh), + Mf::Acosh => MathOp::Ext(spirv::GLOp::Acosh), + Mf::Atanh => MathOp::Ext(spirv::GLOp::Atanh), + Mf::Radians => MathOp::Ext(spirv::GLOp::Radians), + Mf::Degrees => MathOp::Ext(spirv::GLOp::Degrees), + // decomposition + Mf::Ceil => MathOp::Ext(spirv::GLOp::Ceil), + Mf::Round => MathOp::Ext(spirv::GLOp::RoundEven), + Mf::Floor => MathOp::Ext(spirv::GLOp::Floor), + Mf::Fract => MathOp::Ext(spirv::GLOp::Fract), + Mf::Trunc => MathOp::Ext(spirv::GLOp::Trunc), + Mf::Modf => MathOp::Ext(spirv::GLOp::ModfStruct), + Mf::Frexp => MathOp::Ext(spirv::GLOp::FrexpStruct), + Mf::Ldexp => MathOp::Ext(spirv::GLOp::Ldexp), + // geometry + Mf::Dot => match *self.fun_info[arg].ty.inner_with(&self.ir_module.types) { + crate::TypeInner::Vector { + scalar: + crate::Scalar { + kind: crate::ScalarKind::Float, + .. + }, + .. + } => MathOp::Custom(Instruction::binary( + spirv::Op::Dot, + result_type_id, + id, + arg0_id, + arg1_id, + )), + // TODO: consider using integer dot product if VK_KHR_shader_integer_dot_product is available + crate::TypeInner::Vector { size, .. } => { + self.write_dot_product( + id, + result_type_id, + arg0_id, + arg1_id, + size as u32, + block, + ); + self.cached[expr_handle] = id; + return Ok(()); + } + _ => unreachable!( + "Correct TypeInner for dot product should be already validated" + ), + }, + Mf::Outer => MathOp::Custom(Instruction::binary( + spirv::Op::OuterProduct, + result_type_id, + id, + arg0_id, + arg1_id, + )), + Mf::Cross => MathOp::Ext(spirv::GLOp::Cross), + Mf::Distance => MathOp::Ext(spirv::GLOp::Distance), + Mf::Length => MathOp::Ext(spirv::GLOp::Length), + Mf::Normalize => MathOp::Ext(spirv::GLOp::Normalize), + Mf::FaceForward => MathOp::Ext(spirv::GLOp::FaceForward), + Mf::Reflect => MathOp::Ext(spirv::GLOp::Reflect), + Mf::Refract => MathOp::Ext(spirv::GLOp::Refract), + // exponent + Mf::Exp => MathOp::Ext(spirv::GLOp::Exp), + Mf::Exp2 => MathOp::Ext(spirv::GLOp::Exp2), + Mf::Log => MathOp::Ext(spirv::GLOp::Log), + Mf::Log2 => MathOp::Ext(spirv::GLOp::Log2), + Mf::Pow => MathOp::Ext(spirv::GLOp::Pow), + // computational + Mf::Sign => MathOp::Ext(match arg_scalar_kind { + Some(crate::ScalarKind::Float) => spirv::GLOp::FSign, + Some(crate::ScalarKind::Sint) => spirv::GLOp::SSign, + other => unimplemented!("Unexpected sign({:?})", other), + }), + Mf::Fma => MathOp::Ext(spirv::GLOp::Fma), + Mf::Mix => { + let selector = arg2.unwrap(); + let selector_ty = + self.fun_info[selector].ty.inner_with(&self.ir_module.types); + match (arg_ty, selector_ty) { + // if the selector is a scalar, we need to splat it + ( + &crate::TypeInner::Vector { size, .. }, + &crate::TypeInner::Scalar(scalar), + ) => { + let selector_type_id = + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(size), + scalar, + pointer_space: None, + })); + self.temp_list.clear(); + self.temp_list.resize(size as usize, arg2_id); + + let selector_id = self.gen_id(); + block.body.push(Instruction::composite_construct( + selector_type_id, + selector_id, + &self.temp_list, + )); + + MathOp::Custom(Instruction::ext_inst( + self.writer.gl450_ext_inst_id, + spirv::GLOp::FMix, + result_type_id, + id, + &[arg0_id, arg1_id, selector_id], + )) + } + _ => MathOp::Ext(spirv::GLOp::FMix), + } + } + Mf::Step => MathOp::Ext(spirv::GLOp::Step), + Mf::SmoothStep => MathOp::Ext(spirv::GLOp::SmoothStep), + Mf::Sqrt => MathOp::Ext(spirv::GLOp::Sqrt), + Mf::InverseSqrt => MathOp::Ext(spirv::GLOp::InverseSqrt), + Mf::Inverse => MathOp::Ext(spirv::GLOp::MatrixInverse), + Mf::Transpose => MathOp::Custom(Instruction::unary( + spirv::Op::Transpose, + result_type_id, + id, + arg0_id, + )), + Mf::Determinant => MathOp::Ext(spirv::GLOp::Determinant), + Mf::ReverseBits => MathOp::Custom(Instruction::unary( + spirv::Op::BitReverse, + result_type_id, + id, + arg0_id, + )), + Mf::CountTrailingZeros => { + let uint_id = match *arg_ty { + crate::TypeInner::Vector { size, mut scalar } => { + scalar.kind = crate::ScalarKind::Uint; + let ty = LocalType::Value { + vector_size: Some(size), + scalar, + pointer_space: None, + } + .into(); + + self.temp_list.clear(); + self.temp_list.resize( + size as _, + self.writer.get_constant_scalar_with(32, scalar)?, + ); + + self.writer.get_constant_composite(ty, &self.temp_list) + } + crate::TypeInner::Scalar(mut scalar) => { + scalar.kind = crate::ScalarKind::Uint; + self.writer.get_constant_scalar_with(32, scalar)? + } + _ => unreachable!(), + }; + + let lsb_id = self.gen_id(); + block.body.push(Instruction::ext_inst( + self.writer.gl450_ext_inst_id, + spirv::GLOp::FindILsb, + result_type_id, + lsb_id, + &[arg0_id], + )); + + MathOp::Custom(Instruction::ext_inst( + self.writer.gl450_ext_inst_id, + spirv::GLOp::UMin, + result_type_id, + id, + &[uint_id, lsb_id], + )) + } + Mf::CountLeadingZeros => { + let (int_type_id, int_id) = match *arg_ty { + crate::TypeInner::Vector { size, mut scalar } => { + scalar.kind = crate::ScalarKind::Sint; + let ty = LocalType::Value { + vector_size: Some(size), + scalar, + pointer_space: None, + } + .into(); + + self.temp_list.clear(); + self.temp_list.resize( + size as _, + self.writer.get_constant_scalar_with(31, scalar)?, + ); + + ( + self.get_type_id(ty), + self.writer.get_constant_composite(ty, &self.temp_list), + ) + } + crate::TypeInner::Scalar(mut scalar) => { + scalar.kind = crate::ScalarKind::Sint; + ( + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar, + pointer_space: None, + })), + self.writer.get_constant_scalar_with(31, scalar)?, + ) + } + _ => unreachable!(), + }; + + let msb_id = self.gen_id(); + block.body.push(Instruction::ext_inst( + self.writer.gl450_ext_inst_id, + spirv::GLOp::FindUMsb, + int_type_id, + msb_id, + &[arg0_id], + )); + + MathOp::Custom(Instruction::binary( + spirv::Op::ISub, + result_type_id, + id, + int_id, + msb_id, + )) + } + Mf::CountOneBits => MathOp::Custom(Instruction::unary( + spirv::Op::BitCount, + result_type_id, + id, + arg0_id, + )), + Mf::ExtractBits => { + let op = match arg_scalar_kind { + Some(crate::ScalarKind::Uint) => spirv::Op::BitFieldUExtract, + Some(crate::ScalarKind::Sint) => spirv::Op::BitFieldSExtract, + other => unimplemented!("Unexpected sign({:?})", other), + }; + MathOp::Custom(Instruction::ternary( + op, + result_type_id, + id, + arg0_id, + arg1_id, + arg2_id, + )) + } + Mf::InsertBits => MathOp::Custom(Instruction::quaternary( + spirv::Op::BitFieldInsert, + result_type_id, + id, + arg0_id, + arg1_id, + arg2_id, + arg3_id, + )), + Mf::FindLsb => MathOp::Ext(spirv::GLOp::FindILsb), + Mf::FindMsb => MathOp::Ext(match arg_scalar_kind { + Some(crate::ScalarKind::Uint) => spirv::GLOp::FindUMsb, + Some(crate::ScalarKind::Sint) => spirv::GLOp::FindSMsb, + other => unimplemented!("Unexpected findMSB({:?})", other), + }), + Mf::Pack4x8unorm => MathOp::Ext(spirv::GLOp::PackUnorm4x8), + Mf::Pack4x8snorm => MathOp::Ext(spirv::GLOp::PackSnorm4x8), + Mf::Pack2x16float => MathOp::Ext(spirv::GLOp::PackHalf2x16), + Mf::Pack2x16unorm => MathOp::Ext(spirv::GLOp::PackUnorm2x16), + Mf::Pack2x16snorm => MathOp::Ext(spirv::GLOp::PackSnorm2x16), + Mf::Unpack4x8unorm => MathOp::Ext(spirv::GLOp::UnpackUnorm4x8), + Mf::Unpack4x8snorm => MathOp::Ext(spirv::GLOp::UnpackSnorm4x8), + Mf::Unpack2x16float => MathOp::Ext(spirv::GLOp::UnpackHalf2x16), + Mf::Unpack2x16unorm => MathOp::Ext(spirv::GLOp::UnpackUnorm2x16), + Mf::Unpack2x16snorm => MathOp::Ext(spirv::GLOp::UnpackSnorm2x16), + }; + + block.body.push(match math_op { + MathOp::Ext(op) => Instruction::ext_inst( + self.writer.gl450_ext_inst_id, + op, + result_type_id, + id, + &[arg0_id, arg1_id, arg2_id, arg3_id][..fun.argument_count()], + ), + MathOp::Custom(inst) => inst, + }); + id + } + crate::Expression::LocalVariable(variable) => self.function.variables[&variable].id, + crate::Expression::Load { pointer } => { + match self.write_expression_pointer(pointer, block, None)? { + ExpressionPointer::Ready { pointer_id } => { + let id = self.gen_id(); + let atomic_space = + match *self.fun_info[pointer].ty.inner_with(&self.ir_module.types) { + crate::TypeInner::Pointer { base, space } => { + match self.ir_module.types[base].inner { + crate::TypeInner::Atomic { .. } => Some(space), + _ => None, + } + } + _ => None, + }; + let instruction = if let Some(space) = atomic_space { + let (semantics, scope) = space.to_spirv_semantics_and_scope(); + let scope_constant_id = self.get_scope_constant(scope as u32); + let semantics_id = self.get_index_constant(semantics.bits()); + Instruction::atomic_load( + result_type_id, + id, + pointer_id, + scope_constant_id, + semantics_id, + ) + } else { + Instruction::load(result_type_id, id, pointer_id, None) + }; + block.body.push(instruction); + id + } + ExpressionPointer::Conditional { condition, access } => { + //TODO: support atomics? + self.write_conditional_indexed_load( + result_type_id, + condition, + block, + move |id_gen, block| { + // The in-bounds path. Perform the access and the load. + let pointer_id = access.result_id.unwrap(); + let value_id = id_gen.next(); + block.body.push(access); + block.body.push(Instruction::load( + result_type_id, + value_id, + pointer_id, + None, + )); + value_id + }, + ) + } + } + } + crate::Expression::FunctionArgument(index) => self.function.parameter_id(index), + crate::Expression::CallResult(_) + | crate::Expression::AtomicResult { .. } + | crate::Expression::WorkGroupUniformLoadResult { .. } + | crate::Expression::RayQueryProceedResult => self.cached[expr_handle], + crate::Expression::As { + expr, + kind, + convert, + } => { + use crate::ScalarKind as Sk; + + let expr_id = self.cached[expr]; + let (src_scalar, src_size, is_matrix) = + match *self.fun_info[expr].ty.inner_with(&self.ir_module.types) { + crate::TypeInner::Scalar(scalar) => (scalar, None, false), + crate::TypeInner::Vector { scalar, size } => (scalar, Some(size), false), + crate::TypeInner::Matrix { scalar, .. } => (scalar, None, true), + ref other => { + log::error!("As source {:?}", other); + return Err(Error::Validation("Unexpected Expression::As source")); + } + }; + + enum Cast { + Identity, + Unary(spirv::Op), + Binary(spirv::Op, Word), + Ternary(spirv::Op, Word, Word), + } + + let cast = if is_matrix { + // we only support identity casts for matrices + Cast::Unary(spirv::Op::CopyObject) + } else { + match (src_scalar.kind, kind, convert) { + // Filter out identity casts. Some Adreno drivers are + // confused by no-op OpBitCast instructions. + (src_kind, kind, convert) + if src_kind == kind + && convert.filter(|&width| width != src_scalar.width).is_none() => + { + Cast::Identity + } + (Sk::Bool, Sk::Bool, _) => Cast::Unary(spirv::Op::CopyObject), + (_, _, None) => Cast::Unary(spirv::Op::Bitcast), + // casting to a bool - generate `OpXxxNotEqual` + (_, Sk::Bool, Some(_)) => { + let op = match src_scalar.kind { + Sk::Sint | Sk::Uint => spirv::Op::INotEqual, + Sk::Float => spirv::Op::FUnordNotEqual, + Sk::Bool | Sk::AbstractInt | Sk::AbstractFloat => unreachable!(), + }; + let zero_scalar_id = + self.writer.get_constant_scalar_with(0, src_scalar)?; + let zero_id = match src_size { + Some(size) => { + let ty = LocalType::Value { + vector_size: Some(size), + scalar: src_scalar, + pointer_space: None, + } + .into(); + + self.temp_list.clear(); + self.temp_list.resize(size as _, zero_scalar_id); + + self.writer.get_constant_composite(ty, &self.temp_list) + } + None => zero_scalar_id, + }; + + Cast::Binary(op, zero_id) + } + // casting from a bool - generate `OpSelect` + (Sk::Bool, _, Some(dst_width)) => { + let dst_scalar = crate::Scalar { + kind, + width: dst_width, + }; + let zero_scalar_id = + self.writer.get_constant_scalar_with(0, dst_scalar)?; + let one_scalar_id = + self.writer.get_constant_scalar_with(1, dst_scalar)?; + let (accept_id, reject_id) = match src_size { + Some(size) => { + let ty = LocalType::Value { + vector_size: Some(size), + scalar: dst_scalar, + pointer_space: None, + } + .into(); + + self.temp_list.clear(); + self.temp_list.resize(size as _, zero_scalar_id); + + let vec0_id = + self.writer.get_constant_composite(ty, &self.temp_list); + + self.temp_list.fill(one_scalar_id); + + let vec1_id = + self.writer.get_constant_composite(ty, &self.temp_list); + + (vec1_id, vec0_id) + } + None => (one_scalar_id, zero_scalar_id), + }; + + Cast::Ternary(spirv::Op::Select, accept_id, reject_id) + } + (Sk::Float, Sk::Uint, Some(_)) => Cast::Unary(spirv::Op::ConvertFToU), + (Sk::Float, Sk::Sint, Some(_)) => Cast::Unary(spirv::Op::ConvertFToS), + (Sk::Float, Sk::Float, Some(dst_width)) + if src_scalar.width != dst_width => + { + Cast::Unary(spirv::Op::FConvert) + } + (Sk::Sint, Sk::Float, Some(_)) => Cast::Unary(spirv::Op::ConvertSToF), + (Sk::Sint, Sk::Sint, Some(dst_width)) if src_scalar.width != dst_width => { + Cast::Unary(spirv::Op::SConvert) + } + (Sk::Uint, Sk::Float, Some(_)) => Cast::Unary(spirv::Op::ConvertUToF), + (Sk::Uint, Sk::Uint, Some(dst_width)) if src_scalar.width != dst_width => { + Cast::Unary(spirv::Op::UConvert) + } + // We assume it's either an identity cast, or int-uint. + _ => Cast::Unary(spirv::Op::Bitcast), + } + }; + + let id = self.gen_id(); + let instruction = match cast { + Cast::Identity => None, + Cast::Unary(op) => Some(Instruction::unary(op, result_type_id, id, expr_id)), + Cast::Binary(op, operand) => Some(Instruction::binary( + op, + result_type_id, + id, + expr_id, + operand, + )), + Cast::Ternary(op, op1, op2) => Some(Instruction::ternary( + op, + result_type_id, + id, + expr_id, + op1, + op2, + )), + }; + if let Some(instruction) = instruction { + block.body.push(instruction); + id + } else { + expr_id + } + } + crate::Expression::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => self.write_image_load( + result_type_id, + image, + coordinate, + array_index, + level, + sample, + block, + )?, + crate::Expression::ImageSample { + image, + sampler, + gather, + coordinate, + array_index, + offset, + level, + depth_ref, + } => self.write_image_sample( + result_type_id, + image, + sampler, + gather, + coordinate, + array_index, + offset, + level, + depth_ref, + block, + )?, + crate::Expression::Select { + condition, + accept, + reject, + } => { + let id = self.gen_id(); + let mut condition_id = self.cached[condition]; + let accept_id = self.cached[accept]; + let reject_id = self.cached[reject]; + + let condition_ty = self.fun_info[condition] + .ty + .inner_with(&self.ir_module.types); + let object_ty = self.fun_info[accept].ty.inner_with(&self.ir_module.types); + + if let ( + &crate::TypeInner::Scalar( + condition_scalar @ crate::Scalar { + kind: crate::ScalarKind::Bool, + .. + }, + ), + &crate::TypeInner::Vector { size, .. }, + ) = (condition_ty, object_ty) + { + self.temp_list.clear(); + self.temp_list.resize(size as usize, condition_id); + + let bool_vector_type_id = + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(size), + scalar: condition_scalar, + pointer_space: None, + })); + + let id = self.gen_id(); + block.body.push(Instruction::composite_construct( + bool_vector_type_id, + id, + &self.temp_list, + )); + condition_id = id + } + + let instruction = + Instruction::select(result_type_id, id, condition_id, accept_id, reject_id); + block.body.push(instruction); + id + } + crate::Expression::Derivative { axis, ctrl, expr } => { + use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; + match ctrl { + Ctrl::Coarse | Ctrl::Fine => { + self.writer.require_any( + "DerivativeControl", + &[spirv::Capability::DerivativeControl], + )?; + } + Ctrl::None => {} + } + let id = self.gen_id(); + let expr_id = self.cached[expr]; + let op = match (axis, ctrl) { + (Axis::X, Ctrl::Coarse) => spirv::Op::DPdxCoarse, + (Axis::X, Ctrl::Fine) => spirv::Op::DPdxFine, + (Axis::X, Ctrl::None) => spirv::Op::DPdx, + (Axis::Y, Ctrl::Coarse) => spirv::Op::DPdyCoarse, + (Axis::Y, Ctrl::Fine) => spirv::Op::DPdyFine, + (Axis::Y, Ctrl::None) => spirv::Op::DPdy, + (Axis::Width, Ctrl::Coarse) => spirv::Op::FwidthCoarse, + (Axis::Width, Ctrl::Fine) => spirv::Op::FwidthFine, + (Axis::Width, Ctrl::None) => spirv::Op::Fwidth, + }; + block + .body + .push(Instruction::derivative(op, result_type_id, id, expr_id)); + id + } + crate::Expression::ImageQuery { image, query } => { + self.write_image_query(result_type_id, image, query, block)? + } + crate::Expression::Relational { fun, argument } => { + use crate::RelationalFunction as Rf; + let arg_id = self.cached[argument]; + let op = match fun { + Rf::All => spirv::Op::All, + Rf::Any => spirv::Op::Any, + Rf::IsNan => spirv::Op::IsNan, + Rf::IsInf => spirv::Op::IsInf, + }; + let id = self.gen_id(); + block + .body + .push(Instruction::relational(op, result_type_id, id, arg_id)); + id + } + crate::Expression::ArrayLength(expr) => self.write_runtime_array_length(expr, block)?, + crate::Expression::RayQueryGetIntersection { query, committed } => { + if !committed { + return Err(Error::FeatureNotImplemented("candidate intersection")); + } + self.write_ray_query_get_intersection(query, block) + } + }; + + self.cached[expr_handle] = id; + Ok(()) + } + + /// Build an `OpAccessChain` instruction. + /// + /// Emit any needed bounds-checking expressions to `block`. + /// + /// Some cases we need to generate a different return type than what the IR gives us. + /// This is because pointers to binding arrays of handles (such as images or samplers) + /// don't exist in the IR, but we need to create them to create an access chain in SPIRV. + /// + /// On success, the return value is an [`ExpressionPointer`] value; see the + /// documentation for that type. + fn write_expression_pointer( + &mut self, + mut expr_handle: Handle, + block: &mut Block, + return_type_override: Option, + ) -> Result { + let result_lookup_ty = match self.fun_info[expr_handle].ty { + TypeResolution::Handle(ty_handle) => match return_type_override { + // We use the return type override as a special case for handle binding arrays as the OpAccessChain + // needs to return a pointer, but indexing into a handle binding array just gives you the type of + // the binding in the IR. + Some(ty) => ty, + None => LookupType::Handle(ty_handle), + }, + TypeResolution::Value(ref inner) => LookupType::Local(make_local(inner).unwrap()), + }; + let result_type_id = self.get_type_id(result_lookup_ty); + + // The id of the boolean `and` of all dynamic bounds checks up to this point. If + // `None`, then we haven't done any dynamic bounds checks yet. + // + // When we have a chain of bounds checks, we combine them with `OpLogicalAnd`, not + // a short-circuit branch. This means we might do comparisons we don't need to, + // but we expect these checks to almost always succeed, and keeping branches to a + // minimum is essential. + let mut accumulated_checks = None; + // Is true if we are accessing into a binding array with a non-uniform index. + let mut is_non_uniform_binding_array = false; + + self.temp_list.clear(); + let root_id = loop { + expr_handle = match self.ir_function.expressions[expr_handle] { + crate::Expression::Access { base, index } => { + if let crate::Expression::GlobalVariable(var_handle) = + self.ir_function.expressions[base] + { + // The access chain needs to be decorated as NonUniform + // see VUID-RuntimeSpirv-NonUniform-06274 + let gvar = &self.ir_module.global_variables[var_handle]; + if let crate::TypeInner::BindingArray { .. } = + self.ir_module.types[gvar.ty].inner + { + is_non_uniform_binding_array = + self.fun_info[index].uniformity.non_uniform_result.is_some(); + } + } + + let index_id = match self.write_bounds_check(base, index, block)? { + BoundsCheckResult::KnownInBounds(known_index) => { + // Even if the index is known, `OpAccessIndex` + // requires expression operands, not literals. + let scalar = crate::Literal::U32(known_index); + self.writer.get_constant_scalar(scalar) + } + BoundsCheckResult::Computed(computed_index_id) => computed_index_id, + BoundsCheckResult::Conditional(comparison_id) => { + match accumulated_checks { + Some(prior_checks) => { + let combined = self.gen_id(); + block.body.push(Instruction::binary( + spirv::Op::LogicalAnd, + self.writer.get_bool_type_id(), + combined, + prior_checks, + comparison_id, + )); + accumulated_checks = Some(combined); + } + None => { + // Start a fresh chain of checks. + accumulated_checks = Some(comparison_id); + } + } + + // Either way, the index to use is unchanged. + self.cached[index] + } + }; + self.temp_list.push(index_id); + base + } + crate::Expression::AccessIndex { base, index } => { + let const_id = self.get_index_constant(index); + self.temp_list.push(const_id); + base + } + crate::Expression::GlobalVariable(handle) => { + let gv = &self.writer.global_variables[handle.index()]; + break gv.access_id; + } + crate::Expression::LocalVariable(variable) => { + let local_var = &self.function.variables[&variable]; + break local_var.id; + } + crate::Expression::FunctionArgument(index) => { + break self.function.parameter_id(index); + } + ref other => unimplemented!("Unexpected pointer expression {:?}", other), + } + }; + + let (pointer_id, expr_pointer) = if self.temp_list.is_empty() { + ( + root_id, + ExpressionPointer::Ready { + pointer_id: root_id, + }, + ) + } else { + self.temp_list.reverse(); + let pointer_id = self.gen_id(); + let access = + Instruction::access_chain(result_type_id, pointer_id, root_id, &self.temp_list); + + // If we generated some bounds checks, we need to leave it to our + // caller to generate the branch, the access, the load or store, and + // the zero value (for loads). Otherwise, we can emit the access + // ourselves, and just hand them the id of the pointer. + let expr_pointer = match accumulated_checks { + Some(condition) => ExpressionPointer::Conditional { condition, access }, + None => { + block.body.push(access); + ExpressionPointer::Ready { pointer_id } + } + }; + (pointer_id, expr_pointer) + }; + // Subsequent load, store and atomic operations require the pointer to be decorated as NonUniform + // if the binding array was accessed with a non-uniform index + // see VUID-RuntimeSpirv-NonUniform-06274 + if is_non_uniform_binding_array { + self.writer + .decorate_non_uniform_binding_array_access(pointer_id)?; + } + + Ok(expr_pointer) + } + + /// Build the instructions for matrix - matrix column operations + #[allow(clippy::too_many_arguments)] + fn write_matrix_matrix_column_op( + &mut self, + block: &mut Block, + result_id: Word, + result_type_id: Word, + left_id: Word, + right_id: Word, + columns: crate::VectorSize, + rows: crate::VectorSize, + width: u8, + op: spirv::Op, + ) { + self.temp_list.clear(); + + let vector_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(rows), + scalar: crate::Scalar::float(width), + pointer_space: None, + })); + + for index in 0..columns as u32 { + let column_id_left = self.gen_id(); + let column_id_right = self.gen_id(); + let column_id_res = self.gen_id(); + + block.body.push(Instruction::composite_extract( + vector_type_id, + column_id_left, + left_id, + &[index], + )); + block.body.push(Instruction::composite_extract( + vector_type_id, + column_id_right, + right_id, + &[index], + )); + block.body.push(Instruction::binary( + op, + vector_type_id, + column_id_res, + column_id_left, + column_id_right, + )); + + self.temp_list.push(column_id_res); + } + + block.body.push(Instruction::composite_construct( + result_type_id, + result_id, + &self.temp_list, + )); + } + + /// Build the instructions for vector - scalar multiplication + fn write_vector_scalar_mult( + &mut self, + block: &mut Block, + result_id: Word, + result_type_id: Word, + vector_id: Word, + scalar_id: Word, + vector: &crate::TypeInner, + ) { + let (size, kind) = match *vector { + crate::TypeInner::Vector { + size, + scalar: crate::Scalar { kind, .. }, + } => (size, kind), + _ => unreachable!(), + }; + + let (op, operand_id) = match kind { + crate::ScalarKind::Float => (spirv::Op::VectorTimesScalar, scalar_id), + _ => { + let operand_id = self.gen_id(); + self.temp_list.clear(); + self.temp_list.resize(size as usize, scalar_id); + block.body.push(Instruction::composite_construct( + result_type_id, + operand_id, + &self.temp_list, + )); + (spirv::Op::IMul, operand_id) + } + }; + + block.body.push(Instruction::binary( + op, + result_type_id, + result_id, + vector_id, + operand_id, + )); + } + + /// Build the instructions for the arithmetic expression of a dot product + fn write_dot_product( + &mut self, + result_id: Word, + result_type_id: Word, + arg0_id: Word, + arg1_id: Word, + size: u32, + block: &mut Block, + ) { + let mut partial_sum = self.writer.get_constant_null(result_type_id); + let last_component = size - 1; + for index in 0..=last_component { + // compute the product of the current components + let a_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + result_type_id, + a_id, + arg0_id, + &[index], + )); + let b_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + result_type_id, + b_id, + arg1_id, + &[index], + )); + let prod_id = self.gen_id(); + block.body.push(Instruction::binary( + spirv::Op::IMul, + result_type_id, + prod_id, + a_id, + b_id, + )); + + // choose the id for the next sum, depending on current index + let id = if index == last_component { + result_id + } else { + self.gen_id() + }; + + // sum the computed product with the partial sum + block.body.push(Instruction::binary( + spirv::Op::IAdd, + result_type_id, + id, + partial_sum, + prod_id, + )); + // set the id of the result as the previous partial sum + partial_sum = id; + } + } + + pub(super) fn write_block( + &mut self, + label_id: Word, + naga_block: &crate::Block, + exit: BlockExit, + loop_context: LoopContext, + debug_info: Option<&DebugInfoInner>, + ) -> Result<(), Error> { + let mut block = Block::new(label_id); + for (statement, span) in naga_block.span_iter() { + if let (Some(debug_info), false) = ( + debug_info, + matches!( + statement, + &(Statement::Block(..) + | Statement::Break + | Statement::Continue + | Statement::Kill + | Statement::Return { .. } + | Statement::Loop { .. }) + ), + ) { + let loc: crate::SourceLocation = span.location(debug_info.source_code); + block.body.push(Instruction::line( + debug_info.source_file_id, + loc.line_number, + loc.line_position, + )); + }; + match *statement { + crate::Statement::Emit(ref range) => { + for handle in range.clone() { + // omit const expressions as we've already cached those + if !self.expression_constness.is_const(handle) { + self.cache_expression_value(handle, &mut block)?; + } + } + } + crate::Statement::Block(ref block_statements) => { + let scope_id = self.gen_id(); + self.function.consume(block, Instruction::branch(scope_id)); + + let merge_id = self.gen_id(); + self.write_block( + scope_id, + block_statements, + BlockExit::Branch { target: merge_id }, + loop_context, + debug_info, + )?; + + block = Block::new(merge_id); + } + crate::Statement::If { + condition, + ref accept, + ref reject, + } => { + let condition_id = self.cached[condition]; + + let merge_id = self.gen_id(); + block.body.push(Instruction::selection_merge( + merge_id, + spirv::SelectionControl::NONE, + )); + + let accept_id = if accept.is_empty() { + None + } else { + Some(self.gen_id()) + }; + let reject_id = if reject.is_empty() { + None + } else { + Some(self.gen_id()) + }; + + self.function.consume( + block, + Instruction::branch_conditional( + condition_id, + accept_id.unwrap_or(merge_id), + reject_id.unwrap_or(merge_id), + ), + ); + + if let Some(block_id) = accept_id { + self.write_block( + block_id, + accept, + BlockExit::Branch { target: merge_id }, + loop_context, + debug_info, + )?; + } + if let Some(block_id) = reject_id { + self.write_block( + block_id, + reject, + BlockExit::Branch { target: merge_id }, + loop_context, + debug_info, + )?; + } + + block = Block::new(merge_id); + } + crate::Statement::Switch { + selector, + ref cases, + } => { + let selector_id = self.cached[selector]; + + let merge_id = self.gen_id(); + block.body.push(Instruction::selection_merge( + merge_id, + spirv::SelectionControl::NONE, + )); + + let mut default_id = None; + // id of previous empty fall-through case + let mut last_id = None; + + let mut raw_cases = Vec::with_capacity(cases.len()); + let mut case_ids = Vec::with_capacity(cases.len()); + for case in cases.iter() { + // take id of previous empty fall-through case or generate a new one + let label_id = last_id.take().unwrap_or_else(|| self.gen_id()); + + if case.fall_through && case.body.is_empty() { + last_id = Some(label_id); + } + + case_ids.push(label_id); + + match case.value { + crate::SwitchValue::I32(value) => { + raw_cases.push(super::instructions::Case { + value: value as Word, + label_id, + }); + } + crate::SwitchValue::U32(value) => { + raw_cases.push(super::instructions::Case { value, label_id }); + } + crate::SwitchValue::Default => { + default_id = Some(label_id); + } + } + } + + let default_id = default_id.unwrap(); + + self.function.consume( + block, + Instruction::switch(selector_id, default_id, &raw_cases), + ); + + let inner_context = LoopContext { + break_id: Some(merge_id), + ..loop_context + }; + + for (i, (case, label_id)) in cases + .iter() + .zip(case_ids.iter()) + .filter(|&(case, _)| !(case.fall_through && case.body.is_empty())) + .enumerate() + { + let case_finish_id = if case.fall_through { + case_ids[i + 1] + } else { + merge_id + }; + self.write_block( + *label_id, + &case.body, + BlockExit::Branch { + target: case_finish_id, + }, + inner_context, + debug_info, + )?; + } + + block = Block::new(merge_id); + } + crate::Statement::Loop { + ref body, + ref continuing, + break_if, + } => { + let preamble_id = self.gen_id(); + self.function + .consume(block, Instruction::branch(preamble_id)); + + let merge_id = self.gen_id(); + let body_id = self.gen_id(); + let continuing_id = self.gen_id(); + + // SPIR-V requires the continuing to the `OpLoopMerge`, + // so we have to start a new block with it. + block = Block::new(preamble_id); + // HACK the loop statement is begin with branch instruction, + // so we need to put `OpLine` debug info before merge instruction + if let Some(debug_info) = debug_info { + let loc: crate::SourceLocation = span.location(debug_info.source_code); + block.body.push(Instruction::line( + debug_info.source_file_id, + loc.line_number, + loc.line_position, + )) + } + block.body.push(Instruction::loop_merge( + merge_id, + continuing_id, + spirv::SelectionControl::NONE, + )); + self.function.consume(block, Instruction::branch(body_id)); + + self.write_block( + body_id, + body, + BlockExit::Branch { + target: continuing_id, + }, + LoopContext { + continuing_id: Some(continuing_id), + break_id: Some(merge_id), + }, + debug_info, + )?; + + let exit = match break_if { + Some(condition) => BlockExit::BreakIf { + condition, + preamble_id, + }, + None => BlockExit::Branch { + target: preamble_id, + }, + }; + + self.write_block( + continuing_id, + continuing, + exit, + LoopContext { + continuing_id: None, + break_id: Some(merge_id), + }, + debug_info, + )?; + + block = Block::new(merge_id); + } + crate::Statement::Break => { + self.function + .consume(block, Instruction::branch(loop_context.break_id.unwrap())); + return Ok(()); + } + crate::Statement::Continue => { + self.function.consume( + block, + Instruction::branch(loop_context.continuing_id.unwrap()), + ); + return Ok(()); + } + crate::Statement::Return { value: Some(value) } => { + let value_id = self.cached[value]; + let instruction = match self.function.entry_point_context { + // If this is an entry point, and we need to return anything, + // let's instead store the output variables and return `void`. + Some(ref context) => { + self.writer.write_entry_point_return( + value_id, + self.ir_function.result.as_ref().unwrap(), + &context.results, + &mut block.body, + )?; + Instruction::return_void() + } + None => Instruction::return_value(value_id), + }; + self.function.consume(block, instruction); + return Ok(()); + } + crate::Statement::Return { value: None } => { + self.function.consume(block, Instruction::return_void()); + return Ok(()); + } + crate::Statement::Kill => { + self.function.consume(block, Instruction::kill()); + return Ok(()); + } + crate::Statement::Barrier(flags) => { + self.writer.write_barrier(flags, &mut block); + } + crate::Statement::Store { pointer, value } => { + let value_id = self.cached[value]; + match self.write_expression_pointer(pointer, &mut block, None)? { + ExpressionPointer::Ready { pointer_id } => { + let atomic_space = match *self.fun_info[pointer] + .ty + .inner_with(&self.ir_module.types) + { + crate::TypeInner::Pointer { base, space } => { + match self.ir_module.types[base].inner { + crate::TypeInner::Atomic { .. } => Some(space), + _ => None, + } + } + _ => None, + }; + let instruction = if let Some(space) = atomic_space { + let (semantics, scope) = space.to_spirv_semantics_and_scope(); + let scope_constant_id = self.get_scope_constant(scope as u32); + let semantics_id = self.get_index_constant(semantics.bits()); + Instruction::atomic_store( + pointer_id, + scope_constant_id, + semantics_id, + value_id, + ) + } else { + Instruction::store(pointer_id, value_id, None) + }; + block.body.push(instruction); + } + ExpressionPointer::Conditional { condition, access } => { + let mut selection = Selection::start(&mut block, ()); + selection.if_true(self, condition, ()); + + // The in-bounds path. Perform the access and the store. + let pointer_id = access.result_id.unwrap(); + selection.block().body.push(access); + selection + .block() + .body + .push(Instruction::store(pointer_id, value_id, None)); + + // Finish the in-bounds block and start the merge block. This + // is the block we'll leave current on return. + selection.finish(self, ()); + } + }; + } + crate::Statement::ImageStore { + image, + coordinate, + array_index, + value, + } => self.write_image_store(image, coordinate, array_index, value, &mut block)?, + crate::Statement::Call { + function: local_function, + ref arguments, + result, + } => { + let id = self.gen_id(); + self.temp_list.clear(); + for &argument in arguments { + self.temp_list.push(self.cached[argument]); + } + + let type_id = match result { + Some(expr) => { + self.cached[expr] = id; + self.get_expression_type_id(&self.fun_info[expr].ty) + } + None => self.writer.void_type, + }; + + block.body.push(Instruction::function_call( + type_id, + id, + self.writer.lookup_function[&local_function], + &self.temp_list, + )); + } + crate::Statement::Atomic { + pointer, + ref fun, + value, + result, + } => { + let id = self.gen_id(); + let result_type_id = self.get_expression_type_id(&self.fun_info[result].ty); + + self.cached[result] = id; + + let pointer_id = + match self.write_expression_pointer(pointer, &mut block, None)? { + ExpressionPointer::Ready { pointer_id } => pointer_id, + ExpressionPointer::Conditional { .. } => { + return Err(Error::FeatureNotImplemented( + "Atomics out-of-bounds handling", + )); + } + }; + + let space = self.fun_info[pointer] + .ty + .inner_with(&self.ir_module.types) + .pointer_space() + .unwrap(); + let (semantics, scope) = space.to_spirv_semantics_and_scope(); + let scope_constant_id = self.get_scope_constant(scope as u32); + let semantics_id = self.get_index_constant(semantics.bits()); + let value_id = self.cached[value]; + let value_inner = self.fun_info[value].ty.inner_with(&self.ir_module.types); + + let instruction = match *fun { + crate::AtomicFunction::Add => Instruction::atomic_binary( + spirv::Op::AtomicIAdd, + result_type_id, + id, + pointer_id, + scope_constant_id, + semantics_id, + value_id, + ), + crate::AtomicFunction::Subtract => Instruction::atomic_binary( + spirv::Op::AtomicISub, + result_type_id, + id, + pointer_id, + scope_constant_id, + semantics_id, + value_id, + ), + crate::AtomicFunction::And => Instruction::atomic_binary( + spirv::Op::AtomicAnd, + result_type_id, + id, + pointer_id, + scope_constant_id, + semantics_id, + value_id, + ), + crate::AtomicFunction::InclusiveOr => Instruction::atomic_binary( + spirv::Op::AtomicOr, + result_type_id, + id, + pointer_id, + scope_constant_id, + semantics_id, + value_id, + ), + crate::AtomicFunction::ExclusiveOr => Instruction::atomic_binary( + spirv::Op::AtomicXor, + result_type_id, + id, + pointer_id, + scope_constant_id, + semantics_id, + value_id, + ), + crate::AtomicFunction::Min => { + let spirv_op = match *value_inner { + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint, + width: _, + }) => spirv::Op::AtomicSMin, + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + width: _, + }) => spirv::Op::AtomicUMin, + _ => unimplemented!(), + }; + Instruction::atomic_binary( + spirv_op, + result_type_id, + id, + pointer_id, + scope_constant_id, + semantics_id, + value_id, + ) + } + crate::AtomicFunction::Max => { + let spirv_op = match *value_inner { + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint, + width: _, + }) => spirv::Op::AtomicSMax, + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + width: _, + }) => spirv::Op::AtomicUMax, + _ => unimplemented!(), + }; + Instruction::atomic_binary( + spirv_op, + result_type_id, + id, + pointer_id, + scope_constant_id, + semantics_id, + value_id, + ) + } + crate::AtomicFunction::Exchange { compare: None } => { + Instruction::atomic_binary( + spirv::Op::AtomicExchange, + result_type_id, + id, + pointer_id, + scope_constant_id, + semantics_id, + value_id, + ) + } + crate::AtomicFunction::Exchange { compare: Some(cmp) } => { + let scalar_type_id = match *value_inner { + crate::TypeInner::Scalar(scalar) => { + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar, + pointer_space: None, + })) + } + _ => unimplemented!(), + }; + let bool_type_id = + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::BOOL, + pointer_space: None, + })); + + let cas_result_id = self.gen_id(); + let equality_result_id = self.gen_id(); + let mut cas_instr = Instruction::new(spirv::Op::AtomicCompareExchange); + cas_instr.set_type(scalar_type_id); + cas_instr.set_result(cas_result_id); + cas_instr.add_operand(pointer_id); + cas_instr.add_operand(scope_constant_id); + cas_instr.add_operand(semantics_id); // semantics if equal + cas_instr.add_operand(semantics_id); // semantics if not equal + cas_instr.add_operand(value_id); + cas_instr.add_operand(self.cached[cmp]); + block.body.push(cas_instr); + block.body.push(Instruction::binary( + spirv::Op::IEqual, + bool_type_id, + equality_result_id, + cas_result_id, + self.cached[cmp], + )); + Instruction::composite_construct( + result_type_id, + id, + &[cas_result_id, equality_result_id], + ) + } + }; + + block.body.push(instruction); + } + crate::Statement::WorkGroupUniformLoad { pointer, result } => { + self.writer + .write_barrier(crate::Barrier::WORK_GROUP, &mut block); + let result_type_id = self.get_expression_type_id(&self.fun_info[result].ty); + // Embed the body of + match self.write_expression_pointer(pointer, &mut block, None)? { + ExpressionPointer::Ready { pointer_id } => { + let id = self.gen_id(); + block.body.push(Instruction::load( + result_type_id, + id, + pointer_id, + None, + )); + self.cached[result] = id; + } + ExpressionPointer::Conditional { condition, access } => { + self.cached[result] = self.write_conditional_indexed_load( + result_type_id, + condition, + &mut block, + move |id_gen, block| { + // The in-bounds path. Perform the access and the load. + let pointer_id = access.result_id.unwrap(); + let value_id = id_gen.next(); + block.body.push(access); + block.body.push(Instruction::load( + result_type_id, + value_id, + pointer_id, + None, + )); + value_id + }, + ) + } + } + self.writer + .write_barrier(crate::Barrier::WORK_GROUP, &mut block); + } + crate::Statement::RayQuery { query, ref fun } => { + self.write_ray_query_function(query, fun, &mut block); + } + } + } + + let termination = match exit { + // We're generating code for the top-level Block of the function, so we + // need to end it with some kind of return instruction. + BlockExit::Return => match self.ir_function.result { + Some(ref result) if self.function.entry_point_context.is_none() => { + let type_id = self.get_type_id(LookupType::Handle(result.ty)); + let null_id = self.writer.get_constant_null(type_id); + Instruction::return_value(null_id) + } + _ => Instruction::return_void(), + }, + BlockExit::Branch { target } => Instruction::branch(target), + BlockExit::BreakIf { + condition, + preamble_id, + } => { + let condition_id = self.cached[condition]; + + Instruction::branch_conditional( + condition_id, + loop_context.break_id.unwrap(), + preamble_id, + ) + } + }; + + self.function.consume(block, termination); + Ok(()) + } +} diff --git a/naga/src/back/spv/helpers.rs b/naga/src/back/spv/helpers.rs new file mode 100644 index 0000000000..5b6226db85 --- /dev/null +++ b/naga/src/back/spv/helpers.rs @@ -0,0 +1,109 @@ +use crate::{Handle, UniqueArena}; +use spirv::Word; + +pub(super) fn bytes_to_words(bytes: &[u8]) -> Vec { + bytes + .chunks(4) + .map(|chars| chars.iter().rev().fold(0u32, |u, c| (u << 8) | *c as u32)) + .collect() +} + +pub(super) fn string_to_words(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut words = bytes_to_words(bytes); + + if bytes.len() % 4 == 0 { + // nul-termination + words.push(0x0u32); + } + + words +} + +pub(super) const fn map_storage_class(space: crate::AddressSpace) -> spirv::StorageClass { + match space { + crate::AddressSpace::Handle => spirv::StorageClass::UniformConstant, + crate::AddressSpace::Function => spirv::StorageClass::Function, + crate::AddressSpace::Private => spirv::StorageClass::Private, + crate::AddressSpace::Storage { .. } => spirv::StorageClass::StorageBuffer, + crate::AddressSpace::Uniform => spirv::StorageClass::Uniform, + crate::AddressSpace::WorkGroup => spirv::StorageClass::Workgroup, + crate::AddressSpace::PushConstant => spirv::StorageClass::PushConstant, + } +} + +pub(super) fn contains_builtin( + binding: Option<&crate::Binding>, + ty: Handle, + arena: &UniqueArena, + built_in: crate::BuiltIn, +) -> bool { + if let Some(&crate::Binding::BuiltIn(bi)) = binding { + bi == built_in + } else if let crate::TypeInner::Struct { ref members, .. } = arena[ty].inner { + members + .iter() + .any(|member| contains_builtin(member.binding.as_ref(), member.ty, arena, built_in)) + } else { + false // unreachable + } +} + +impl crate::AddressSpace { + pub(super) const fn to_spirv_semantics_and_scope( + self, + ) -> (spirv::MemorySemantics, spirv::Scope) { + match self { + Self::Storage { .. } => (spirv::MemorySemantics::UNIFORM_MEMORY, spirv::Scope::Device), + Self::WorkGroup => ( + spirv::MemorySemantics::WORKGROUP_MEMORY, + spirv::Scope::Workgroup, + ), + _ => (spirv::MemorySemantics::empty(), spirv::Scope::Invocation), + } + } +} + +/// Return true if the global requires a type decorated with `Block`. +/// +/// Vulkan spec v1.3 §15.6.2, "Descriptor Set Interface", says: +/// +/// > Variables identified with the `Uniform` storage class are used to +/// > access transparent buffer backed resources. Such variables must +/// > be: +/// > +/// > - typed as `OpTypeStruct`, or an array of this type, +/// > +/// > - identified with a `Block` or `BufferBlock` decoration, and +/// > +/// > - laid out explicitly using the `Offset`, `ArrayStride`, and +/// > `MatrixStride` decorations as specified in §15.6.4, "Offset +/// > and Stride Assignment." +// See `back::spv::GlobalVariable::access_id` for details. +pub fn global_needs_wrapper(ir_module: &crate::Module, var: &crate::GlobalVariable) -> bool { + match var.space { + crate::AddressSpace::Uniform + | crate::AddressSpace::Storage { .. } + | crate::AddressSpace::PushConstant => {} + _ => return false, + }; + match ir_module.types[var.ty].inner { + crate::TypeInner::Struct { + ref members, + span: _, + } => match members.last() { + Some(member) => match ir_module.types[member.ty].inner { + // Structs with dynamically sized arrays can't be copied and can't be wrapped. + crate::TypeInner::Array { + size: crate::ArraySize::Dynamic, + .. + } => false, + _ => true, + }, + None => false, + }, + crate::TypeInner::BindingArray { .. } => false, + // if it's not a structure or a binding array, let's wrap it to be able to put "Block" + _ => true, + } +} diff --git a/naga/src/back/spv/image.rs b/naga/src/back/spv/image.rs new file mode 100644 index 0000000000..460c906d47 --- /dev/null +++ b/naga/src/back/spv/image.rs @@ -0,0 +1,1210 @@ +/*! +Generating SPIR-V for image operations. +*/ + +use super::{ + selection::{MergeTuple, Selection}, + Block, BlockContext, Error, IdGenerator, Instruction, LocalType, LookupType, +}; +use crate::arena::Handle; +use spirv::Word; + +/// Information about a vector of coordinates. +/// +/// The coordinate vectors expected by SPIR-V `OpImageRead` and `OpImageFetch` +/// supply the array index for arrayed images as an additional component at +/// the end, whereas Naga's `ImageLoad`, `ImageStore`, and `ImageSample` carry +/// the array index as a separate field. +/// +/// In the process of generating code to compute the combined vector, we also +/// produce SPIR-V types and vector lengths that are useful elsewhere. This +/// struct gathers that information into one place, with standard names. +struct ImageCoordinates { + /// The SPIR-V id of the combined coordinate/index vector value. + /// + /// Note: when indexing a non-arrayed 1D image, this will be a scalar. + value_id: Word, + + /// The SPIR-V id of the type of `value`. + type_id: Word, + + /// The number of components in `value`, if it is a vector, or `None` if it + /// is a scalar. + size: Option, +} + +/// A trait for image access (load or store) code generators. +/// +/// Types implementing this trait hold information about an `ImageStore` or +/// `ImageLoad` operation that is not affected by the bounds check policy. The +/// `generate` method emits code for the access, given the results of bounds +/// checking. +/// +/// The [`image`] bounds checks policy affects access coordinates, level of +/// detail, and sample index, but never the image id, result type (if any), or +/// the specific SPIR-V instruction used. Types that implement this trait gather +/// together the latter category, so we don't have to plumb them through the +/// bounds-checking code. +/// +/// [`image`]: crate::proc::BoundsCheckPolicies::index +trait Access { + /// The Rust type that represents SPIR-V values and types for this access. + /// + /// For operations like loads, this is `Word`. For operations like stores, + /// this is `()`. + /// + /// For `ReadZeroSkipWrite`, this will be the type of the selection + /// construct that performs the bounds checks, so it must implement + /// `MergeTuple`. + type Output: MergeTuple + Copy + Clone; + + /// Write an image access to `block`. + /// + /// Access the texel at `coordinates_id`. The optional `level_id` indicates + /// the level of detail, and `sample_id` is the index of the sample to + /// access in a multisampled texel. + /// + /// Ths method assumes that `coordinates_id` has already had the image array + /// index, if any, folded in, as done by `write_image_coordinates`. + /// + /// Return the value id produced by the instruction, if any. + /// + /// Use `id_gen` to generate SPIR-V ids as necessary. + fn generate( + &self, + id_gen: &mut IdGenerator, + coordinates_id: Word, + level_id: Option, + sample_id: Option, + block: &mut Block, + ) -> Self::Output; + + /// Return the SPIR-V type of the value produced by the code written by + /// `generate`. If the access does not produce a value, `Self::Output` + /// should be `()`. + fn result_type(&self) -> Self::Output; + + /// Construct the SPIR-V 'zero' value to be returned for an out-of-bounds + /// access under the `ReadZeroSkipWrite` policy. If the access does not + /// produce a value, `Self::Output` should be `()`. + fn out_of_bounds_value(&self, ctx: &mut BlockContext<'_>) -> Self::Output; +} + +/// Texel access information for an [`ImageLoad`] expression. +/// +/// [`ImageLoad`]: crate::Expression::ImageLoad +struct Load { + /// The specific opcode we'll use to perform the fetch. Storage images + /// require `OpImageRead`, while sampled images require `OpImageFetch`. + opcode: spirv::Op, + + /// The type id produced by the actual image access instruction. + type_id: Word, + + /// The id of the image being accessed. + image_id: Word, +} + +impl Load { + fn from_image_expr( + ctx: &mut BlockContext<'_>, + image_id: Word, + image_class: crate::ImageClass, + result_type_id: Word, + ) -> Result { + let opcode = match image_class { + crate::ImageClass::Storage { .. } => spirv::Op::ImageRead, + crate::ImageClass::Depth { .. } | crate::ImageClass::Sampled { .. } => { + spirv::Op::ImageFetch + } + }; + + // `OpImageRead` and `OpImageFetch` instructions produce vec4 + // values. Most of the time, we can just use `result_type_id` for + // this. The exception is that `Expression::ImageLoad` from a depth + // image produces a scalar `f32`, so in that case we need to find + // the right SPIR-V type for the access instruction here. + let type_id = match image_class { + crate::ImageClass::Depth { .. } => { + ctx.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(crate::VectorSize::Quad), + scalar: crate::Scalar::F32, + pointer_space: None, + })) + } + _ => result_type_id, + }; + + Ok(Load { + opcode, + type_id, + image_id, + }) + } +} + +impl Access for Load { + type Output = Word; + + /// Write an instruction to access a given texel of this image. + fn generate( + &self, + id_gen: &mut IdGenerator, + coordinates_id: Word, + level_id: Option, + sample_id: Option, + block: &mut Block, + ) -> Word { + let texel_id = id_gen.next(); + let mut instruction = Instruction::image_fetch_or_read( + self.opcode, + self.type_id, + texel_id, + self.image_id, + coordinates_id, + ); + + match (level_id, sample_id) { + (None, None) => {} + (Some(level_id), None) => { + instruction.add_operand(spirv::ImageOperands::LOD.bits()); + instruction.add_operand(level_id); + } + (None, Some(sample_id)) => { + instruction.add_operand(spirv::ImageOperands::SAMPLE.bits()); + instruction.add_operand(sample_id); + } + // There's no such thing as a multi-sampled mipmap. + (Some(_), Some(_)) => unreachable!(), + } + + block.body.push(instruction); + + texel_id + } + + fn result_type(&self) -> Word { + self.type_id + } + + fn out_of_bounds_value(&self, ctx: &mut BlockContext<'_>) -> Word { + ctx.writer.get_constant_null(self.type_id) + } +} + +/// Texel access information for a [`Store`] statement. +/// +/// [`Store`]: crate::Statement::Store +struct Store { + /// The id of the image being written to. + image_id: Word, + + /// The value we're going to write to the texel. + value_id: Word, +} + +impl Access for Store { + /// Stores don't generate any value. + type Output = (); + + fn generate( + &self, + _id_gen: &mut IdGenerator, + coordinates_id: Word, + _level_id: Option, + _sample_id: Option, + block: &mut Block, + ) { + block.body.push(Instruction::image_write( + self.image_id, + coordinates_id, + self.value_id, + )); + } + + /// Stores don't generate any value, so this just returns `()`. + fn result_type(&self) {} + + /// Stores don't generate any value, so this just returns `()`. + fn out_of_bounds_value(&self, _ctx: &mut BlockContext<'_>) {} +} + +impl<'w> BlockContext<'w> { + /// Extend image coordinates with an array index, if necessary. + /// + /// Whereas [`Expression::ImageLoad`] and [`ImageSample`] treat the array + /// index as a separate operand from the coordinates, SPIR-V image access + /// instructions include the array index in the `coordinates` operand. This + /// function builds a SPIR-V coordinate vector from a Naga coordinate vector + /// and array index, if one is supplied, and returns a `ImageCoordinates` + /// struct describing what it built. + /// + /// If `array_index` is `Some(expr)`, then this function constructs a new + /// vector that is `coordinates` with `array_index` concatenated onto the + /// end: a `vec2` becomes a `vec3`, a scalar becomes a `vec2`, and so on. + /// + /// If `array_index` is `None`, then the return value uses `coordinates` + /// unchanged. Note that, when indexing a non-arrayed 1D image, this will be + /// a scalar value. + /// + /// If needed, this function generates code to convert the array index, + /// always an integer scalar, to match the component type of `coordinates`. + /// Naga's `ImageLoad` and SPIR-V's `OpImageRead`, `OpImageFetch`, and + /// `OpImageWrite` all use integer coordinates, while Naga's `ImageSample` + /// and SPIR-V's `OpImageSample...` instructions all take floating-point + /// coordinate vectors. + /// + /// [`Expression::ImageLoad`]: crate::Expression::ImageLoad + /// [`ImageSample`]: crate::Expression::ImageSample + fn write_image_coordinates( + &mut self, + coordinates: Handle, + array_index: Option>, + block: &mut Block, + ) -> Result { + use crate::TypeInner as Ti; + use crate::VectorSize as Vs; + + let coordinates_id = self.cached[coordinates]; + let ty = &self.fun_info[coordinates].ty; + let inner_ty = ty.inner_with(&self.ir_module.types); + + // If there's no array index, the image coordinates are exactly the + // `coordinate` field of the `Expression::ImageLoad`. No work is needed. + let array_index = match array_index { + None => { + let value_id = coordinates_id; + let type_id = self.get_expression_type_id(ty); + let size = match *inner_ty { + Ti::Scalar { .. } => None, + Ti::Vector { size, .. } => Some(size), + _ => return Err(Error::Validation("coordinate type")), + }; + return Ok(ImageCoordinates { + value_id, + type_id, + size, + }); + } + Some(ix) => ix, + }; + + // Find the component type of `coordinates`, and figure out the size the + // combined coordinate vector will have. + let (component_scalar, size) = match *inner_ty { + Ti::Scalar(scalar @ crate::Scalar { width: 4, .. }) => (scalar, Some(Vs::Bi)), + Ti::Vector { + scalar: scalar @ crate::Scalar { width: 4, .. }, + size: Vs::Bi, + } => (scalar, Some(Vs::Tri)), + Ti::Vector { + scalar: scalar @ crate::Scalar { width: 4, .. }, + size: Vs::Tri, + } => (scalar, Some(Vs::Quad)), + Ti::Vector { size: Vs::Quad, .. } => { + return Err(Error::Validation("extending vec4 coordinate")); + } + ref other => { + log::error!("wrong coordinate type {:?}", other); + return Err(Error::Validation("coordinate type")); + } + }; + + // Convert the index to the coordinate component type, if necessary. + let array_index_id = self.cached[array_index]; + let ty = &self.fun_info[array_index].ty; + let inner_ty = ty.inner_with(&self.ir_module.types); + let array_index_scalar = match *inner_ty { + Ti::Scalar( + scalar @ crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + width: 4, + }, + ) => scalar, + _ => unreachable!("we only allow i32 and u32"), + }; + let cast = match (component_scalar.kind, array_index_scalar.kind) { + (crate::ScalarKind::Sint, crate::ScalarKind::Sint) + | (crate::ScalarKind::Uint, crate::ScalarKind::Uint) => None, + (crate::ScalarKind::Sint, crate::ScalarKind::Uint) + | (crate::ScalarKind::Uint, crate::ScalarKind::Sint) => Some(spirv::Op::Bitcast), + (crate::ScalarKind::Float, crate::ScalarKind::Sint) => Some(spirv::Op::ConvertSToF), + (crate::ScalarKind::Float, crate::ScalarKind::Uint) => Some(spirv::Op::ConvertUToF), + (crate::ScalarKind::Bool, _) => unreachable!("we don't allow bool for component"), + (_, crate::ScalarKind::Bool | crate::ScalarKind::Float) => { + unreachable!("we don't allow bool or float for array index") + } + (crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat, _) + | (_, crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat) => { + unreachable!("abstract types should never reach backends") + } + }; + let reconciled_array_index_id = if let Some(cast) = cast { + let component_ty_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: component_scalar, + pointer_space: None, + })); + let reconciled_id = self.gen_id(); + block.body.push(Instruction::unary( + cast, + component_ty_id, + reconciled_id, + array_index_id, + )); + reconciled_id + } else { + array_index_id + }; + + // Find the SPIR-V type for the combined coordinates/index vector. + let type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: size, + scalar: component_scalar, + pointer_space: None, + })); + + // Schmear the coordinates and index together. + let value_id = self.gen_id(); + block.body.push(Instruction::composite_construct( + type_id, + value_id, + &[coordinates_id, reconciled_array_index_id], + )); + Ok(ImageCoordinates { + value_id, + type_id, + size, + }) + } + + pub(super) fn get_handle_id(&mut self, expr_handle: Handle) -> Word { + let id = match self.ir_function.expressions[expr_handle] { + crate::Expression::GlobalVariable(handle) => { + self.writer.global_variables[handle.index()].handle_id + } + crate::Expression::FunctionArgument(i) => { + self.function.parameters[i as usize].handle_id + } + crate::Expression::Access { .. } | crate::Expression::AccessIndex { .. } => { + self.cached[expr_handle] + } + ref other => unreachable!("Unexpected image expression {:?}", other), + }; + + if id == 0 { + unreachable!( + "Image expression {:?} doesn't have a handle ID", + expr_handle + ); + } + + id + } + + /// Generate a vector or scalar 'one' for arithmetic on `coordinates`. + /// + /// If `coordinates` is a scalar, return a scalar one. Otherwise, return + /// a vector of ones. + fn write_coordinate_one(&mut self, coordinates: &ImageCoordinates) -> Result { + let one = self.get_scope_constant(1); + match coordinates.size { + None => Ok(one), + Some(vector_size) => { + let ones = [one; 4]; + let id = self.gen_id(); + Instruction::constant_composite( + coordinates.type_id, + id, + &ones[..vector_size as usize], + ) + .to_words(&mut self.writer.logical_layout.declarations); + Ok(id) + } + } + } + + /// Generate code to restrict `input` to fall between zero and one less than + /// `size_id`. + /// + /// Both must be 32-bit scalar integer values, whose type is given by + /// `type_id`. The computed value is also of type `type_id`. + fn restrict_scalar( + &mut self, + type_id: Word, + input_id: Word, + size_id: Word, + block: &mut Block, + ) -> Result { + let i32_one_id = self.get_scope_constant(1); + + // Subtract one from `size` to get the largest valid value. + let limit_id = self.gen_id(); + block.body.push(Instruction::binary( + spirv::Op::ISub, + type_id, + limit_id, + size_id, + i32_one_id, + )); + + // Use an unsigned minimum, to handle both positive out-of-range values + // and negative values in a single instruction: negative values of + // `input_id` get treated as very large positive values. + let restricted_id = self.gen_id(); + block.body.push(Instruction::ext_inst( + self.writer.gl450_ext_inst_id, + spirv::GLOp::UMin, + type_id, + restricted_id, + &[input_id, limit_id], + )); + + Ok(restricted_id) + } + + /// Write instructions to query the size of an image. + /// + /// This takes care of selecting the right instruction depending on whether + /// a level of detail parameter is present. + fn write_coordinate_bounds( + &mut self, + type_id: Word, + image_id: Word, + level_id: Option, + block: &mut Block, + ) -> Word { + let coordinate_bounds_id = self.gen_id(); + match level_id { + Some(level_id) => { + // A level of detail was provided, so fetch the image size for + // that level. + let mut inst = Instruction::image_query( + spirv::Op::ImageQuerySizeLod, + type_id, + coordinate_bounds_id, + image_id, + ); + inst.add_operand(level_id); + block.body.push(inst); + } + _ => { + // No level of detail was given. + block.body.push(Instruction::image_query( + spirv::Op::ImageQuerySize, + type_id, + coordinate_bounds_id, + image_id, + )); + } + } + + coordinate_bounds_id + } + + /// Write code to restrict coordinates for an image reference. + /// + /// First, clamp the level of detail or sample index to fall within bounds. + /// Then, obtain the image size, possibly using the clamped level of detail. + /// Finally, use an unsigned minimum instruction to force all coordinates + /// into range. + /// + /// Return a triple `(COORDS, LEVEL, SAMPLE)`, where `COORDS` is a coordinate + /// vector (including the array index, if any), `LEVEL` is an optional level + /// of detail, and `SAMPLE` is an optional sample index, all guaranteed to + /// be in-bounds for `image_id`. + /// + /// The result is usually a vector, but it is a scalar when indexing + /// non-arrayed 1D images. + fn write_restricted_coordinates( + &mut self, + image_id: Word, + coordinates: ImageCoordinates, + level_id: Option, + sample_id: Option, + block: &mut Block, + ) -> Result<(Word, Option, Option), Error> { + self.writer.require_any( + "the `Restrict` image bounds check policy", + &[spirv::Capability::ImageQuery], + )?; + + let i32_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::I32, + pointer_space: None, + })); + + // If `level` is `Some`, clamp it to fall within bounds. This must + // happen first, because we'll use it to query the image size for + // clamping the actual coordinates. + let level_id = level_id + .map(|level_id| { + // Find the number of mipmap levels in this image. + let num_levels_id = self.gen_id(); + block.body.push(Instruction::image_query( + spirv::Op::ImageQueryLevels, + i32_type_id, + num_levels_id, + image_id, + )); + + self.restrict_scalar(i32_type_id, level_id, num_levels_id, block) + }) + .transpose()?; + + // If `sample_id` is `Some`, clamp it to fall within bounds. + let sample_id = sample_id + .map(|sample_id| { + // Find the number of samples per texel. + let num_samples_id = self.gen_id(); + block.body.push(Instruction::image_query( + spirv::Op::ImageQuerySamples, + i32_type_id, + num_samples_id, + image_id, + )); + + self.restrict_scalar(i32_type_id, sample_id, num_samples_id, block) + }) + .transpose()?; + + // Obtain the image bounds, including the array element count. + let coordinate_bounds_id = + self.write_coordinate_bounds(coordinates.type_id, image_id, level_id, block); + + // Compute maximum valid values from the bounds. + let ones = self.write_coordinate_one(&coordinates)?; + let coordinate_limit_id = self.gen_id(); + block.body.push(Instruction::binary( + spirv::Op::ISub, + coordinates.type_id, + coordinate_limit_id, + coordinate_bounds_id, + ones, + )); + + // Restrict the coordinates to fall within those bounds. + // + // Use an unsigned minimum, to handle both positive out-of-range values + // and negative values in a single instruction: negative values of + // `coordinates` get treated as very large positive values. + let restricted_coordinates_id = self.gen_id(); + block.body.push(Instruction::ext_inst( + self.writer.gl450_ext_inst_id, + spirv::GLOp::UMin, + coordinates.type_id, + restricted_coordinates_id, + &[coordinates.value_id, coordinate_limit_id], + )); + + Ok((restricted_coordinates_id, level_id, sample_id)) + } + + fn write_conditional_image_access( + &mut self, + image_id: Word, + coordinates: ImageCoordinates, + level_id: Option, + sample_id: Option, + block: &mut Block, + access: &A, + ) -> Result { + self.writer.require_any( + "the `ReadZeroSkipWrite` image bounds check policy", + &[spirv::Capability::ImageQuery], + )?; + + let bool_type_id = self.writer.get_bool_type_id(); + let i32_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::I32, + pointer_space: None, + })); + + let null_id = access.out_of_bounds_value(self); + + let mut selection = Selection::start(block, access.result_type()); + + // If `level_id` is `Some`, check whether it is within bounds. This must + // happen first, because we'll be supplying this as an argument when we + // query the image size. + if let Some(level_id) = level_id { + // Find the number of mipmap levels in this image. + let num_levels_id = self.gen_id(); + selection.block().body.push(Instruction::image_query( + spirv::Op::ImageQueryLevels, + i32_type_id, + num_levels_id, + image_id, + )); + + let lod_cond_id = self.gen_id(); + selection.block().body.push(Instruction::binary( + spirv::Op::ULessThan, + bool_type_id, + lod_cond_id, + level_id, + num_levels_id, + )); + + selection.if_true(self, lod_cond_id, null_id); + } + + // If `sample_id` is `Some`, check whether it is in bounds. + if let Some(sample_id) = sample_id { + // Find the number of samples per texel. + let num_samples_id = self.gen_id(); + selection.block().body.push(Instruction::image_query( + spirv::Op::ImageQuerySamples, + i32_type_id, + num_samples_id, + image_id, + )); + + let samples_cond_id = self.gen_id(); + selection.block().body.push(Instruction::binary( + spirv::Op::ULessThan, + bool_type_id, + samples_cond_id, + sample_id, + num_samples_id, + )); + + selection.if_true(self, samples_cond_id, null_id); + } + + // Obtain the image bounds, including any array element count. + let coordinate_bounds_id = self.write_coordinate_bounds( + coordinates.type_id, + image_id, + level_id, + selection.block(), + ); + + // Compare the coordinates against the bounds. + let coords_bool_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: coordinates.size, + scalar: crate::Scalar::BOOL, + pointer_space: None, + })); + let coords_conds_id = self.gen_id(); + selection.block().body.push(Instruction::binary( + spirv::Op::ULessThan, + coords_bool_type_id, + coords_conds_id, + coordinates.value_id, + coordinate_bounds_id, + )); + + // If the comparison above was a vector comparison, then we need to + // check that all components of the comparison are true. + let coords_cond_id = if coords_bool_type_id != bool_type_id { + let id = self.gen_id(); + selection.block().body.push(Instruction::relational( + spirv::Op::All, + bool_type_id, + id, + coords_conds_id, + )); + id + } else { + coords_conds_id + }; + + selection.if_true(self, coords_cond_id, null_id); + + // All conditions are met. We can carry out the access. + let texel_id = access.generate( + &mut self.writer.id_gen, + coordinates.value_id, + level_id, + sample_id, + selection.block(), + ); + + // This, then, is the value of the 'true' branch. + Ok(selection.finish(self, texel_id)) + } + + /// Generate code for an `ImageLoad` expression. + /// + /// The arguments are the components of an `Expression::ImageLoad` variant. + #[allow(clippy::too_many_arguments)] + pub(super) fn write_image_load( + &mut self, + result_type_id: Word, + image: Handle, + coordinate: Handle, + array_index: Option>, + level: Option>, + sample: Option>, + block: &mut Block, + ) -> Result { + let image_id = self.get_handle_id(image); + let image_type = self.fun_info[image].ty.inner_with(&self.ir_module.types); + let image_class = match *image_type { + crate::TypeInner::Image { class, .. } => class, + _ => return Err(Error::Validation("image type")), + }; + + let access = Load::from_image_expr(self, image_id, image_class, result_type_id)?; + let coordinates = self.write_image_coordinates(coordinate, array_index, block)?; + + let level_id = level.map(|expr| self.cached[expr]); + let sample_id = sample.map(|expr| self.cached[expr]); + + // Perform the access, according to the bounds check policy. + let access_id = match self.writer.bounds_check_policies.image_load { + crate::proc::BoundsCheckPolicy::Restrict => { + let (coords, level_id, sample_id) = self.write_restricted_coordinates( + image_id, + coordinates, + level_id, + sample_id, + block, + )?; + access.generate(&mut self.writer.id_gen, coords, level_id, sample_id, block) + } + crate::proc::BoundsCheckPolicy::ReadZeroSkipWrite => self + .write_conditional_image_access( + image_id, + coordinates, + level_id, + sample_id, + block, + &access, + )?, + crate::proc::BoundsCheckPolicy::Unchecked => access.generate( + &mut self.writer.id_gen, + coordinates.value_id, + level_id, + sample_id, + block, + ), + }; + + // For depth images, `ImageLoad` expressions produce a single f32, + // whereas the SPIR-V instructions always produce a vec4. So we may have + // to pull out the component we need. + let result_id = if result_type_id == access.result_type() { + // The instruction produced the type we expected. We can use + // its result as-is. + access_id + } else { + // For `ImageClass::Depth` images, SPIR-V gave us four components, + // but we only want the first one. + let component_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + result_type_id, + component_id, + access_id, + &[0], + )); + component_id + }; + + Ok(result_id) + } + + /// Generate code for an `ImageSample` expression. + /// + /// The arguments are the components of an `Expression::ImageSample` variant. + #[allow(clippy::too_many_arguments)] + pub(super) fn write_image_sample( + &mut self, + result_type_id: Word, + image: Handle, + sampler: Handle, + gather: Option, + coordinate: Handle, + array_index: Option>, + offset: Option>, + level: crate::SampleLevel, + depth_ref: Option>, + block: &mut Block, + ) -> Result { + use super::instructions::SampleLod; + // image + let image_id = self.get_handle_id(image); + let image_type = self.fun_info[image].ty.handle().unwrap(); + // SPIR-V doesn't know about our `Depth` class, and it returns + // `vec4`, so we need to grab the first component out of it. + let needs_sub_access = match self.ir_module.types[image_type].inner { + crate::TypeInner::Image { + class: crate::ImageClass::Depth { .. }, + .. + } => depth_ref.is_none() && gather.is_none(), + _ => false, + }; + let sample_result_type_id = if needs_sub_access { + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(crate::VectorSize::Quad), + scalar: crate::Scalar::F32, + pointer_space: None, + })) + } else { + result_type_id + }; + + // OpTypeSampledImage + let image_type_id = self.get_type_id(LookupType::Handle(image_type)); + let sampled_image_type_id = + self.get_type_id(LookupType::Local(LocalType::SampledImage { image_type_id })); + + let sampler_id = self.get_handle_id(sampler); + let coordinates_id = self + .write_image_coordinates(coordinate, array_index, block)? + .value_id; + + let sampled_image_id = self.gen_id(); + block.body.push(Instruction::sampled_image( + sampled_image_type_id, + sampled_image_id, + image_id, + sampler_id, + )); + let id = self.gen_id(); + + let depth_id = depth_ref.map(|handle| self.cached[handle]); + let mut mask = spirv::ImageOperands::empty(); + mask.set(spirv::ImageOperands::CONST_OFFSET, offset.is_some()); + + let mut main_instruction = match (level, gather) { + (_, Some(component)) => { + let component_id = self.get_index_constant(component as u32); + let mut inst = Instruction::image_gather( + sample_result_type_id, + id, + sampled_image_id, + coordinates_id, + component_id, + depth_id, + ); + if !mask.is_empty() { + inst.add_operand(mask.bits()); + } + inst + } + (crate::SampleLevel::Zero, None) => { + let mut inst = Instruction::image_sample( + sample_result_type_id, + id, + SampleLod::Explicit, + sampled_image_id, + coordinates_id, + depth_id, + ); + + let zero_id = self.writer.get_constant_scalar(crate::Literal::F32(0.0)); + + mask |= spirv::ImageOperands::LOD; + inst.add_operand(mask.bits()); + inst.add_operand(zero_id); + + inst + } + (crate::SampleLevel::Auto, None) => { + let mut inst = Instruction::image_sample( + sample_result_type_id, + id, + SampleLod::Implicit, + sampled_image_id, + coordinates_id, + depth_id, + ); + if !mask.is_empty() { + inst.add_operand(mask.bits()); + } + inst + } + (crate::SampleLevel::Exact(lod_handle), None) => { + let mut inst = Instruction::image_sample( + sample_result_type_id, + id, + SampleLod::Explicit, + sampled_image_id, + coordinates_id, + depth_id, + ); + + let lod_id = self.cached[lod_handle]; + mask |= spirv::ImageOperands::LOD; + inst.add_operand(mask.bits()); + inst.add_operand(lod_id); + + inst + } + (crate::SampleLevel::Bias(bias_handle), None) => { + let mut inst = Instruction::image_sample( + sample_result_type_id, + id, + SampleLod::Implicit, + sampled_image_id, + coordinates_id, + depth_id, + ); + + let bias_id = self.cached[bias_handle]; + mask |= spirv::ImageOperands::BIAS; + inst.add_operand(mask.bits()); + inst.add_operand(bias_id); + + inst + } + (crate::SampleLevel::Gradient { x, y }, None) => { + let mut inst = Instruction::image_sample( + sample_result_type_id, + id, + SampleLod::Explicit, + sampled_image_id, + coordinates_id, + depth_id, + ); + + let x_id = self.cached[x]; + let y_id = self.cached[y]; + mask |= spirv::ImageOperands::GRAD; + inst.add_operand(mask.bits()); + inst.add_operand(x_id); + inst.add_operand(y_id); + + inst + } + }; + + if let Some(offset_const) = offset { + let offset_id = self.writer.constant_ids[offset_const.index()]; + main_instruction.add_operand(offset_id); + } + + block.body.push(main_instruction); + + let id = if needs_sub_access { + let sub_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + result_type_id, + sub_id, + id, + &[0], + )); + sub_id + } else { + id + }; + + Ok(id) + } + + /// Generate code for an `ImageQuery` expression. + /// + /// The arguments are the components of an `Expression::ImageQuery` variant. + pub(super) fn write_image_query( + &mut self, + result_type_id: Word, + image: Handle, + query: crate::ImageQuery, + block: &mut Block, + ) -> Result { + use crate::{ImageClass as Ic, ImageDimension as Id, ImageQuery as Iq}; + + let image_id = self.get_handle_id(image); + let image_type = self.fun_info[image].ty.handle().unwrap(); + let (dim, arrayed, class) = match self.ir_module.types[image_type].inner { + crate::TypeInner::Image { + dim, + arrayed, + class, + } => (dim, arrayed, class), + _ => { + return Err(Error::Validation("image type")); + } + }; + + self.writer + .require_any("image queries", &[spirv::Capability::ImageQuery])?; + + let id = match query { + Iq::Size { level } => { + let dim_coords = match dim { + Id::D1 => 1, + Id::D2 | Id::Cube => 2, + Id::D3 => 3, + }; + let array_coords = usize::from(arrayed); + let vector_size = match dim_coords + array_coords { + 2 => Some(crate::VectorSize::Bi), + 3 => Some(crate::VectorSize::Tri), + 4 => Some(crate::VectorSize::Quad), + _ => None, + }; + let extended_size_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size, + scalar: crate::Scalar::U32, + pointer_space: None, + })); + + let (query_op, level_id) = match class { + Ic::Sampled { multi: true, .. } + | Ic::Depth { multi: true } + | Ic::Storage { .. } => (spirv::Op::ImageQuerySize, None), + _ => { + let level_id = match level { + Some(expr) => self.cached[expr], + None => self.get_index_constant(0), + }; + (spirv::Op::ImageQuerySizeLod, Some(level_id)) + } + }; + + // The ID of the vector returned by SPIR-V, which contains the dimensions + // as well as the layer count. + let id_extended = self.gen_id(); + let mut inst = Instruction::image_query( + query_op, + extended_size_type_id, + id_extended, + image_id, + ); + if let Some(expr_id) = level_id { + inst.add_operand(expr_id); + } + block.body.push(inst); + + if result_type_id != extended_size_type_id { + let id = self.gen_id(); + let components = match dim { + // always pick the first component, and duplicate it for all 3 dimensions + Id::Cube => &[0u32, 0][..], + _ => &[0u32, 1, 2, 3][..dim_coords], + }; + block.body.push(Instruction::vector_shuffle( + result_type_id, + id, + id_extended, + id_extended, + components, + )); + + id + } else { + id_extended + } + } + Iq::NumLevels => { + let query_id = self.gen_id(); + block.body.push(Instruction::image_query( + spirv::Op::ImageQueryLevels, + result_type_id, + query_id, + image_id, + )); + + query_id + } + Iq::NumLayers => { + let vec_size = match dim { + Id::D1 => crate::VectorSize::Bi, + Id::D2 | Id::Cube => crate::VectorSize::Tri, + Id::D3 => crate::VectorSize::Quad, + }; + let extended_size_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(vec_size), + scalar: crate::Scalar::U32, + pointer_space: None, + })); + let id_extended = self.gen_id(); + let mut inst = Instruction::image_query( + spirv::Op::ImageQuerySizeLod, + extended_size_type_id, + id_extended, + image_id, + ); + inst.add_operand(self.get_index_constant(0)); + block.body.push(inst); + + let extract_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + result_type_id, + extract_id, + id_extended, + &[vec_size as u32 - 1], + )); + + extract_id + } + Iq::NumSamples => { + let query_id = self.gen_id(); + block.body.push(Instruction::image_query( + spirv::Op::ImageQuerySamples, + result_type_id, + query_id, + image_id, + )); + + query_id + } + }; + + Ok(id) + } + + pub(super) fn write_image_store( + &mut self, + image: Handle, + coordinate: Handle, + array_index: Option>, + value: Handle, + block: &mut Block, + ) -> Result<(), Error> { + let image_id = self.get_handle_id(image); + let coordinates = self.write_image_coordinates(coordinate, array_index, block)?; + let value_id = self.cached[value]; + + let write = Store { image_id, value_id }; + + match *self.fun_info[image].ty.inner_with(&self.ir_module.types) { + crate::TypeInner::Image { + class: + crate::ImageClass::Storage { + format: crate::StorageFormat::Bgra8Unorm, + .. + }, + .. + } => self.writer.require_any( + "Bgra8Unorm storage write", + &[spirv::Capability::StorageImageWriteWithoutFormat], + )?, + _ => {} + } + + match self.writer.bounds_check_policies.image_store { + crate::proc::BoundsCheckPolicy::Restrict => { + let (coords, _, _) = + self.write_restricted_coordinates(image_id, coordinates, None, None, block)?; + write.generate(&mut self.writer.id_gen, coords, None, None, block); + } + crate::proc::BoundsCheckPolicy::ReadZeroSkipWrite => { + self.write_conditional_image_access( + image_id, + coordinates, + None, + None, + block, + &write, + )?; + } + crate::proc::BoundsCheckPolicy::Unchecked => { + write.generate( + &mut self.writer.id_gen, + coordinates.value_id, + None, + None, + block, + ); + } + } + + Ok(()) + } +} diff --git a/naga/src/back/spv/index.rs b/naga/src/back/spv/index.rs new file mode 100644 index 0000000000..92e0f88d9a --- /dev/null +++ b/naga/src/back/spv/index.rs @@ -0,0 +1,421 @@ +/*! +Bounds-checking for SPIR-V output. +*/ + +use super::{ + helpers::global_needs_wrapper, selection::Selection, Block, BlockContext, Error, IdGenerator, + Instruction, Word, +}; +use crate::{arena::Handle, proc::BoundsCheckPolicy}; + +/// The results of performing a bounds check. +/// +/// On success, `write_bounds_check` returns a value of this type. +pub(super) enum BoundsCheckResult { + /// The index is statically known and in bounds, with the given value. + KnownInBounds(u32), + + /// The given instruction computes the index to be used. + Computed(Word), + + /// The given instruction computes a boolean condition which is true + /// if the index is in bounds. + Conditional(Word), +} + +/// A value that we either know at translation time, or need to compute at runtime. +pub(super) enum MaybeKnown { + /// The value is known at shader translation time. + Known(T), + + /// The value is computed by the instruction with the given id. + Computed(Word), +} + +impl<'w> BlockContext<'w> { + /// Emit code to compute the length of a run-time array. + /// + /// Given `array`, an expression referring a runtime-sized array, return the + /// instruction id for the array's length. + pub(super) fn write_runtime_array_length( + &mut self, + array: Handle, + block: &mut Block, + ) -> Result { + // Naga IR permits runtime-sized arrays as global variables or as the + // final member of a struct that is a global variable. SPIR-V permits + // only the latter, so this back end wraps bare runtime-sized arrays + // in a made-up struct; see `helpers::global_needs_wrapper` and its uses. + // This code must handle both cases. + let (structure_id, last_member_index) = match self.ir_function.expressions[array] { + crate::Expression::AccessIndex { base, index } => { + match self.ir_function.expressions[base] { + crate::Expression::GlobalVariable(handle) => ( + self.writer.global_variables[handle.index()].access_id, + index, + ), + _ => return Err(Error::Validation("array length expression")), + } + } + crate::Expression::GlobalVariable(handle) => { + let global = &self.ir_module.global_variables[handle]; + if !global_needs_wrapper(self.ir_module, global) { + return Err(Error::Validation("array length expression")); + } + + (self.writer.global_variables[handle.index()].var_id, 0) + } + _ => return Err(Error::Validation("array length expression")), + }; + + let length_id = self.gen_id(); + block.body.push(Instruction::array_length( + self.writer.get_uint_type_id(), + length_id, + structure_id, + last_member_index, + )); + + Ok(length_id) + } + + /// Compute the length of a subscriptable value. + /// + /// Given `sequence`, an expression referring to some indexable type, return + /// its length. The result may either be computed by SPIR-V instructions, or + /// known at shader translation time. + /// + /// `sequence` may be a `Vector`, `Matrix`, or `Array`, a `Pointer` to any + /// of those, or a `ValuePointer`. An array may be fixed-size, dynamically + /// sized, or use a specializable constant as its length. + fn write_sequence_length( + &mut self, + sequence: Handle, + block: &mut Block, + ) -> Result, Error> { + let sequence_ty = self.fun_info[sequence].ty.inner_with(&self.ir_module.types); + match sequence_ty.indexable_length(self.ir_module) { + Ok(crate::proc::IndexableLength::Known(known_length)) => { + Ok(MaybeKnown::Known(known_length)) + } + Ok(crate::proc::IndexableLength::Dynamic) => { + let length_id = self.write_runtime_array_length(sequence, block)?; + Ok(MaybeKnown::Computed(length_id)) + } + Err(err) => { + log::error!("Sequence length for {:?} failed: {}", sequence, err); + Err(Error::Validation("indexable length")) + } + } + } + + /// Compute the maximum valid index of a subscriptable value. + /// + /// Given `sequence`, an expression referring to some indexable type, return + /// its maximum valid index - one less than its length. The result may + /// either be computed, or known at shader translation time. + /// + /// `sequence` may be a `Vector`, `Matrix`, or `Array`, a `Pointer` to any + /// of those, or a `ValuePointer`. An array may be fixed-size, dynamically + /// sized, or use a specializable constant as its length. + fn write_sequence_max_index( + &mut self, + sequence: Handle, + block: &mut Block, + ) -> Result, Error> { + match self.write_sequence_length(sequence, block)? { + MaybeKnown::Known(known_length) => { + // We should have thrown out all attempts to subscript zero-length + // sequences during validation, so the following subtraction should never + // underflow. + assert!(known_length > 0); + // Compute the max index from the length now. + Ok(MaybeKnown::Known(known_length - 1)) + } + MaybeKnown::Computed(length_id) => { + // Emit code to compute the max index from the length. + let const_one_id = self.get_index_constant(1); + let max_index_id = self.gen_id(); + block.body.push(Instruction::binary( + spirv::Op::ISub, + self.writer.get_uint_type_id(), + max_index_id, + length_id, + const_one_id, + )); + Ok(MaybeKnown::Computed(max_index_id)) + } + } + } + + /// Restrict an index to be in range for a vector, matrix, or array. + /// + /// This is used to implement `BoundsCheckPolicy::Restrict`. An in-bounds + /// index is left unchanged. An out-of-bounds index is replaced with some + /// arbitrary in-bounds index. Note,this is not necessarily clamping; for + /// example, negative indices might be changed to refer to the last element + /// of the sequence, not the first, as clamping would do. + /// + /// Either return the restricted index value, if known, or add instructions + /// to `block` to compute it, and return the id of the result. See the + /// documentation for `BoundsCheckResult` for details. + /// + /// The `sequence` expression may be a `Vector`, `Matrix`, or `Array`, a + /// `Pointer` to any of those, or a `ValuePointer`. An array may be + /// fixed-size, dynamically sized, or use a specializable constant as its + /// length. + pub(super) fn write_restricted_index( + &mut self, + sequence: Handle, + index: Handle, + block: &mut Block, + ) -> Result { + let index_id = self.cached[index]; + + // Get the sequence's maximum valid index. Return early if we've already + // done the bounds check. + let max_index_id = match self.write_sequence_max_index(sequence, block)? { + MaybeKnown::Known(known_max_index) => { + if let Ok(known_index) = self + .ir_module + .to_ctx() + .eval_expr_to_u32_from(index, &self.ir_function.expressions) + { + // Both the index and length are known at compile time. + // + // In strict WGSL compliance mode, out-of-bounds indices cannot be + // reported at shader translation time, and must be replaced with + // in-bounds indices at run time. So we cannot assume that + // validation ensured the index was in bounds. Restrict now. + let restricted = std::cmp::min(known_index, known_max_index); + return Ok(BoundsCheckResult::KnownInBounds(restricted)); + } + + self.get_index_constant(known_max_index) + } + MaybeKnown::Computed(max_index_id) => max_index_id, + }; + + // One or the other of the index or length is dynamic, so emit code for + // BoundsCheckPolicy::Restrict. + let restricted_index_id = self.gen_id(); + block.body.push(Instruction::ext_inst( + self.writer.gl450_ext_inst_id, + spirv::GLOp::UMin, + self.writer.get_uint_type_id(), + restricted_index_id, + &[index_id, max_index_id], + )); + Ok(BoundsCheckResult::Computed(restricted_index_id)) + } + + /// Write an index bounds comparison to `block`, if needed. + /// + /// If we're able to determine statically that `index` is in bounds for + /// `sequence`, return `KnownInBounds(value)`, where `value` is the actual + /// value of the index. (In principle, one could know that the index is in + /// bounds without knowing its specific value, but in our simple-minded + /// situation, we always know it.) + /// + /// If instead we must generate code to perform the comparison at run time, + /// return `Conditional(comparison_id)`, where `comparison_id` is an + /// instruction producing a boolean value that is true if `index` is in + /// bounds for `sequence`. + /// + /// The `sequence` expression may be a `Vector`, `Matrix`, or `Array`, a + /// `Pointer` to any of those, or a `ValuePointer`. An array may be + /// fixed-size, dynamically sized, or use a specializable constant as its + /// length. + fn write_index_comparison( + &mut self, + sequence: Handle, + index: Handle, + block: &mut Block, + ) -> Result { + let index_id = self.cached[index]; + + // Get the sequence's length. Return early if we've already done the + // bounds check. + let length_id = match self.write_sequence_length(sequence, block)? { + MaybeKnown::Known(known_length) => { + if let Ok(known_index) = self + .ir_module + .to_ctx() + .eval_expr_to_u32_from(index, &self.ir_function.expressions) + { + // Both the index and length are known at compile time. + // + // It would be nice to assume that, since we are using the + // `ReadZeroSkipWrite` policy, we are not in strict WGSL + // compliance mode, and thus we can count on the validator to have + // rejected any programs with known out-of-bounds indices, and + // thus just return `KnownInBounds` here without actually + // checking. + // + // But it's also reasonable to expect that bounds check policies + // and error reporting policies should be able to vary + // independently without introducing security holes. So, we should + // support the case where bad indices do not cause validation + // errors, and are handled via `ReadZeroSkipWrite`. + // + // In theory, when `known_index` is bad, we could return a new + // `KnownOutOfBounds` variant here. But it's simpler just to fall + // through and let the bounds check take place. The shader is + // broken anyway, so it doesn't make sense to invest in emitting + // the ideal code for it. + if known_index < known_length { + return Ok(BoundsCheckResult::KnownInBounds(known_index)); + } + } + + self.get_index_constant(known_length) + } + MaybeKnown::Computed(length_id) => length_id, + }; + + // Compare the index against the length. + let condition_id = self.gen_id(); + block.body.push(Instruction::binary( + spirv::Op::ULessThan, + self.writer.get_bool_type_id(), + condition_id, + index_id, + length_id, + )); + + // Indicate that we did generate the check. + Ok(BoundsCheckResult::Conditional(condition_id)) + } + + /// Emit a conditional load for `BoundsCheckPolicy::ReadZeroSkipWrite`. + /// + /// Generate code to load a value of `result_type` if `condition` is true, + /// and generate a null value of that type if it is false. Call `emit_load` + /// to emit the instructions to perform the load. Return the id of the + /// merged value of the two branches. + pub(super) fn write_conditional_indexed_load( + &mut self, + result_type: Word, + condition: Word, + block: &mut Block, + emit_load: F, + ) -> Word + where + F: FnOnce(&mut IdGenerator, &mut Block) -> Word, + { + // For the out-of-bounds case, we produce a zero value. + let null_id = self.writer.get_constant_null(result_type); + + let mut selection = Selection::start(block, result_type); + + // As it turns out, we don't actually need a full 'if-then-else' + // structure for this: SPIR-V constants are declared up front, so the + // 'else' block would have no instructions. Instead we emit something + // like this: + // + // result = zero; + // if in_bounds { + // result = do the load; + // } + // use result; + + // Continue only if the index was in bounds. Otherwise, branch to the + // merge block. + selection.if_true(self, condition, null_id); + + // The in-bounds path. Perform the access and the load. + let loaded_value = emit_load(&mut self.writer.id_gen, selection.block()); + + selection.finish(self, loaded_value) + } + + /// Emit code for bounds checks for an array, vector, or matrix access. + /// + /// This implements either `index_bounds_check_policy` or + /// `buffer_bounds_check_policy`, depending on the address space of the + /// pointer being accessed. + /// + /// Return a `BoundsCheckResult` indicating how the index should be + /// consumed. See that type's documentation for details. + pub(super) fn write_bounds_check( + &mut self, + base: Handle, + index: Handle, + block: &mut Block, + ) -> Result { + let policy = self.writer.bounds_check_policies.choose_policy( + base, + &self.ir_module.types, + self.fun_info, + ); + + Ok(match policy { + BoundsCheckPolicy::Restrict => self.write_restricted_index(base, index, block)?, + BoundsCheckPolicy::ReadZeroSkipWrite => { + self.write_index_comparison(base, index, block)? + } + BoundsCheckPolicy::Unchecked => BoundsCheckResult::Computed(self.cached[index]), + }) + } + + /// Emit code to subscript a vector by value with a computed index. + /// + /// Return the id of the element value. + pub(super) fn write_vector_access( + &mut self, + expr_handle: Handle, + base: Handle, + index: Handle, + block: &mut Block, + ) -> Result { + let result_type_id = self.get_expression_type_id(&self.fun_info[expr_handle].ty); + + let base_id = self.cached[base]; + let index_id = self.cached[index]; + + let result_id = match self.write_bounds_check(base, index, block)? { + BoundsCheckResult::KnownInBounds(known_index) => { + let result_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + result_type_id, + result_id, + base_id, + &[known_index], + )); + result_id + } + BoundsCheckResult::Computed(computed_index_id) => { + let result_id = self.gen_id(); + block.body.push(Instruction::vector_extract_dynamic( + result_type_id, + result_id, + base_id, + computed_index_id, + )); + result_id + } + BoundsCheckResult::Conditional(comparison_id) => { + // Run-time bounds checks were required. Emit + // conditional load. + self.write_conditional_indexed_load( + result_type_id, + comparison_id, + block, + |id_gen, block| { + // The in-bounds path. Generate the access. + let element_id = id_gen.next(); + block.body.push(Instruction::vector_extract_dynamic( + result_type_id, + element_id, + base_id, + index_id, + )); + element_id + }, + ) + } + }; + + Ok(result_id) + } +} diff --git a/naga/src/back/spv/instructions.rs b/naga/src/back/spv/instructions.rs new file mode 100644 index 0000000000..b963793ad3 --- /dev/null +++ b/naga/src/back/spv/instructions.rs @@ -0,0 +1,1100 @@ +use super::{block::DebugInfoInner, helpers}; +use spirv::{Op, Word}; + +pub(super) enum Signedness { + Unsigned = 0, + Signed = 1, +} + +pub(super) enum SampleLod { + Explicit, + Implicit, +} + +pub(super) struct Case { + pub value: Word, + pub label_id: Word, +} + +impl super::Instruction { + // + // Debug Instructions + // + + pub(super) fn string(name: &str, id: Word) -> Self { + let mut instruction = Self::new(Op::String); + instruction.set_result(id); + instruction.add_operands(helpers::string_to_words(name)); + instruction + } + + pub(super) fn source( + source_language: spirv::SourceLanguage, + version: u32, + source: &Option, + ) -> Self { + let mut instruction = Self::new(Op::Source); + instruction.add_operand(source_language as u32); + instruction.add_operands(helpers::bytes_to_words(&version.to_le_bytes())); + if let Some(source) = source.as_ref() { + instruction.add_operand(source.source_file_id); + instruction.add_operands(helpers::string_to_words(source.source_code)); + } + instruction + } + + pub(super) fn name(target_id: Word, name: &str) -> Self { + let mut instruction = Self::new(Op::Name); + instruction.add_operand(target_id); + instruction.add_operands(helpers::string_to_words(name)); + instruction + } + + pub(super) fn member_name(target_id: Word, member: Word, name: &str) -> Self { + let mut instruction = Self::new(Op::MemberName); + instruction.add_operand(target_id); + instruction.add_operand(member); + instruction.add_operands(helpers::string_to_words(name)); + instruction + } + + pub(super) fn line(file: Word, line: Word, column: Word) -> Self { + let mut instruction = Self::new(Op::Line); + instruction.add_operand(file); + instruction.add_operand(line); + instruction.add_operand(column); + instruction + } + + pub(super) const fn no_line() -> Self { + Self::new(Op::NoLine) + } + + // + // Annotation Instructions + // + + pub(super) fn decorate( + target_id: Word, + decoration: spirv::Decoration, + operands: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::Decorate); + instruction.add_operand(target_id); + instruction.add_operand(decoration as u32); + for operand in operands { + instruction.add_operand(*operand) + } + instruction + } + + pub(super) fn member_decorate( + target_id: Word, + member_index: Word, + decoration: spirv::Decoration, + operands: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::MemberDecorate); + instruction.add_operand(target_id); + instruction.add_operand(member_index); + instruction.add_operand(decoration as u32); + for operand in operands { + instruction.add_operand(*operand) + } + instruction + } + + // + // Extension Instructions + // + + pub(super) fn extension(name: &str) -> Self { + let mut instruction = Self::new(Op::Extension); + instruction.add_operands(helpers::string_to_words(name)); + instruction + } + + pub(super) fn ext_inst_import(id: Word, name: &str) -> Self { + let mut instruction = Self::new(Op::ExtInstImport); + instruction.set_result(id); + instruction.add_operands(helpers::string_to_words(name)); + instruction + } + + pub(super) fn ext_inst( + set_id: Word, + op: spirv::GLOp, + result_type_id: Word, + id: Word, + operands: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::ExtInst); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(set_id); + instruction.add_operand(op as u32); + for operand in operands { + instruction.add_operand(*operand) + } + instruction + } + + // + // Mode-Setting Instructions + // + + pub(super) fn memory_model( + addressing_model: spirv::AddressingModel, + memory_model: spirv::MemoryModel, + ) -> Self { + let mut instruction = Self::new(Op::MemoryModel); + instruction.add_operand(addressing_model as u32); + instruction.add_operand(memory_model as u32); + instruction + } + + pub(super) fn entry_point( + execution_model: spirv::ExecutionModel, + entry_point_id: Word, + name: &str, + interface_ids: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::EntryPoint); + instruction.add_operand(execution_model as u32); + instruction.add_operand(entry_point_id); + instruction.add_operands(helpers::string_to_words(name)); + + for interface_id in interface_ids { + instruction.add_operand(*interface_id); + } + + instruction + } + + pub(super) fn execution_mode( + entry_point_id: Word, + execution_mode: spirv::ExecutionMode, + args: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::ExecutionMode); + instruction.add_operand(entry_point_id); + instruction.add_operand(execution_mode as u32); + for arg in args { + instruction.add_operand(*arg); + } + instruction + } + + pub(super) fn capability(capability: spirv::Capability) -> Self { + let mut instruction = Self::new(Op::Capability); + instruction.add_operand(capability as u32); + instruction + } + + // + // Type-Declaration Instructions + // + + pub(super) fn type_void(id: Word) -> Self { + let mut instruction = Self::new(Op::TypeVoid); + instruction.set_result(id); + instruction + } + + pub(super) fn type_bool(id: Word) -> Self { + let mut instruction = Self::new(Op::TypeBool); + instruction.set_result(id); + instruction + } + + pub(super) fn type_int(id: Word, width: Word, signedness: Signedness) -> Self { + let mut instruction = Self::new(Op::TypeInt); + instruction.set_result(id); + instruction.add_operand(width); + instruction.add_operand(signedness as u32); + instruction + } + + pub(super) fn type_float(id: Word, width: Word) -> Self { + let mut instruction = Self::new(Op::TypeFloat); + instruction.set_result(id); + instruction.add_operand(width); + instruction + } + + pub(super) fn type_vector( + id: Word, + component_type_id: Word, + component_count: crate::VectorSize, + ) -> Self { + let mut instruction = Self::new(Op::TypeVector); + instruction.set_result(id); + instruction.add_operand(component_type_id); + instruction.add_operand(component_count as u32); + instruction + } + + pub(super) fn type_matrix( + id: Word, + column_type_id: Word, + column_count: crate::VectorSize, + ) -> Self { + let mut instruction = Self::new(Op::TypeMatrix); + instruction.set_result(id); + instruction.add_operand(column_type_id); + instruction.add_operand(column_count as u32); + instruction + } + + #[allow(clippy::too_many_arguments)] + pub(super) fn type_image( + id: Word, + sampled_type_id: Word, + dim: spirv::Dim, + flags: super::ImageTypeFlags, + image_format: spirv::ImageFormat, + ) -> Self { + let mut instruction = Self::new(Op::TypeImage); + instruction.set_result(id); + instruction.add_operand(sampled_type_id); + instruction.add_operand(dim as u32); + instruction.add_operand(flags.contains(super::ImageTypeFlags::DEPTH) as u32); + instruction.add_operand(flags.contains(super::ImageTypeFlags::ARRAYED) as u32); + instruction.add_operand(flags.contains(super::ImageTypeFlags::MULTISAMPLED) as u32); + instruction.add_operand(if flags.contains(super::ImageTypeFlags::SAMPLED) { + 1 + } else { + 2 + }); + instruction.add_operand(image_format as u32); + instruction + } + + pub(super) fn type_sampler(id: Word) -> Self { + let mut instruction = Self::new(Op::TypeSampler); + instruction.set_result(id); + instruction + } + + pub(super) fn type_acceleration_structure(id: Word) -> Self { + let mut instruction = Self::new(Op::TypeAccelerationStructureKHR); + instruction.set_result(id); + instruction + } + + pub(super) fn type_ray_query(id: Word) -> Self { + let mut instruction = Self::new(Op::TypeRayQueryKHR); + instruction.set_result(id); + instruction + } + + pub(super) fn type_sampled_image(id: Word, image_type_id: Word) -> Self { + let mut instruction = Self::new(Op::TypeSampledImage); + instruction.set_result(id); + instruction.add_operand(image_type_id); + instruction + } + + pub(super) fn type_array(id: Word, element_type_id: Word, length_id: Word) -> Self { + let mut instruction = Self::new(Op::TypeArray); + instruction.set_result(id); + instruction.add_operand(element_type_id); + instruction.add_operand(length_id); + instruction + } + + pub(super) fn type_runtime_array(id: Word, element_type_id: Word) -> Self { + let mut instruction = Self::new(Op::TypeRuntimeArray); + instruction.set_result(id); + instruction.add_operand(element_type_id); + instruction + } + + pub(super) fn type_struct(id: Word, member_ids: &[Word]) -> Self { + let mut instruction = Self::new(Op::TypeStruct); + instruction.set_result(id); + + for member_id in member_ids { + instruction.add_operand(*member_id) + } + + instruction + } + + pub(super) fn type_pointer( + id: Word, + storage_class: spirv::StorageClass, + type_id: Word, + ) -> Self { + let mut instruction = Self::new(Op::TypePointer); + instruction.set_result(id); + instruction.add_operand(storage_class as u32); + instruction.add_operand(type_id); + instruction + } + + pub(super) fn type_function(id: Word, return_type_id: Word, parameter_ids: &[Word]) -> Self { + let mut instruction = Self::new(Op::TypeFunction); + instruction.set_result(id); + instruction.add_operand(return_type_id); + + for parameter_id in parameter_ids { + instruction.add_operand(*parameter_id); + } + + instruction + } + + // + // Constant-Creation Instructions + // + + pub(super) fn constant_null(result_type_id: Word, id: Word) -> Self { + let mut instruction = Self::new(Op::ConstantNull); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction + } + + pub(super) fn constant_true(result_type_id: Word, id: Word) -> Self { + let mut instruction = Self::new(Op::ConstantTrue); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction + } + + pub(super) fn constant_false(result_type_id: Word, id: Word) -> Self { + let mut instruction = Self::new(Op::ConstantFalse); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction + } + + pub(super) fn constant_32bit(result_type_id: Word, id: Word, value: Word) -> Self { + Self::constant(result_type_id, id, &[value]) + } + + pub(super) fn constant_64bit(result_type_id: Word, id: Word, low: Word, high: Word) -> Self { + Self::constant(result_type_id, id, &[low, high]) + } + + pub(super) fn constant(result_type_id: Word, id: Word, values: &[Word]) -> Self { + let mut instruction = Self::new(Op::Constant); + instruction.set_type(result_type_id); + instruction.set_result(id); + + for value in values { + instruction.add_operand(*value); + } + + instruction + } + + pub(super) fn constant_composite( + result_type_id: Word, + id: Word, + constituent_ids: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::ConstantComposite); + instruction.set_type(result_type_id); + instruction.set_result(id); + + for constituent_id in constituent_ids { + instruction.add_operand(*constituent_id); + } + + instruction + } + + // + // Memory Instructions + // + + pub(super) fn variable( + result_type_id: Word, + id: Word, + storage_class: spirv::StorageClass, + initializer_id: Option, + ) -> Self { + let mut instruction = Self::new(Op::Variable); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(storage_class as u32); + + if let Some(initializer_id) = initializer_id { + instruction.add_operand(initializer_id); + } + + instruction + } + + pub(super) fn load( + result_type_id: Word, + id: Word, + pointer_id: Word, + memory_access: Option, + ) -> Self { + let mut instruction = Self::new(Op::Load); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(pointer_id); + + if let Some(memory_access) = memory_access { + instruction.add_operand(memory_access.bits()); + } + + instruction + } + + pub(super) fn atomic_load( + result_type_id: Word, + id: Word, + pointer_id: Word, + scope_id: Word, + semantics_id: Word, + ) -> Self { + let mut instruction = Self::new(Op::AtomicLoad); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(pointer_id); + instruction.add_operand(scope_id); + instruction.add_operand(semantics_id); + instruction + } + + pub(super) fn store( + pointer_id: Word, + value_id: Word, + memory_access: Option, + ) -> Self { + let mut instruction = Self::new(Op::Store); + instruction.add_operand(pointer_id); + instruction.add_operand(value_id); + + if let Some(memory_access) = memory_access { + instruction.add_operand(memory_access.bits()); + } + + instruction + } + + pub(super) fn atomic_store( + pointer_id: Word, + scope_id: Word, + semantics_id: Word, + value_id: Word, + ) -> Self { + let mut instruction = Self::new(Op::AtomicStore); + instruction.add_operand(pointer_id); + instruction.add_operand(scope_id); + instruction.add_operand(semantics_id); + instruction.add_operand(value_id); + instruction + } + + pub(super) fn access_chain( + result_type_id: Word, + id: Word, + base_id: Word, + index_ids: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::AccessChain); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(base_id); + + for index_id in index_ids { + instruction.add_operand(*index_id); + } + + instruction + } + + pub(super) fn array_length( + result_type_id: Word, + id: Word, + structure_id: Word, + array_member: Word, + ) -> Self { + let mut instruction = Self::new(Op::ArrayLength); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(structure_id); + instruction.add_operand(array_member); + instruction + } + + // + // Function Instructions + // + + pub(super) fn function( + return_type_id: Word, + id: Word, + function_control: spirv::FunctionControl, + function_type_id: Word, + ) -> Self { + let mut instruction = Self::new(Op::Function); + instruction.set_type(return_type_id); + instruction.set_result(id); + instruction.add_operand(function_control.bits()); + instruction.add_operand(function_type_id); + instruction + } + + pub(super) fn function_parameter(result_type_id: Word, id: Word) -> Self { + let mut instruction = Self::new(Op::FunctionParameter); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction + } + + pub(super) const fn function_end() -> Self { + Self::new(Op::FunctionEnd) + } + + pub(super) fn function_call( + result_type_id: Word, + id: Word, + function_id: Word, + argument_ids: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::FunctionCall); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(function_id); + + for argument_id in argument_ids { + instruction.add_operand(*argument_id); + } + + instruction + } + + // + // Image Instructions + // + + pub(super) fn sampled_image( + result_type_id: Word, + id: Word, + image: Word, + sampler: Word, + ) -> Self { + let mut instruction = Self::new(Op::SampledImage); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(image); + instruction.add_operand(sampler); + instruction + } + + pub(super) fn image_sample( + result_type_id: Word, + id: Word, + lod: SampleLod, + sampled_image: Word, + coordinates: Word, + depth_ref: Option, + ) -> Self { + let op = match (lod, depth_ref) { + (SampleLod::Explicit, None) => Op::ImageSampleExplicitLod, + (SampleLod::Implicit, None) => Op::ImageSampleImplicitLod, + (SampleLod::Explicit, Some(_)) => Op::ImageSampleDrefExplicitLod, + (SampleLod::Implicit, Some(_)) => Op::ImageSampleDrefImplicitLod, + }; + + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(sampled_image); + instruction.add_operand(coordinates); + if let Some(dref) = depth_ref { + instruction.add_operand(dref); + } + + instruction + } + + pub(super) fn image_gather( + result_type_id: Word, + id: Word, + sampled_image: Word, + coordinates: Word, + component_id: Word, + depth_ref: Option, + ) -> Self { + let op = match depth_ref { + None => Op::ImageGather, + Some(_) => Op::ImageDrefGather, + }; + + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(sampled_image); + instruction.add_operand(coordinates); + if let Some(dref) = depth_ref { + instruction.add_operand(dref); + } else { + instruction.add_operand(component_id); + } + + instruction + } + + pub(super) fn image_fetch_or_read( + op: Op, + result_type_id: Word, + id: Word, + image: Word, + coordinates: Word, + ) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(image); + instruction.add_operand(coordinates); + instruction + } + + pub(super) fn image_write(image: Word, coordinates: Word, value: Word) -> Self { + let mut instruction = Self::new(Op::ImageWrite); + instruction.add_operand(image); + instruction.add_operand(coordinates); + instruction.add_operand(value); + instruction + } + + pub(super) fn image_query(op: Op, result_type_id: Word, id: Word, image: Word) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(image); + instruction + } + + // + // Ray Query Instructions + // + #[allow(clippy::too_many_arguments)] + pub(super) fn ray_query_initialize( + query: Word, + acceleration_structure: Word, + ray_flags: Word, + cull_mask: Word, + ray_origin: Word, + ray_tmin: Word, + ray_dir: Word, + ray_tmax: Word, + ) -> Self { + let mut instruction = Self::new(Op::RayQueryInitializeKHR); + instruction.add_operand(query); + instruction.add_operand(acceleration_structure); + instruction.add_operand(ray_flags); + instruction.add_operand(cull_mask); + instruction.add_operand(ray_origin); + instruction.add_operand(ray_tmin); + instruction.add_operand(ray_dir); + instruction.add_operand(ray_tmax); + instruction + } + + pub(super) fn ray_query_proceed(result_type_id: Word, id: Word, query: Word) -> Self { + let mut instruction = Self::new(Op::RayQueryProceedKHR); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(query); + instruction + } + + pub(super) fn ray_query_get_intersection( + op: Op, + result_type_id: Word, + id: Word, + query: Word, + intersection: Word, + ) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(query); + instruction.add_operand(intersection); + instruction + } + + // + // Conversion Instructions + // + pub(super) fn unary(op: Op, result_type_id: Word, id: Word, value: Word) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(value); + instruction + } + + // + // Composite Instructions + // + + pub(super) fn composite_construct( + result_type_id: Word, + id: Word, + constituent_ids: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::CompositeConstruct); + instruction.set_type(result_type_id); + instruction.set_result(id); + + for constituent_id in constituent_ids { + instruction.add_operand(*constituent_id); + } + + instruction + } + + pub(super) fn composite_extract( + result_type_id: Word, + id: Word, + composite_id: Word, + indices: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::CompositeExtract); + instruction.set_type(result_type_id); + instruction.set_result(id); + + instruction.add_operand(composite_id); + for index in indices { + instruction.add_operand(*index); + } + + instruction + } + + pub(super) fn vector_extract_dynamic( + result_type_id: Word, + id: Word, + vector_id: Word, + index_id: Word, + ) -> Self { + let mut instruction = Self::new(Op::VectorExtractDynamic); + instruction.set_type(result_type_id); + instruction.set_result(id); + + instruction.add_operand(vector_id); + instruction.add_operand(index_id); + + instruction + } + + pub(super) fn vector_shuffle( + result_type_id: Word, + id: Word, + v1_id: Word, + v2_id: Word, + components: &[Word], + ) -> Self { + let mut instruction = Self::new(Op::VectorShuffle); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(v1_id); + instruction.add_operand(v2_id); + + for &component in components { + instruction.add_operand(component); + } + + instruction + } + + // + // Arithmetic Instructions + // + pub(super) fn binary( + op: Op, + result_type_id: Word, + id: Word, + operand_1: Word, + operand_2: Word, + ) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(operand_1); + instruction.add_operand(operand_2); + instruction + } + + pub(super) fn ternary( + op: Op, + result_type_id: Word, + id: Word, + operand_1: Word, + operand_2: Word, + operand_3: Word, + ) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(operand_1); + instruction.add_operand(operand_2); + instruction.add_operand(operand_3); + instruction + } + + pub(super) fn quaternary( + op: Op, + result_type_id: Word, + id: Word, + operand_1: Word, + operand_2: Word, + operand_3: Word, + operand_4: Word, + ) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(operand_1); + instruction.add_operand(operand_2); + instruction.add_operand(operand_3); + instruction.add_operand(operand_4); + instruction + } + + pub(super) fn relational(op: Op, result_type_id: Word, id: Word, expr_id: Word) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(expr_id); + instruction + } + + pub(super) fn atomic_binary( + op: Op, + result_type_id: Word, + id: Word, + pointer: Word, + scope_id: Word, + semantics_id: Word, + value: Word, + ) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(pointer); + instruction.add_operand(scope_id); + instruction.add_operand(semantics_id); + instruction.add_operand(value); + instruction + } + + // + // Bit Instructions + // + + // + // Relational and Logical Instructions + // + + // + // Derivative Instructions + // + + pub(super) fn derivative(op: Op, result_type_id: Word, id: Word, expr_id: Word) -> Self { + let mut instruction = Self::new(op); + instruction.set_type(result_type_id); + instruction.set_result(id); + instruction.add_operand(expr_id); + instruction + } + + // + // Control-Flow Instructions + // + + pub(super) fn phi( + result_type_id: Word, + result_id: Word, + var_parent_pairs: &[(Word, Word)], + ) -> Self { + let mut instruction = Self::new(Op::Phi); + instruction.add_operand(result_type_id); + instruction.add_operand(result_id); + for &(variable, parent) in var_parent_pairs { + instruction.add_operand(variable); + instruction.add_operand(parent); + } + instruction + } + + pub(super) fn selection_merge( + merge_id: Word, + selection_control: spirv::SelectionControl, + ) -> Self { + let mut instruction = Self::new(Op::SelectionMerge); + instruction.add_operand(merge_id); + instruction.add_operand(selection_control.bits()); + instruction + } + + pub(super) fn loop_merge( + merge_id: Word, + continuing_id: Word, + selection_control: spirv::SelectionControl, + ) -> Self { + let mut instruction = Self::new(Op::LoopMerge); + instruction.add_operand(merge_id); + instruction.add_operand(continuing_id); + instruction.add_operand(selection_control.bits()); + instruction + } + + pub(super) fn label(id: Word) -> Self { + let mut instruction = Self::new(Op::Label); + instruction.set_result(id); + instruction + } + + pub(super) fn branch(id: Word) -> Self { + let mut instruction = Self::new(Op::Branch); + instruction.add_operand(id); + instruction + } + + // TODO Branch Weights not implemented. + pub(super) fn branch_conditional( + condition_id: Word, + true_label: Word, + false_label: Word, + ) -> Self { + let mut instruction = Self::new(Op::BranchConditional); + instruction.add_operand(condition_id); + instruction.add_operand(true_label); + instruction.add_operand(false_label); + instruction + } + + pub(super) fn switch(selector_id: Word, default_id: Word, cases: &[Case]) -> Self { + let mut instruction = Self::new(Op::Switch); + instruction.add_operand(selector_id); + instruction.add_operand(default_id); + for case in cases { + instruction.add_operand(case.value); + instruction.add_operand(case.label_id); + } + instruction + } + + pub(super) fn select( + result_type_id: Word, + id: Word, + condition_id: Word, + accept_id: Word, + reject_id: Word, + ) -> Self { + let mut instruction = Self::new(Op::Select); + instruction.add_operand(result_type_id); + instruction.add_operand(id); + instruction.add_operand(condition_id); + instruction.add_operand(accept_id); + instruction.add_operand(reject_id); + instruction + } + + pub(super) const fn kill() -> Self { + Self::new(Op::Kill) + } + + pub(super) const fn return_void() -> Self { + Self::new(Op::Return) + } + + pub(super) fn return_value(value_id: Word) -> Self { + let mut instruction = Self::new(Op::ReturnValue); + instruction.add_operand(value_id); + instruction + } + + // + // Atomic Instructions + // + + // + // Primitive Instructions + // + + // Barriers + + pub(super) fn control_barrier( + exec_scope_id: Word, + mem_scope_id: Word, + semantics_id: Word, + ) -> Self { + let mut instruction = Self::new(Op::ControlBarrier); + instruction.add_operand(exec_scope_id); + instruction.add_operand(mem_scope_id); + instruction.add_operand(semantics_id); + instruction + } +} + +impl From for spirv::ImageFormat { + fn from(format: crate::StorageFormat) -> Self { + use crate::StorageFormat as Sf; + match format { + Sf::R8Unorm => Self::R8, + Sf::R8Snorm => Self::R8Snorm, + Sf::R8Uint => Self::R8ui, + Sf::R8Sint => Self::R8i, + Sf::R16Uint => Self::R16ui, + Sf::R16Sint => Self::R16i, + Sf::R16Float => Self::R16f, + Sf::Rg8Unorm => Self::Rg8, + Sf::Rg8Snorm => Self::Rg8Snorm, + Sf::Rg8Uint => Self::Rg8ui, + Sf::Rg8Sint => Self::Rg8i, + Sf::R32Uint => Self::R32ui, + Sf::R32Sint => Self::R32i, + Sf::R32Float => Self::R32f, + Sf::Rg16Uint => Self::Rg16ui, + Sf::Rg16Sint => Self::Rg16i, + Sf::Rg16Float => Self::Rg16f, + Sf::Rgba8Unorm => Self::Rgba8, + Sf::Rgba8Snorm => Self::Rgba8Snorm, + Sf::Rgba8Uint => Self::Rgba8ui, + Sf::Rgba8Sint => Self::Rgba8i, + Sf::Bgra8Unorm => Self::Unknown, + Sf::Rgb10a2Uint => Self::Rgb10a2ui, + Sf::Rgb10a2Unorm => Self::Rgb10A2, + Sf::Rg11b10Float => Self::R11fG11fB10f, + Sf::Rg32Uint => Self::Rg32ui, + Sf::Rg32Sint => Self::Rg32i, + Sf::Rg32Float => Self::Rg32f, + Sf::Rgba16Uint => Self::Rgba16ui, + Sf::Rgba16Sint => Self::Rgba16i, + Sf::Rgba16Float => Self::Rgba16f, + Sf::Rgba32Uint => Self::Rgba32ui, + Sf::Rgba32Sint => Self::Rgba32i, + Sf::Rgba32Float => Self::Rgba32f, + Sf::R16Unorm => Self::R16, + Sf::R16Snorm => Self::R16Snorm, + Sf::Rg16Unorm => Self::Rg16, + Sf::Rg16Snorm => Self::Rg16Snorm, + Sf::Rgba16Unorm => Self::Rgba16, + Sf::Rgba16Snorm => Self::Rgba16Snorm, + } + } +} + +impl From for spirv::Dim { + fn from(dim: crate::ImageDimension) -> Self { + use crate::ImageDimension as Id; + match dim { + Id::D1 => Self::Dim1D, + Id::D2 => Self::Dim2D, + Id::D3 => Self::Dim3D, + Id::Cube => Self::DimCube, + } + } +} diff --git a/naga/src/back/spv/layout.rs b/naga/src/back/spv/layout.rs new file mode 100644 index 0000000000..39117a3d2a --- /dev/null +++ b/naga/src/back/spv/layout.rs @@ -0,0 +1,210 @@ +use super::{Instruction, LogicalLayout, PhysicalLayout}; +use spirv::{Op, Word, MAGIC_NUMBER}; +use std::iter; + +// https://github.com/KhronosGroup/SPIRV-Headers/pull/195 +const GENERATOR: Word = 28; + +impl PhysicalLayout { + pub(super) const fn new(version: Word) -> Self { + PhysicalLayout { + magic_number: MAGIC_NUMBER, + version, + generator: GENERATOR, + bound: 0, + instruction_schema: 0x0u32, + } + } + + pub(super) fn in_words(&self, sink: &mut impl Extend) { + sink.extend(iter::once(self.magic_number)); + sink.extend(iter::once(self.version)); + sink.extend(iter::once(self.generator)); + sink.extend(iter::once(self.bound)); + sink.extend(iter::once(self.instruction_schema)); + } +} + +impl super::recyclable::Recyclable for PhysicalLayout { + fn recycle(self) -> Self { + PhysicalLayout { + magic_number: self.magic_number, + version: self.version, + generator: self.generator, + instruction_schema: self.instruction_schema, + bound: 0, + } + } +} + +impl LogicalLayout { + pub(super) fn in_words(&self, sink: &mut impl Extend) { + sink.extend(self.capabilities.iter().cloned()); + sink.extend(self.extensions.iter().cloned()); + sink.extend(self.ext_inst_imports.iter().cloned()); + sink.extend(self.memory_model.iter().cloned()); + sink.extend(self.entry_points.iter().cloned()); + sink.extend(self.execution_modes.iter().cloned()); + sink.extend(self.debugs.iter().cloned()); + sink.extend(self.annotations.iter().cloned()); + sink.extend(self.declarations.iter().cloned()); + sink.extend(self.function_declarations.iter().cloned()); + sink.extend(self.function_definitions.iter().cloned()); + } +} + +impl super::recyclable::Recyclable for LogicalLayout { + fn recycle(self) -> Self { + Self { + capabilities: self.capabilities.recycle(), + extensions: self.extensions.recycle(), + ext_inst_imports: self.ext_inst_imports.recycle(), + memory_model: self.memory_model.recycle(), + entry_points: self.entry_points.recycle(), + execution_modes: self.execution_modes.recycle(), + debugs: self.debugs.recycle(), + annotations: self.annotations.recycle(), + declarations: self.declarations.recycle(), + function_declarations: self.function_declarations.recycle(), + function_definitions: self.function_definitions.recycle(), + } + } +} + +impl Instruction { + pub(super) const fn new(op: Op) -> Self { + Instruction { + op, + wc: 1, // Always start at 1 for the first word (OP + WC), + type_id: None, + result_id: None, + operands: vec![], + } + } + + #[allow(clippy::panic)] + pub(super) fn set_type(&mut self, id: Word) { + assert!(self.type_id.is_none(), "Type can only be set once"); + self.type_id = Some(id); + self.wc += 1; + } + + #[allow(clippy::panic)] + pub(super) fn set_result(&mut self, id: Word) { + assert!(self.result_id.is_none(), "Result can only be set once"); + self.result_id = Some(id); + self.wc += 1; + } + + pub(super) fn add_operand(&mut self, operand: Word) { + self.operands.push(operand); + self.wc += 1; + } + + pub(super) fn add_operands(&mut self, operands: Vec) { + for operand in operands.into_iter() { + self.add_operand(operand) + } + } + + pub(super) fn to_words(&self, sink: &mut impl Extend) { + sink.extend(Some(self.wc << 16 | self.op as u32)); + sink.extend(self.type_id); + sink.extend(self.result_id); + sink.extend(self.operands.iter().cloned()); + } +} + +impl Instruction { + #[cfg(test)] + fn validate(&self, words: &[Word]) { + let mut inst_index = 0; + let (wc, op) = ((words[inst_index] >> 16) as u16, words[inst_index] as u16); + inst_index += 1; + + assert_eq!(wc, words.len() as u16); + assert_eq!(op, self.op as u16); + + if self.type_id.is_some() { + assert_eq!(words[inst_index], self.type_id.unwrap()); + inst_index += 1; + } + + if self.result_id.is_some() { + assert_eq!(words[inst_index], self.result_id.unwrap()); + inst_index += 1; + } + + for (op_index, i) in (inst_index..wc as usize).enumerate() { + assert_eq!(words[i], self.operands[op_index]); + } + } +} + +#[test] +fn test_physical_layout_in_words() { + let bound = 5; + let version = 0x10203; + + let mut output = vec![]; + let mut layout = PhysicalLayout::new(version); + layout.bound = bound; + + layout.in_words(&mut output); + + assert_eq!(&output, &[MAGIC_NUMBER, version, GENERATOR, bound, 0,]); +} + +#[test] +fn test_logical_layout_in_words() { + let mut output = vec![]; + let mut layout = LogicalLayout::default(); + let layout_vectors = 11; + let mut instructions = Vec::with_capacity(layout_vectors); + + let vector_names = &[ + "Capabilities", + "Extensions", + "External Instruction Imports", + "Memory Model", + "Entry Points", + "Execution Modes", + "Debugs", + "Annotations", + "Declarations", + "Function Declarations", + "Function Definitions", + ]; + + for (i, _) in vector_names.iter().enumerate().take(layout_vectors) { + let mut dummy_instruction = Instruction::new(Op::Constant); + dummy_instruction.set_type((i + 1) as u32); + dummy_instruction.set_result((i + 2) as u32); + dummy_instruction.add_operand((i + 3) as u32); + dummy_instruction.add_operands(super::helpers::string_to_words( + format!("This is the vector: {}", vector_names[i]).as_str(), + )); + instructions.push(dummy_instruction); + } + + instructions[0].to_words(&mut layout.capabilities); + instructions[1].to_words(&mut layout.extensions); + instructions[2].to_words(&mut layout.ext_inst_imports); + instructions[3].to_words(&mut layout.memory_model); + instructions[4].to_words(&mut layout.entry_points); + instructions[5].to_words(&mut layout.execution_modes); + instructions[6].to_words(&mut layout.debugs); + instructions[7].to_words(&mut layout.annotations); + instructions[8].to_words(&mut layout.declarations); + instructions[9].to_words(&mut layout.function_declarations); + instructions[10].to_words(&mut layout.function_definitions); + + layout.in_words(&mut output); + + let mut index: usize = 0; + for instruction in instructions { + let wc = instruction.wc as usize; + instruction.validate(&output[index..index + wc]); + index += wc; + } +} diff --git a/naga/src/back/spv/mod.rs b/naga/src/back/spv/mod.rs new file mode 100644 index 0000000000..b7d57be0d4 --- /dev/null +++ b/naga/src/back/spv/mod.rs @@ -0,0 +1,748 @@ +/*! +Backend for [SPIR-V][spv] (Standard Portable Intermediate Representation). + +[spv]: https://www.khronos.org/registry/SPIR-V/ +*/ + +mod block; +mod helpers; +mod image; +mod index; +mod instructions; +mod layout; +mod ray; +mod recyclable; +mod selection; +mod writer; + +pub use spirv::Capability; + +use crate::arena::Handle; +use crate::proc::{BoundsCheckPolicies, TypeResolution}; + +use spirv::Word; +use std::ops; +use thiserror::Error; + +#[derive(Clone)] +struct PhysicalLayout { + magic_number: Word, + version: Word, + generator: Word, + bound: Word, + instruction_schema: Word, +} + +#[derive(Default)] +struct LogicalLayout { + capabilities: Vec, + extensions: Vec, + ext_inst_imports: Vec, + memory_model: Vec, + entry_points: Vec, + execution_modes: Vec, + debugs: Vec, + annotations: Vec, + declarations: Vec, + function_declarations: Vec, + function_definitions: Vec, +} + +struct Instruction { + op: spirv::Op, + wc: u32, + type_id: Option, + result_id: Option, + operands: Vec, +} + +const BITS_PER_BYTE: crate::Bytes = 8; + +#[derive(Clone, Debug, Error)] +pub enum Error { + #[error("The requested entry point couldn't be found")] + EntryPointNotFound, + #[error("target SPIRV-{0}.{1} is not supported")] + UnsupportedVersion(u8, u8), + #[error("using {0} requires at least one of the capabilities {1:?}, but none are available")] + MissingCapabilities(&'static str, Vec), + #[error("unimplemented {0}")] + FeatureNotImplemented(&'static str), + #[error("module is not validated properly: {0}")] + Validation(&'static str), +} + +#[derive(Default)] +struct IdGenerator(Word); + +impl IdGenerator { + fn next(&mut self) -> Word { + self.0 += 1; + self.0 + } +} + +#[derive(Debug, Clone)] +pub struct DebugInfo<'a> { + pub source_code: &'a str, + pub file_name: &'a std::path::Path, +} + +/// A SPIR-V block to which we are still adding instructions. +/// +/// A `Block` represents a SPIR-V block that does not yet have a termination +/// instruction like `OpBranch` or `OpReturn`. +/// +/// The `OpLabel` that starts the block is implicit. It will be emitted based on +/// `label_id` when we write the block to a `LogicalLayout`. +/// +/// To terminate a `Block`, pass the block and the termination instruction to +/// `Function::consume`. This takes ownership of the `Block` and transforms it +/// into a `TerminatedBlock`. +struct Block { + label_id: Word, + body: Vec, +} + +/// A SPIR-V block that ends with a termination instruction. +struct TerminatedBlock { + label_id: Word, + body: Vec, +} + +impl Block { + const fn new(label_id: Word) -> Self { + Block { + label_id, + body: Vec::new(), + } + } +} + +struct LocalVariable { + id: Word, + instruction: Instruction, +} + +struct ResultMember { + id: Word, + type_id: Word, + built_in: Option, +} + +struct EntryPointContext { + argument_ids: Vec, + results: Vec, +} + +#[derive(Default)] +struct Function { + signature: Option, + parameters: Vec, + variables: crate::FastHashMap, LocalVariable>, + blocks: Vec, + entry_point_context: Option, +} + +impl Function { + fn consume(&mut self, mut block: Block, termination: Instruction) { + block.body.push(termination); + self.blocks.push(TerminatedBlock { + label_id: block.label_id, + body: block.body, + }) + } + + fn parameter_id(&self, index: u32) -> Word { + match self.entry_point_context { + Some(ref context) => context.argument_ids[index as usize], + None => self.parameters[index as usize] + .instruction + .result_id + .unwrap(), + } + } +} + +/// Characteristics of a SPIR-V `OpTypeImage` type. +/// +/// SPIR-V requires non-composite types to be unique, including images. Since we +/// use `LocalType` for this deduplication, it's essential that `LocalImageType` +/// be equal whenever the corresponding `OpTypeImage`s would be. To reduce the +/// likelihood of mistakes, we use fields that correspond exactly to the +/// operands of an `OpTypeImage` instruction, using the actual SPIR-V types +/// where practical. +#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] +struct LocalImageType { + sampled_type: crate::ScalarKind, + dim: spirv::Dim, + flags: ImageTypeFlags, + image_format: spirv::ImageFormat, +} + +bitflags::bitflags! { + /// Flags corresponding to the boolean(-ish) parameters to OpTypeImage. + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + pub struct ImageTypeFlags: u8 { + const DEPTH = 0x1; + const ARRAYED = 0x2; + const MULTISAMPLED = 0x4; + const SAMPLED = 0x8; + } +} + +impl LocalImageType { + /// Construct a `LocalImageType` from the fields of a `TypeInner::Image`. + fn from_inner(dim: crate::ImageDimension, arrayed: bool, class: crate::ImageClass) -> Self { + let make_flags = |multi: bool, other: ImageTypeFlags| -> ImageTypeFlags { + let mut flags = other; + flags.set(ImageTypeFlags::ARRAYED, arrayed); + flags.set(ImageTypeFlags::MULTISAMPLED, multi); + flags + }; + + let dim = spirv::Dim::from(dim); + + match class { + crate::ImageClass::Sampled { kind, multi } => LocalImageType { + sampled_type: kind, + dim, + flags: make_flags(multi, ImageTypeFlags::SAMPLED), + image_format: spirv::ImageFormat::Unknown, + }, + crate::ImageClass::Depth { multi } => LocalImageType { + sampled_type: crate::ScalarKind::Float, + dim, + flags: make_flags(multi, ImageTypeFlags::DEPTH | ImageTypeFlags::SAMPLED), + image_format: spirv::ImageFormat::Unknown, + }, + crate::ImageClass::Storage { format, access: _ } => LocalImageType { + sampled_type: crate::ScalarKind::from(format), + dim, + flags: make_flags(false, ImageTypeFlags::empty()), + image_format: format.into(), + }, + } + } +} + +/// A SPIR-V type constructed during code generation. +/// +/// This is the variant of [`LookupType`] used to represent types that might not +/// be available in the arena. Variants are present here for one of two reasons: +/// +/// - They represent types synthesized during code generation, as explained +/// in the documentation for [`LookupType`]. +/// +/// - They represent types for which SPIR-V forbids duplicate `OpType...` +/// instructions, requiring deduplication. +/// +/// This is not a complete copy of [`TypeInner`]: for example, SPIR-V generation +/// never synthesizes new struct types, so `LocalType` has nothing for that. +/// +/// Each `LocalType` variant should be handled identically to its analogous +/// `TypeInner` variant. You can use the [`make_local`] function to help with +/// this, by converting everything possible to a `LocalType` before inspecting +/// it. +/// +/// ## `Localtype` equality and SPIR-V `OpType` uniqueness +/// +/// The definition of `Eq` on `LocalType` is carefully chosen to help us follow +/// certain SPIR-V rules. SPIR-V §2.8 requires some classes of `OpType...` +/// instructions to be unique; for example, you can't have two `OpTypeInt 32 1` +/// instructions in the same module. All 32-bit signed integers must use the +/// same type id. +/// +/// All SPIR-V types that must be unique can be represented as a `LocalType`, +/// and two `LocalType`s are always `Eq` if SPIR-V would require them to use the +/// same `OpType...` instruction. This lets us avoid duplicates by recording the +/// ids of the type instructions we've already generated in a hash table, +/// [`Writer::lookup_type`], keyed by `LocalType`. +/// +/// As another example, [`LocalImageType`], stored in the `LocalType::Image` +/// variant, is designed to help us deduplicate `OpTypeImage` instructions. See +/// its documentation for details. +/// +/// `LocalType` also includes variants like `Pointer` that do not need to be +/// unique - but it is harmless to avoid the duplication. +/// +/// As it always must, the `Hash` implementation respects the `Eq` relation. +/// +/// [`TypeInner`]: crate::TypeInner +#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] +enum LocalType { + /// A scalar, vector, or pointer to one of those. + Value { + /// If `None`, this represents a scalar type. If `Some`, this represents + /// a vector type of the given size. + vector_size: Option, + scalar: crate::Scalar, + pointer_space: Option, + }, + /// A matrix of floating-point values. + Matrix { + columns: crate::VectorSize, + rows: crate::VectorSize, + width: crate::Bytes, + }, + Pointer { + base: Handle, + class: spirv::StorageClass, + }, + Image(LocalImageType), + SampledImage { + image_type_id: Word, + }, + Sampler, + /// Equivalent to a [`LocalType::Pointer`] whose `base` is a Naga IR [`BindingArray`]. SPIR-V + /// permits duplicated `OpTypePointer` ids, so it's fine to have two different [`LocalType`] + /// representations for pointer types. + /// + /// [`BindingArray`]: crate::TypeInner::BindingArray + PointerToBindingArray { + base: Handle, + size: u32, + space: crate::AddressSpace, + }, + BindingArray { + base: Handle, + size: u32, + }, + AccelerationStructure, + RayQuery, +} + +/// A type encountered during SPIR-V generation. +/// +/// In the process of writing SPIR-V, we need to synthesize various types for +/// intermediate results and such: pointer types, vector/matrix component types, +/// or even booleans, which usually appear in SPIR-V code even when they're not +/// used by the module source. +/// +/// However, we can't use `crate::Type` or `crate::TypeInner` for these, as the +/// type arena may not contain what we need (it only contains types used +/// directly by other parts of the IR), and the IR module is immutable, so we +/// can't add anything to it. +/// +/// So for local use in the SPIR-V writer, we use this type, which holds either +/// a handle into the arena, or a [`LocalType`] containing something synthesized +/// locally. +/// +/// This is very similar to the [`proc::TypeResolution`] enum, with `LocalType` +/// playing the role of `TypeInner`. However, `LocalType` also has other +/// properties needed for SPIR-V generation; see the description of +/// [`LocalType`] for details. +/// +/// [`proc::TypeResolution`]: crate::proc::TypeResolution +#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)] +enum LookupType { + Handle(Handle), + Local(LocalType), +} + +impl From for LookupType { + fn from(local: LocalType) -> Self { + Self::Local(local) + } +} + +#[derive(Debug, PartialEq, Clone, Hash, Eq)] +struct LookupFunctionType { + parameter_type_ids: Vec, + return_type_id: Word, +} + +fn make_local(inner: &crate::TypeInner) -> Option { + Some(match *inner { + crate::TypeInner::Scalar(scalar) | crate::TypeInner::Atomic(scalar) => LocalType::Value { + vector_size: None, + scalar, + pointer_space: None, + }, + crate::TypeInner::Vector { size, scalar } => LocalType::Value { + vector_size: Some(size), + scalar, + pointer_space: None, + }, + crate::TypeInner::Matrix { + columns, + rows, + scalar, + } => LocalType::Matrix { + columns, + rows, + width: scalar.width, + }, + crate::TypeInner::Pointer { base, space } => LocalType::Pointer { + base, + class: helpers::map_storage_class(space), + }, + crate::TypeInner::ValuePointer { + size, + scalar, + space, + } => LocalType::Value { + vector_size: size, + scalar, + pointer_space: Some(helpers::map_storage_class(space)), + }, + crate::TypeInner::Image { + dim, + arrayed, + class, + } => LocalType::Image(LocalImageType::from_inner(dim, arrayed, class)), + crate::TypeInner::Sampler { comparison: _ } => LocalType::Sampler, + crate::TypeInner::AccelerationStructure => LocalType::AccelerationStructure, + crate::TypeInner::RayQuery => LocalType::RayQuery, + crate::TypeInner::Array { .. } + | crate::TypeInner::Struct { .. } + | crate::TypeInner::BindingArray { .. } => return None, + }) +} + +#[derive(Debug)] +enum Dimension { + Scalar, + Vector, + Matrix, +} + +/// A map from evaluated [`Expression`](crate::Expression)s to their SPIR-V ids. +/// +/// When we emit code to evaluate a given `Expression`, we record the +/// SPIR-V id of its value here, under its `Handle` index. +/// +/// A `CachedExpressions` value can be indexed by a `Handle` value. +/// +/// [emit]: index.html#expression-evaluation-time-and-scope +#[derive(Default)] +struct CachedExpressions { + ids: Vec, +} +impl CachedExpressions { + fn reset(&mut self, length: usize) { + self.ids.clear(); + self.ids.resize(length, 0); + } +} +impl ops::Index> for CachedExpressions { + type Output = Word; + fn index(&self, h: Handle) -> &Word { + let id = &self.ids[h.index()]; + if *id == 0 { + unreachable!("Expression {:?} is not cached!", h); + } + id + } +} +impl ops::IndexMut> for CachedExpressions { + fn index_mut(&mut self, h: Handle) -> &mut Word { + let id = &mut self.ids[h.index()]; + if *id != 0 { + unreachable!("Expression {:?} is already cached!", h); + } + id + } +} +impl recyclable::Recyclable for CachedExpressions { + fn recycle(self) -> Self { + CachedExpressions { + ids: self.ids.recycle(), + } + } +} + +#[derive(Eq, Hash, PartialEq)] +enum CachedConstant { + Literal(crate::Literal), + Composite { + ty: LookupType, + constituent_ids: Vec, + }, + ZeroValue(Word), +} + +#[derive(Clone)] +struct GlobalVariable { + /// ID of the OpVariable that declares the global. + /// + /// If you need the variable's value, use [`access_id`] instead of this + /// field. If we wrapped the Naga IR `GlobalVariable`'s type in a struct to + /// comply with Vulkan's requirements, then this points to the `OpVariable` + /// with the synthesized struct type, whereas `access_id` points to the + /// field of said struct that holds the variable's actual value. + /// + /// This is used to compute the `access_id` pointer in function prologues, + /// and used for `ArrayLength` expressions, which do need the struct. + /// + /// [`access_id`]: GlobalVariable::access_id + var_id: Word, + + /// For `AddressSpace::Handle` variables, this ID is recorded in the function + /// prelude block (and reset before every function) as `OpLoad` of the variable. + /// It is then used for all the global ops, such as `OpImageSample`. + handle_id: Word, + + /// Actual ID used to access this variable. + /// For wrapped buffer variables, this ID is `OpAccessChain` into the + /// wrapper. Otherwise, the same as `var_id`. + /// + /// Vulkan requires that globals in the `StorageBuffer` and `Uniform` storage + /// classes must be structs with the `Block` decoration, but WGSL and Naga IR + /// make no such requirement. So for such variables, we generate a wrapper struct + /// type with a single element of the type given by Naga, generate an + /// `OpAccessChain` for that member in the function prelude, and use that pointer + /// to refer to the global in the function body. This is the id of that access, + /// updated for each function in `write_function`. + access_id: Word, +} + +impl GlobalVariable { + const fn dummy() -> Self { + Self { + var_id: 0, + handle_id: 0, + access_id: 0, + } + } + + const fn new(id: Word) -> Self { + Self { + var_id: id, + handle_id: 0, + access_id: 0, + } + } + + /// Prepare `self` for use within a single function. + fn reset_for_function(&mut self) { + self.handle_id = 0; + self.access_id = 0; + } +} + +struct FunctionArgument { + /// Actual instruction of the argument. + instruction: Instruction, + handle_id: Word, +} + +/// General information needed to emit SPIR-V for Naga statements. +struct BlockContext<'w> { + /// The writer handling the module to which this code belongs. + writer: &'w mut Writer, + + /// The [`Module`](crate::Module) for which we're generating code. + ir_module: &'w crate::Module, + + /// The [`Function`](crate::Function) for which we're generating code. + ir_function: &'w crate::Function, + + /// Information module validation produced about + /// [`ir_function`](BlockContext::ir_function). + fun_info: &'w crate::valid::FunctionInfo, + + /// The [`spv::Function`](Function) to which we are contributing SPIR-V instructions. + function: &'w mut Function, + + /// SPIR-V ids for expressions we've evaluated. + cached: CachedExpressions, + + /// The `Writer`'s temporary vector, for convenience. + temp_list: Vec, + + /// Tracks the constness of `Expression`s residing in `self.ir_function.expressions` + expression_constness: crate::proc::ExpressionConstnessTracker, +} + +impl BlockContext<'_> { + fn gen_id(&mut self) -> Word { + self.writer.id_gen.next() + } + + fn get_type_id(&mut self, lookup_type: LookupType) -> Word { + self.writer.get_type_id(lookup_type) + } + + fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word { + self.writer.get_expression_type_id(tr) + } + + fn get_index_constant(&mut self, index: Word) -> Word { + self.writer.get_constant_scalar(crate::Literal::U32(index)) + } + + fn get_scope_constant(&mut self, scope: Word) -> Word { + self.writer + .get_constant_scalar(crate::Literal::I32(scope as _)) + } +} + +#[derive(Clone, Copy, Default)] +struct LoopContext { + continuing_id: Option, + break_id: Option, +} + +pub struct Writer { + physical_layout: PhysicalLayout, + logical_layout: LogicalLayout, + id_gen: IdGenerator, + + /// The set of capabilities modules are permitted to use. + /// + /// This is initialized from `Options::capabilities`. + capabilities_available: Option>, + + /// The set of capabilities used by this module. + /// + /// If `capabilities_available` is `Some`, then this is always a subset of + /// that. + capabilities_used: crate::FastIndexSet, + + /// The set of spirv extensions used. + extensions_used: crate::FastIndexSet<&'static str>, + + debugs: Vec, + annotations: Vec, + flags: WriterFlags, + bounds_check_policies: BoundsCheckPolicies, + zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode, + void_type: Word, + //TODO: convert most of these into vectors, addressable by handle indices + lookup_type: crate::FastHashMap, + lookup_function: crate::FastHashMap, Word>, + lookup_function_type: crate::FastHashMap, + /// Indexed by const-expression handle indexes + constant_ids: Vec, + cached_constants: crate::FastHashMap, + global_variables: Vec, + binding_map: BindingMap, + + // Cached expressions are only meaningful within a BlockContext, but we + // retain the table here between functions to save heap allocations. + saved_cached: CachedExpressions, + + gl450_ext_inst_id: Word, + + // Just a temporary list of SPIR-V ids + temp_list: Vec, +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct WriterFlags: u32 { + /// Include debug labels for everything. + const DEBUG = 0x1; + /// Flip Y coordinate of `BuiltIn::Position` output. + const ADJUST_COORDINATE_SPACE = 0x2; + /// Emit `OpName` for input/output locations. + /// Contrary to spec, some drivers treat it as semantic, not allowing + /// any conflicts. + const LABEL_VARYINGS = 0x4; + /// Emit `PointSize` output builtin to vertex shaders, which is + /// required for drawing with `PointList` topology. + const FORCE_POINT_SIZE = 0x8; + /// Clamp `BuiltIn::FragDepth` output between 0 and 1. + const CLAMP_FRAG_DEPTH = 0x10; + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct BindingInfo { + /// If the binding is an unsized binding array, this overrides the size. + pub binding_array_size: Option, +} + +// Using `BTreeMap` instead of `HashMap` so that we can hash itself. +pub type BindingMap = std::collections::BTreeMap; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ZeroInitializeWorkgroupMemoryMode { + /// Via `VK_KHR_zero_initialize_workgroup_memory` or Vulkan 1.3 + Native, + /// Via assignments + barrier + Polyfill, + None, +} + +#[derive(Debug, Clone)] +pub struct Options<'a> { + /// (Major, Minor) target version of the SPIR-V. + pub lang_version: (u8, u8), + + /// Configuration flags for the writer. + pub flags: WriterFlags, + + /// Map of resources to information about the binding. + pub binding_map: BindingMap, + + /// If given, the set of capabilities modules are allowed to use. Code that + /// requires capabilities beyond these is rejected with an error. + /// + /// If this is `None`, all capabilities are permitted. + pub capabilities: Option>, + + /// How should generate code handle array, vector, matrix, or image texel + /// indices that are out of range? + pub bounds_check_policies: BoundsCheckPolicies, + + /// Dictates the way workgroup variables should be zero initialized + pub zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode, + + pub debug_info: Option>, +} + +impl<'a> Default for Options<'a> { + fn default() -> Self { + let mut flags = WriterFlags::ADJUST_COORDINATE_SPACE + | WriterFlags::LABEL_VARYINGS + | WriterFlags::CLAMP_FRAG_DEPTH; + if cfg!(debug_assertions) { + flags |= WriterFlags::DEBUG; + } + Options { + lang_version: (1, 0), + flags, + binding_map: BindingMap::default(), + capabilities: None, + bounds_check_policies: crate::proc::BoundsCheckPolicies::default(), + zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode::Polyfill, + debug_info: None, + } + } +} + +// A subset of options meant to be changed per pipeline. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct PipelineOptions { + /// The stage of the entry point. + pub shader_stage: crate::ShaderStage, + /// The name of the entry point. + /// + /// If no entry point that matches is found while creating a [`Writer`], a error will be thrown. + pub entry_point: String, +} + +pub fn write_vec( + module: &crate::Module, + info: &crate::valid::ModuleInfo, + options: &Options, + pipeline_options: Option<&PipelineOptions>, +) -> Result, Error> { + let mut words: Vec = Vec::new(); + let mut w = Writer::new(options)?; + + w.write( + module, + info, + pipeline_options, + &options.debug_info, + &mut words, + )?; + Ok(words) +} diff --git a/naga/src/back/spv/ray.rs b/naga/src/back/spv/ray.rs new file mode 100644 index 0000000000..bc2c4ce3c6 --- /dev/null +++ b/naga/src/back/spv/ray.rs @@ -0,0 +1,261 @@ +/*! +Generating SPIR-V for ray query operations. +*/ + +use super::{Block, BlockContext, Instruction, LocalType, LookupType}; +use crate::arena::Handle; + +impl<'w> BlockContext<'w> { + pub(super) fn write_ray_query_function( + &mut self, + query: Handle, + function: &crate::RayQueryFunction, + block: &mut Block, + ) { + let query_id = self.cached[query]; + match *function { + crate::RayQueryFunction::Initialize { + acceleration_structure, + descriptor, + } => { + //Note: composite extract indices and types must match `generate_ray_desc_type` + let desc_id = self.cached[descriptor]; + let acc_struct_id = self.get_handle_id(acceleration_structure); + + let flag_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::U32, + pointer_space: None, + })); + let ray_flags_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + flag_type_id, + ray_flags_id, + desc_id, + &[0], + )); + let cull_mask_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + flag_type_id, + cull_mask_id, + desc_id, + &[1], + )); + + let scalar_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::F32, + pointer_space: None, + })); + let tmin_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + scalar_type_id, + tmin_id, + desc_id, + &[2], + )); + let tmax_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + scalar_type_id, + tmax_id, + desc_id, + &[3], + )); + + let vector_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(crate::VectorSize::Tri), + scalar: crate::Scalar::F32, + pointer_space: None, + })); + let ray_origin_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + vector_type_id, + ray_origin_id, + desc_id, + &[4], + )); + let ray_dir_id = self.gen_id(); + block.body.push(Instruction::composite_extract( + vector_type_id, + ray_dir_id, + desc_id, + &[5], + )); + + block.body.push(Instruction::ray_query_initialize( + query_id, + acc_struct_id, + ray_flags_id, + cull_mask_id, + ray_origin_id, + tmin_id, + ray_dir_id, + tmax_id, + )); + } + crate::RayQueryFunction::Proceed { result } => { + let id = self.gen_id(); + self.cached[result] = id; + let result_type_id = self.get_expression_type_id(&self.fun_info[result].ty); + + block + .body + .push(Instruction::ray_query_proceed(result_type_id, id, query_id)); + } + crate::RayQueryFunction::Terminate => {} + } + } + + pub(super) fn write_ray_query_get_intersection( + &mut self, + query: Handle, + block: &mut Block, + ) -> spirv::Word { + let query_id = self.cached[query]; + let intersection_id = self.writer.get_constant_scalar(crate::Literal::U32( + spirv::RayQueryIntersection::RayQueryCommittedIntersectionKHR as _, + )); + + let flag_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::U32, + pointer_space: None, + })); + let kind_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionTypeKHR, + flag_type_id, + kind_id, + query_id, + intersection_id, + )); + let instance_custom_index_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionInstanceCustomIndexKHR, + flag_type_id, + instance_custom_index_id, + query_id, + intersection_id, + )); + let instance_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionInstanceIdKHR, + flag_type_id, + instance_id, + query_id, + intersection_id, + )); + let sbt_record_offset_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR, + flag_type_id, + sbt_record_offset_id, + query_id, + intersection_id, + )); + let geometry_index_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionGeometryIndexKHR, + flag_type_id, + geometry_index_id, + query_id, + intersection_id, + )); + let primitive_index_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionPrimitiveIndexKHR, + flag_type_id, + primitive_index_id, + query_id, + intersection_id, + )); + + let scalar_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::F32, + pointer_space: None, + })); + let t_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionTKHR, + scalar_type_id, + t_id, + query_id, + intersection_id, + )); + + let barycentrics_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(crate::VectorSize::Bi), + scalar: crate::Scalar::F32, + pointer_space: None, + })); + let barycentrics_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionBarycentricsKHR, + barycentrics_type_id, + barycentrics_id, + query_id, + intersection_id, + )); + + let bool_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::BOOL, + pointer_space: None, + })); + let front_face_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionFrontFaceKHR, + bool_type_id, + front_face_id, + query_id, + intersection_id, + )); + + let transform_type_id = self.get_type_id(LookupType::Local(LocalType::Matrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Tri, + width: 4, + })); + let object_to_world_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionObjectToWorldKHR, + transform_type_id, + object_to_world_id, + query_id, + intersection_id, + )); + let world_to_object_id = self.gen_id(); + block.body.push(Instruction::ray_query_get_intersection( + spirv::Op::RayQueryGetIntersectionWorldToObjectKHR, + transform_type_id, + world_to_object_id, + query_id, + intersection_id, + )); + + let id = self.gen_id(); + let intersection_type_id = self.get_type_id(LookupType::Handle( + self.ir_module.special_types.ray_intersection.unwrap(), + )); + //Note: the arguments must match `generate_ray_intersection_type` layout + block.body.push(Instruction::composite_construct( + intersection_type_id, + id, + &[ + kind_id, + t_id, + instance_custom_index_id, + instance_id, + sbt_record_offset_id, + geometry_index_id, + primitive_index_id, + barycentrics_id, + front_face_id, + object_to_world_id, + world_to_object_id, + ], + )); + id + } +} diff --git a/naga/src/back/spv/recyclable.rs b/naga/src/back/spv/recyclable.rs new file mode 100644 index 0000000000..cd1466e3c7 --- /dev/null +++ b/naga/src/back/spv/recyclable.rs @@ -0,0 +1,67 @@ +/*! +Reusing collections' previous allocations. +*/ + +/// A value that can be reset to its initial state, retaining its current allocations. +/// +/// Naga attempts to lower the cost of SPIR-V generation by allowing clients to +/// reuse the same `Writer` for multiple Module translations. Reusing a `Writer` +/// means that the `Vec`s, `HashMap`s, and other heap-allocated structures the +/// `Writer` uses internally begin the translation with heap-allocated buffers +/// ready to use. +/// +/// But this approach introduces the risk of `Writer` state leaking from one +/// module to the next. When a developer adds fields to `Writer` or its internal +/// types, they must remember to reset their contents between modules. +/// +/// One trick to ensure that every field has been accounted for is to use Rust's +/// struct literal syntax to construct a new, reset value. If a developer adds a +/// field, but neglects to update the reset code, the compiler will complain +/// that a field is missing from the literal. This trait's `recycle` method +/// takes `self` by value, and returns `Self` by value, encouraging the use of +/// struct literal expressions in its implementation. +pub trait Recyclable { + /// Clear `self`, retaining its current memory allocations. + /// + /// Shrink the buffer if it's currently much larger than was actually used. + /// This prevents a module with exceptionally large allocations from causing + /// the `Writer` to retain more memory than it needs indefinitely. + fn recycle(self) -> Self; +} + +// Stock values for various collections. + +impl Recyclable for Vec { + fn recycle(mut self) -> Self { + self.clear(); + self + } +} + +impl Recyclable for std::collections::HashMap { + fn recycle(mut self) -> Self { + self.clear(); + self + } +} + +impl Recyclable for std::collections::HashSet { + fn recycle(mut self) -> Self { + self.clear(); + self + } +} + +impl Recyclable for indexmap::IndexSet { + fn recycle(mut self) -> Self { + self.clear(); + self + } +} + +impl Recyclable for std::collections::BTreeMap { + fn recycle(mut self) -> Self { + self.clear(); + self + } +} diff --git a/naga/src/back/spv/selection.rs b/naga/src/back/spv/selection.rs new file mode 100644 index 0000000000..788b1f10ab --- /dev/null +++ b/naga/src/back/spv/selection.rs @@ -0,0 +1,257 @@ +/*! +Generate SPIR-V conditional structures. + +Builders for `if` structures with `and`s. + +The types in this module track the information needed to emit SPIR-V code +for complex conditional structures, like those whose conditions involve +short-circuiting 'and' and 'or' structures. These track labels and can emit +`OpPhi` instructions to merge values produced along different paths. + +This currently only supports exactly the forms Naga uses, so it doesn't +support `or` or `else`, and only supports zero or one merged values. + +Naga needs to emit code roughly like this: + +```ignore + + value = DEFAULT; + if COND1 && COND2 { + value = THEN_VALUE; + } + // use value + +``` + +Assuming `ctx` and `block` are a mutable references to a [`BlockContext`] +and the current [`Block`], and `merge_type` is the SPIR-V type for the +merged value `value`, we can build SPIR-V for the code above like so: + +```ignore + + let cond = Selection::start(block, merge_type); + // ... compute `cond1` ... + cond.if_true(ctx, cond1, DEFAULT); + // ... compute `cond2` ... + cond.if_true(ctx, cond2, DEFAULT); + // ... compute THEN_VALUE + let merged_value = cond.finish(ctx, THEN_VALUE); + +``` + +After this, `merged_value` is either `DEFAULT` or `THEN_VALUE`, depending on +the path by which the merged block was reached. + +This takes care of writing all branch instructions, including an +`OpSelectionMerge` annotation in the header block; starting new blocks and +assigning them labels; and emitting the `OpPhi` that gathers together the +right sources for the merged values, for every path through the selection +construct. + +When there is no merged value to produce, you can pass `()` for `merge_type` +and the merge values. In this case no `OpPhi` instructions are produced, and +the `finish` method returns `()`. + +To enforce proper nesting, a `Selection` takes ownership of the `&mut Block` +pointer for the duration of its lifetime. To obtain the block for generating +code in the selection's body, call the `Selection::block` method. +*/ + +use super::{Block, BlockContext, Instruction}; +use spirv::Word; + +/// A private struct recording what we know about the selection construct so far. +pub(super) struct Selection<'b, M: MergeTuple> { + /// The block pointer we're emitting code into. + block: &'b mut Block, + + /// The label of the selection construct's merge block, or `None` if we + /// haven't yet written the `OpSelectionMerge` merge instruction. + merge_label: Option, + + /// A set of `(VALUES, PARENT)` pairs, used to build `OpPhi` instructions in + /// the merge block. Each `PARENT` is the label of a predecessor block of + /// the merge block. The corresponding `VALUES` holds the ids of the values + /// that `PARENT` contributes to the merged values. + /// + /// We emit all branches to the merge block, so we know all its + /// predecessors. And we refuse to emit a branch unless we're given the + /// values the branching block contributes to the merge, so we always have + /// everything we need to emit the correct phis, by construction. + values: Vec<(M, Word)>, + + /// The types of the values in each element of `values`. + merge_types: M, +} + +impl<'b, M: MergeTuple> Selection<'b, M> { + /// Start a new selection construct. + /// + /// The `block` argument indicates the selection's header block. + /// + /// The `merge_types` argument should be a `Word` or tuple of `Word`s, each + /// value being the SPIR-V result type id of an `OpPhi` instruction that + /// will be written to the selection's merge block when this selection's + /// [`finish`] method is called. This argument may also be `()`, for + /// selections that produce no values. + /// + /// (This function writes no code to `block` itself; it simply constructs a + /// fresh `Selection`.) + /// + /// [`finish`]: Selection::finish + pub(super) fn start(block: &'b mut Block, merge_types: M) -> Self { + Selection { + block, + merge_label: None, + values: vec![], + merge_types, + } + } + + pub(super) fn block(&mut self) -> &mut Block { + self.block + } + + /// Branch to a successor block if `cond` is true, otherwise merge. + /// + /// If `cond` is false, branch to the merge block, using `values` as the + /// merged values. Otherwise, proceed to a new block. + /// + /// The `values` argument must be the same shape as the `merge_types` + /// argument passed to `Selection::start`. + pub(super) fn if_true(&mut self, ctx: &mut BlockContext, cond: Word, values: M) { + self.values.push((values, self.block.label_id)); + + let merge_label = self.make_merge_label(ctx); + let next_label = ctx.gen_id(); + ctx.function.consume( + std::mem::replace(self.block, Block::new(next_label)), + Instruction::branch_conditional(cond, next_label, merge_label), + ); + } + + /// Emit an unconditional branch to the merge block, and compute merged + /// values. + /// + /// Use `final_values` as the merged values contributed by the current + /// block, and transition to the merge block, emitting `OpPhi` instructions + /// to produce the merged values. This must be the same shape as the + /// `merge_types` argument passed to [`Selection::start`]. + /// + /// Return the SPIR-V ids of the merged values. This value has the same + /// shape as the `merge_types` argument passed to `Selection::start`. + pub(super) fn finish(self, ctx: &mut BlockContext, final_values: M) -> M { + match self { + Selection { + merge_label: None, .. + } => { + // We didn't actually emit any branches, so `self.values` must + // be empty, and `final_values` are the only sources we have for + // the merged values. Easy peasy. + final_values + } + + Selection { + block, + merge_label: Some(merge_label), + mut values, + merge_types, + } => { + // Emit the final branch and transition to the merge block. + values.push((final_values, block.label_id)); + ctx.function.consume( + std::mem::replace(block, Block::new(merge_label)), + Instruction::branch(merge_label), + ); + + // Now that we're in the merge block, build the phi instructions. + merge_types.write_phis(ctx, block, &values) + } + } + } + + /// Return the id of the merge block, writing a merge instruction if needed. + fn make_merge_label(&mut self, ctx: &mut BlockContext) -> Word { + match self.merge_label { + None => { + let merge_label = ctx.gen_id(); + self.block.body.push(Instruction::selection_merge( + merge_label, + spirv::SelectionControl::NONE, + )); + self.merge_label = Some(merge_label); + merge_label + } + Some(merge_label) => merge_label, + } + } +} + +/// A trait to help `Selection` manage any number of merged values. +/// +/// Some selection constructs, like a `ReadZeroSkipWrite` bounds check on a +/// [`Load`] expression, produce a single merged value. Others produce no merged +/// value, like a bounds check on a [`Store`] statement. +/// +/// To let `Selection` work nicely with both cases, we let the merge type +/// argument passed to [`Selection::start`] be any type that implements this +/// `MergeTuple` trait. `MergeTuple` is then implemented for `()`, `Word`, +/// `(Word, Word)`, and so on. +/// +/// A `MergeTuple` type can represent either a bunch of SPIR-V types or values; +/// the `merge_types` argument to `Selection::start` are type ids, whereas the +/// `values` arguments to the [`if_true`] and [`finish`] methods are value ids. +/// The set of merged value returned by `finish` is a tuple of value ids. +/// +/// In fact, since Naga only uses zero- and single-valued selection constructs +/// at present, we only implement `MergeTuple` for `()` and `Word`. But if you +/// add more cases, feel free to add more implementations. Once const generics +/// are available, we could have a single implementation of `MergeTuple` for all +/// lengths of arrays, and be done with it. +/// +/// [`Load`]: crate::Expression::Load +/// [`Store`]: crate::Statement::Store +/// [`if_true`]: Selection::if_true +/// [`finish`]: Selection::finish +pub(super) trait MergeTuple: Sized { + /// Write OpPhi instructions for the given set of predecessors. + /// + /// The `predecessors` vector should be a vector of `(LABEL, VALUES)` pairs, + /// where each `VALUES` holds the values contributed by the branch from + /// `LABEL`, which should be one of the current block's predecessors. + fn write_phis( + self, + ctx: &mut BlockContext, + block: &mut Block, + predecessors: &[(Self, Word)], + ) -> Self; +} + +/// Selections that produce a single merged value. +/// +/// For example, `ImageLoad` with `BoundsCheckPolicy::ReadZeroSkipWrite` either +/// returns a texel value or zeros. +impl MergeTuple for Word { + fn write_phis( + self, + ctx: &mut BlockContext, + block: &mut Block, + predecessors: &[(Word, Word)], + ) -> Word { + let merged_value = ctx.gen_id(); + block + .body + .push(Instruction::phi(self, merged_value, predecessors)); + merged_value + } +} + +/// Selections that produce no merged values. +/// +/// For example, `ImageStore` under `BoundsCheckPolicy::ReadZeroSkipWrite` +/// either does the store or skips it, but in neither case does it produce a +/// value. +impl MergeTuple for () { + /// No phis need to be generated. + fn write_phis(self, _: &mut BlockContext, _: &mut Block, _: &[((), Word)]) {} +} diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs new file mode 100644 index 0000000000..4db86c93a7 --- /dev/null +++ b/naga/src/back/spv/writer.rs @@ -0,0 +1,2063 @@ +use super::{ + block::DebugInfoInner, + helpers::{contains_builtin, global_needs_wrapper, map_storage_class}, + make_local, Block, BlockContext, CachedConstant, CachedExpressions, DebugInfo, + EntryPointContext, Error, Function, FunctionArgument, GlobalVariable, IdGenerator, Instruction, + LocalType, LocalVariable, LogicalLayout, LookupFunctionType, LookupType, LoopContext, Options, + PhysicalLayout, PipelineOptions, ResultMember, Writer, WriterFlags, BITS_PER_BYTE, +}; +use crate::{ + arena::{Handle, UniqueArena}, + back::spv::BindingInfo, + proc::{Alignment, TypeResolution}, + valid::{FunctionInfo, ModuleInfo}, +}; +use spirv::Word; +use std::collections::hash_map::Entry; + +struct FunctionInterface<'a> { + varying_ids: &'a mut Vec, + stage: crate::ShaderStage, +} + +impl Function { + fn to_words(&self, sink: &mut impl Extend) { + self.signature.as_ref().unwrap().to_words(sink); + for argument in self.parameters.iter() { + argument.instruction.to_words(sink); + } + for (index, block) in self.blocks.iter().enumerate() { + Instruction::label(block.label_id).to_words(sink); + if index == 0 { + for local_var in self.variables.values() { + local_var.instruction.to_words(sink); + } + } + for instruction in block.body.iter() { + instruction.to_words(sink); + } + } + } +} + +impl Writer { + pub fn new(options: &Options) -> Result { + let (major, minor) = options.lang_version; + if major != 1 { + return Err(Error::UnsupportedVersion(major, minor)); + } + let raw_version = ((major as u32) << 16) | ((minor as u32) << 8); + + let mut capabilities_used = crate::FastIndexSet::default(); + capabilities_used.insert(spirv::Capability::Shader); + + let mut id_gen = IdGenerator::default(); + let gl450_ext_inst_id = id_gen.next(); + let void_type = id_gen.next(); + + Ok(Writer { + physical_layout: PhysicalLayout::new(raw_version), + logical_layout: LogicalLayout::default(), + id_gen, + capabilities_available: options.capabilities.clone(), + capabilities_used, + extensions_used: crate::FastIndexSet::default(), + debugs: vec![], + annotations: vec![], + flags: options.flags, + bounds_check_policies: options.bounds_check_policies, + zero_initialize_workgroup_memory: options.zero_initialize_workgroup_memory, + void_type, + lookup_type: crate::FastHashMap::default(), + lookup_function: crate::FastHashMap::default(), + lookup_function_type: crate::FastHashMap::default(), + constant_ids: Vec::new(), + cached_constants: crate::FastHashMap::default(), + global_variables: Vec::new(), + binding_map: options.binding_map.clone(), + saved_cached: CachedExpressions::default(), + gl450_ext_inst_id, + temp_list: Vec::new(), + }) + } + + /// Reset `Writer` to its initial state, retaining any allocations. + /// + /// Why not just implement `Recyclable` for `Writer`? By design, + /// `Recyclable::recycle` requires ownership of the value, not just + /// `&mut`; see the trait documentation. But we need to use this method + /// from functions like `Writer::write`, which only have `&mut Writer`. + /// Workarounds include unsafe code (`std::ptr::read`, then `write`, ugh) + /// or something like a `Default` impl that returns an oddly-initialized + /// `Writer`, which is worse. + fn reset(&mut self) { + use super::recyclable::Recyclable; + use std::mem::take; + + let mut id_gen = IdGenerator::default(); + let gl450_ext_inst_id = id_gen.next(); + let void_type = id_gen.next(); + + // Every field of the old writer that is not determined by the `Options` + // passed to `Writer::new` should be reset somehow. + let fresh = Writer { + // Copied from the old Writer: + flags: self.flags, + bounds_check_policies: self.bounds_check_policies, + zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory, + capabilities_available: take(&mut self.capabilities_available), + binding_map: take(&mut self.binding_map), + + // Initialized afresh: + id_gen, + void_type, + gl450_ext_inst_id, + + // Recycled: + capabilities_used: take(&mut self.capabilities_used).recycle(), + extensions_used: take(&mut self.extensions_used).recycle(), + physical_layout: self.physical_layout.clone().recycle(), + logical_layout: take(&mut self.logical_layout).recycle(), + debugs: take(&mut self.debugs).recycle(), + annotations: take(&mut self.annotations).recycle(), + lookup_type: take(&mut self.lookup_type).recycle(), + lookup_function: take(&mut self.lookup_function).recycle(), + lookup_function_type: take(&mut self.lookup_function_type).recycle(), + constant_ids: take(&mut self.constant_ids).recycle(), + cached_constants: take(&mut self.cached_constants).recycle(), + global_variables: take(&mut self.global_variables).recycle(), + saved_cached: take(&mut self.saved_cached).recycle(), + temp_list: take(&mut self.temp_list).recycle(), + }; + + *self = fresh; + + self.capabilities_used.insert(spirv::Capability::Shader); + } + + /// Indicate that the code requires any one of the listed capabilities. + /// + /// If nothing in `capabilities` appears in the available capabilities + /// specified in the [`Options`] from which this `Writer` was created, + /// return an error. The `what` string is used in the error message to + /// explain what provoked the requirement. (If no available capabilities were + /// given, assume everything is available.) + /// + /// The first acceptable capability will be added to this `Writer`'s + /// [`capabilities_used`] table, and an `OpCapability` emitted for it in the + /// result. For this reason, more specific capabilities should be listed + /// before more general. + /// + /// [`capabilities_used`]: Writer::capabilities_used + pub(super) fn require_any( + &mut self, + what: &'static str, + capabilities: &[spirv::Capability], + ) -> Result<(), Error> { + match *capabilities { + [] => Ok(()), + [first, ..] => { + // Find the first acceptable capability, or return an error if + // there is none. + let selected = match self.capabilities_available { + None => first, + Some(ref available) => { + match capabilities.iter().find(|cap| available.contains(cap)) { + Some(&cap) => cap, + None => { + return Err(Error::MissingCapabilities(what, capabilities.to_vec())) + } + } + } + }; + self.capabilities_used.insert(selected); + Ok(()) + } + } + } + + /// Indicate that the code uses the given extension. + pub(super) fn use_extension(&mut self, extension: &'static str) { + self.extensions_used.insert(extension); + } + + pub(super) fn get_type_id(&mut self, lookup_ty: LookupType) -> Word { + match self.lookup_type.entry(lookup_ty) { + Entry::Occupied(e) => *e.get(), + Entry::Vacant(e) => { + let local = match lookup_ty { + LookupType::Handle(_handle) => unreachable!("Handles are populated at start"), + LookupType::Local(local) => local, + }; + + let id = self.id_gen.next(); + e.insert(id); + self.write_type_declaration_local(id, local); + id + } + } + } + + pub(super) fn get_expression_lookup_type(&mut self, tr: &TypeResolution) -> LookupType { + match *tr { + TypeResolution::Handle(ty_handle) => LookupType::Handle(ty_handle), + TypeResolution::Value(ref inner) => LookupType::Local(make_local(inner).unwrap()), + } + } + + pub(super) fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word { + let lookup_ty = self.get_expression_lookup_type(tr); + self.get_type_id(lookup_ty) + } + + pub(super) fn get_pointer_id( + &mut self, + arena: &UniqueArena, + handle: Handle, + class: spirv::StorageClass, + ) -> Result { + let ty_id = self.get_type_id(LookupType::Handle(handle)); + if let crate::TypeInner::Pointer { .. } = arena[handle].inner { + return Ok(ty_id); + } + let lookup_type = LookupType::Local(LocalType::Pointer { + base: handle, + class, + }); + Ok(if let Some(&id) = self.lookup_type.get(&lookup_type) { + id + } else { + let id = self.id_gen.next(); + let instruction = Instruction::type_pointer(id, class, ty_id); + instruction.to_words(&mut self.logical_layout.declarations); + self.lookup_type.insert(lookup_type, id); + id + }) + } + + pub(super) fn get_uint_type_id(&mut self) -> Word { + let local_type = LocalType::Value { + vector_size: None, + scalar: crate::Scalar::U32, + pointer_space: None, + }; + self.get_type_id(local_type.into()) + } + + pub(super) fn get_float_type_id(&mut self) -> Word { + let local_type = LocalType::Value { + vector_size: None, + scalar: crate::Scalar::F32, + pointer_space: None, + }; + self.get_type_id(local_type.into()) + } + + pub(super) fn get_uint3_type_id(&mut self) -> Word { + let local_type = LocalType::Value { + vector_size: Some(crate::VectorSize::Tri), + scalar: crate::Scalar::U32, + pointer_space: None, + }; + self.get_type_id(local_type.into()) + } + + pub(super) fn get_float_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { + let lookup_type = LookupType::Local(LocalType::Value { + vector_size: None, + scalar: crate::Scalar::F32, + pointer_space: Some(class), + }); + if let Some(&id) = self.lookup_type.get(&lookup_type) { + id + } else { + let id = self.id_gen.next(); + let ty_id = self.get_float_type_id(); + let instruction = Instruction::type_pointer(id, class, ty_id); + instruction.to_words(&mut self.logical_layout.declarations); + self.lookup_type.insert(lookup_type, id); + id + } + } + + pub(super) fn get_uint3_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { + let lookup_type = LookupType::Local(LocalType::Value { + vector_size: Some(crate::VectorSize::Tri), + scalar: crate::Scalar::U32, + pointer_space: Some(class), + }); + if let Some(&id) = self.lookup_type.get(&lookup_type) { + id + } else { + let id = self.id_gen.next(); + let ty_id = self.get_uint3_type_id(); + let instruction = Instruction::type_pointer(id, class, ty_id); + instruction.to_words(&mut self.logical_layout.declarations); + self.lookup_type.insert(lookup_type, id); + id + } + } + + pub(super) fn get_bool_type_id(&mut self) -> Word { + let local_type = LocalType::Value { + vector_size: None, + scalar: crate::Scalar::BOOL, + pointer_space: None, + }; + self.get_type_id(local_type.into()) + } + + pub(super) fn get_bool3_type_id(&mut self) -> Word { + let local_type = LocalType::Value { + vector_size: Some(crate::VectorSize::Tri), + scalar: crate::Scalar::BOOL, + pointer_space: None, + }; + self.get_type_id(local_type.into()) + } + + pub(super) fn decorate(&mut self, id: Word, decoration: spirv::Decoration, operands: &[Word]) { + self.annotations + .push(Instruction::decorate(id, decoration, operands)); + } + + fn write_function( + &mut self, + ir_function: &crate::Function, + info: &FunctionInfo, + ir_module: &crate::Module, + mut interface: Option, + debug_info: &Option, + ) -> Result { + let mut function = Function::default(); + + let prelude_id = self.id_gen.next(); + let mut prelude = Block::new(prelude_id); + let mut ep_context = EntryPointContext { + argument_ids: Vec::new(), + results: Vec::new(), + }; + + let mut local_invocation_id = None; + + let mut parameter_type_ids = Vec::with_capacity(ir_function.arguments.len()); + for argument in ir_function.arguments.iter() { + let class = spirv::StorageClass::Input; + let handle_ty = ir_module.types[argument.ty].inner.is_handle(); + let argument_type_id = match handle_ty { + true => self.get_pointer_id( + &ir_module.types, + argument.ty, + spirv::StorageClass::UniformConstant, + )?, + false => self.get_type_id(LookupType::Handle(argument.ty)), + }; + + if let Some(ref mut iface) = interface { + let id = if let Some(ref binding) = argument.binding { + let name = argument.name.as_deref(); + + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + name, + argument.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + let id = self.id_gen.next(); + prelude + .body + .push(Instruction::load(argument_type_id, id, varying_id, None)); + + if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { + local_invocation_id = Some(id); + } + + id + } else if let crate::TypeInner::Struct { ref members, .. } = + ir_module.types[argument.ty].inner + { + let struct_id = self.id_gen.next(); + let mut constituent_ids = Vec::with_capacity(members.len()); + for member in members { + let type_id = self.get_type_id(LookupType::Handle(member.ty)); + let name = member.name.as_deref(); + let binding = member.binding.as_ref().unwrap(); + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + name, + member.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + let id = self.id_gen.next(); + prelude + .body + .push(Instruction::load(type_id, id, varying_id, None)); + constituent_ids.push(id); + + if binding == &crate::Binding::BuiltIn(crate::BuiltIn::GlobalInvocationId) { + local_invocation_id = Some(id); + } + } + prelude.body.push(Instruction::composite_construct( + argument_type_id, + struct_id, + &constituent_ids, + )); + struct_id + } else { + unreachable!("Missing argument binding on an entry point"); + }; + ep_context.argument_ids.push(id); + } else { + let argument_id = self.id_gen.next(); + let instruction = Instruction::function_parameter(argument_type_id, argument_id); + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = argument.name { + self.debugs.push(Instruction::name(argument_id, name)); + } + } + function.parameters.push(FunctionArgument { + instruction, + handle_id: if handle_ty { + let id = self.id_gen.next(); + prelude.body.push(Instruction::load( + self.get_type_id(LookupType::Handle(argument.ty)), + id, + argument_id, + None, + )); + id + } else { + 0 + }, + }); + parameter_type_ids.push(argument_type_id); + }; + } + + let return_type_id = match ir_function.result { + Some(ref result) => { + if let Some(ref mut iface) = interface { + let mut has_point_size = false; + let class = spirv::StorageClass::Output; + if let Some(ref binding) = result.binding { + has_point_size |= + *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); + let type_id = self.get_type_id(LookupType::Handle(result.ty)); + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + None, + result.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + ep_context.results.push(ResultMember { + id: varying_id, + type_id, + built_in: binding.to_built_in(), + }); + } else if let crate::TypeInner::Struct { ref members, .. } = + ir_module.types[result.ty].inner + { + for member in members { + let type_id = self.get_type_id(LookupType::Handle(member.ty)); + let name = member.name.as_deref(); + let binding = member.binding.as_ref().unwrap(); + has_point_size |= + *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + name, + member.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + ep_context.results.push(ResultMember { + id: varying_id, + type_id, + built_in: binding.to_built_in(), + }); + } + } else { + unreachable!("Missing result binding on an entry point"); + } + + if self.flags.contains(WriterFlags::FORCE_POINT_SIZE) + && iface.stage == crate::ShaderStage::Vertex + && !has_point_size + { + // add point size artificially + let varying_id = self.id_gen.next(); + let pointer_type_id = self.get_float_pointer_type_id(class); + Instruction::variable(pointer_type_id, varying_id, class, None) + .to_words(&mut self.logical_layout.declarations); + self.decorate( + varying_id, + spirv::Decoration::BuiltIn, + &[spirv::BuiltIn::PointSize as u32], + ); + iface.varying_ids.push(varying_id); + + let default_value_id = self.get_constant_scalar(crate::Literal::F32(1.0)); + prelude + .body + .push(Instruction::store(varying_id, default_value_id, None)); + } + self.void_type + } else { + self.get_type_id(LookupType::Handle(result.ty)) + } + } + None => self.void_type, + }; + + let lookup_function_type = LookupFunctionType { + parameter_type_ids, + return_type_id, + }; + + let function_id = self.id_gen.next(); + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = ir_function.name { + self.debugs.push(Instruction::name(function_id, name)); + } + } + + let function_type = self.get_function_type(lookup_function_type); + function.signature = Some(Instruction::function( + return_type_id, + function_id, + spirv::FunctionControl::empty(), + function_type, + )); + + if interface.is_some() { + function.entry_point_context = Some(ep_context); + } + + // fill up the `GlobalVariable::access_id` + for gv in self.global_variables.iter_mut() { + gv.reset_for_function(); + } + for (handle, var) in ir_module.global_variables.iter() { + if info[handle].is_empty() { + continue; + } + + let mut gv = self.global_variables[handle.index()].clone(); + if let Some(ref mut iface) = interface { + // Have to include global variables in the interface + if self.physical_layout.version >= 0x10400 { + iface.varying_ids.push(gv.var_id); + } + } + + // Handle globals are pre-emitted and should be loaded automatically. + // + // Any that are binding arrays we skip as we cannot load the array, we must load the result after indexing. + let is_binding_array = match ir_module.types[var.ty].inner { + crate::TypeInner::BindingArray { .. } => true, + _ => false, + }; + + if var.space == crate::AddressSpace::Handle && !is_binding_array { + let var_type_id = self.get_type_id(LookupType::Handle(var.ty)); + let id = self.id_gen.next(); + prelude + .body + .push(Instruction::load(var_type_id, id, gv.var_id, None)); + gv.access_id = gv.var_id; + gv.handle_id = id; + } else if global_needs_wrapper(ir_module, var) { + let class = map_storage_class(var.space); + let pointer_type_id = self.get_pointer_id(&ir_module.types, var.ty, class)?; + let index_id = self.get_index_constant(0); + + let id = self.id_gen.next(); + prelude.body.push(Instruction::access_chain( + pointer_type_id, + id, + gv.var_id, + &[index_id], + )); + gv.access_id = id; + } else { + // by default, the variable ID is accessed as is + gv.access_id = gv.var_id; + }; + + // work around borrow checking in the presence of `self.xxx()` calls + self.global_variables[handle.index()] = gv; + } + + // Create a `BlockContext` for generating SPIR-V for the function's + // body. + let mut context = BlockContext { + ir_module, + ir_function, + fun_info: info, + function: &mut function, + // Re-use the cached expression table from prior functions. + cached: std::mem::take(&mut self.saved_cached), + + // Steal the Writer's temp list for a bit. + temp_list: std::mem::take(&mut self.temp_list), + writer: self, + expression_constness: crate::proc::ExpressionConstnessTracker::from_arena( + &ir_function.expressions, + ), + }; + + // fill up the pre-emitted and const expressions + context.cached.reset(ir_function.expressions.len()); + for (handle, expr) in ir_function.expressions.iter() { + if (expr.needs_pre_emit() && !matches!(*expr, crate::Expression::LocalVariable(_))) + || context.expression_constness.is_const(handle) + { + context.cache_expression_value(handle, &mut prelude)?; + } + } + + for (handle, variable) in ir_function.local_variables.iter() { + let id = context.gen_id(); + + if context.writer.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = variable.name { + context.writer.debugs.push(Instruction::name(id, name)); + } + } + + let init_word = variable.init.map(|constant| context.cached[constant]); + let pointer_type_id = context.writer.get_pointer_id( + &ir_module.types, + variable.ty, + spirv::StorageClass::Function, + )?; + let instruction = Instruction::variable( + pointer_type_id, + id, + spirv::StorageClass::Function, + init_word.or_else(|| match ir_module.types[variable.ty].inner { + crate::TypeInner::RayQuery => None, + _ => { + let type_id = context.get_type_id(LookupType::Handle(variable.ty)); + Some(context.writer.write_constant_null(type_id)) + } + }), + ); + context + .function + .variables + .insert(handle, LocalVariable { id, instruction }); + } + + // cache local variable expressions + for (handle, expr) in ir_function.expressions.iter() { + if matches!(*expr, crate::Expression::LocalVariable(_)) { + context.cache_expression_value(handle, &mut prelude)?; + } + } + + let next_id = context.gen_id(); + + context + .function + .consume(prelude, Instruction::branch(next_id)); + + let workgroup_vars_init_exit_block_id = + match (context.writer.zero_initialize_workgroup_memory, interface) { + ( + super::ZeroInitializeWorkgroupMemoryMode::Polyfill, + Some( + ref mut interface @ FunctionInterface { + stage: crate::ShaderStage::Compute, + .. + }, + ), + ) => context.writer.generate_workgroup_vars_init_block( + next_id, + ir_module, + info, + local_invocation_id, + interface, + context.function, + ), + _ => None, + }; + + let main_id = if let Some(exit_id) = workgroup_vars_init_exit_block_id { + exit_id + } else { + next_id + }; + + context.write_block( + main_id, + &ir_function.body, + super::block::BlockExit::Return, + LoopContext::default(), + debug_info.as_ref(), + )?; + + // Consume the `BlockContext`, ending its borrows and letting the + // `Writer` steal back its cached expression table and temp_list. + let BlockContext { + cached, temp_list, .. + } = context; + self.saved_cached = cached; + self.temp_list = temp_list; + + function.to_words(&mut self.logical_layout.function_definitions); + Instruction::function_end().to_words(&mut self.logical_layout.function_definitions); + + Ok(function_id) + } + + fn write_execution_mode( + &mut self, + function_id: Word, + mode: spirv::ExecutionMode, + ) -> Result<(), Error> { + //self.check(mode.required_capabilities())?; + Instruction::execution_mode(function_id, mode, &[]) + .to_words(&mut self.logical_layout.execution_modes); + Ok(()) + } + + // TODO Move to instructions module + fn write_entry_point( + &mut self, + entry_point: &crate::EntryPoint, + info: &FunctionInfo, + ir_module: &crate::Module, + debug_info: &Option, + ) -> Result { + let mut interface_ids = Vec::new(); + let function_id = self.write_function( + &entry_point.function, + info, + ir_module, + Some(FunctionInterface { + varying_ids: &mut interface_ids, + stage: entry_point.stage, + }), + debug_info, + )?; + + let exec_model = match entry_point.stage { + crate::ShaderStage::Vertex => spirv::ExecutionModel::Vertex, + crate::ShaderStage::Fragment => { + self.write_execution_mode(function_id, spirv::ExecutionMode::OriginUpperLeft)?; + if let Some(ref result) = entry_point.function.result { + if contains_builtin( + result.binding.as_ref(), + result.ty, + &ir_module.types, + crate::BuiltIn::FragDepth, + ) { + self.write_execution_mode( + function_id, + spirv::ExecutionMode::DepthReplacing, + )?; + } + } + spirv::ExecutionModel::Fragment + } + crate::ShaderStage::Compute => { + let execution_mode = spirv::ExecutionMode::LocalSize; + //self.check(execution_mode.required_capabilities())?; + Instruction::execution_mode( + function_id, + execution_mode, + &entry_point.workgroup_size, + ) + .to_words(&mut self.logical_layout.execution_modes); + spirv::ExecutionModel::GLCompute + } + }; + //self.check(exec_model.required_capabilities())?; + + Ok(Instruction::entry_point( + exec_model, + function_id, + &entry_point.name, + interface_ids.as_slice(), + )) + } + + fn make_scalar(&mut self, id: Word, scalar: crate::Scalar) -> Instruction { + use crate::ScalarKind as Sk; + + let bits = (scalar.width * BITS_PER_BYTE) as u32; + match scalar.kind { + Sk::Sint | Sk::Uint => { + let signedness = if scalar.kind == Sk::Sint { + super::instructions::Signedness::Signed + } else { + super::instructions::Signedness::Unsigned + }; + let cap = match bits { + 8 => Some(spirv::Capability::Int8), + 16 => Some(spirv::Capability::Int16), + 64 => Some(spirv::Capability::Int64), + _ => None, + }; + if let Some(cap) = cap { + self.capabilities_used.insert(cap); + } + Instruction::type_int(id, bits, signedness) + } + Sk::Float => { + if bits == 64 { + self.capabilities_used.insert(spirv::Capability::Float64); + } + Instruction::type_float(id, bits) + } + Sk::Bool => Instruction::type_bool(id), + Sk::AbstractInt | Sk::AbstractFloat => { + unreachable!("abstract types should never reach the backend"); + } + } + } + + fn request_type_capabilities(&mut self, inner: &crate::TypeInner) -> Result<(), Error> { + match *inner { + crate::TypeInner::Image { + dim, + arrayed, + class, + } => { + let sampled = match class { + crate::ImageClass::Sampled { .. } => true, + crate::ImageClass::Depth { .. } => true, + crate::ImageClass::Storage { format, .. } => { + self.request_image_format_capabilities(format.into())?; + false + } + }; + + match dim { + crate::ImageDimension::D1 => { + if sampled { + self.require_any("sampled 1D images", &[spirv::Capability::Sampled1D])?; + } else { + self.require_any("1D storage images", &[spirv::Capability::Image1D])?; + } + } + crate::ImageDimension::Cube if arrayed => { + if sampled { + self.require_any( + "sampled cube array images", + &[spirv::Capability::SampledCubeArray], + )?; + } else { + self.require_any( + "cube array storage images", + &[spirv::Capability::ImageCubeArray], + )?; + } + } + _ => {} + } + } + crate::TypeInner::AccelerationStructure => { + self.require_any("Acceleration Structure", &[spirv::Capability::RayQueryKHR])?; + } + crate::TypeInner::RayQuery => { + self.require_any("Ray Query", &[spirv::Capability::RayQueryKHR])?; + } + _ => {} + } + Ok(()) + } + + fn write_type_declaration_local(&mut self, id: Word, local_ty: LocalType) { + let instruction = match local_ty { + LocalType::Value { + vector_size: None, + scalar, + pointer_space: None, + } => self.make_scalar(id, scalar), + LocalType::Value { + vector_size: Some(size), + scalar, + pointer_space: None, + } => { + let scalar_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar, + pointer_space: None, + })); + Instruction::type_vector(id, scalar_id, size) + } + LocalType::Matrix { + columns, + rows, + width, + } => { + let vector_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(rows), + scalar: crate::Scalar::float(width), + pointer_space: None, + })); + Instruction::type_matrix(id, vector_id, columns) + } + LocalType::Pointer { base, class } => { + let type_id = self.get_type_id(LookupType::Handle(base)); + Instruction::type_pointer(id, class, type_id) + } + LocalType::Value { + vector_size, + scalar, + pointer_space: Some(class), + } => { + let type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size, + scalar, + pointer_space: None, + })); + Instruction::type_pointer(id, class, type_id) + } + LocalType::Image(image) => { + let local_type = LocalType::Value { + vector_size: None, + scalar: crate::Scalar { + kind: image.sampled_type, + width: 4, + }, + pointer_space: None, + }; + let type_id = self.get_type_id(LookupType::Local(local_type)); + Instruction::type_image(id, type_id, image.dim, image.flags, image.image_format) + } + LocalType::Sampler => Instruction::type_sampler(id), + LocalType::SampledImage { image_type_id } => { + Instruction::type_sampled_image(id, image_type_id) + } + LocalType::BindingArray { base, size } => { + let inner_ty = self.get_type_id(LookupType::Handle(base)); + let scalar_id = self.get_constant_scalar(crate::Literal::U32(size)); + Instruction::type_array(id, inner_ty, scalar_id) + } + LocalType::PointerToBindingArray { base, size, space } => { + let inner_ty = + self.get_type_id(LookupType::Local(LocalType::BindingArray { base, size })); + let class = map_storage_class(space); + Instruction::type_pointer(id, class, inner_ty) + } + LocalType::AccelerationStructure => Instruction::type_acceleration_structure(id), + LocalType::RayQuery => Instruction::type_ray_query(id), + }; + + instruction.to_words(&mut self.logical_layout.declarations); + } + + fn write_type_declaration_arena( + &mut self, + arena: &UniqueArena, + handle: Handle, + ) -> Result { + let ty = &arena[handle]; + let id = if let Some(local) = make_local(&ty.inner) { + // This type can be represented as a `LocalType`, so check if we've + // already written an instruction for it. If not, do so now, with + // `write_type_declaration_local`. + match self.lookup_type.entry(LookupType::Local(local)) { + // We already have an id for this `LocalType`. + Entry::Occupied(e) => *e.get(), + + // It's a type we haven't seen before. + Entry::Vacant(e) => { + let id = self.id_gen.next(); + e.insert(id); + + self.write_type_declaration_local(id, local); + + // If it's a type that needs SPIR-V capabilities, request them now, + // so write_type_declaration_local can stay infallible. + self.request_type_capabilities(&ty.inner)?; + + id + } + } + } else { + use spirv::Decoration; + + let id = self.id_gen.next(); + let instruction = match ty.inner { + crate::TypeInner::Array { base, size, stride } => { + self.decorate(id, Decoration::ArrayStride, &[stride]); + + let type_id = self.get_type_id(LookupType::Handle(base)); + match size { + crate::ArraySize::Constant(length) => { + let length_id = self.get_index_constant(length.get()); + Instruction::type_array(id, type_id, length_id) + } + crate::ArraySize::Dynamic => Instruction::type_runtime_array(id, type_id), + } + } + crate::TypeInner::BindingArray { base, size } => { + let type_id = self.get_type_id(LookupType::Handle(base)); + match size { + crate::ArraySize::Constant(length) => { + let length_id = self.get_index_constant(length.get()); + Instruction::type_array(id, type_id, length_id) + } + crate::ArraySize::Dynamic => Instruction::type_runtime_array(id, type_id), + } + } + crate::TypeInner::Struct { + ref members, + span: _, + } => { + let mut has_runtime_array = false; + let mut member_ids = Vec::with_capacity(members.len()); + for (index, member) in members.iter().enumerate() { + let member_ty = &arena[member.ty]; + match member_ty.inner { + crate::TypeInner::Array { + base: _, + size: crate::ArraySize::Dynamic, + stride: _, + } => { + has_runtime_array = true; + } + _ => (), + } + self.decorate_struct_member(id, index, member, arena)?; + let member_id = self.get_type_id(LookupType::Handle(member.ty)); + member_ids.push(member_id); + } + if has_runtime_array { + self.decorate(id, Decoration::Block, &[]); + } + Instruction::type_struct(id, member_ids.as_slice()) + } + + // These all have TypeLocal representations, so they should have been + // handled by `write_type_declaration_local` above. + crate::TypeInner::Scalar(_) + | crate::TypeInner::Atomic(_) + | crate::TypeInner::Vector { .. } + | crate::TypeInner::Matrix { .. } + | crate::TypeInner::Pointer { .. } + | crate::TypeInner::ValuePointer { .. } + | crate::TypeInner::Image { .. } + | crate::TypeInner::Sampler { .. } + | crate::TypeInner::AccelerationStructure + | crate::TypeInner::RayQuery => unreachable!(), + }; + + instruction.to_words(&mut self.logical_layout.declarations); + id + }; + + // Add this handle as a new alias for that type. + self.lookup_type.insert(LookupType::Handle(handle), id); + + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = ty.name { + self.debugs.push(Instruction::name(id, name)); + } + } + + Ok(id) + } + + fn request_image_format_capabilities( + &mut self, + format: spirv::ImageFormat, + ) -> Result<(), Error> { + use spirv::ImageFormat as If; + match format { + If::Rg32f + | If::Rg16f + | If::R11fG11fB10f + | If::R16f + | If::Rgba16 + | If::Rgb10A2 + | If::Rg16 + | If::Rg8 + | If::R16 + | If::R8 + | If::Rgba16Snorm + | If::Rg16Snorm + | If::Rg8Snorm + | If::R16Snorm + | If::R8Snorm + | If::Rg32i + | If::Rg16i + | If::Rg8i + | If::R16i + | If::R8i + | If::Rgb10a2ui + | If::Rg32ui + | If::Rg16ui + | If::Rg8ui + | If::R16ui + | If::R8ui => self.require_any( + "storage image format", + &[spirv::Capability::StorageImageExtendedFormats], + ), + If::R64ui | If::R64i => self.require_any( + "64-bit integer storage image format", + &[spirv::Capability::Int64ImageEXT], + ), + If::Unknown + | If::Rgba32f + | If::Rgba16f + | If::R32f + | If::Rgba8 + | If::Rgba8Snorm + | If::Rgba32i + | If::Rgba16i + | If::Rgba8i + | If::R32i + | If::Rgba32ui + | If::Rgba16ui + | If::Rgba8ui + | If::R32ui => Ok(()), + } + } + + pub(super) fn get_index_constant(&mut self, index: Word) -> Word { + self.get_constant_scalar(crate::Literal::U32(index)) + } + + pub(super) fn get_constant_scalar_with( + &mut self, + value: u8, + scalar: crate::Scalar, + ) -> Result { + Ok( + self.get_constant_scalar(crate::Literal::new(value, scalar).ok_or( + Error::Validation("Unexpected kind and/or width for Literal"), + )?), + ) + } + + pub(super) fn get_constant_scalar(&mut self, value: crate::Literal) -> Word { + let scalar = CachedConstant::Literal(value); + if let Some(&id) = self.cached_constants.get(&scalar) { + return id; + } + let id = self.id_gen.next(); + self.write_constant_scalar(id, &value, None); + self.cached_constants.insert(scalar, id); + id + } + + fn write_constant_scalar( + &mut self, + id: Word, + value: &crate::Literal, + debug_name: Option<&String>, + ) { + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(name) = debug_name { + self.debugs.push(Instruction::name(id, name)); + } + } + let type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + scalar: value.scalar(), + pointer_space: None, + })); + let instruction = match *value { + crate::Literal::F64(value) => { + let bits = value.to_bits(); + Instruction::constant_64bit(type_id, id, bits as u32, (bits >> 32) as u32) + } + crate::Literal::F32(value) => Instruction::constant_32bit(type_id, id, value.to_bits()), + crate::Literal::U32(value) => Instruction::constant_32bit(type_id, id, value), + crate::Literal::I32(value) => Instruction::constant_32bit(type_id, id, value as u32), + crate::Literal::I64(value) => { + Instruction::constant_64bit(type_id, id, value as u32, (value >> 32) as u32) + } + crate::Literal::Bool(true) => Instruction::constant_true(type_id, id), + crate::Literal::Bool(false) => Instruction::constant_false(type_id, id), + crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { + unreachable!("Abstract types should not appear in IR presented to backends"); + } + }; + + instruction.to_words(&mut self.logical_layout.declarations); + } + + pub(super) fn get_constant_composite( + &mut self, + ty: LookupType, + constituent_ids: &[Word], + ) -> Word { + let composite = CachedConstant::Composite { + ty, + constituent_ids: constituent_ids.to_vec(), + }; + if let Some(&id) = self.cached_constants.get(&composite) { + return id; + } + let id = self.id_gen.next(); + self.write_constant_composite(id, ty, constituent_ids, None); + self.cached_constants.insert(composite, id); + id + } + + fn write_constant_composite( + &mut self, + id: Word, + ty: LookupType, + constituent_ids: &[Word], + debug_name: Option<&String>, + ) { + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(name) = debug_name { + self.debugs.push(Instruction::name(id, name)); + } + } + let type_id = self.get_type_id(ty); + Instruction::constant_composite(type_id, id, constituent_ids) + .to_words(&mut self.logical_layout.declarations); + } + + pub(super) fn get_constant_null(&mut self, type_id: Word) -> Word { + let null = CachedConstant::ZeroValue(type_id); + if let Some(&id) = self.cached_constants.get(&null) { + return id; + } + let id = self.write_constant_null(type_id); + self.cached_constants.insert(null, id); + id + } + + pub(super) fn write_constant_null(&mut self, type_id: Word) -> Word { + let null_id = self.id_gen.next(); + Instruction::constant_null(type_id, null_id) + .to_words(&mut self.logical_layout.declarations); + null_id + } + + fn write_constant_expr( + &mut self, + handle: Handle, + ir_module: &crate::Module, + mod_info: &ModuleInfo, + ) -> Result { + let id = match ir_module.const_expressions[handle] { + crate::Expression::Literal(literal) => self.get_constant_scalar(literal), + crate::Expression::Constant(constant) => { + let constant = &ir_module.constants[constant]; + self.constant_ids[constant.init.index()] + } + crate::Expression::ZeroValue(ty) => { + let type_id = self.get_type_id(LookupType::Handle(ty)); + self.get_constant_null(type_id) + } + crate::Expression::Compose { ty, ref components } => { + let component_ids: Vec<_> = crate::proc::flatten_compose( + ty, + components, + &ir_module.const_expressions, + &ir_module.types, + ) + .map(|component| self.constant_ids[component.index()]) + .collect(); + self.get_constant_composite(LookupType::Handle(ty), component_ids.as_slice()) + } + crate::Expression::Splat { size, value } => { + let value_id = self.constant_ids[value.index()]; + let component_ids = &[value_id; 4][..size as usize]; + + let ty = self.get_expression_lookup_type(&mod_info[handle]); + + self.get_constant_composite(ty, component_ids) + } + _ => unreachable!(), + }; + + self.constant_ids[handle.index()] = id; + + Ok(id) + } + + pub(super) fn write_barrier(&mut self, flags: crate::Barrier, block: &mut Block) { + let memory_scope = if flags.contains(crate::Barrier::STORAGE) { + spirv::Scope::Device + } else { + spirv::Scope::Workgroup + }; + let mut semantics = spirv::MemorySemantics::ACQUIRE_RELEASE; + semantics.set( + spirv::MemorySemantics::UNIFORM_MEMORY, + flags.contains(crate::Barrier::STORAGE), + ); + semantics.set( + spirv::MemorySemantics::WORKGROUP_MEMORY, + flags.contains(crate::Barrier::WORK_GROUP), + ); + let exec_scope_id = self.get_index_constant(spirv::Scope::Workgroup as u32); + let mem_scope_id = self.get_index_constant(memory_scope as u32); + let semantics_id = self.get_index_constant(semantics.bits()); + block.body.push(Instruction::control_barrier( + exec_scope_id, + mem_scope_id, + semantics_id, + )); + } + + fn generate_workgroup_vars_init_block( + &mut self, + entry_id: Word, + ir_module: &crate::Module, + info: &FunctionInfo, + local_invocation_id: Option, + interface: &mut FunctionInterface, + function: &mut Function, + ) -> Option { + let body = ir_module + .global_variables + .iter() + .filter(|&(handle, var)| { + !info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup + }) + .map(|(handle, var)| { + // It's safe to use `var_id` here, not `access_id`, because only + // variables in the `Uniform` and `StorageBuffer` address spaces + // get wrapped, and we're initializing `WorkGroup` variables. + let var_id = self.global_variables[handle.index()].var_id; + let var_type_id = self.get_type_id(LookupType::Handle(var.ty)); + let init_word = self.get_constant_null(var_type_id); + Instruction::store(var_id, init_word, None) + }) + .collect::>(); + + if body.is_empty() { + return None; + } + + let uint3_type_id = self.get_uint3_type_id(); + + let mut pre_if_block = Block::new(entry_id); + + let local_invocation_id = if let Some(local_invocation_id) = local_invocation_id { + local_invocation_id + } else { + let varying_id = self.id_gen.next(); + let class = spirv::StorageClass::Input; + let pointer_type_id = self.get_uint3_pointer_type_id(class); + + Instruction::variable(pointer_type_id, varying_id, class, None) + .to_words(&mut self.logical_layout.declarations); + + self.decorate( + varying_id, + spirv::Decoration::BuiltIn, + &[spirv::BuiltIn::LocalInvocationId as u32], + ); + + interface.varying_ids.push(varying_id); + let id = self.id_gen.next(); + pre_if_block + .body + .push(Instruction::load(uint3_type_id, id, varying_id, None)); + + id + }; + + let zero_id = self.get_constant_null(uint3_type_id); + let bool3_type_id = self.get_bool3_type_id(); + + let eq_id = self.id_gen.next(); + pre_if_block.body.push(Instruction::binary( + spirv::Op::IEqual, + bool3_type_id, + eq_id, + local_invocation_id, + zero_id, + )); + + let condition_id = self.id_gen.next(); + let bool_type_id = self.get_bool_type_id(); + pre_if_block.body.push(Instruction::relational( + spirv::Op::All, + bool_type_id, + condition_id, + eq_id, + )); + + let merge_id = self.id_gen.next(); + pre_if_block.body.push(Instruction::selection_merge( + merge_id, + spirv::SelectionControl::NONE, + )); + + let accept_id = self.id_gen.next(); + function.consume( + pre_if_block, + Instruction::branch_conditional(condition_id, accept_id, merge_id), + ); + + let accept_block = Block { + label_id: accept_id, + body, + }; + function.consume(accept_block, Instruction::branch(merge_id)); + + let mut post_if_block = Block::new(merge_id); + + self.write_barrier(crate::Barrier::WORK_GROUP, &mut post_if_block); + + let next_id = self.id_gen.next(); + function.consume(post_if_block, Instruction::branch(next_id)); + Some(next_id) + } + + /// Generate an `OpVariable` for one value in an [`EntryPoint`]'s IO interface. + /// + /// The [`Binding`]s of the arguments and result of an [`EntryPoint`]'s + /// [`Function`] describe a SPIR-V shader interface. In SPIR-V, the + /// interface is represented by global variables in the `Input` and `Output` + /// storage classes, with decorations indicating which builtin or location + /// each variable corresponds to. + /// + /// This function emits a single global `OpVariable` for a single value from + /// the interface, and adds appropriate decorations to indicate which + /// builtin or location it represents, how it should be interpolated, and so + /// on. The `class` argument gives the variable's SPIR-V storage class, + /// which should be either [`Input`] or [`Output`]. + /// + /// [`Binding`]: crate::Binding + /// [`Function`]: crate::Function + /// [`EntryPoint`]: crate::EntryPoint + /// [`Input`]: spirv::StorageClass::Input + /// [`Output`]: spirv::StorageClass::Output + fn write_varying( + &mut self, + ir_module: &crate::Module, + stage: crate::ShaderStage, + class: spirv::StorageClass, + debug_name: Option<&str>, + ty: Handle, + binding: &crate::Binding, + ) -> Result { + let id = self.id_gen.next(); + let pointer_type_id = self.get_pointer_id(&ir_module.types, ty, class)?; + Instruction::variable(pointer_type_id, id, class, None) + .to_words(&mut self.logical_layout.declarations); + + if self + .flags + .contains(WriterFlags::DEBUG | WriterFlags::LABEL_VARYINGS) + { + if let Some(name) = debug_name { + self.debugs.push(Instruction::name(id, name)); + } + } + + use spirv::{BuiltIn, Decoration}; + + match *binding { + crate::Binding::Location { + location, + interpolation, + sampling, + second_blend_source, + } => { + self.decorate(id, Decoration::Location, &[location]); + + let no_decorations = + // VUID-StandaloneSpirv-Flat-06202 + // > The Flat, NoPerspective, Sample, and Centroid decorations + // > must not be used on variables with the Input storage class in a vertex shader + (class == spirv::StorageClass::Input && stage == crate::ShaderStage::Vertex) || + // VUID-StandaloneSpirv-Flat-06201 + // > The Flat, NoPerspective, Sample, and Centroid decorations + // > must not be used on variables with the Output storage class in a fragment shader + (class == spirv::StorageClass::Output && stage == crate::ShaderStage::Fragment); + + if !no_decorations { + match interpolation { + // Perspective-correct interpolation is the default in SPIR-V. + None | Some(crate::Interpolation::Perspective) => (), + Some(crate::Interpolation::Flat) => { + self.decorate(id, Decoration::Flat, &[]); + } + Some(crate::Interpolation::Linear) => { + self.decorate(id, Decoration::NoPerspective, &[]); + } + } + match sampling { + // Center sampling is the default in SPIR-V. + None | Some(crate::Sampling::Center) => (), + Some(crate::Sampling::Centroid) => { + self.decorate(id, Decoration::Centroid, &[]); + } + Some(crate::Sampling::Sample) => { + self.require_any( + "per-sample interpolation", + &[spirv::Capability::SampleRateShading], + )?; + self.decorate(id, Decoration::Sample, &[]); + } + } + } + if second_blend_source { + self.decorate(id, Decoration::Index, &[1]); + } + } + crate::Binding::BuiltIn(built_in) => { + use crate::BuiltIn as Bi; + let built_in = match built_in { + Bi::Position { invariant } => { + if invariant { + self.decorate(id, Decoration::Invariant, &[]); + } + + if class == spirv::StorageClass::Output { + BuiltIn::Position + } else { + BuiltIn::FragCoord + } + } + Bi::ViewIndex => { + self.require_any("`view_index` built-in", &[spirv::Capability::MultiView])?; + BuiltIn::ViewIndex + } + // vertex + Bi::BaseInstance => BuiltIn::BaseInstance, + Bi::BaseVertex => BuiltIn::BaseVertex, + Bi::ClipDistance => { + self.require_any( + "`clip_distance` built-in", + &[spirv::Capability::ClipDistance], + )?; + BuiltIn::ClipDistance + } + Bi::CullDistance => { + self.require_any( + "`cull_distance` built-in", + &[spirv::Capability::CullDistance], + )?; + BuiltIn::CullDistance + } + Bi::InstanceIndex => BuiltIn::InstanceIndex, + Bi::PointSize => BuiltIn::PointSize, + Bi::VertexIndex => BuiltIn::VertexIndex, + // fragment + Bi::FragDepth => BuiltIn::FragDepth, + Bi::PointCoord => BuiltIn::PointCoord, + Bi::FrontFacing => BuiltIn::FrontFacing, + Bi::PrimitiveIndex => { + self.require_any( + "`primitive_index` built-in", + &[spirv::Capability::Geometry], + )?; + BuiltIn::PrimitiveId + } + Bi::SampleIndex => { + self.require_any( + "`sample_index` built-in", + &[spirv::Capability::SampleRateShading], + )?; + + BuiltIn::SampleId + } + Bi::SampleMask => BuiltIn::SampleMask, + // compute + Bi::GlobalInvocationId => BuiltIn::GlobalInvocationId, + Bi::LocalInvocationId => BuiltIn::LocalInvocationId, + Bi::LocalInvocationIndex => BuiltIn::LocalInvocationIndex, + Bi::WorkGroupId => BuiltIn::WorkgroupId, + Bi::WorkGroupSize => BuiltIn::WorkgroupSize, + Bi::NumWorkGroups => BuiltIn::NumWorkgroups, + }; + + self.decorate(id, Decoration::BuiltIn, &[built_in as u32]); + + use crate::ScalarKind as Sk; + + // Per the Vulkan spec, `VUID-StandaloneSpirv-Flat-04744`: + // + // > Any variable with integer or double-precision floating- + // > point type and with Input storage class in a fragment + // > shader, must be decorated Flat + if class == spirv::StorageClass::Input && stage == crate::ShaderStage::Fragment { + let is_flat = match ir_module.types[ty].inner { + crate::TypeInner::Scalar(scalar) + | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { + Sk::Uint | Sk::Sint | Sk::Bool => true, + Sk::Float => false, + Sk::AbstractInt | Sk::AbstractFloat => { + return Err(Error::Validation( + "Abstract types should not appear in IR presented to backends", + )) + } + }, + _ => false, + }; + + if is_flat { + self.decorate(id, Decoration::Flat, &[]); + } + } + } + } + + Ok(id) + } + + fn write_global_variable( + &mut self, + ir_module: &crate::Module, + global_variable: &crate::GlobalVariable, + ) -> Result { + use spirv::Decoration; + + let id = self.id_gen.next(); + let class = map_storage_class(global_variable.space); + + //self.check(class.required_capabilities())?; + + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = global_variable.name { + self.debugs.push(Instruction::name(id, name)); + } + } + + let storage_access = match global_variable.space { + crate::AddressSpace::Storage { access } => Some(access), + _ => match ir_module.types[global_variable.ty].inner { + crate::TypeInner::Image { + class: crate::ImageClass::Storage { access, .. }, + .. + } => Some(access), + _ => None, + }, + }; + if let Some(storage_access) = storage_access { + if !storage_access.contains(crate::StorageAccess::LOAD) { + self.decorate(id, Decoration::NonReadable, &[]); + } + if !storage_access.contains(crate::StorageAccess::STORE) { + self.decorate(id, Decoration::NonWritable, &[]); + } + } + + // Note: we should be able to substitute `binding_array`, + // but there is still code that tries to register the pre-substituted type, + // and it is failing on 0. + let mut substitute_inner_type_lookup = None; + if let Some(ref res_binding) = global_variable.binding { + self.decorate(id, Decoration::DescriptorSet, &[res_binding.group]); + self.decorate(id, Decoration::Binding, &[res_binding.binding]); + + if let Some(&BindingInfo { + binding_array_size: Some(remapped_binding_array_size), + }) = self.binding_map.get(res_binding) + { + if let crate::TypeInner::BindingArray { base, .. } = + ir_module.types[global_variable.ty].inner + { + substitute_inner_type_lookup = + Some(LookupType::Local(LocalType::PointerToBindingArray { + base, + size: remapped_binding_array_size, + space: global_variable.space, + })) + } + } + }; + + let init_word = global_variable + .init + .map(|constant| self.constant_ids[constant.index()]); + let inner_type_id = self.get_type_id( + substitute_inner_type_lookup.unwrap_or(LookupType::Handle(global_variable.ty)), + ); + + // generate the wrapping structure if needed + let pointer_type_id = if global_needs_wrapper(ir_module, global_variable) { + let wrapper_type_id = self.id_gen.next(); + + self.decorate(wrapper_type_id, Decoration::Block, &[]); + let member = crate::StructMember { + name: None, + ty: global_variable.ty, + binding: None, + offset: 0, + }; + self.decorate_struct_member(wrapper_type_id, 0, &member, &ir_module.types)?; + + Instruction::type_struct(wrapper_type_id, &[inner_type_id]) + .to_words(&mut self.logical_layout.declarations); + + let pointer_type_id = self.id_gen.next(); + Instruction::type_pointer(pointer_type_id, class, wrapper_type_id) + .to_words(&mut self.logical_layout.declarations); + + pointer_type_id + } else { + // This is a global variable in the Storage address space. The only + // way it could have `global_needs_wrapper() == false` is if it has + // a runtime-sized or binding array. + // Runtime-sized arrays were decorated when iterating through struct content. + // Now binding arrays require Block decorating. + if let crate::AddressSpace::Storage { .. } = global_variable.space { + match ir_module.types[global_variable.ty].inner { + crate::TypeInner::BindingArray { base, .. } => { + let decorated_id = self.get_type_id(LookupType::Handle(base)); + self.decorate(decorated_id, Decoration::Block, &[]); + } + _ => (), + }; + } + if substitute_inner_type_lookup.is_some() { + inner_type_id + } else { + self.get_pointer_id(&ir_module.types, global_variable.ty, class)? + } + }; + + let init_word = match (global_variable.space, self.zero_initialize_workgroup_memory) { + (crate::AddressSpace::Private, _) + | (crate::AddressSpace::WorkGroup, super::ZeroInitializeWorkgroupMemoryMode::Native) => { + init_word.or_else(|| Some(self.get_constant_null(inner_type_id))) + } + _ => init_word, + }; + + Instruction::variable(pointer_type_id, id, class, init_word) + .to_words(&mut self.logical_layout.declarations); + Ok(id) + } + + /// Write the necessary decorations for a struct member. + /// + /// Emit decorations for the `index`'th member of the struct type + /// designated by `struct_id`, described by `member`. + fn decorate_struct_member( + &mut self, + struct_id: Word, + index: usize, + member: &crate::StructMember, + arena: &UniqueArena, + ) -> Result<(), Error> { + use spirv::Decoration; + + self.annotations.push(Instruction::member_decorate( + struct_id, + index as u32, + Decoration::Offset, + &[member.offset], + )); + + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = member.name { + self.debugs + .push(Instruction::member_name(struct_id, index as u32, name)); + } + } + + // Matrices and arrays of matrices both require decorations, + // so "see through" an array to determine if they're needed. + let member_array_subty_inner = match arena[member.ty].inner { + crate::TypeInner::Array { base, .. } => &arena[base].inner, + ref other => other, + }; + if let crate::TypeInner::Matrix { + columns: _, + rows, + scalar, + } = *member_array_subty_inner + { + let byte_stride = Alignment::from(rows) * scalar.width as u32; + self.annotations.push(Instruction::member_decorate( + struct_id, + index as u32, + Decoration::ColMajor, + &[], + )); + self.annotations.push(Instruction::member_decorate( + struct_id, + index as u32, + Decoration::MatrixStride, + &[byte_stride], + )); + } + + Ok(()) + } + + fn get_function_type(&mut self, lookup_function_type: LookupFunctionType) -> Word { + match self + .lookup_function_type + .entry(lookup_function_type.clone()) + { + Entry::Occupied(e) => *e.get(), + Entry::Vacant(_) => { + let id = self.id_gen.next(); + let instruction = Instruction::type_function( + id, + lookup_function_type.return_type_id, + &lookup_function_type.parameter_type_ids, + ); + instruction.to_words(&mut self.logical_layout.declarations); + self.lookup_function_type.insert(lookup_function_type, id); + id + } + } + } + + fn write_physical_layout(&mut self) { + self.physical_layout.bound = self.id_gen.0 + 1; + } + + fn write_logical_layout( + &mut self, + ir_module: &crate::Module, + mod_info: &ModuleInfo, + ep_index: Option, + debug_info: &Option, + ) -> Result<(), Error> { + fn has_view_index_check( + ir_module: &crate::Module, + binding: Option<&crate::Binding>, + ty: Handle, + ) -> bool { + match ir_module.types[ty].inner { + crate::TypeInner::Struct { ref members, .. } => members.iter().any(|member| { + has_view_index_check(ir_module, member.binding.as_ref(), member.ty) + }), + _ => binding == Some(&crate::Binding::BuiltIn(crate::BuiltIn::ViewIndex)), + } + } + + let has_storage_buffers = + ir_module + .global_variables + .iter() + .any(|(_, var)| match var.space { + crate::AddressSpace::Storage { .. } => true, + _ => false, + }); + let has_view_index = ir_module + .entry_points + .iter() + .flat_map(|entry| entry.function.arguments.iter()) + .any(|arg| has_view_index_check(ir_module, arg.binding.as_ref(), arg.ty)); + let has_ray_query = ir_module.special_types.ray_desc.is_some() + | ir_module.special_types.ray_intersection.is_some(); + + if self.physical_layout.version < 0x10300 && has_storage_buffers { + // enable the storage buffer class on < SPV-1.3 + Instruction::extension("SPV_KHR_storage_buffer_storage_class") + .to_words(&mut self.logical_layout.extensions); + } + if has_view_index { + Instruction::extension("SPV_KHR_multiview") + .to_words(&mut self.logical_layout.extensions) + } + if has_ray_query { + Instruction::extension("SPV_KHR_ray_query") + .to_words(&mut self.logical_layout.extensions) + } + Instruction::type_void(self.void_type).to_words(&mut self.logical_layout.declarations); + Instruction::ext_inst_import(self.gl450_ext_inst_id, "GLSL.std.450") + .to_words(&mut self.logical_layout.ext_inst_imports); + + let mut debug_info_inner = None; + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(debug_info) = debug_info.as_ref() { + let source_file_id = self.id_gen.next(); + self.debugs.push(Instruction::string( + &debug_info.file_name.display().to_string(), + source_file_id, + )); + + debug_info_inner = Some(DebugInfoInner { + source_code: debug_info.source_code, + source_file_id, + }); + self.debugs.push(Instruction::source( + spirv::SourceLanguage::Unknown, + 0, + &debug_info_inner, + )); + } + } + + // write all types + for (handle, _) in ir_module.types.iter() { + self.write_type_declaration_arena(&ir_module.types, handle)?; + } + + // write all const-expressions as constants + self.constant_ids + .resize(ir_module.const_expressions.len(), 0); + for (handle, _) in ir_module.const_expressions.iter() { + self.write_constant_expr(handle, ir_module, mod_info)?; + } + debug_assert!(self.constant_ids.iter().all(|&id| id != 0)); + + // write the name of constants on their respective const-expression initializer + if self.flags.contains(WriterFlags::DEBUG) { + for (_, constant) in ir_module.constants.iter() { + if let Some(ref name) = constant.name { + let id = self.constant_ids[constant.init.index()]; + self.debugs.push(Instruction::name(id, name)); + } + } + } + + // write all global variables + for (handle, var) in ir_module.global_variables.iter() { + // If a single entry point was specified, only write `OpVariable` instructions + // for the globals it actually uses. Emit dummies for the others, + // to preserve the indices in `global_variables`. + let gvar = match ep_index { + Some(index) if mod_info.get_entry_point(index)[handle].is_empty() => { + GlobalVariable::dummy() + } + _ => { + let id = self.write_global_variable(ir_module, var)?; + GlobalVariable::new(id) + } + }; + self.global_variables.push(gvar); + } + + // write all functions + for (handle, ir_function) in ir_module.functions.iter() { + let info = &mod_info[handle]; + if let Some(index) = ep_index { + let ep_info = mod_info.get_entry_point(index); + // If this function uses globals that we omitted from the SPIR-V + // because the entry point and its callees didn't use them, + // then we must skip it. + if !ep_info.dominates_global_use(info) { + log::info!("Skip function {:?}", ir_function.name); + continue; + } + + // Skip functions that that are not compatible with this entry point's stage. + // + // When validation is enabled, it rejects modules whose entry points try to call + // incompatible functions, so if we got this far, then any functions incompatible + // with our selected entry point must not be used. + // + // When validation is disabled, `fun_info.available_stages` is always just + // `ShaderStages::all()`, so this will write all functions in the module, and + // the downstream GLSL compiler will catch any problems. + if !info.available_stages.contains(ep_info.available_stages) { + continue; + } + } + let id = self.write_function(ir_function, info, ir_module, None, &debug_info_inner)?; + self.lookup_function.insert(handle, id); + } + + // write all or one entry points + for (index, ir_ep) in ir_module.entry_points.iter().enumerate() { + if ep_index.is_some() && ep_index != Some(index) { + continue; + } + let info = mod_info.get_entry_point(index); + let ep_instruction = + self.write_entry_point(ir_ep, info, ir_module, &debug_info_inner)?; + ep_instruction.to_words(&mut self.logical_layout.entry_points); + } + + for capability in self.capabilities_used.iter() { + Instruction::capability(*capability).to_words(&mut self.logical_layout.capabilities); + } + for extension in self.extensions_used.iter() { + Instruction::extension(extension).to_words(&mut self.logical_layout.extensions); + } + if ir_module.entry_points.is_empty() { + // SPIR-V doesn't like modules without entry points + Instruction::capability(spirv::Capability::Linkage) + .to_words(&mut self.logical_layout.capabilities); + } + + let addressing_model = spirv::AddressingModel::Logical; + let memory_model = spirv::MemoryModel::GLSL450; + //self.check(addressing_model.required_capabilities())?; + //self.check(memory_model.required_capabilities())?; + + Instruction::memory_model(addressing_model, memory_model) + .to_words(&mut self.logical_layout.memory_model); + + if self.flags.contains(WriterFlags::DEBUG) { + for debug in self.debugs.iter() { + debug.to_words(&mut self.logical_layout.debugs); + } + } + + for annotation in self.annotations.iter() { + annotation.to_words(&mut self.logical_layout.annotations); + } + + Ok(()) + } + + pub fn write( + &mut self, + ir_module: &crate::Module, + info: &ModuleInfo, + pipeline_options: Option<&PipelineOptions>, + debug_info: &Option, + words: &mut Vec, + ) -> Result<(), Error> { + self.reset(); + + // Try to find the entry point and corresponding index + let ep_index = match pipeline_options { + Some(po) => { + let index = ir_module + .entry_points + .iter() + .position(|ep| po.shader_stage == ep.stage && po.entry_point == ep.name) + .ok_or(Error::EntryPointNotFound)?; + Some(index) + } + None => None, + }; + + self.write_logical_layout(ir_module, info, ep_index, debug_info)?; + self.write_physical_layout(); + + self.physical_layout.in_words(words); + self.logical_layout.in_words(words); + Ok(()) + } + + /// Return the set of capabilities the last module written used. + pub const fn get_capabilities_used(&self) -> &crate::FastIndexSet { + &self.capabilities_used + } + + pub fn decorate_non_uniform_binding_array_access(&mut self, id: Word) -> Result<(), Error> { + self.require_any("NonUniformEXT", &[spirv::Capability::ShaderNonUniform])?; + self.use_extension("SPV_EXT_descriptor_indexing"); + self.decorate(id, spirv::Decoration::NonUniform, &[]); + Ok(()) + } +} + +#[test] +fn test_write_physical_layout() { + let mut writer = Writer::new(&Options::default()).unwrap(); + assert_eq!(writer.physical_layout.bound, 0); + writer.write_physical_layout(); + assert_eq!(writer.physical_layout.bound, 3); +} diff --git a/naga/src/back/wgsl/mod.rs b/naga/src/back/wgsl/mod.rs new file mode 100644 index 0000000000..d731b1ca0c --- /dev/null +++ b/naga/src/back/wgsl/mod.rs @@ -0,0 +1,52 @@ +/*! +Backend for [WGSL][wgsl] (WebGPU Shading Language). + +[wgsl]: https://gpuweb.github.io/gpuweb/wgsl.html +*/ + +mod writer; + +use thiserror::Error; + +pub use writer::{Writer, WriterFlags}; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + FmtError(#[from] std::fmt::Error), + #[error("{0}")] + Custom(String), + #[error("{0}")] + Unimplemented(String), // TODO: Error used only during development + #[error("Unsupported math function: {0:?}")] + UnsupportedMathFunction(crate::MathFunction), + #[error("Unsupported relational function: {0:?}")] + UnsupportedRelationalFunction(crate::RelationalFunction), +} + +pub fn write_string( + module: &crate::Module, + info: &crate::valid::ModuleInfo, + flags: WriterFlags, +) -> Result { + let mut w = Writer::new(String::new(), flags); + w.write(module, info)?; + let output = w.finish(); + Ok(output) +} + +impl crate::AtomicFunction { + const fn to_wgsl(self) -> &'static str { + match self { + Self::Add => "Add", + Self::Subtract => "Sub", + Self::And => "And", + Self::InclusiveOr => "Or", + Self::ExclusiveOr => "Xor", + Self::Min => "Min", + Self::Max => "Max", + Self::Exchange { compare: None } => "Exchange", + Self::Exchange { .. } => "CompareExchangeWeak", + } + } +} diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs new file mode 100644 index 0000000000..c737934f5e --- /dev/null +++ b/naga/src/back/wgsl/writer.rs @@ -0,0 +1,1961 @@ +use super::Error; +use crate::{ + back, + proc::{self, NameKey}, + valid, Handle, Module, ShaderStage, TypeInner, +}; +use std::fmt::Write; + +/// Shorthand result used internally by the backend +type BackendResult = Result<(), Error>; + +/// WGSL [attribute](https://gpuweb.github.io/gpuweb/wgsl/#attributes) +enum Attribute { + Binding(u32), + BuiltIn(crate::BuiltIn), + Group(u32), + Invariant, + Interpolate(Option, Option), + Location(u32), + SecondBlendSource, + Stage(ShaderStage), + WorkGroupSize([u32; 3]), +} + +/// The WGSL form that `write_expr_with_indirection` should use to render a Naga +/// expression. +/// +/// Sometimes a Naga `Expression` alone doesn't provide enough information to +/// choose the right rendering for it in WGSL. For example, one natural WGSL +/// rendering of a Naga `LocalVariable(x)` expression might be `&x`, since +/// `LocalVariable` produces a pointer to the local variable's storage. But when +/// rendering a `Store` statement, the `pointer` operand must be the left hand +/// side of a WGSL assignment, so the proper rendering is `x`. +/// +/// The caller of `write_expr_with_indirection` must provide an `Expected` value +/// to indicate how ambiguous expressions should be rendered. +#[derive(Clone, Copy, Debug)] +enum Indirection { + /// Render pointer-construction expressions as WGSL `ptr`-typed expressions. + /// + /// This is the right choice for most cases. Whenever a Naga pointer + /// expression is not the `pointer` operand of a `Load` or `Store`, it + /// must be a WGSL pointer expression. + Ordinary, + + /// Render pointer-construction expressions as WGSL reference-typed + /// expressions. + /// + /// For example, this is the right choice for the `pointer` operand when + /// rendering a `Store` statement as a WGSL assignment. + Reference, +} + +bitflags::bitflags! { + #[cfg_attr(feature = "serialize", derive(serde::Serialize))] + #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct WriterFlags: u32 { + /// Always annotate the type information instead of inferring. + const EXPLICIT_TYPES = 0x1; + } +} + +pub struct Writer { + out: W, + flags: WriterFlags, + names: crate::FastHashMap, + namer: proc::Namer, + named_expressions: crate::NamedExpressions, + ep_results: Vec<(ShaderStage, Handle)>, +} + +impl Writer { + pub fn new(out: W, flags: WriterFlags) -> Self { + Writer { + out, + flags, + names: crate::FastHashMap::default(), + namer: proc::Namer::default(), + named_expressions: crate::NamedExpressions::default(), + ep_results: vec![], + } + } + + fn reset(&mut self, module: &Module) { + self.names.clear(); + self.namer.reset( + module, + crate::keywords::wgsl::RESERVED, + // an identifier must not start with two underscore + &[], + &[], + &["__"], + &mut self.names, + ); + self.named_expressions.clear(); + self.ep_results.clear(); + } + + fn is_builtin_wgsl_struct(&self, module: &Module, handle: Handle) -> bool { + module + .special_types + .predeclared_types + .values() + .any(|t| *t == handle) + } + + pub fn write(&mut self, module: &Module, info: &valid::ModuleInfo) -> BackendResult { + self.reset(module); + + // Save all ep result types + for (_, ep) in module.entry_points.iter().enumerate() { + if let Some(ref result) = ep.function.result { + self.ep_results.push((ep.stage, result.ty)); + } + } + + // Write all structs + for (handle, ty) in module.types.iter() { + if let TypeInner::Struct { ref members, .. } = ty.inner { + { + if !self.is_builtin_wgsl_struct(module, handle) { + self.write_struct(module, handle, members)?; + writeln!(self.out)?; + } + } + } + } + + // Write all named constants + let mut constants = module + .constants + .iter() + .filter(|&(_, c)| c.name.is_some()) + .peekable(); + while let Some((handle, _)) = constants.next() { + self.write_global_constant(module, handle)?; + // Add extra newline for readability on last iteration + if constants.peek().is_none() { + writeln!(self.out)?; + } + } + + // Write all globals + for (ty, global) in module.global_variables.iter() { + self.write_global(module, global, ty)?; + } + + if !module.global_variables.is_empty() { + // Add extra newline for readability + writeln!(self.out)?; + } + + // Write all regular functions + for (handle, function) in module.functions.iter() { + let fun_info = &info[handle]; + + let func_ctx = back::FunctionCtx { + ty: back::FunctionType::Function(handle), + info: fun_info, + expressions: &function.expressions, + named_expressions: &function.named_expressions, + }; + + // Write the function + self.write_function(module, function, &func_ctx)?; + + writeln!(self.out)?; + } + + // Write all entry points + for (index, ep) in module.entry_points.iter().enumerate() { + let attributes = match ep.stage { + ShaderStage::Vertex | ShaderStage::Fragment => vec![Attribute::Stage(ep.stage)], + ShaderStage::Compute => vec![ + Attribute::Stage(ShaderStage::Compute), + Attribute::WorkGroupSize(ep.workgroup_size), + ], + }; + + self.write_attributes(&attributes)?; + // Add a newline after attribute + writeln!(self.out)?; + + let func_ctx = back::FunctionCtx { + ty: back::FunctionType::EntryPoint(index as u16), + info: info.get_entry_point(index), + expressions: &ep.function.expressions, + named_expressions: &ep.function.named_expressions, + }; + self.write_function(module, &ep.function, &func_ctx)?; + + if index < module.entry_points.len() - 1 { + writeln!(self.out)?; + } + } + + Ok(()) + } + + /// Helper method used to write struct name + /// + /// # Notes + /// Adds no trailing or leading whitespace + fn write_struct_name(&mut self, module: &Module, handle: Handle) -> BackendResult { + if module.types[handle].name.is_none() { + if let Some(&(stage, _)) = self.ep_results.iter().find(|&&(_, ty)| ty == handle) { + let name = match stage { + ShaderStage::Compute => "ComputeOutput", + ShaderStage::Fragment => "FragmentOutput", + ShaderStage::Vertex => "VertexOutput", + }; + + write!(self.out, "{name}")?; + return Ok(()); + } + } + + write!(self.out, "{}", self.names[&NameKey::Type(handle)])?; + + Ok(()) + } + + /// Helper method used to write + /// [functions](https://gpuweb.github.io/gpuweb/wgsl/#functions) + /// + /// # Notes + /// Ends in a newline + fn write_function( + &mut self, + module: &Module, + func: &crate::Function, + func_ctx: &back::FunctionCtx<'_>, + ) -> BackendResult { + let func_name = match func_ctx.ty { + back::FunctionType::EntryPoint(index) => &self.names[&NameKey::EntryPoint(index)], + back::FunctionType::Function(handle) => &self.names[&NameKey::Function(handle)], + }; + + // Write function name + write!(self.out, "fn {func_name}(")?; + + // Write function arguments + for (index, arg) in func.arguments.iter().enumerate() { + // Write argument attribute if a binding is present + if let Some(ref binding) = arg.binding { + self.write_attributes(&map_binding_to_attribute(binding))?; + } + // Write argument name + let argument_name = &self.names[&func_ctx.argument_key(index as u32)]; + + write!(self.out, "{argument_name}: ")?; + // Write argument type + self.write_type(module, arg.ty)?; + if index < func.arguments.len() - 1 { + // Add a separator between args + write!(self.out, ", ")?; + } + } + + write!(self.out, ")")?; + + // Write function return type + if let Some(ref result) = func.result { + write!(self.out, " -> ")?; + if let Some(ref binding) = result.binding { + self.write_attributes(&map_binding_to_attribute(binding))?; + } + self.write_type(module, result.ty)?; + } + + write!(self.out, " {{")?; + writeln!(self.out)?; + + // Write function local variables + for (handle, local) in func.local_variables.iter() { + // Write indentation (only for readability) + write!(self.out, "{}", back::INDENT)?; + + // Write the local name + // The leading space is important + write!(self.out, "var {}: ", self.names[&func_ctx.name_key(handle)])?; + + // Write the local type + self.write_type(module, local.ty)?; + + // Write the local initializer if needed + if let Some(init) = local.init { + // Put the equal signal only if there's a initializer + // The leading and trailing spaces aren't needed but help with readability + write!(self.out, " = ")?; + + // Write the constant + // `write_constant` adds no trailing or leading space/newline + self.write_expr(module, init, func_ctx)?; + } + + // Finish the local with `;` and add a newline (only for readability) + writeln!(self.out, ";")? + } + + if !func.local_variables.is_empty() { + writeln!(self.out)?; + } + + // Write the function body (statement list) + for sta in func.body.iter() { + // The indentation should always be 1 when writing the function body + self.write_stmt(module, sta, func_ctx, back::Level(1))?; + } + + writeln!(self.out, "}}")?; + + self.named_expressions.clear(); + + Ok(()) + } + + /// Helper method to write a attribute + fn write_attributes(&mut self, attributes: &[Attribute]) -> BackendResult { + for attribute in attributes { + match *attribute { + Attribute::Location(id) => write!(self.out, "@location({id}) ")?, + Attribute::SecondBlendSource => write!(self.out, "@second_blend_source ")?, + Attribute::BuiltIn(builtin_attrib) => { + let builtin = builtin_str(builtin_attrib)?; + write!(self.out, "@builtin({builtin}) ")?; + } + Attribute::Stage(shader_stage) => { + let stage_str = match shader_stage { + ShaderStage::Vertex => "vertex", + ShaderStage::Fragment => "fragment", + ShaderStage::Compute => "compute", + }; + write!(self.out, "@{stage_str} ")?; + } + Attribute::WorkGroupSize(size) => { + write!( + self.out, + "@workgroup_size({}, {}, {}) ", + size[0], size[1], size[2] + )?; + } + Attribute::Binding(id) => write!(self.out, "@binding({id}) ")?, + Attribute::Group(id) => write!(self.out, "@group({id}) ")?, + Attribute::Invariant => write!(self.out, "@invariant ")?, + Attribute::Interpolate(interpolation, sampling) => { + if sampling.is_some() && sampling != Some(crate::Sampling::Center) { + write!( + self.out, + "@interpolate({}, {}) ", + interpolation_str( + interpolation.unwrap_or(crate::Interpolation::Perspective) + ), + sampling_str(sampling.unwrap_or(crate::Sampling::Center)) + )?; + } else if interpolation.is_some() + && interpolation != Some(crate::Interpolation::Perspective) + { + write!( + self.out, + "@interpolate({}) ", + interpolation_str( + interpolation.unwrap_or(crate::Interpolation::Perspective) + ) + )?; + } + } + }; + } + Ok(()) + } + + /// Helper method used to write structs + /// + /// # Notes + /// Ends in a newline + fn write_struct( + &mut self, + module: &Module, + handle: Handle, + members: &[crate::StructMember], + ) -> BackendResult { + write!(self.out, "struct ")?; + self.write_struct_name(module, handle)?; + write!(self.out, " {{")?; + writeln!(self.out)?; + for (index, member) in members.iter().enumerate() { + // The indentation is only for readability + write!(self.out, "{}", back::INDENT)?; + if let Some(ref binding) = member.binding { + self.write_attributes(&map_binding_to_attribute(binding))?; + } + // Write struct member name and type + let member_name = &self.names[&NameKey::StructMember(handle, index as u32)]; + write!(self.out, "{member_name}: ")?; + self.write_type(module, member.ty)?; + write!(self.out, ",")?; + writeln!(self.out)?; + } + + write!(self.out, "}}")?; + + writeln!(self.out)?; + + Ok(()) + } + + /// Helper method used to write non image/sampler types + /// + /// # Notes + /// Adds no trailing or leading whitespace + fn write_type(&mut self, module: &Module, ty: Handle) -> BackendResult { + let inner = &module.types[ty].inner; + match *inner { + TypeInner::Struct { .. } => self.write_struct_name(module, ty)?, + ref other => self.write_value_type(module, other)?, + } + + Ok(()) + } + + /// Helper method used to write value types + /// + /// # Notes + /// Adds no trailing or leading whitespace + fn write_value_type(&mut self, module: &Module, inner: &TypeInner) -> BackendResult { + match *inner { + TypeInner::Vector { size, scalar } => write!( + self.out, + "vec{}<{}>", + back::vector_size_str(size), + scalar_kind_str(scalar), + )?, + TypeInner::Sampler { comparison: false } => { + write!(self.out, "sampler")?; + } + TypeInner::Sampler { comparison: true } => { + write!(self.out, "sampler_comparison")?; + } + TypeInner::Image { + dim, + arrayed, + class, + } => { + // More about texture types: https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type + use crate::ImageClass as Ic; + + let dim_str = image_dimension_str(dim); + let arrayed_str = if arrayed { "_array" } else { "" }; + let (class_str, multisampled_str, format_str, storage_str) = match class { + Ic::Sampled { kind, multi } => ( + "", + if multi { "multisampled_" } else { "" }, + scalar_kind_str(crate::Scalar { kind, width: 4 }), + "", + ), + Ic::Depth { multi } => { + ("depth_", if multi { "multisampled_" } else { "" }, "", "") + } + Ic::Storage { format, access } => ( + "storage_", + "", + storage_format_str(format), + if access.contains(crate::StorageAccess::LOAD | crate::StorageAccess::STORE) + { + ",read_write" + } else if access.contains(crate::StorageAccess::LOAD) { + ",read" + } else { + ",write" + }, + ), + }; + write!( + self.out, + "texture_{class_str}{multisampled_str}{dim_str}{arrayed_str}" + )?; + + if !format_str.is_empty() { + write!(self.out, "<{format_str}{storage_str}>")?; + } + } + TypeInner::Scalar(scalar) => { + write!(self.out, "{}", scalar_kind_str(scalar))?; + } + TypeInner::Atomic(scalar) => { + write!(self.out, "atomic<{}>", scalar_kind_str(scalar))?; + } + TypeInner::Array { + base, + size, + stride: _, + } => { + // More info https://gpuweb.github.io/gpuweb/wgsl/#array-types + // array -- Constant array + // array -- Dynamic array + write!(self.out, "array<")?; + match size { + crate::ArraySize::Constant(len) => { + self.write_type(module, base)?; + write!(self.out, ", {len}")?; + } + crate::ArraySize::Dynamic => { + self.write_type(module, base)?; + } + } + write!(self.out, ">")?; + } + TypeInner::BindingArray { base, size } => { + // More info https://github.com/gpuweb/gpuweb/issues/2105 + write!(self.out, "binding_array<")?; + match size { + crate::ArraySize::Constant(len) => { + self.write_type(module, base)?; + write!(self.out, ", {len}")?; + } + crate::ArraySize::Dynamic => { + self.write_type(module, base)?; + } + } + write!(self.out, ">")?; + } + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + write!( + self.out, + "mat{}x{}<{}>", + back::vector_size_str(columns), + back::vector_size_str(rows), + scalar_kind_str(scalar) + )?; + } + TypeInner::Pointer { base, space } => { + let (address, maybe_access) = address_space_str(space); + // Everything but `AddressSpace::Handle` gives us a `address` name, but + // Naga IR never produces pointers to handles, so it doesn't matter much + // how we write such a type. Just write it as the base type alone. + if let Some(space) = address { + write!(self.out, "ptr<{space}, ")?; + } + self.write_type(module, base)?; + if address.is_some() { + if let Some(access) = maybe_access { + write!(self.out, ", {access}")?; + } + write!(self.out, ">")?; + } + } + TypeInner::ValuePointer { + size: None, + scalar, + space, + } => { + let (address, maybe_access) = address_space_str(space); + if let Some(space) = address { + write!(self.out, "ptr<{}, {}", space, scalar_kind_str(scalar))?; + if let Some(access) = maybe_access { + write!(self.out, ", {access}")?; + } + write!(self.out, ">")?; + } else { + return Err(Error::Unimplemented(format!( + "ValuePointer to AddressSpace::Handle {inner:?}" + ))); + } + } + TypeInner::ValuePointer { + size: Some(size), + scalar, + space, + } => { + let (address, maybe_access) = address_space_str(space); + if let Some(space) = address { + write!( + self.out, + "ptr<{}, vec{}<{}>", + space, + back::vector_size_str(size), + scalar_kind_str(scalar) + )?; + if let Some(access) = maybe_access { + write!(self.out, ", {access}")?; + } + write!(self.out, ">")?; + } else { + return Err(Error::Unimplemented(format!( + "ValuePointer to AddressSpace::Handle {inner:?}" + ))); + } + write!(self.out, ">")?; + } + _ => { + return Err(Error::Unimplemented(format!("write_value_type {inner:?}"))); + } + } + + Ok(()) + } + /// Helper method used to write statements + /// + /// # Notes + /// Always adds a newline + fn write_stmt( + &mut self, + module: &Module, + stmt: &crate::Statement, + func_ctx: &back::FunctionCtx<'_>, + level: back::Level, + ) -> BackendResult { + use crate::{Expression, Statement}; + + match *stmt { + Statement::Emit(ref range) => { + for handle in range.clone() { + let info = &func_ctx.info[handle]; + let expr_name = if let Some(name) = func_ctx.named_expressions.get(&handle) { + // Front end provides names for all variables at the start of writing. + // But we write them to step by step. We need to recache them + // Otherwise, we could accidentally write variable name instead of full expression. + // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords. + Some(self.namer.call(name)) + } else { + let expr = &func_ctx.expressions[handle]; + let min_ref_count = expr.bake_ref_count(); + // Forcefully creating baking expressions in some cases to help with readability + let required_baking_expr = match *expr { + Expression::ImageLoad { .. } + | Expression::ImageQuery { .. } + | Expression::ImageSample { .. } => true, + _ => false, + }; + if min_ref_count <= info.ref_count || required_baking_expr { + Some(format!("{}{}", back::BAKE_PREFIX, handle.index())) + } else { + None + } + }; + + if let Some(name) = expr_name { + write!(self.out, "{level}")?; + self.start_named_expr(module, handle, func_ctx, &name)?; + self.write_expr(module, handle, func_ctx)?; + self.named_expressions.insert(handle, name); + writeln!(self.out, ";")?; + } + } + } + // TODO: copy-paste from glsl-out + Statement::If { + condition, + ref accept, + ref reject, + } => { + write!(self.out, "{level}")?; + write!(self.out, "if ")?; + self.write_expr(module, condition, func_ctx)?; + writeln!(self.out, " {{")?; + + let l2 = level.next(); + for sta in accept { + // Increase indentation to help with readability + self.write_stmt(module, sta, func_ctx, l2)?; + } + + // If there are no statements in the reject block we skip writing it + // This is only for readability + if !reject.is_empty() { + writeln!(self.out, "{level}}} else {{")?; + + for sta in reject { + // Increase indentation to help with readability + self.write_stmt(module, sta, func_ctx, l2)?; + } + } + + writeln!(self.out, "{level}}}")? + } + Statement::Return { value } => { + write!(self.out, "{level}")?; + write!(self.out, "return")?; + if let Some(return_value) = value { + // The leading space is important + write!(self.out, " ")?; + self.write_expr(module, return_value, func_ctx)?; + } + writeln!(self.out, ";")?; + } + // TODO: copy-paste from glsl-out + Statement::Kill => { + write!(self.out, "{level}")?; + writeln!(self.out, "discard;")? + } + Statement::Store { pointer, value } => { + write!(self.out, "{level}")?; + + let is_atomic_pointer = func_ctx + .resolve_type(pointer, &module.types) + .is_atomic_pointer(&module.types); + + if is_atomic_pointer { + write!(self.out, "atomicStore(")?; + self.write_expr(module, pointer, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, value, func_ctx)?; + write!(self.out, ")")?; + } else { + self.write_expr_with_indirection( + module, + pointer, + func_ctx, + Indirection::Reference, + )?; + write!(self.out, " = ")?; + self.write_expr(module, value, func_ctx)?; + } + writeln!(self.out, ";")? + } + Statement::Call { + function, + ref arguments, + result, + } => { + write!(self.out, "{level}")?; + if let Some(expr) = result { + let name = format!("{}{}", back::BAKE_PREFIX, expr.index()); + self.start_named_expr(module, expr, func_ctx, &name)?; + self.named_expressions.insert(expr, name); + } + let func_name = &self.names[&NameKey::Function(function)]; + write!(self.out, "{func_name}(")?; + for (index, &argument) in arguments.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + self.write_expr(module, argument, func_ctx)?; + } + writeln!(self.out, ");")? + } + Statement::Atomic { + pointer, + ref fun, + value, + result, + } => { + write!(self.out, "{level}")?; + let res_name = format!("{}{}", back::BAKE_PREFIX, result.index()); + self.start_named_expr(module, result, func_ctx, &res_name)?; + self.named_expressions.insert(result, res_name); + + let fun_str = fun.to_wgsl(); + write!(self.out, "atomic{fun_str}(")?; + self.write_expr(module, pointer, func_ctx)?; + if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun { + write!(self.out, ", ")?; + self.write_expr(module, cmp, func_ctx)?; + } + write!(self.out, ", ")?; + self.write_expr(module, value, func_ctx)?; + writeln!(self.out, ");")? + } + Statement::WorkGroupUniformLoad { pointer, result } => { + write!(self.out, "{level}")?; + // TODO: Obey named expressions here. + let res_name = format!("{}{}", back::BAKE_PREFIX, result.index()); + self.start_named_expr(module, result, func_ctx, &res_name)?; + self.named_expressions.insert(result, res_name); + write!(self.out, "workgroupUniformLoad(")?; + self.write_expr(module, pointer, func_ctx)?; + writeln!(self.out, ");")?; + } + Statement::ImageStore { + image, + coordinate, + array_index, + value, + } => { + write!(self.out, "{level}")?; + write!(self.out, "textureStore(")?; + self.write_expr(module, image, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, coordinate, func_ctx)?; + if let Some(array_index_expr) = array_index { + write!(self.out, ", ")?; + self.write_expr(module, array_index_expr, func_ctx)?; + } + write!(self.out, ", ")?; + self.write_expr(module, value, func_ctx)?; + writeln!(self.out, ");")?; + } + // TODO: copy-paste from glsl-out + Statement::Block(ref block) => { + write!(self.out, "{level}")?; + writeln!(self.out, "{{")?; + for sta in block.iter() { + // Increase the indentation to help with readability + self.write_stmt(module, sta, func_ctx, level.next())? + } + writeln!(self.out, "{level}}}")? + } + Statement::Switch { + selector, + ref cases, + } => { + // Start the switch + write!(self.out, "{level}")?; + write!(self.out, "switch ")?; + self.write_expr(module, selector, func_ctx)?; + writeln!(self.out, " {{")?; + + let l2 = level.next(); + let mut new_case = true; + for case in cases { + if case.fall_through && !case.body.is_empty() { + // TODO: we could do the same workaround as we did for the HLSL backend + return Err(Error::Unimplemented( + "fall-through switch case block".into(), + )); + } + + match case.value { + crate::SwitchValue::I32(value) => { + if new_case { + write!(self.out, "{l2}case ")?; + } + write!(self.out, "{value}")?; + } + crate::SwitchValue::U32(value) => { + if new_case { + write!(self.out, "{l2}case ")?; + } + write!(self.out, "{value}u")?; + } + crate::SwitchValue::Default => { + if new_case { + if case.fall_through { + write!(self.out, "{l2}case ")?; + } else { + write!(self.out, "{l2}")?; + } + } + write!(self.out, "default")?; + } + } + + new_case = !case.fall_through; + + if case.fall_through { + write!(self.out, ", ")?; + } else { + writeln!(self.out, ": {{")?; + } + + for sta in case.body.iter() { + self.write_stmt(module, sta, func_ctx, l2.next())?; + } + + if !case.fall_through { + writeln!(self.out, "{l2}}}")?; + } + } + + writeln!(self.out, "{level}}}")? + } + Statement::Loop { + ref body, + ref continuing, + break_if, + } => { + write!(self.out, "{level}")?; + writeln!(self.out, "loop {{")?; + + let l2 = level.next(); + for sta in body.iter() { + self.write_stmt(module, sta, func_ctx, l2)?; + } + + // The continuing is optional so we don't need to write it if + // it is empty, but the `break if` counts as a continuing statement + // so even if `continuing` is empty we must generate it if a + // `break if` exists + if !continuing.is_empty() || break_if.is_some() { + writeln!(self.out, "{l2}continuing {{")?; + for sta in continuing.iter() { + self.write_stmt(module, sta, func_ctx, l2.next())?; + } + + // The `break if` is always the last + // statement of the `continuing` block + if let Some(condition) = break_if { + // The trailing space is important + write!(self.out, "{}break if ", l2.next())?; + self.write_expr(module, condition, func_ctx)?; + // Close the `break if` statement + writeln!(self.out, ";")?; + } + + writeln!(self.out, "{l2}}}")?; + } + + writeln!(self.out, "{level}}}")? + } + Statement::Break => { + writeln!(self.out, "{level}break;")?; + } + Statement::Continue => { + writeln!(self.out, "{level}continue;")?; + } + Statement::Barrier(barrier) => { + if barrier.contains(crate::Barrier::STORAGE) { + writeln!(self.out, "{level}storageBarrier();")?; + } + + if barrier.contains(crate::Barrier::WORK_GROUP) { + writeln!(self.out, "{level}workgroupBarrier();")?; + } + } + Statement::RayQuery { .. } => unreachable!(), + } + + Ok(()) + } + + /// Return the sort of indirection that `expr`'s plain form evaluates to. + /// + /// An expression's 'plain form' is the most general rendition of that + /// expression into WGSL, lacking `&` or `*` operators: + /// + /// - The plain form of `LocalVariable(x)` is simply `x`, which is a reference + /// to the local variable's storage. + /// + /// - The plain form of `GlobalVariable(g)` is simply `g`, which is usually a + /// reference to the global variable's storage. However, globals in the + /// `Handle` address space are immutable, and `GlobalVariable` expressions for + /// those produce the value directly, not a pointer to it. Such + /// `GlobalVariable` expressions are `Ordinary`. + /// + /// - `Access` and `AccessIndex` are `Reference` when their `base` operand is a + /// pointer. If they are applied directly to a composite value, they are + /// `Ordinary`. + /// + /// Note that `FunctionArgument` expressions are never `Reference`, even when + /// the argument's type is `Pointer`. `FunctionArgument` always evaluates to the + /// argument's value directly, so any pointer it produces is merely the value + /// passed by the caller. + fn plain_form_indirection( + &self, + expr: Handle, + module: &Module, + func_ctx: &back::FunctionCtx<'_>, + ) -> Indirection { + use crate::Expression as Ex; + + // Named expressions are `let` expressions, which apply the Load Rule, + // so if their type is a Naga pointer, then that must be a WGSL pointer + // as well. + if self.named_expressions.contains_key(&expr) { + return Indirection::Ordinary; + } + + match func_ctx.expressions[expr] { + Ex::LocalVariable(_) => Indirection::Reference, + Ex::GlobalVariable(handle) => { + let global = &module.global_variables[handle]; + match global.space { + crate::AddressSpace::Handle => Indirection::Ordinary, + _ => Indirection::Reference, + } + } + Ex::Access { base, .. } | Ex::AccessIndex { base, .. } => { + let base_ty = func_ctx.resolve_type(base, &module.types); + match *base_ty { + crate::TypeInner::Pointer { .. } | crate::TypeInner::ValuePointer { .. } => { + Indirection::Reference + } + _ => Indirection::Ordinary, + } + } + _ => Indirection::Ordinary, + } + } + + fn start_named_expr( + &mut self, + module: &Module, + handle: Handle, + func_ctx: &back::FunctionCtx, + name: &str, + ) -> BackendResult { + // Write variable name + write!(self.out, "let {name}")?; + if self.flags.contains(WriterFlags::EXPLICIT_TYPES) { + write!(self.out, ": ")?; + let ty = &func_ctx.info[handle].ty; + // Write variable type + match *ty { + proc::TypeResolution::Handle(handle) => { + self.write_type(module, handle)?; + } + proc::TypeResolution::Value(ref inner) => { + self.write_value_type(module, inner)?; + } + } + } + + write!(self.out, " = ")?; + Ok(()) + } + + /// Write the ordinary WGSL form of `expr`. + /// + /// See `write_expr_with_indirection` for details. + fn write_expr( + &mut self, + module: &Module, + expr: Handle, + func_ctx: &back::FunctionCtx<'_>, + ) -> BackendResult { + self.write_expr_with_indirection(module, expr, func_ctx, Indirection::Ordinary) + } + + /// Write `expr` as a WGSL expression with the requested indirection. + /// + /// In terms of the WGSL grammar, the resulting expression is a + /// `singular_expression`. It may be parenthesized. This makes it suitable + /// for use as the operand of a unary or binary operator without worrying + /// about precedence. + /// + /// This does not produce newlines or indentation. + /// + /// The `requested` argument indicates (roughly) whether Naga + /// `Pointer`-valued expressions represent WGSL references or pointers. See + /// `Indirection` for details. + fn write_expr_with_indirection( + &mut self, + module: &Module, + expr: Handle, + func_ctx: &back::FunctionCtx<'_>, + requested: Indirection, + ) -> BackendResult { + // If the plain form of the expression is not what we need, emit the + // operator necessary to correct that. + let plain = self.plain_form_indirection(expr, module, func_ctx); + match (requested, plain) { + (Indirection::Ordinary, Indirection::Reference) => { + write!(self.out, "(&")?; + self.write_expr_plain_form(module, expr, func_ctx, plain)?; + write!(self.out, ")")?; + } + (Indirection::Reference, Indirection::Ordinary) => { + write!(self.out, "(*")?; + self.write_expr_plain_form(module, expr, func_ctx, plain)?; + write!(self.out, ")")?; + } + (_, _) => self.write_expr_plain_form(module, expr, func_ctx, plain)?, + } + + Ok(()) + } + + fn write_const_expression( + &mut self, + module: &Module, + expr: Handle, + ) -> BackendResult { + self.write_possibly_const_expression( + module, + expr, + &module.const_expressions, + |writer, expr| writer.write_const_expression(module, expr), + ) + } + + fn write_possibly_const_expression( + &mut self, + module: &Module, + expr: Handle, + expressions: &crate::Arena, + write_expression: E, + ) -> BackendResult + where + E: Fn(&mut Self, Handle) -> BackendResult, + { + use crate::Expression; + + match expressions[expr] { + Expression::Literal(literal) => match literal { + crate::Literal::F32(value) => write!(self.out, "{}f", value)?, + crate::Literal::U32(value) => write!(self.out, "{}u", value)?, + crate::Literal::I32(value) => { + // `-2147483648i` is not valid WGSL. The most negative `i32` + // value can only be expressed in WGSL using AbstractInt and + // a unary negation operator. + if value == i32::MIN { + write!(self.out, "i32(-2147483648)")?; + } else { + write!(self.out, "{}i", value)?; + } + } + crate::Literal::Bool(value) => write!(self.out, "{}", value)?, + crate::Literal::F64(value) => write!(self.out, "{:?}lf", value)?, + crate::Literal::I64(_) => { + return Err(Error::Custom("unsupported i64 literal".to_string())); + } + crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { + return Err(Error::Custom( + "Abstract types should not appear in IR presented to backends".into(), + )); + } + }, + Expression::Constant(handle) => { + let constant = &module.constants[handle]; + if constant.name.is_some() { + write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?; + } else { + self.write_const_expression(module, constant.init)?; + } + } + Expression::ZeroValue(ty) => { + self.write_type(module, ty)?; + write!(self.out, "()")?; + } + Expression::Compose { ty, ref components } => { + self.write_type(module, ty)?; + write!(self.out, "(")?; + for (index, component) in components.iter().enumerate() { + if index != 0 { + write!(self.out, ", ")?; + } + write_expression(self, *component)?; + } + write!(self.out, ")")? + } + Expression::Splat { size, value } => { + let size = back::vector_size_str(size); + write!(self.out, "vec{size}(")?; + write_expression(self, value)?; + write!(self.out, ")")?; + } + _ => unreachable!(), + } + + Ok(()) + } + + /// Write the 'plain form' of `expr`. + /// + /// An expression's 'plain form' is the most general rendition of that + /// expression into WGSL, lacking `&` or `*` operators. The plain forms of + /// `LocalVariable(x)` and `GlobalVariable(g)` are simply `x` and `g`. Such + /// Naga expressions represent both WGSL pointers and references; it's the + /// caller's responsibility to distinguish those cases appropriately. + fn write_expr_plain_form( + &mut self, + module: &Module, + expr: Handle, + func_ctx: &back::FunctionCtx<'_>, + indirection: Indirection, + ) -> BackendResult { + use crate::Expression; + + if let Some(name) = self.named_expressions.get(&expr) { + write!(self.out, "{name}")?; + return Ok(()); + } + + let expression = &func_ctx.expressions[expr]; + + // Write the plain WGSL form of a Naga expression. + // + // The plain form of `LocalVariable` and `GlobalVariable` expressions is + // simply the variable name; `*` and `&` operators are never emitted. + // + // The plain form of `Access` and `AccessIndex` expressions are WGSL + // `postfix_expression` forms for member/component access and + // subscripting. + match *expression { + Expression::Literal(_) + | Expression::Constant(_) + | Expression::ZeroValue(_) + | Expression::Compose { .. } + | Expression::Splat { .. } => { + self.write_possibly_const_expression( + module, + expr, + func_ctx.expressions, + |writer, expr| writer.write_expr(module, expr, func_ctx), + )?; + } + Expression::FunctionArgument(pos) => { + let name_key = func_ctx.argument_key(pos); + let name = &self.names[&name_key]; + write!(self.out, "{name}")?; + } + Expression::Binary { op, left, right } => { + write!(self.out, "(")?; + self.write_expr(module, left, func_ctx)?; + write!(self.out, " {} ", back::binary_operation_str(op))?; + self.write_expr(module, right, func_ctx)?; + write!(self.out, ")")?; + } + Expression::Access { base, index } => { + self.write_expr_with_indirection(module, base, func_ctx, indirection)?; + write!(self.out, "[")?; + self.write_expr(module, index, func_ctx)?; + write!(self.out, "]")? + } + Expression::AccessIndex { base, index } => { + let base_ty_res = &func_ctx.info[base].ty; + let mut resolved = base_ty_res.inner_with(&module.types); + + self.write_expr_with_indirection(module, base, func_ctx, indirection)?; + + let base_ty_handle = match *resolved { + TypeInner::Pointer { base, space: _ } => { + resolved = &module.types[base].inner; + Some(base) + } + _ => base_ty_res.handle(), + }; + + match *resolved { + TypeInner::Vector { .. } => { + // Write vector access as a swizzle + write!(self.out, ".{}", back::COMPONENTS[index as usize])? + } + TypeInner::Matrix { .. } + | TypeInner::Array { .. } + | TypeInner::BindingArray { .. } + | TypeInner::ValuePointer { .. } => write!(self.out, "[{index}]")?, + TypeInner::Struct { .. } => { + // This will never panic in case the type is a `Struct`, this is not true + // for other types so we can only check while inside this match arm + let ty = base_ty_handle.unwrap(); + + write!( + self.out, + ".{}", + &self.names[&NameKey::StructMember(ty, index)] + )? + } + ref other => return Err(Error::Custom(format!("Cannot index {other:?}"))), + } + } + Expression::ImageSample { + image, + sampler, + gather: None, + coordinate, + array_index, + offset, + level, + depth_ref, + } => { + use crate::SampleLevel as Sl; + + let suffix_cmp = match depth_ref { + Some(_) => "Compare", + None => "", + }; + let suffix_level = match level { + Sl::Auto => "", + Sl::Zero | Sl::Exact(_) => "Level", + Sl::Bias(_) => "Bias", + Sl::Gradient { .. } => "Grad", + }; + + write!(self.out, "textureSample{suffix_cmp}{suffix_level}(")?; + self.write_expr(module, image, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, sampler, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, coordinate, func_ctx)?; + + if let Some(array_index) = array_index { + write!(self.out, ", ")?; + self.write_expr(module, array_index, func_ctx)?; + } + + if let Some(depth_ref) = depth_ref { + write!(self.out, ", ")?; + self.write_expr(module, depth_ref, func_ctx)?; + } + + match level { + Sl::Auto => {} + Sl::Zero => { + // Level 0 is implied for depth comparison + if depth_ref.is_none() { + write!(self.out, ", 0.0")?; + } + } + Sl::Exact(expr) => { + write!(self.out, ", ")?; + self.write_expr(module, expr, func_ctx)?; + } + Sl::Bias(expr) => { + write!(self.out, ", ")?; + self.write_expr(module, expr, func_ctx)?; + } + Sl::Gradient { x, y } => { + write!(self.out, ", ")?; + self.write_expr(module, x, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, y, func_ctx)?; + } + } + + if let Some(offset) = offset { + write!(self.out, ", ")?; + self.write_const_expression(module, offset)?; + } + + write!(self.out, ")")?; + } + + Expression::ImageSample { + image, + sampler, + gather: Some(component), + coordinate, + array_index, + offset, + level: _, + depth_ref, + } => { + let suffix_cmp = match depth_ref { + Some(_) => "Compare", + None => "", + }; + + write!(self.out, "textureGather{suffix_cmp}(")?; + match *func_ctx.resolve_type(image, &module.types) { + TypeInner::Image { + class: crate::ImageClass::Depth { multi: _ }, + .. + } => {} + _ => { + write!(self.out, "{}, ", component as u8)?; + } + } + self.write_expr(module, image, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, sampler, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, coordinate, func_ctx)?; + + if let Some(array_index) = array_index { + write!(self.out, ", ")?; + self.write_expr(module, array_index, func_ctx)?; + } + + if let Some(depth_ref) = depth_ref { + write!(self.out, ", ")?; + self.write_expr(module, depth_ref, func_ctx)?; + } + + if let Some(offset) = offset { + write!(self.out, ", ")?; + self.write_const_expression(module, offset)?; + } + + write!(self.out, ")")?; + } + Expression::ImageQuery { image, query } => { + use crate::ImageQuery as Iq; + + let texture_function = match query { + Iq::Size { .. } => "textureDimensions", + Iq::NumLevels => "textureNumLevels", + Iq::NumLayers => "textureNumLayers", + Iq::NumSamples => "textureNumSamples", + }; + + write!(self.out, "{texture_function}(")?; + self.write_expr(module, image, func_ctx)?; + if let Iq::Size { level: Some(level) } = query { + write!(self.out, ", ")?; + self.write_expr(module, level, func_ctx)?; + }; + write!(self.out, ")")?; + } + + Expression::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => { + write!(self.out, "textureLoad(")?; + self.write_expr(module, image, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, coordinate, func_ctx)?; + if let Some(array_index) = array_index { + write!(self.out, ", ")?; + self.write_expr(module, array_index, func_ctx)?; + } + if let Some(index) = sample.or(level) { + write!(self.out, ", ")?; + self.write_expr(module, index, func_ctx)?; + } + write!(self.out, ")")?; + } + Expression::GlobalVariable(handle) => { + let name = &self.names[&NameKey::GlobalVariable(handle)]; + write!(self.out, "{name}")?; + } + + Expression::As { + expr, + kind, + convert, + } => { + let inner = func_ctx.resolve_type(expr, &module.types); + match *inner { + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + let scalar = crate::Scalar { + kind, + width: convert.unwrap_or(scalar.width), + }; + let scalar_kind_str = scalar_kind_str(scalar); + write!( + self.out, + "mat{}x{}<{}>", + back::vector_size_str(columns), + back::vector_size_str(rows), + scalar_kind_str + )?; + } + TypeInner::Vector { + size, + scalar: crate::Scalar { width, .. }, + } => { + let scalar = crate::Scalar { + kind, + width: convert.unwrap_or(width), + }; + let vector_size_str = back::vector_size_str(size); + let scalar_kind_str = scalar_kind_str(scalar); + if convert.is_some() { + write!(self.out, "vec{vector_size_str}<{scalar_kind_str}>")?; + } else { + write!(self.out, "bitcast>")?; + } + } + TypeInner::Scalar(crate::Scalar { width, .. }) => { + let scalar = crate::Scalar { + kind, + width: convert.unwrap_or(width), + }; + let scalar_kind_str = scalar_kind_str(scalar); + if convert.is_some() { + write!(self.out, "{scalar_kind_str}")? + } else { + write!(self.out, "bitcast<{scalar_kind_str}>")? + } + } + _ => { + return Err(Error::Unimplemented(format!( + "write_expr expression::as {inner:?}" + ))); + } + }; + write!(self.out, "(")?; + self.write_expr(module, expr, func_ctx)?; + write!(self.out, ")")?; + } + Expression::Load { pointer } => { + let is_atomic_pointer = func_ctx + .resolve_type(pointer, &module.types) + .is_atomic_pointer(&module.types); + + if is_atomic_pointer { + write!(self.out, "atomicLoad(")?; + self.write_expr(module, pointer, func_ctx)?; + write!(self.out, ")")?; + } else { + self.write_expr_with_indirection( + module, + pointer, + func_ctx, + Indirection::Reference, + )?; + } + } + Expression::LocalVariable(handle) => { + write!(self.out, "{}", self.names[&func_ctx.name_key(handle)])? + } + Expression::ArrayLength(expr) => { + write!(self.out, "arrayLength(")?; + self.write_expr(module, expr, func_ctx)?; + write!(self.out, ")")?; + } + + Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + } => { + use crate::MathFunction as Mf; + + enum Function { + Regular(&'static str), + } + + let function = match fun { + Mf::Abs => Function::Regular("abs"), + Mf::Min => Function::Regular("min"), + Mf::Max => Function::Regular("max"), + Mf::Clamp => Function::Regular("clamp"), + Mf::Saturate => Function::Regular("saturate"), + // trigonometry + Mf::Cos => Function::Regular("cos"), + Mf::Cosh => Function::Regular("cosh"), + Mf::Sin => Function::Regular("sin"), + Mf::Sinh => Function::Regular("sinh"), + Mf::Tan => Function::Regular("tan"), + Mf::Tanh => Function::Regular("tanh"), + Mf::Acos => Function::Regular("acos"), + Mf::Asin => Function::Regular("asin"), + Mf::Atan => Function::Regular("atan"), + Mf::Atan2 => Function::Regular("atan2"), + Mf::Asinh => Function::Regular("asinh"), + Mf::Acosh => Function::Regular("acosh"), + Mf::Atanh => Function::Regular("atanh"), + Mf::Radians => Function::Regular("radians"), + Mf::Degrees => Function::Regular("degrees"), + // decomposition + Mf::Ceil => Function::Regular("ceil"), + Mf::Floor => Function::Regular("floor"), + Mf::Round => Function::Regular("round"), + Mf::Fract => Function::Regular("fract"), + Mf::Trunc => Function::Regular("trunc"), + Mf::Modf => Function::Regular("modf"), + Mf::Frexp => Function::Regular("frexp"), + Mf::Ldexp => Function::Regular("ldexp"), + // exponent + Mf::Exp => Function::Regular("exp"), + Mf::Exp2 => Function::Regular("exp2"), + Mf::Log => Function::Regular("log"), + Mf::Log2 => Function::Regular("log2"), + Mf::Pow => Function::Regular("pow"), + // geometry + Mf::Dot => Function::Regular("dot"), + Mf::Cross => Function::Regular("cross"), + Mf::Distance => Function::Regular("distance"), + Mf::Length => Function::Regular("length"), + Mf::Normalize => Function::Regular("normalize"), + Mf::FaceForward => Function::Regular("faceForward"), + Mf::Reflect => Function::Regular("reflect"), + Mf::Refract => Function::Regular("refract"), + // computational + Mf::Sign => Function::Regular("sign"), + Mf::Fma => Function::Regular("fma"), + Mf::Mix => Function::Regular("mix"), + Mf::Step => Function::Regular("step"), + Mf::SmoothStep => Function::Regular("smoothstep"), + Mf::Sqrt => Function::Regular("sqrt"), + Mf::InverseSqrt => Function::Regular("inverseSqrt"), + Mf::Transpose => Function::Regular("transpose"), + Mf::Determinant => Function::Regular("determinant"), + // bits + Mf::CountTrailingZeros => Function::Regular("countTrailingZeros"), + Mf::CountLeadingZeros => Function::Regular("countLeadingZeros"), + Mf::CountOneBits => Function::Regular("countOneBits"), + Mf::ReverseBits => Function::Regular("reverseBits"), + Mf::ExtractBits => Function::Regular("extractBits"), + Mf::InsertBits => Function::Regular("insertBits"), + Mf::FindLsb => Function::Regular("firstTrailingBit"), + Mf::FindMsb => Function::Regular("firstLeadingBit"), + // data packing + Mf::Pack4x8snorm => Function::Regular("pack4x8snorm"), + Mf::Pack4x8unorm => Function::Regular("pack4x8unorm"), + Mf::Pack2x16snorm => Function::Regular("pack2x16snorm"), + Mf::Pack2x16unorm => Function::Regular("pack2x16unorm"), + Mf::Pack2x16float => Function::Regular("pack2x16float"), + // data unpacking + Mf::Unpack4x8snorm => Function::Regular("unpack4x8snorm"), + Mf::Unpack4x8unorm => Function::Regular("unpack4x8unorm"), + Mf::Unpack2x16snorm => Function::Regular("unpack2x16snorm"), + Mf::Unpack2x16unorm => Function::Regular("unpack2x16unorm"), + Mf::Unpack2x16float => Function::Regular("unpack2x16float"), + Mf::Inverse | Mf::Outer => { + return Err(Error::UnsupportedMathFunction(fun)); + } + }; + + match function { + Function::Regular(fun_name) => { + write!(self.out, "{fun_name}(")?; + self.write_expr(module, arg, func_ctx)?; + for arg in IntoIterator::into_iter([arg1, arg2, arg3]).flatten() { + write!(self.out, ", ")?; + self.write_expr(module, arg, func_ctx)?; + } + write!(self.out, ")")? + } + } + } + + Expression::Swizzle { + size, + vector, + pattern, + } => { + self.write_expr(module, vector, func_ctx)?; + write!(self.out, ".")?; + for &sc in pattern[..size as usize].iter() { + self.out.write_char(back::COMPONENTS[sc as usize])?; + } + } + Expression::Unary { op, expr } => { + let unary = match op { + crate::UnaryOperator::Negate => "-", + crate::UnaryOperator::LogicalNot => "!", + crate::UnaryOperator::BitwiseNot => "~", + }; + + write!(self.out, "{unary}(")?; + self.write_expr(module, expr, func_ctx)?; + + write!(self.out, ")")? + } + + Expression::Select { + condition, + accept, + reject, + } => { + write!(self.out, "select(")?; + self.write_expr(module, reject, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, accept, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, condition, func_ctx)?; + write!(self.out, ")")? + } + Expression::Derivative { axis, ctrl, expr } => { + use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; + let op = match (axis, ctrl) { + (Axis::X, Ctrl::Coarse) => "dpdxCoarse", + (Axis::X, Ctrl::Fine) => "dpdxFine", + (Axis::X, Ctrl::None) => "dpdx", + (Axis::Y, Ctrl::Coarse) => "dpdyCoarse", + (Axis::Y, Ctrl::Fine) => "dpdyFine", + (Axis::Y, Ctrl::None) => "dpdy", + (Axis::Width, Ctrl::Coarse) => "fwidthCoarse", + (Axis::Width, Ctrl::Fine) => "fwidthFine", + (Axis::Width, Ctrl::None) => "fwidth", + }; + write!(self.out, "{op}(")?; + self.write_expr(module, expr, func_ctx)?; + write!(self.out, ")")? + } + Expression::Relational { fun, argument } => { + use crate::RelationalFunction as Rf; + + let fun_name = match fun { + Rf::All => "all", + Rf::Any => "any", + _ => return Err(Error::UnsupportedRelationalFunction(fun)), + }; + write!(self.out, "{fun_name}(")?; + + self.write_expr(module, argument, func_ctx)?; + + write!(self.out, ")")? + } + // Not supported yet + Expression::RayQueryGetIntersection { .. } => unreachable!(), + // Nothing to do here, since call expression already cached + Expression::CallResult(_) + | Expression::AtomicResult { .. } + | Expression::RayQueryProceedResult + | Expression::WorkGroupUniformLoadResult { .. } => {} + } + + Ok(()) + } + + /// Helper method used to write global variables + /// # Notes + /// Always adds a newline + fn write_global( + &mut self, + module: &Module, + global: &crate::GlobalVariable, + handle: Handle, + ) -> BackendResult { + // Write group and binding attributes if present + if let Some(ref binding) = global.binding { + self.write_attributes(&[ + Attribute::Group(binding.group), + Attribute::Binding(binding.binding), + ])?; + writeln!(self.out)?; + } + + // First write global name and address space if supported + write!(self.out, "var")?; + let (address, maybe_access) = address_space_str(global.space); + if let Some(space) = address { + write!(self.out, "<{space}")?; + if let Some(access) = maybe_access { + write!(self.out, ", {access}")?; + } + write!(self.out, ">")?; + } + write!( + self.out, + " {}: ", + &self.names[&NameKey::GlobalVariable(handle)] + )?; + + // Write global type + self.write_type(module, global.ty)?; + + // Write initializer + if let Some(init) = global.init { + write!(self.out, " = ")?; + self.write_const_expression(module, init)?; + } + + // End with semicolon + writeln!(self.out, ";")?; + + Ok(()) + } + + /// Helper method used to write global constants + /// + /// # Notes + /// Ends in a newline + fn write_global_constant( + &mut self, + module: &Module, + handle: Handle, + ) -> BackendResult { + let name = &self.names[&NameKey::Constant(handle)]; + // First write only constant name + write!(self.out, "const {name}: ")?; + self.write_type(module, module.constants[handle].ty)?; + write!(self.out, " = ")?; + let init = module.constants[handle].init; + self.write_const_expression(module, init)?; + writeln!(self.out, ";")?; + + Ok(()) + } + + // See https://github.com/rust-lang/rust-clippy/issues/4979. + #[allow(clippy::missing_const_for_fn)] + pub fn finish(self) -> W { + self.out + } +} + +fn builtin_str(built_in: crate::BuiltIn) -> Result<&'static str, Error> { + use crate::BuiltIn as Bi; + + Ok(match built_in { + Bi::VertexIndex => "vertex_index", + Bi::InstanceIndex => "instance_index", + Bi::Position { .. } => "position", + Bi::FrontFacing => "front_facing", + Bi::FragDepth => "frag_depth", + Bi::LocalInvocationId => "local_invocation_id", + Bi::LocalInvocationIndex => "local_invocation_index", + Bi::GlobalInvocationId => "global_invocation_id", + Bi::WorkGroupId => "workgroup_id", + Bi::NumWorkGroups => "num_workgroups", + Bi::SampleIndex => "sample_index", + Bi::SampleMask => "sample_mask", + Bi::PrimitiveIndex => "primitive_index", + Bi::ViewIndex => "view_index", + Bi::BaseInstance + | Bi::BaseVertex + | Bi::ClipDistance + | Bi::CullDistance + | Bi::PointSize + | Bi::PointCoord + | Bi::WorkGroupSize => { + return Err(Error::Custom(format!("Unsupported builtin {built_in:?}"))) + } + }) +} + +const fn image_dimension_str(dim: crate::ImageDimension) -> &'static str { + use crate::ImageDimension as IDim; + + match dim { + IDim::D1 => "1d", + IDim::D2 => "2d", + IDim::D3 => "3d", + IDim::Cube => "cube", + } +} + +const fn scalar_kind_str(scalar: crate::Scalar) -> &'static str { + use crate::Scalar; + use crate::ScalarKind as Sk; + + match scalar { + Scalar { + kind: Sk::Float, + width: 8, + } => "f64", + Scalar { + kind: Sk::Float, + width: 4, + } => "f32", + Scalar { + kind: Sk::Sint, + width: 4, + } => "i32", + Scalar { + kind: Sk::Uint, + width: 4, + } => "u32", + Scalar { + kind: Sk::Bool, + width: 1, + } => "bool", + _ => unreachable!(), + } +} + +const fn storage_format_str(format: crate::StorageFormat) -> &'static str { + use crate::StorageFormat as Sf; + + match format { + Sf::R8Unorm => "r8unorm", + Sf::R8Snorm => "r8snorm", + Sf::R8Uint => "r8uint", + Sf::R8Sint => "r8sint", + Sf::R16Uint => "r16uint", + Sf::R16Sint => "r16sint", + Sf::R16Float => "r16float", + Sf::Rg8Unorm => "rg8unorm", + Sf::Rg8Snorm => "rg8snorm", + Sf::Rg8Uint => "rg8uint", + Sf::Rg8Sint => "rg8sint", + Sf::R32Uint => "r32uint", + Sf::R32Sint => "r32sint", + Sf::R32Float => "r32float", + Sf::Rg16Uint => "rg16uint", + Sf::Rg16Sint => "rg16sint", + Sf::Rg16Float => "rg16float", + Sf::Rgba8Unorm => "rgba8unorm", + Sf::Rgba8Snorm => "rgba8snorm", + Sf::Rgba8Uint => "rgba8uint", + Sf::Rgba8Sint => "rgba8sint", + Sf::Bgra8Unorm => "bgra8unorm", + Sf::Rgb10a2Uint => "rgb10a2uint", + Sf::Rgb10a2Unorm => "rgb10a2unorm", + Sf::Rg11b10Float => "rg11b10float", + Sf::Rg32Uint => "rg32uint", + Sf::Rg32Sint => "rg32sint", + Sf::Rg32Float => "rg32float", + Sf::Rgba16Uint => "rgba16uint", + Sf::Rgba16Sint => "rgba16sint", + Sf::Rgba16Float => "rgba16float", + Sf::Rgba32Uint => "rgba32uint", + Sf::Rgba32Sint => "rgba32sint", + Sf::Rgba32Float => "rgba32float", + Sf::R16Unorm => "r16unorm", + Sf::R16Snorm => "r16snorm", + Sf::Rg16Unorm => "rg16unorm", + Sf::Rg16Snorm => "rg16snorm", + Sf::Rgba16Unorm => "rgba16unorm", + Sf::Rgba16Snorm => "rgba16snorm", + } +} + +/// Helper function that returns the string corresponding to the WGSL interpolation qualifier +const fn interpolation_str(interpolation: crate::Interpolation) -> &'static str { + use crate::Interpolation as I; + + match interpolation { + I::Perspective => "perspective", + I::Linear => "linear", + I::Flat => "flat", + } +} + +/// Return the WGSL auxiliary qualifier for the given sampling value. +const fn sampling_str(sampling: crate::Sampling) -> &'static str { + use crate::Sampling as S; + + match sampling { + S::Center => "", + S::Centroid => "centroid", + S::Sample => "sample", + } +} + +const fn address_space_str( + space: crate::AddressSpace, +) -> (Option<&'static str>, Option<&'static str>) { + use crate::AddressSpace as As; + + ( + Some(match space { + As::Private => "private", + As::Uniform => "uniform", + As::Storage { access } => { + if access.contains(crate::StorageAccess::STORE) { + return (Some("storage"), Some("read_write")); + } else { + "storage" + } + } + As::PushConstant => "push_constant", + As::WorkGroup => "workgroup", + As::Handle => return (None, None), + As::Function => "function", + }), + None, + ) +} + +fn map_binding_to_attribute(binding: &crate::Binding) -> Vec { + match *binding { + crate::Binding::BuiltIn(built_in) => { + if let crate::BuiltIn::Position { invariant: true } = built_in { + vec![Attribute::BuiltIn(built_in), Attribute::Invariant] + } else { + vec![Attribute::BuiltIn(built_in)] + } + } + crate::Binding::Location { + location, + interpolation, + sampling, + second_blend_source: false, + } => vec![ + Attribute::Location(location), + Attribute::Interpolate(interpolation, sampling), + ], + crate::Binding::Location { + location, + interpolation, + sampling, + second_blend_source: true, + } => vec![ + Attribute::Location(location), + Attribute::SecondBlendSource, + Attribute::Interpolate(interpolation, sampling), + ], + } +} diff --git a/naga/src/block.rs b/naga/src/block.rs new file mode 100644 index 0000000000..0abda9da7c --- /dev/null +++ b/naga/src/block.rs @@ -0,0 +1,123 @@ +use crate::{Span, Statement}; +use std::ops::{Deref, DerefMut, RangeBounds}; + +/// A code block is a vector of statements, with maybe a vector of spans. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "serialize", serde(transparent))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct Block { + body: Vec, + #[cfg_attr(feature = "serialize", serde(skip))] + span_info: Vec, +} + +impl Block { + pub const fn new() -> Self { + Self { + body: Vec::new(), + span_info: Vec::new(), + } + } + + pub fn from_vec(body: Vec) -> Self { + let span_info = std::iter::repeat(Span::default()) + .take(body.len()) + .collect(); + Self { body, span_info } + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + body: Vec::with_capacity(capacity), + span_info: Vec::with_capacity(capacity), + } + } + + #[allow(unused_variables)] + pub fn push(&mut self, end: Statement, span: Span) { + self.body.push(end); + self.span_info.push(span); + } + + pub fn extend(&mut self, item: Option<(Statement, Span)>) { + if let Some((end, span)) = item { + self.push(end, span) + } + } + + pub fn extend_block(&mut self, other: Self) { + self.span_info.extend(other.span_info); + self.body.extend(other.body); + } + + pub fn append(&mut self, other: &mut Self) { + self.span_info.append(&mut other.span_info); + self.body.append(&mut other.body); + } + + pub fn cull + Clone>(&mut self, range: R) { + self.span_info.drain(range.clone()); + self.body.drain(range); + } + + pub fn splice + Clone>(&mut self, range: R, other: Self) { + self.span_info.splice(range.clone(), other.span_info); + self.body.splice(range, other.body); + } + pub fn span_iter(&self) -> impl Iterator { + let span_iter = self.span_info.iter(); + self.body.iter().zip(span_iter) + } + + pub fn span_iter_mut(&mut self) -> impl Iterator)> { + let span_iter = self.span_info.iter_mut().map(Some); + self.body.iter_mut().zip(span_iter) + } + + pub fn is_empty(&self) -> bool { + self.body.is_empty() + } + + pub fn len(&self) -> usize { + self.body.len() + } +} + +impl Deref for Block { + type Target = [Statement]; + fn deref(&self) -> &[Statement] { + &self.body + } +} + +impl DerefMut for Block { + fn deref_mut(&mut self) -> &mut [Statement] { + &mut self.body + } +} + +impl<'a> IntoIterator for &'a Block { + type Item = &'a Statement; + type IntoIter = std::slice::Iter<'a, Statement>; + + fn into_iter(self) -> std::slice::Iter<'a, Statement> { + self.iter() + } +} + +#[cfg(feature = "deserialize")] +impl<'de> serde::Deserialize<'de> for Block { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self::from_vec(Vec::deserialize(deserializer)?)) + } +} + +impl From> for Block { + fn from(body: Vec) -> Self { + Self::from_vec(body) + } +} diff --git a/naga/src/compact/expressions.rs b/naga/src/compact/expressions.rs new file mode 100644 index 0000000000..301bbe3240 --- /dev/null +++ b/naga/src/compact/expressions.rs @@ -0,0 +1,389 @@ +use super::{HandleMap, HandleSet, ModuleMap}; +use crate::arena::{Arena, Handle}; + +pub struct ExpressionTracer<'tracer> { + pub constants: &'tracer Arena, + + /// The arena in which we are currently tracing expressions. + pub expressions: &'tracer Arena, + + /// The used map for `types`. + pub types_used: &'tracer mut HandleSet, + + /// The used map for `constants`. + pub constants_used: &'tracer mut HandleSet, + + /// The used set for `arena`. + /// + /// This points to whatever arena holds the expressions we are + /// currently tracing: either a function's expression arena, or + /// the module's constant expression arena. + pub expressions_used: &'tracer mut HandleSet, + + /// The used set for the module's `const_expressions` arena. + /// + /// If `None`, we are already tracing the constant expressions, + /// and `expressions_used` already refers to their handle set. + pub const_expressions_used: Option<&'tracer mut HandleSet>, +} + +impl<'tracer> ExpressionTracer<'tracer> { + /// Propagate usage through `self.expressions`, starting with `self.expressions_used`. + /// + /// Treat `self.expressions_used` as the initial set of "known + /// live" expressions, and follow through to identify all + /// transitively used expressions. + /// + /// Mark types, constants, and constant expressions used directly + /// by `self.expressions` as used. Items used indirectly are not + /// marked. + /// + /// [fe]: crate::Function::expressions + /// [ce]: crate::Module::const_expressions + pub fn trace_expressions(&mut self) { + log::trace!( + "entering trace_expression of {}", + if self.const_expressions_used.is_some() { + "function expressions" + } else { + "const expressions" + } + ); + + // We don't need recursion or a work list. Because an + // expression may only refer to other expressions that precede + // it in the arena, it suffices to make a single pass over the + // arena from back to front, marking the referents of used + // expressions as used themselves. + for (handle, expr) in self.expressions.iter().rev() { + // If this expression isn't used, it doesn't matter what it uses. + if !self.expressions_used.contains(handle) { + continue; + } + + log::trace!("tracing new expression {:?}", expr); + + use crate::Expression as Ex; + match *expr { + // Expressions that do not contain handles that need to be traced. + Ex::Literal(_) + | Ex::FunctionArgument(_) + | Ex::GlobalVariable(_) + | Ex::LocalVariable(_) + | Ex::CallResult(_) + | Ex::RayQueryProceedResult => {} + + Ex::Constant(handle) => { + self.constants_used.insert(handle); + // Constants and expressions are mutually recursive, which + // complicates our nice one-pass algorithm. However, since + // constants don't refer to each other, we can get around + // this by looking *through* each constant and marking its + // initializer as used. Since `expr` refers to the constant, + // and the constant refers to the initializer, it must + // precede `expr` in the arena. + let init = self.constants[handle].init; + match self.const_expressions_used { + Some(ref mut used) => used.insert(init), + None => self.expressions_used.insert(init), + } + } + Ex::ZeroValue(ty) => self.types_used.insert(ty), + Ex::Compose { ty, ref components } => { + self.types_used.insert(ty); + self.expressions_used + .insert_iter(components.iter().cloned()); + } + Ex::Access { base, index } => self.expressions_used.insert_iter([base, index]), + Ex::AccessIndex { base, index: _ } => self.expressions_used.insert(base), + Ex::Splat { size: _, value } => self.expressions_used.insert(value), + Ex::Swizzle { + size: _, + vector, + pattern: _, + } => self.expressions_used.insert(vector), + Ex::Load { pointer } => self.expressions_used.insert(pointer), + Ex::ImageSample { + image, + sampler, + gather: _, + coordinate, + array_index, + offset, + ref level, + depth_ref, + } => { + self.expressions_used + .insert_iter([image, sampler, coordinate]); + self.expressions_used.insert_iter(array_index); + match self.const_expressions_used { + Some(ref mut used) => used.insert_iter(offset), + None => self.expressions_used.insert_iter(offset), + } + use crate::SampleLevel as Sl; + match *level { + Sl::Auto | Sl::Zero => {} + Sl::Exact(expr) | Sl::Bias(expr) => self.expressions_used.insert(expr), + Sl::Gradient { x, y } => self.expressions_used.insert_iter([x, y]), + } + self.expressions_used.insert_iter(depth_ref); + } + Ex::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => { + self.expressions_used.insert(image); + self.expressions_used.insert(coordinate); + self.expressions_used.insert_iter(array_index); + self.expressions_used.insert_iter(sample); + self.expressions_used.insert_iter(level); + } + Ex::ImageQuery { image, ref query } => { + self.expressions_used.insert(image); + use crate::ImageQuery as Iq; + match *query { + Iq::Size { level } => self.expressions_used.insert_iter(level), + Iq::NumLevels | Iq::NumLayers | Iq::NumSamples => {} + } + } + Ex::Unary { op: _, expr } => self.expressions_used.insert(expr), + Ex::Binary { op: _, left, right } => { + self.expressions_used.insert_iter([left, right]); + } + Ex::Select { + condition, + accept, + reject, + } => self + .expressions_used + .insert_iter([condition, accept, reject]), + Ex::Derivative { + axis: _, + ctrl: _, + expr, + } => self.expressions_used.insert(expr), + Ex::Relational { fun: _, argument } => self.expressions_used.insert(argument), + Ex::Math { + fun: _, + arg, + arg1, + arg2, + arg3, + } => { + self.expressions_used.insert(arg); + self.expressions_used.insert_iter(arg1); + self.expressions_used.insert_iter(arg2); + self.expressions_used.insert_iter(arg3); + } + Ex::As { + expr, + kind: _, + convert: _, + } => self.expressions_used.insert(expr), + Ex::AtomicResult { ty, comparison: _ } => self.types_used.insert(ty), + Ex::WorkGroupUniformLoadResult { ty } => self.types_used.insert(ty), + Ex::ArrayLength(expr) => self.expressions_used.insert(expr), + Ex::RayQueryGetIntersection { + query, + committed: _, + } => self.expressions_used.insert(query), + } + } + } +} + +impl ModuleMap { + /// Fix up all handles in `expr`. + /// + /// Use the expression handle remappings in `operand_map`, and all + /// other mappings from `self`. + pub fn adjust_expression( + &self, + expr: &mut crate::Expression, + operand_map: &HandleMap, + ) { + let adjust = |expr: &mut Handle| { + operand_map.adjust(expr); + }; + + use crate::Expression as Ex; + match *expr { + // Expressions that do not contain handles that need to be adjusted. + Ex::Literal(_) + | Ex::FunctionArgument(_) + | Ex::GlobalVariable(_) + | Ex::LocalVariable(_) + | Ex::CallResult(_) + | Ex::RayQueryProceedResult => {} + + // Expressions that contain handles that need to be adjusted. + Ex::Constant(ref mut constant) => self.constants.adjust(constant), + Ex::ZeroValue(ref mut ty) => self.types.adjust(ty), + Ex::Compose { + ref mut ty, + ref mut components, + } => { + self.types.adjust(ty); + for component in components { + adjust(component); + } + } + Ex::Access { + ref mut base, + ref mut index, + } => { + adjust(base); + adjust(index); + } + Ex::AccessIndex { + ref mut base, + index: _, + } => adjust(base), + Ex::Splat { + size: _, + ref mut value, + } => adjust(value), + Ex::Swizzle { + size: _, + ref mut vector, + pattern: _, + } => adjust(vector), + Ex::Load { ref mut pointer } => adjust(pointer), + Ex::ImageSample { + ref mut image, + ref mut sampler, + gather: _, + ref mut coordinate, + ref mut array_index, + ref mut offset, + ref mut level, + ref mut depth_ref, + } => { + adjust(image); + adjust(sampler); + adjust(coordinate); + operand_map.adjust_option(array_index); + if let Some(ref mut offset) = *offset { + self.const_expressions.adjust(offset); + } + self.adjust_sample_level(level, operand_map); + operand_map.adjust_option(depth_ref); + } + Ex::ImageLoad { + ref mut image, + ref mut coordinate, + ref mut array_index, + ref mut sample, + ref mut level, + } => { + adjust(image); + adjust(coordinate); + operand_map.adjust_option(array_index); + operand_map.adjust_option(sample); + operand_map.adjust_option(level); + } + Ex::ImageQuery { + ref mut image, + ref mut query, + } => { + adjust(image); + self.adjust_image_query(query, operand_map); + } + Ex::Unary { + op: _, + ref mut expr, + } => adjust(expr), + Ex::Binary { + op: _, + ref mut left, + ref mut right, + } => { + adjust(left); + adjust(right); + } + Ex::Select { + ref mut condition, + ref mut accept, + ref mut reject, + } => { + adjust(condition); + adjust(accept); + adjust(reject); + } + Ex::Derivative { + axis: _, + ctrl: _, + ref mut expr, + } => adjust(expr), + Ex::Relational { + fun: _, + ref mut argument, + } => adjust(argument), + Ex::Math { + fun: _, + ref mut arg, + ref mut arg1, + ref mut arg2, + ref mut arg3, + } => { + adjust(arg); + operand_map.adjust_option(arg1); + operand_map.adjust_option(arg2); + operand_map.adjust_option(arg3); + } + Ex::As { + ref mut expr, + kind: _, + convert: _, + } => adjust(expr), + Ex::AtomicResult { + ref mut ty, + comparison: _, + } => self.types.adjust(ty), + Ex::WorkGroupUniformLoadResult { ref mut ty } => self.types.adjust(ty), + Ex::ArrayLength(ref mut expr) => adjust(expr), + Ex::RayQueryGetIntersection { + ref mut query, + committed: _, + } => adjust(query), + } + } + + fn adjust_sample_level( + &self, + level: &mut crate::SampleLevel, + operand_map: &HandleMap, + ) { + let adjust = |expr: &mut Handle| operand_map.adjust(expr); + + use crate::SampleLevel as Sl; + match *level { + Sl::Auto | Sl::Zero => {} + Sl::Exact(ref mut expr) => adjust(expr), + Sl::Bias(ref mut expr) => adjust(expr), + Sl::Gradient { + ref mut x, + ref mut y, + } => { + adjust(x); + adjust(y); + } + } + } + + fn adjust_image_query( + &self, + query: &mut crate::ImageQuery, + operand_map: &HandleMap, + ) { + use crate::ImageQuery as Iq; + + match *query { + Iq::Size { ref mut level } => operand_map.adjust_option(level), + Iq::NumLevels | Iq::NumLayers | Iq::NumSamples => {} + } + } +} diff --git a/naga/src/compact/functions.rs b/naga/src/compact/functions.rs new file mode 100644 index 0000000000..b0d08c7e96 --- /dev/null +++ b/naga/src/compact/functions.rs @@ -0,0 +1,106 @@ +use super::handle_set_map::HandleSet; +use super::{FunctionMap, ModuleMap}; + +pub struct FunctionTracer<'a> { + pub function: &'a crate::Function, + pub constants: &'a crate::Arena, + + pub types_used: &'a mut HandleSet, + pub constants_used: &'a mut HandleSet, + pub const_expressions_used: &'a mut HandleSet, + + /// Function-local expressions used. + pub expressions_used: HandleSet, +} + +impl<'a> FunctionTracer<'a> { + pub fn trace(&mut self) { + for argument in self.function.arguments.iter() { + self.types_used.insert(argument.ty); + } + + if let Some(ref result) = self.function.result { + self.types_used.insert(result.ty); + } + + for (_, local) in self.function.local_variables.iter() { + self.types_used.insert(local.ty); + if let Some(init) = local.init { + self.expressions_used.insert(init); + } + } + + // Treat named expressions as alive, for the sake of our test suite, + // which uses `let blah = expr;` to exercise lots of things. + for (&value, _name) in &self.function.named_expressions { + self.expressions_used.insert(value); + } + + self.trace_block(&self.function.body); + + // Given that `trace_block` has marked the expressions used + // directly by statements, walk the arena to find all + // expressions used, directly or indirectly. + self.as_expression().trace_expressions(); + } + + fn as_expression(&mut self) -> super::expressions::ExpressionTracer { + super::expressions::ExpressionTracer { + constants: self.constants, + expressions: &self.function.expressions, + + types_used: self.types_used, + constants_used: self.constants_used, + expressions_used: &mut self.expressions_used, + const_expressions_used: Some(&mut self.const_expressions_used), + } + } +} + +impl FunctionMap { + pub fn compact( + &self, + function: &mut crate::Function, + module_map: &ModuleMap, + reuse: &mut crate::NamedExpressions, + ) { + assert!(reuse.is_empty()); + + for argument in function.arguments.iter_mut() { + module_map.types.adjust(&mut argument.ty); + } + + if let Some(ref mut result) = function.result { + module_map.types.adjust(&mut result.ty); + } + + for (_, local) in function.local_variables.iter_mut() { + log::trace!("adjusting local variable {:?}", local.name); + module_map.types.adjust(&mut local.ty); + if let Some(ref mut init) = local.init { + self.expressions.adjust(init); + } + } + + // Drop unused expressions, reusing existing storage. + function.expressions.retain_mut(|handle, expr| { + if self.expressions.used(handle) { + module_map.adjust_expression(expr, &self.expressions); + true + } else { + false + } + }); + + // Adjust named expressions. + for (mut handle, name) in function.named_expressions.drain(..) { + self.expressions.adjust(&mut handle); + reuse.insert(handle, name); + } + std::mem::swap(&mut function.named_expressions, reuse); + assert!(reuse.is_empty()); + + // Adjust statements. + self.adjust_body(function); + } +} diff --git a/naga/src/compact/handle_set_map.rs b/naga/src/compact/handle_set_map.rs new file mode 100644 index 0000000000..c716ca8294 --- /dev/null +++ b/naga/src/compact/handle_set_map.rs @@ -0,0 +1,170 @@ +use crate::arena::{Arena, Handle, Range, UniqueArena}; + +type Index = std::num::NonZeroU32; + +/// A set of `Handle` values. +pub struct HandleSet { + /// Bound on zero-based indexes of handles stored in this set. + len: usize, + + /// `members[i]` is true if the handle with zero-based index `i` + /// is a member. + members: bit_set::BitSet, + + /// This type is indexed by values of type `T`. + as_keys: std::marker::PhantomData, +} + +impl HandleSet { + pub fn for_arena(arena: &impl ArenaType) -> Self { + let len = arena.len(); + Self { + len, + members: bit_set::BitSet::with_capacity(len), + as_keys: std::marker::PhantomData, + } + } + + /// Add `handle` to the set. + pub fn insert(&mut self, handle: Handle) { + // Note that, oddly, `Handle::index` does not return a 1-based + // `Index`, but rather a zero-based `usize`. + self.members.insert(handle.index()); + } + + /// Add handles from `iter` to the set. + pub fn insert_iter(&mut self, iter: impl IntoIterator>) { + for handle in iter { + self.insert(handle); + } + } + + pub fn contains(&self, handle: Handle) -> bool { + // Note that, oddly, `Handle::index` does not return a 1-based + // `Index`, but rather a zero-based `usize`. + self.members.contains(handle.index()) + } +} + +pub trait ArenaType { + fn len(&self) -> usize; +} + +impl ArenaType for Arena { + fn len(&self) -> usize { + self.len() + } +} + +impl ArenaType for UniqueArena { + fn len(&self) -> usize { + self.len() + } +} + +/// A map from old handle indices to new, compressed handle indices. +pub struct HandleMap { + /// The indices assigned to handles in the compacted module. + /// + /// If `new_index[i]` is `Some(n)`, then `n` is the 1-based + /// `Index` of the compacted `Handle` corresponding to the + /// pre-compacted `Handle` whose zero-based index is `i`. ("Clear + /// as mud.") + new_index: Vec>, + + /// This type is indexed by values of type `T`. + as_keys: std::marker::PhantomData, +} + +impl HandleMap { + pub fn from_set(set: HandleSet) -> Self { + let mut next_index = Index::new(1).unwrap(); + Self { + new_index: (0..set.len) + .map(|zero_based_index| { + if set.members.contains(zero_based_index) { + // This handle will be retained in the compacted version, + // so assign it a new index. + let this = next_index; + next_index = next_index.checked_add(1).unwrap(); + Some(this) + } else { + // This handle will be omitted in the compacted version. + None + } + }) + .collect(), + as_keys: std::marker::PhantomData, + } + } + + /// Return true if `old` is used in the compacted module. + pub fn used(&self, old: Handle) -> bool { + self.new_index[old.index()].is_some() + } + + /// Return the counterpart to `old` in the compacted module. + /// + /// If we thought `old` wouldn't be used in the compacted module, return + /// `None`. + pub fn try_adjust(&self, old: Handle) -> Option> { + log::trace!( + "adjusting {} handle [{}] -> [{:?}]", + std::any::type_name::(), + old.index() + 1, + self.new_index[old.index()] + ); + // Note that `Handle::index` returns a zero-based index, + // but `Handle::new` accepts a 1-based `Index`. + self.new_index[old.index()].map(Handle::new) + } + + /// Return the counterpart to `old` in the compacted module. + /// + /// If we thought `old` wouldn't be used in the compacted module, panic. + pub fn adjust(&self, handle: &mut Handle) { + *handle = self.try_adjust(*handle).unwrap(); + } + + /// Like `adjust`, but for optional handles. + pub fn adjust_option(&self, handle: &mut Option>) { + if let Some(ref mut handle) = *handle { + self.adjust(handle); + } + } + + /// Shrink `range` to include only used handles. + /// + /// Fortunately, compaction doesn't arbitrarily scramble the expressions + /// in the arena, but instead preserves the order of the elements while + /// squeezing out unused ones. That means that a contiguous range in the + /// pre-compacted arena always maps to a contiguous range in the + /// post-compacted arena. So we just need to adjust the endpoints. + /// + /// Compaction may have eliminated the endpoints themselves. + /// + /// Use `compacted_arena` to bounds-check the result. + pub fn adjust_range(&self, range: &mut Range, compacted_arena: &Arena) { + let mut index_range = range.zero_based_index_range(); + let compacted; + // Remember that the indices we retrieve from `new_index` are 1-based + // compacted indices, but the index range we're computing is zero-based + // compacted indices. + if let Some(first1) = index_range.find_map(|i| self.new_index[i as usize]) { + // The first call to `find_map` mutated `index_range` to hold the + // remainder of original range, which is exactly the range we need + // to search for the new last handle. + if let Some(last1) = index_range.rev().find_map(|i| self.new_index[i as usize]) { + // Build a zero-based end-exclusive range, given one-based handle indices. + compacted = first1.get() - 1..last1.get(); + } else { + // The range contains only a single live handle, which + // we identified with the first `find_map` call. + compacted = first1.get() - 1..first1.get(); + } + } else { + compacted = 0..0; + }; + *range = Range::from_zero_based_index_range(compacted, compacted_arena); + } +} diff --git a/naga/src/compact/mod.rs b/naga/src/compact/mod.rs new file mode 100644 index 0000000000..7dfb8ee80d --- /dev/null +++ b/naga/src/compact/mod.rs @@ -0,0 +1,307 @@ +mod expressions; +mod functions; +mod handle_set_map; +mod statements; +mod types; + +use crate::{arena, compact::functions::FunctionTracer}; +use handle_set_map::{HandleMap, HandleSet}; + +/// Remove unused types, expressions, and constants from `module`. +/// +/// Assuming that all globals, named constants, special types, +/// functions and entry points in `module` are used, determine which +/// types, constants, and expressions (both function-local and global +/// constant expressions) are actually used, and remove the rest, +/// adjusting all handles as necessary. The result should be a module +/// functionally identical to the original. +/// +/// This may be useful to apply to modules generated in the snapshot +/// tests. Our backends often generate temporary names based on handle +/// indices, which means that adding or removing unused arena entries +/// can affect the output even though they have no semantic effect. +/// Such meaningless changes add noise to snapshot diffs, making +/// accurate patch review difficult. Compacting the modules before +/// generating snapshots makes the output independent of unused arena +/// entries. +/// +/// # Panics +/// +/// If `module` has not passed validation, this may panic. +pub fn compact(module: &mut crate::Module) { + let mut module_tracer = ModuleTracer::new(module); + + // We treat all globals as used by definition. + log::trace!("tracing global variables"); + { + for (_, global) in module.global_variables.iter() { + log::trace!("tracing global {:?}", global.name); + module_tracer.types_used.insert(global.ty); + if let Some(init) = global.init { + module_tracer.const_expressions_used.insert(init); + } + } + } + + // We treat all special types as used by definition. + module_tracer.trace_special_types(&module.special_types); + + // We treat all named constants as used by definition. + for (handle, constant) in module.constants.iter() { + if constant.name.is_some() { + module_tracer.constants_used.insert(handle); + module_tracer.const_expressions_used.insert(constant.init); + } + } + + // We assume that all functions are used. + // + // Observe which types, constant expressions, constants, and + // expressions each function uses, and produce maps for each + // function from pre-compaction to post-compaction expression + // handles. + log::trace!("tracing functions"); + let function_maps: Vec = module + .functions + .iter() + .map(|(_, f)| { + log::trace!("tracing function {:?}", f.name); + let mut function_tracer = module_tracer.as_function(f); + function_tracer.trace(); + FunctionMap::from(function_tracer) + }) + .collect(); + + // Similiarly, observe what each entry point actually uses. + log::trace!("tracing entry points"); + let entry_point_maps: Vec = module + .entry_points + .iter() + .map(|e| { + log::trace!("tracing entry point {:?}", e.function.name); + let mut used = module_tracer.as_function(&e.function); + used.trace(); + FunctionMap::from(used) + }) + .collect(); + + // Given that the above steps have marked all the constant + // expressions used directly by globals, constants, functions, and + // entry points, walk the constant expression arena to find all + // constant expressions used, directly or indirectly. + module_tracer.as_const_expression().trace_expressions(); + + // Constants' initializers are taken care of already, because + // expression tracing sees through constants. But we still need to + // note type usage. + for (handle, constant) in module.constants.iter() { + if module_tracer.constants_used.contains(handle) { + module_tracer.types_used.insert(constant.ty); + } + } + + // Treat all named types as used. + for (handle, ty) in module.types.iter() { + log::trace!("tracing type {:?}, name {:?}", handle, ty.name); + if ty.name.is_some() { + module_tracer.types_used.insert(handle); + } + } + + // Propagate usage through types. + module_tracer.as_type().trace_types(); + + // Now that we know what is used and what is never touched, + // produce maps from the `Handle`s that appear in `module` now to + // the corresponding `Handle`s that will refer to the same items + // in the compacted module. + let module_map = ModuleMap::from(module_tracer); + + // Drop unused types from the type arena. + // + // `FastIndexSet`s don't have an underlying Vec that we can + // steal, compact in place, and then rebuild the `FastIndexSet` + // from. So we have to rebuild the type arena from scratch. + log::trace!("compacting types"); + let mut new_types = arena::UniqueArena::new(); + for (old_handle, mut ty, span) in module.types.drain_all() { + if let Some(expected_new_handle) = module_map.types.try_adjust(old_handle) { + module_map.adjust_type(&mut ty); + let actual_new_handle = new_types.insert(ty, span); + assert_eq!(actual_new_handle, expected_new_handle); + } + } + module.types = new_types; + log::trace!("adjusting special types"); + module_map.adjust_special_types(&mut module.special_types); + + // Drop unused constant expressions, reusing existing storage. + log::trace!("adjusting constant expressions"); + module.const_expressions.retain_mut(|handle, expr| { + if module_map.const_expressions.used(handle) { + module_map.adjust_expression(expr, &module_map.const_expressions); + true + } else { + false + } + }); + + // Drop unused constants in place, reusing existing storage. + log::trace!("adjusting constants"); + module.constants.retain_mut(|handle, constant| { + if module_map.constants.used(handle) { + module_map.types.adjust(&mut constant.ty); + module_map.const_expressions.adjust(&mut constant.init); + true + } else { + false + } + }); + + // Adjust global variables' types and initializers. + log::trace!("adjusting global variables"); + for (_, global) in module.global_variables.iter_mut() { + log::trace!("adjusting global {:?}", global.name); + module_map.types.adjust(&mut global.ty); + if let Some(ref mut init) = global.init { + module_map.const_expressions.adjust(init); + } + } + + // Temporary storage to help us reuse allocations of existing + // named expression tables. + let mut reused_named_expressions = crate::NamedExpressions::default(); + + // Compact each function. + for ((_, function), map) in module.functions.iter_mut().zip(function_maps.iter()) { + log::trace!("compacting function {:?}", function.name); + map.compact(function, &module_map, &mut reused_named_expressions); + } + + // Compact each entry point. + for (entry, map) in module.entry_points.iter_mut().zip(entry_point_maps.iter()) { + log::trace!("compacting entry point {:?}", entry.function.name); + map.compact( + &mut entry.function, + &module_map, + &mut reused_named_expressions, + ); + } +} + +struct ModuleTracer<'module> { + module: &'module crate::Module, + types_used: HandleSet, + constants_used: HandleSet, + const_expressions_used: HandleSet, +} + +impl<'module> ModuleTracer<'module> { + fn new(module: &'module crate::Module) -> Self { + Self { + module, + types_used: HandleSet::for_arena(&module.types), + constants_used: HandleSet::for_arena(&module.constants), + const_expressions_used: HandleSet::for_arena(&module.const_expressions), + } + } + + fn trace_special_types(&mut self, special_types: &crate::SpecialTypes) { + let crate::SpecialTypes { + ref ray_desc, + ref ray_intersection, + ref predeclared_types, + } = *special_types; + + if let Some(ray_desc) = *ray_desc { + self.types_used.insert(ray_desc); + } + if let Some(ray_intersection) = *ray_intersection { + self.types_used.insert(ray_intersection); + } + for (_, &handle) in predeclared_types { + self.types_used.insert(handle); + } + } + + fn as_type(&mut self) -> types::TypeTracer { + types::TypeTracer { + types: &self.module.types, + types_used: &mut self.types_used, + } + } + + fn as_const_expression(&mut self) -> expressions::ExpressionTracer { + expressions::ExpressionTracer { + expressions: &self.module.const_expressions, + constants: &self.module.constants, + types_used: &mut self.types_used, + constants_used: &mut self.constants_used, + expressions_used: &mut self.const_expressions_used, + const_expressions_used: None, + } + } + + pub fn as_function<'tracer>( + &'tracer mut self, + function: &'tracer crate::Function, + ) -> FunctionTracer<'tracer> { + FunctionTracer { + function, + constants: &self.module.constants, + types_used: &mut self.types_used, + constants_used: &mut self.constants_used, + const_expressions_used: &mut self.const_expressions_used, + expressions_used: HandleSet::for_arena(&function.expressions), + } + } +} + +struct ModuleMap { + types: HandleMap, + constants: HandleMap, + const_expressions: HandleMap, +} + +impl From> for ModuleMap { + fn from(used: ModuleTracer) -> Self { + ModuleMap { + types: HandleMap::from_set(used.types_used), + constants: HandleMap::from_set(used.constants_used), + const_expressions: HandleMap::from_set(used.const_expressions_used), + } + } +} + +impl ModuleMap { + fn adjust_special_types(&self, special: &mut crate::SpecialTypes) { + let crate::SpecialTypes { + ref mut ray_desc, + ref mut ray_intersection, + ref mut predeclared_types, + } = *special; + + if let Some(ref mut ray_desc) = *ray_desc { + self.types.adjust(ray_desc); + } + if let Some(ref mut ray_intersection) = *ray_intersection { + self.types.adjust(ray_intersection); + } + + for handle in predeclared_types.values_mut() { + self.types.adjust(handle); + } + } +} + +struct FunctionMap { + expressions: HandleMap, +} + +impl From> for FunctionMap { + fn from(used: FunctionTracer) -> Self { + FunctionMap { + expressions: HandleMap::from_set(used.expressions_used), + } + } +} diff --git a/naga/src/compact/statements.rs b/naga/src/compact/statements.rs new file mode 100644 index 0000000000..0698b57258 --- /dev/null +++ b/naga/src/compact/statements.rs @@ -0,0 +1,300 @@ +use super::functions::FunctionTracer; +use super::FunctionMap; +use crate::arena::Handle; + +impl FunctionTracer<'_> { + pub fn trace_block(&mut self, block: &[crate::Statement]) { + let mut worklist: Vec<&[crate::Statement]> = vec![block]; + while let Some(last) = worklist.pop() { + for stmt in last { + use crate::Statement as St; + match *stmt { + St::Emit(ref _range) => { + // If we come across a statement that actually uses an + // expression in this range, it'll get traced from + // there. But since evaluating expressions has no + // effect, we don't need to assume that everything + // emitted is live. + } + St::Block(ref block) => worklist.push(block), + St::If { + condition, + ref accept, + ref reject, + } => { + self.expressions_used.insert(condition); + worklist.push(accept); + worklist.push(reject); + } + St::Switch { + selector, + ref cases, + } => { + self.expressions_used.insert(selector); + for case in cases { + worklist.push(&case.body); + } + } + St::Loop { + ref body, + ref continuing, + break_if, + } => { + if let Some(break_if) = break_if { + self.expressions_used.insert(break_if); + } + worklist.push(body); + worklist.push(continuing); + } + St::Return { value: Some(value) } => { + self.expressions_used.insert(value); + } + St::Store { pointer, value } => { + self.expressions_used.insert(pointer); + self.expressions_used.insert(value); + } + St::ImageStore { + image, + coordinate, + array_index, + value, + } => { + self.expressions_used.insert(image); + self.expressions_used.insert(coordinate); + if let Some(array_index) = array_index { + self.expressions_used.insert(array_index); + } + self.expressions_used.insert(value); + } + St::Atomic { + pointer, + ref fun, + value, + result, + } => { + self.expressions_used.insert(pointer); + self.trace_atomic_function(fun); + self.expressions_used.insert(value); + self.expressions_used.insert(result); + } + St::WorkGroupUniformLoad { pointer, result } => { + self.expressions_used.insert(pointer); + self.expressions_used.insert(result); + } + St::Call { + function: _, + ref arguments, + result, + } => { + for expr in arguments { + self.expressions_used.insert(*expr); + } + if let Some(result) = result { + self.expressions_used.insert(result); + } + } + St::RayQuery { query, ref fun } => { + self.expressions_used.insert(query); + self.trace_ray_query_function(fun); + } + + // Trivial statements. + St::Break + | St::Continue + | St::Kill + | St::Barrier(_) + | St::Return { value: None } => {} + } + } + } + } + + fn trace_atomic_function(&mut self, fun: &crate::AtomicFunction) { + use crate::AtomicFunction as Af; + match *fun { + Af::Exchange { + compare: Some(expr), + } => { + self.expressions_used.insert(expr); + } + Af::Exchange { compare: None } + | Af::Add + | Af::Subtract + | Af::And + | Af::ExclusiveOr + | Af::InclusiveOr + | Af::Min + | Af::Max => {} + } + } + + fn trace_ray_query_function(&mut self, fun: &crate::RayQueryFunction) { + use crate::RayQueryFunction as Qf; + match *fun { + Qf::Initialize { + acceleration_structure, + descriptor, + } => { + self.expressions_used.insert(acceleration_structure); + self.expressions_used.insert(descriptor); + } + Qf::Proceed { result } => { + self.expressions_used.insert(result); + } + Qf::Terminate => {} + } + } +} + +impl FunctionMap { + pub fn adjust_body(&self, function: &mut crate::Function) { + let block = &mut function.body; + let mut worklist: Vec<&mut [crate::Statement]> = vec![block]; + let adjust = |handle: &mut Handle| { + self.expressions.adjust(handle); + }; + while let Some(last) = worklist.pop() { + for stmt in last { + use crate::Statement as St; + match *stmt { + St::Emit(ref mut range) => { + self.expressions.adjust_range(range, &function.expressions); + } + St::Block(ref mut block) => worklist.push(block), + St::If { + ref mut condition, + ref mut accept, + ref mut reject, + } => { + adjust(condition); + worklist.push(accept); + worklist.push(reject); + } + St::Switch { + ref mut selector, + ref mut cases, + } => { + adjust(selector); + for case in cases { + worklist.push(&mut case.body); + } + } + St::Loop { + ref mut body, + ref mut continuing, + ref mut break_if, + } => { + if let Some(ref mut break_if) = *break_if { + adjust(break_if); + } + worklist.push(body); + worklist.push(continuing); + } + St::Return { + value: Some(ref mut value), + } => adjust(value), + St::Store { + ref mut pointer, + ref mut value, + } => { + adjust(pointer); + adjust(value); + } + St::ImageStore { + ref mut image, + ref mut coordinate, + ref mut array_index, + ref mut value, + } => { + adjust(image); + adjust(coordinate); + if let Some(ref mut array_index) = *array_index { + adjust(array_index); + } + adjust(value); + } + St::Atomic { + ref mut pointer, + ref mut fun, + ref mut value, + ref mut result, + } => { + adjust(pointer); + self.adjust_atomic_function(fun); + adjust(value); + adjust(result); + } + St::WorkGroupUniformLoad { + ref mut pointer, + ref mut result, + } => { + adjust(pointer); + adjust(result); + } + St::Call { + function: _, + ref mut arguments, + ref mut result, + } => { + for expr in arguments { + adjust(expr); + } + if let Some(ref mut result) = *result { + adjust(result); + } + } + St::RayQuery { + ref mut query, + ref mut fun, + } => { + adjust(query); + self.adjust_ray_query_function(fun); + } + + // Trivial statements. + St::Break + | St::Continue + | St::Kill + | St::Barrier(_) + | St::Return { value: None } => {} + } + } + } + } + + fn adjust_atomic_function(&self, fun: &mut crate::AtomicFunction) { + use crate::AtomicFunction as Af; + match *fun { + Af::Exchange { + compare: Some(ref mut expr), + } => { + self.expressions.adjust(expr); + } + Af::Exchange { compare: None } + | Af::Add + | Af::Subtract + | Af::And + | Af::ExclusiveOr + | Af::InclusiveOr + | Af::Min + | Af::Max => {} + } + } + + fn adjust_ray_query_function(&self, fun: &mut crate::RayQueryFunction) { + use crate::RayQueryFunction as Qf; + match *fun { + Qf::Initialize { + ref mut acceleration_structure, + ref mut descriptor, + } => { + self.expressions.adjust(acceleration_structure); + self.expressions.adjust(descriptor); + } + Qf::Proceed { ref mut result } => { + self.expressions.adjust(result); + } + Qf::Terminate => {} + } + } +} diff --git a/naga/src/compact/types.rs b/naga/src/compact/types.rs new file mode 100644 index 0000000000..b78619d9a8 --- /dev/null +++ b/naga/src/compact/types.rs @@ -0,0 +1,102 @@ +use super::{HandleSet, ModuleMap}; +use crate::{Handle, UniqueArena}; + +pub struct TypeTracer<'a> { + pub types: &'a UniqueArena, + pub types_used: &'a mut HandleSet, +} + +impl<'a> TypeTracer<'a> { + /// Propagate usage through `self.types`, starting with `self.types_used`. + /// + /// Treat `self.types_used` as the initial set of "known + /// live" types, and follow through to identify all + /// transitively used types. + pub fn trace_types(&mut self) { + // We don't need recursion or a work list. Because an + // expression may only refer to other expressions that precede + // it in the arena, it suffices to make a single pass over the + // arena from back to front, marking the referents of used + // expressions as used themselves. + for (handle, ty) in self.types.iter().rev() { + // If this type isn't used, it doesn't matter what it uses. + if !self.types_used.contains(handle) { + continue; + } + + use crate::TypeInner as Ti; + match ty.inner { + // Types that do not contain handles. + Ti::Scalar { .. } + | Ti::Vector { .. } + | Ti::Matrix { .. } + | Ti::Atomic { .. } + | Ti::ValuePointer { .. } + | Ti::Image { .. } + | Ti::Sampler { .. } + | Ti::AccelerationStructure + | Ti::RayQuery => {} + + // Types that do contain handles. + Ti::Pointer { base, space: _ } + | Ti::Array { + base, + size: _, + stride: _, + } + | Ti::BindingArray { base, size: _ } => self.types_used.insert(base), + Ti::Struct { + ref members, + span: _, + } => { + self.types_used.insert_iter(members.iter().map(|m| m.ty)); + } + } + } + } +} + +impl ModuleMap { + pub fn adjust_type(&self, ty: &mut crate::Type) { + let adjust = |ty: &mut Handle| self.types.adjust(ty); + + use crate::TypeInner as Ti; + match ty.inner { + // Types that do not contain handles. + Ti::Scalar(_) + | Ti::Vector { .. } + | Ti::Matrix { .. } + | Ti::Atomic(_) + | Ti::ValuePointer { .. } + | Ti::Image { .. } + | Ti::Sampler { .. } + | Ti::AccelerationStructure + | Ti::RayQuery => {} + + // Types that do contain handles. + Ti::Pointer { + ref mut base, + space: _, + } => adjust(base), + Ti::Array { + ref mut base, + size: _, + stride: _, + } => adjust(base), + Ti::Struct { + ref mut members, + span: _, + } => { + for member in members { + self.types.adjust(&mut member.ty); + } + } + Ti::BindingArray { + ref mut base, + size: _, + } => { + adjust(base); + } + }; + } +} diff --git a/naga/src/front/glsl/ast.rs b/naga/src/front/glsl/ast.rs new file mode 100644 index 0000000000..0548fc25e5 --- /dev/null +++ b/naga/src/front/glsl/ast.rs @@ -0,0 +1,394 @@ +use std::{borrow::Cow, fmt}; + +use super::{builtins::MacroCall, context::ExprPos, Span}; +use crate::{ + AddressSpace, BinaryOperator, Binding, Constant, Expression, Function, GlobalVariable, Handle, + Interpolation, Literal, Sampling, StorageAccess, Type, UnaryOperator, +}; + +#[derive(Debug, Clone, Copy)] +pub enum GlobalLookupKind { + Variable(Handle), + Constant(Handle, Handle), + BlockSelect(Handle, u32), +} + +#[derive(Debug, Clone, Copy)] +pub struct GlobalLookup { + pub kind: GlobalLookupKind, + pub entry_arg: Option, + pub mutable: bool, +} + +#[derive(Debug, Clone)] +pub struct ParameterInfo { + pub qualifier: ParameterQualifier, + /// Whether the parameter should be treated as a depth image instead of a + /// sampled image. + pub depth: bool, +} + +/// How the function is implemented +#[derive(Clone, Copy)] +pub enum FunctionKind { + /// The function is user defined + Call(Handle), + /// The function is a builtin + Macro(MacroCall), +} + +impl fmt::Debug for FunctionKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::Call(_) => write!(f, "Call"), + Self::Macro(_) => write!(f, "Macro"), + } + } +} + +#[derive(Debug)] +pub struct Overload { + /// Normalized function parameters, modifiers are not applied + pub parameters: Vec>, + pub parameters_info: Vec, + /// How the function is implemented + pub kind: FunctionKind, + /// Whether this function was already defined or is just a prototype + pub defined: bool, + /// Whether this overload is the one provided by the language or has + /// been redeclared by the user (builtins only) + pub internal: bool, + /// Whether or not this function returns void (nothing) + pub void: bool, +} + +bitflags::bitflags! { + /// Tracks the variations of the builtin already generated, this is needed because some + /// builtins overloads can't be generated unless explicitly used, since they might cause + /// unneeded capabilities to be requested + #[derive(Default)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct BuiltinVariations: u32 { + /// Request the standard overloads + const STANDARD = 1 << 0; + /// Request overloads that use the double type + const DOUBLE = 1 << 1; + /// Request overloads that use samplerCubeArray(Shadow) + const CUBE_TEXTURES_ARRAY = 1 << 2; + /// Request overloads that use sampler2DMSArray + const D2_MULTI_TEXTURES_ARRAY = 1 << 3; + } +} + +#[derive(Debug, Default)] +pub struct FunctionDeclaration { + pub overloads: Vec, + /// Tracks the builtin overload variations that were already generated + pub variations: BuiltinVariations, +} + +#[derive(Debug)] +pub struct EntryArg { + pub name: Option, + pub binding: Binding, + pub handle: Handle, + pub storage: StorageQualifier, +} + +#[derive(Debug, Clone)] +pub struct VariableReference { + pub expr: Handle, + /// Wether the variable is of a pointer type (and needs loading) or not + pub load: bool, + /// Wether the value of the variable can be changed or not + pub mutable: bool, + pub constant: Option<(Handle, Handle)>, + pub entry_arg: Option, +} + +#[derive(Debug, Clone)] +pub struct HirExpr { + pub kind: HirExprKind, + pub meta: Span, +} + +#[derive(Debug, Clone)] +pub enum HirExprKind { + Access { + base: Handle, + index: Handle, + }, + Select { + base: Handle, + field: String, + }, + Literal(Literal), + Binary { + left: Handle, + op: BinaryOperator, + right: Handle, + }, + Unary { + op: UnaryOperator, + expr: Handle, + }, + Variable(VariableReference), + Call(FunctionCall), + /// Represents the ternary operator in glsl (`:?`) + Conditional { + /// The expression that will decide which branch to take, must evaluate to a boolean + condition: Handle, + /// The expression that will be evaluated if [`condition`] returns `true` + /// + /// [`condition`]: Self::Conditional::condition + accept: Handle, + /// The expression that will be evaluated if [`condition`] returns `false` + /// + /// [`condition`]: Self::Conditional::condition + reject: Handle, + }, + Assign { + tgt: Handle, + value: Handle, + }, + /// A prefix/postfix operator like `++` + PrePostfix { + /// The operation to be performed + op: BinaryOperator, + /// Whether this is a postfix or a prefix + postfix: bool, + /// The target expression + expr: Handle, + }, + /// A method call like `what.something(a, b, c)` + Method { + /// expression the method call applies to (`what` in the example) + expr: Handle, + /// the method name (`something` in the example) + name: String, + /// the arguments to the method (`a`, `b`, and `c` in the example) + args: Vec>, + }, +} + +#[derive(Debug, Hash, PartialEq, Eq)] +pub enum QualifierKey<'a> { + String(Cow<'a, str>), + /// Used for `std140` and `std430` layout qualifiers + Layout, + /// Used for image formats + Format, +} + +#[derive(Debug)] +pub enum QualifierValue { + None, + Uint(u32), + Layout(StructLayout), + Format(crate::StorageFormat), +} + +#[derive(Debug, Default)] +pub struct TypeQualifiers<'a> { + pub span: Span, + pub storage: (StorageQualifier, Span), + pub invariant: Option, + pub interpolation: Option<(Interpolation, Span)>, + pub precision: Option<(Precision, Span)>, + pub sampling: Option<(Sampling, Span)>, + /// Memory qualifiers used in the declaration to set the storage access to be used + /// in declarations that support it (storage images and buffers) + pub storage_access: Option<(StorageAccess, Span)>, + pub layout_qualifiers: crate::FastHashMap, (QualifierValue, Span)>, +} + +impl<'a> TypeQualifiers<'a> { + /// Appends `errors` with errors for all unused qualifiers + pub fn unused_errors(&self, errors: &mut Vec) { + if let Some(meta) = self.invariant { + errors.push(super::Error { + kind: super::ErrorKind::SemanticError( + "Invariant qualifier can only be used in in/out variables".into(), + ), + meta, + }); + } + + if let Some((_, meta)) = self.interpolation { + errors.push(super::Error { + kind: super::ErrorKind::SemanticError( + "Interpolation qualifiers can only be used in in/out variables".into(), + ), + meta, + }); + } + + if let Some((_, meta)) = self.sampling { + errors.push(super::Error { + kind: super::ErrorKind::SemanticError( + "Sampling qualifiers can only be used in in/out variables".into(), + ), + meta, + }); + } + + if let Some((_, meta)) = self.storage_access { + errors.push(super::Error { + kind: super::ErrorKind::SemanticError( + "Memory qualifiers can only be used in storage variables".into(), + ), + meta, + }); + } + + for &(_, meta) in self.layout_qualifiers.values() { + errors.push(super::Error { + kind: super::ErrorKind::SemanticError("Unexpected qualifier".into()), + meta, + }); + } + } + + /// Removes the layout qualifier with `name`, if it exists and adds an error if it isn't + /// a [`QualifierValue::Uint`] + pub fn uint_layout_qualifier( + &mut self, + name: &'a str, + errors: &mut Vec, + ) -> Option { + match self + .layout_qualifiers + .remove(&QualifierKey::String(name.into())) + { + Some((QualifierValue::Uint(v), _)) => Some(v), + Some((_, meta)) => { + errors.push(super::Error { + kind: super::ErrorKind::SemanticError("Qualifier expects a uint value".into()), + meta, + }); + // Return a dummy value instead of `None` to differentiate from + // the qualifier not existing, since some parts might require the + // qualifier to exist and throwing another error that it doesn't + // exist would be unhelpful + Some(0) + } + _ => None, + } + } + + /// Removes the layout qualifier with `name`, if it exists and adds an error if it isn't + /// a [`QualifierValue::None`] + pub fn none_layout_qualifier(&mut self, name: &'a str, errors: &mut Vec) -> bool { + match self + .layout_qualifiers + .remove(&QualifierKey::String(name.into())) + { + Some((QualifierValue::None, _)) => true, + Some((_, meta)) => { + errors.push(super::Error { + kind: super::ErrorKind::SemanticError( + "Qualifier doesn't expect a value".into(), + ), + meta, + }); + // Return a `true` to since the qualifier is defined and adding + // another error for it not being defined would be unhelpful + true + } + _ => false, + } + } +} + +#[derive(Debug, Clone)] +pub enum FunctionCallKind { + TypeConstructor(Handle), + Function(String), +} + +#[derive(Debug, Clone)] +pub struct FunctionCall { + pub kind: FunctionCallKind, + pub args: Vec>, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum StorageQualifier { + AddressSpace(AddressSpace), + Input, + Output, + Const, +} + +impl Default for StorageQualifier { + fn default() -> Self { + StorageQualifier::AddressSpace(AddressSpace::Function) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StructLayout { + Std140, + Std430, +} + +// TODO: Encode precision hints in the IR +/// A precision hint used in GLSL declarations. +/// +/// Precision hints can be used to either speed up shader execution or control +/// the precision of arithmetic operations. +/// +/// To use a precision hint simply add it before the type in the declaration. +/// ```glsl +/// mediump float a; +/// ``` +/// +/// The default when no precision is declared is `highp` which means that all +/// operations operate with the type defined width. +/// +/// For `mediump` and `lowp` operations follow the spir-v +/// [`RelaxedPrecision`][RelaxedPrecision] decoration semantics. +/// +/// [RelaxedPrecision]: https://www.khronos.org/registry/SPIR-V/specs/unified1/SPIRV.html#_a_id_relaxedprecisionsection_a_relaxed_precision +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum Precision { + /// `lowp` precision + Low, + /// `mediump` precision + Medium, + /// `highp` precision + High, +} + +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum ParameterQualifier { + In, + Out, + InOut, + Const, +} + +impl ParameterQualifier { + /// Returns true if the argument should be passed as a lhs expression + pub const fn is_lhs(&self) -> bool { + match *self { + ParameterQualifier::Out | ParameterQualifier::InOut => true, + _ => false, + } + } + + /// Converts from a parameter qualifier into a [`ExprPos`] + pub const fn as_pos(&self) -> ExprPos { + match *self { + ParameterQualifier::Out | ParameterQualifier::InOut => ExprPos::Lhs, + _ => ExprPos::Rhs, + } + } +} + +/// The GLSL profile used by a shader. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Profile { + /// The `core` profile, default when no profile is specified. + Core, +} diff --git a/naga/src/front/glsl/builtins.rs b/naga/src/front/glsl/builtins.rs new file mode 100644 index 0000000000..9e3a578c6b --- /dev/null +++ b/naga/src/front/glsl/builtins.rs @@ -0,0 +1,2314 @@ +use super::{ + ast::{ + BuiltinVariations, FunctionDeclaration, FunctionKind, Overload, ParameterInfo, + ParameterQualifier, + }, + context::Context, + Error, ErrorKind, Frontend, Result, +}; +use crate::{ + BinaryOperator, DerivativeAxis as Axis, DerivativeControl as Ctrl, Expression, Handle, + ImageClass, ImageDimension as Dim, ImageQuery, MathFunction, Module, RelationalFunction, + SampleLevel, Scalar, ScalarKind as Sk, Span, Type, TypeInner, UnaryOperator, VectorSize, +}; + +impl crate::ScalarKind { + const fn dummy_storage_format(&self) -> crate::StorageFormat { + match *self { + Sk::Sint => crate::StorageFormat::R16Sint, + Sk::Uint => crate::StorageFormat::R16Uint, + _ => crate::StorageFormat::R16Float, + } + } +} + +impl Module { + /// Helper function, to create a function prototype for a builtin + fn add_builtin(&mut self, args: Vec, builtin: MacroCall) -> Overload { + let mut parameters = Vec::with_capacity(args.len()); + let mut parameters_info = Vec::with_capacity(args.len()); + + for arg in args { + parameters.push(self.types.insert( + Type { + name: None, + inner: arg, + }, + Span::default(), + )); + parameters_info.push(ParameterInfo { + qualifier: ParameterQualifier::In, + depth: false, + }); + } + + Overload { + parameters, + parameters_info, + kind: FunctionKind::Macro(builtin), + defined: false, + internal: true, + void: false, + } + } +} + +const fn make_coords_arg(number_of_components: usize, kind: Sk) -> TypeInner { + let scalar = Scalar { kind, width: 4 }; + + match number_of_components { + 1 => TypeInner::Scalar(scalar), + _ => TypeInner::Vector { + size: match number_of_components { + 2 => VectorSize::Bi, + 3 => VectorSize::Tri, + _ => VectorSize::Quad, + }, + scalar, + }, + } +} + +/// Inject builtins into the declaration +/// +/// This is done to not add a large startup cost and not increase memory +/// usage if it isn't needed. +pub fn inject_builtin( + declaration: &mut FunctionDeclaration, + module: &mut Module, + name: &str, + mut variations: BuiltinVariations, +) { + log::trace!( + "{} variations: {:?} {:?}", + name, + variations, + declaration.variations + ); + // Don't regeneate variations + variations.remove(declaration.variations); + declaration.variations |= variations; + + if variations.contains(BuiltinVariations::STANDARD) { + inject_standard_builtins(declaration, module, name) + } + + if variations.contains(BuiltinVariations::DOUBLE) { + inject_double_builtin(declaration, module, name) + } + + match name { + "texture" + | "textureGrad" + | "textureGradOffset" + | "textureLod" + | "textureLodOffset" + | "textureOffset" + | "textureProj" + | "textureProjGrad" + | "textureProjGradOffset" + | "textureProjLod" + | "textureProjLodOffset" + | "textureProjOffset" => { + let f = |kind, dim, arrayed, multi, shadow| { + for bits in 0..=0b11 { + let variant = bits & 0b1 != 0; + let bias = bits & 0b10 != 0; + + let (proj, offset, level_type) = match name { + // texture(gsampler, gvec P, [float bias]); + "texture" => (false, false, TextureLevelType::None), + // textureGrad(gsampler, gvec P, gvec dPdx, gvec dPdy); + "textureGrad" => (false, false, TextureLevelType::Grad), + // textureGradOffset(gsampler, gvec P, gvec dPdx, gvec dPdy, ivec offset); + "textureGradOffset" => (false, true, TextureLevelType::Grad), + // textureLod(gsampler, gvec P, float lod); + "textureLod" => (false, false, TextureLevelType::Lod), + // textureLodOffset(gsampler, gvec P, float lod, ivec offset); + "textureLodOffset" => (false, true, TextureLevelType::Lod), + // textureOffset(gsampler, gvec+1 P, ivec offset, [float bias]); + "textureOffset" => (false, true, TextureLevelType::None), + // textureProj(gsampler, gvec+1 P, [float bias]); + "textureProj" => (true, false, TextureLevelType::None), + // textureProjGrad(gsampler, gvec+1 P, gvec dPdx, gvec dPdy); + "textureProjGrad" => (true, false, TextureLevelType::Grad), + // textureProjGradOffset(gsampler, gvec+1 P, gvec dPdx, gvec dPdy, ivec offset); + "textureProjGradOffset" => (true, true, TextureLevelType::Grad), + // textureProjLod(gsampler, gvec+1 P, float lod); + "textureProjLod" => (true, false, TextureLevelType::Lod), + // textureProjLodOffset(gsampler, gvec+1 P, gvec dPdx, gvec dPdy, ivec offset); + "textureProjLodOffset" => (true, true, TextureLevelType::Lod), + // textureProjOffset(gsampler, gvec+1 P, ivec offset, [float bias]); + "textureProjOffset" => (true, true, TextureLevelType::None), + _ => unreachable!(), + }; + + let builtin = MacroCall::Texture { + proj, + offset, + shadow, + level_type, + }; + + // Parse out the variant settings. + let grad = level_type == TextureLevelType::Grad; + let lod = level_type == TextureLevelType::Lod; + + let supports_variant = proj && !shadow; + if variant && !supports_variant { + continue; + } + + if bias && !matches!(level_type, TextureLevelType::None) { + continue; + } + + // Proj doesn't work with arrayed or Cube + if proj && (arrayed || dim == Dim::Cube) { + continue; + } + + // texture operations with offset are not supported for cube maps + if dim == Dim::Cube && offset { + continue; + } + + // sampler2DArrayShadow can't be used in textureLod or in texture with bias + if (lod || bias) && arrayed && shadow && dim == Dim::D2 { + continue; + } + + // TODO: glsl supports using bias with depth samplers but naga doesn't + if bias && shadow { + continue; + } + + let class = match shadow { + true => ImageClass::Depth { multi }, + false => ImageClass::Sampled { kind, multi }, + }; + + let image = TypeInner::Image { + dim, + arrayed, + class, + }; + + let num_coords_from_dim = image_dims_to_coords_size(dim).min(3); + let mut num_coords = num_coords_from_dim; + + if shadow && proj { + num_coords = 4; + } else if dim == Dim::D1 && shadow { + num_coords = 3; + } else if shadow { + num_coords += 1; + } else if proj { + if variant && num_coords == 4 { + // Normal form already has 4 components, no need to have a variant form. + continue; + } else if variant { + num_coords = 4; + } else { + num_coords += 1; + } + } + + if !(dim == Dim::D1 && shadow) { + num_coords += arrayed as usize; + } + + // Special case: texture(gsamplerCubeArrayShadow) kicks the shadow compare ref to a separate argument, + // since it would otherwise take five arguments. It also can't take a bias, nor can it be proj/grad/lod/offset + // (presumably because nobody asked for it, and implementation complexity?) + if num_coords >= 5 { + if lod || grad || offset || proj || bias { + continue; + } + debug_assert!(dim == Dim::Cube && shadow && arrayed); + } + debug_assert!(num_coords <= 5); + + let vector = make_coords_arg(num_coords, Sk::Float); + let mut args = vec![image, vector]; + + if num_coords == 5 { + args.push(TypeInner::Scalar(Scalar::F32)); + } + + match level_type { + TextureLevelType::Lod => { + args.push(TypeInner::Scalar(Scalar::F32)); + } + TextureLevelType::Grad => { + args.push(make_coords_arg(num_coords_from_dim, Sk::Float)); + args.push(make_coords_arg(num_coords_from_dim, Sk::Float)); + } + TextureLevelType::None => {} + }; + + if offset { + args.push(make_coords_arg(num_coords_from_dim, Sk::Sint)); + } + + if bias { + args.push(TypeInner::Scalar(Scalar::F32)); + } + + declaration + .overloads + .push(module.add_builtin(args, builtin)); + } + }; + + texture_args_generator(TextureArgsOptions::SHADOW | variations.into(), f) + } + "textureSize" => { + let f = |kind, dim, arrayed, multi, shadow| { + let class = match shadow { + true => ImageClass::Depth { multi }, + false => ImageClass::Sampled { kind, multi }, + }; + + let image = TypeInner::Image { + dim, + arrayed, + class, + }; + + let mut args = vec![image]; + + if !multi { + args.push(TypeInner::Scalar(Scalar::I32)) + } + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::TextureSize { arrayed })) + }; + + texture_args_generator( + TextureArgsOptions::SHADOW | TextureArgsOptions::MULTI | variations.into(), + f, + ) + } + "texelFetch" | "texelFetchOffset" => { + let offset = "texelFetchOffset" == name; + let f = |kind, dim, arrayed, multi, _shadow| { + // Cube images aren't supported + if let Dim::Cube = dim { + return; + } + + let image = TypeInner::Image { + dim, + arrayed, + class: ImageClass::Sampled { kind, multi }, + }; + + let dim_value = image_dims_to_coords_size(dim); + let coordinates = make_coords_arg(dim_value + arrayed as usize, Sk::Sint); + + let mut args = vec![image, coordinates, TypeInner::Scalar(Scalar::I32)]; + + if offset { + args.push(make_coords_arg(dim_value, Sk::Sint)); + } + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::ImageLoad { multi })) + }; + + // Don't generate shadow images since they aren't supported + texture_args_generator(TextureArgsOptions::MULTI | variations.into(), f) + } + "imageSize" => { + let f = |kind: Sk, dim, arrayed, _, _| { + // Naga doesn't support cube images and it's usefulness + // is questionable, so they won't be supported for now + if dim == Dim::Cube { + return; + } + + let image = TypeInner::Image { + dim, + arrayed, + class: ImageClass::Storage { + format: kind.dummy_storage_format(), + access: crate::StorageAccess::empty(), + }, + }; + + declaration + .overloads + .push(module.add_builtin(vec![image], MacroCall::TextureSize { arrayed })) + }; + + texture_args_generator(variations.into(), f) + } + "imageLoad" => { + let f = |kind: Sk, dim, arrayed, _, _| { + // Naga doesn't support cube images and it's usefulness + // is questionable, so they won't be supported for now + if dim == Dim::Cube { + return; + } + + let image = TypeInner::Image { + dim, + arrayed, + class: ImageClass::Storage { + format: kind.dummy_storage_format(), + access: crate::StorageAccess::LOAD, + }, + }; + + let dim_value = image_dims_to_coords_size(dim); + let mut coord_size = dim_value + arrayed as usize; + // > Every OpenGL API call that operates on cubemap array + // > textures takes layer-faces, not array layers + // + // So this means that imageCubeArray only takes a three component + // vector coordinate and the third component is a layer index. + if Dim::Cube == dim && arrayed { + coord_size = 3 + } + let coordinates = make_coords_arg(coord_size, Sk::Sint); + + let args = vec![image, coordinates]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::ImageLoad { multi: false })) + }; + + // Don't generate shadow nor multisampled images since they aren't supported + texture_args_generator(variations.into(), f) + } + "imageStore" => { + let f = |kind: Sk, dim, arrayed, _, _| { + // Naga doesn't support cube images and it's usefulness + // is questionable, so they won't be supported for now + if dim == Dim::Cube { + return; + } + + let image = TypeInner::Image { + dim, + arrayed, + class: ImageClass::Storage { + format: kind.dummy_storage_format(), + access: crate::StorageAccess::STORE, + }, + }; + + let dim_value = image_dims_to_coords_size(dim); + let mut coord_size = dim_value + arrayed as usize; + // > Every OpenGL API call that operates on cubemap array + // > textures takes layer-faces, not array layers + // + // So this means that imageCubeArray only takes a three component + // vector coordinate and the third component is a layer index. + if Dim::Cube == dim && arrayed { + coord_size = 3 + } + let coordinates = make_coords_arg(coord_size, Sk::Sint); + + let args = vec![ + image, + coordinates, + TypeInner::Vector { + size: VectorSize::Quad, + scalar: Scalar { kind, width: 4 }, + }, + ]; + + let mut overload = module.add_builtin(args, MacroCall::ImageStore); + overload.void = true; + declaration.overloads.push(overload) + }; + + // Don't generate shadow nor multisampled images since they aren't supported + texture_args_generator(variations.into(), f) + } + _ => {} + } +} + +/// Injects the builtins into declaration that don't need any special variations +fn inject_standard_builtins( + declaration: &mut FunctionDeclaration, + module: &mut Module, + name: &str, +) { + match name { + "sampler1D" | "sampler1DArray" | "sampler2D" | "sampler2DArray" | "sampler2DMS" + | "sampler2DMSArray" | "sampler3D" | "samplerCube" | "samplerCubeArray" => { + declaration.overloads.push(module.add_builtin( + vec![ + TypeInner::Image { + dim: match name { + "sampler1D" | "sampler1DArray" => Dim::D1, + "sampler2D" | "sampler2DArray" | "sampler2DMS" | "sampler2DMSArray" => { + Dim::D2 + } + "sampler3D" => Dim::D3, + _ => Dim::Cube, + }, + arrayed: matches!( + name, + "sampler1DArray" + | "sampler2DArray" + | "sampler2DMSArray" + | "samplerCubeArray" + ), + class: ImageClass::Sampled { + kind: Sk::Float, + multi: matches!(name, "sampler2DMS" | "sampler2DMSArray"), + }, + }, + TypeInner::Sampler { comparison: false }, + ], + MacroCall::Sampler, + )) + } + "sampler1DShadow" + | "sampler1DArrayShadow" + | "sampler2DShadow" + | "sampler2DArrayShadow" + | "samplerCubeShadow" + | "samplerCubeArrayShadow" => { + let dim = match name { + "sampler1DShadow" | "sampler1DArrayShadow" => Dim::D1, + "sampler2DShadow" | "sampler2DArrayShadow" => Dim::D2, + _ => Dim::Cube, + }; + let arrayed = matches!( + name, + "sampler1DArrayShadow" | "sampler2DArrayShadow" | "samplerCubeArrayShadow" + ); + + for i in 0..2 { + let ty = TypeInner::Image { + dim, + arrayed, + class: match i { + 0 => ImageClass::Sampled { + kind: Sk::Float, + multi: false, + }, + _ => ImageClass::Depth { multi: false }, + }, + }; + + declaration.overloads.push(module.add_builtin( + vec![ty, TypeInner::Sampler { comparison: true }], + MacroCall::SamplerShadow, + )) + } + } + "sin" | "exp" | "exp2" | "sinh" | "cos" | "cosh" | "tan" | "tanh" | "acos" | "asin" + | "log" | "log2" | "radians" | "degrees" | "asinh" | "acosh" | "atanh" + | "floatBitsToInt" | "floatBitsToUint" | "dFdx" | "dFdxFine" | "dFdxCoarse" | "dFdy" + | "dFdyFine" | "dFdyCoarse" | "fwidth" | "fwidthFine" | "fwidthCoarse" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = Scalar::F32; + + declaration.overloads.push(module.add_builtin( + vec![match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }], + match name { + "sin" => MacroCall::MathFunction(MathFunction::Sin), + "exp" => MacroCall::MathFunction(MathFunction::Exp), + "exp2" => MacroCall::MathFunction(MathFunction::Exp2), + "sinh" => MacroCall::MathFunction(MathFunction::Sinh), + "cos" => MacroCall::MathFunction(MathFunction::Cos), + "cosh" => MacroCall::MathFunction(MathFunction::Cosh), + "tan" => MacroCall::MathFunction(MathFunction::Tan), + "tanh" => MacroCall::MathFunction(MathFunction::Tanh), + "acos" => MacroCall::MathFunction(MathFunction::Acos), + "asin" => MacroCall::MathFunction(MathFunction::Asin), + "log" => MacroCall::MathFunction(MathFunction::Log), + "log2" => MacroCall::MathFunction(MathFunction::Log2), + "asinh" => MacroCall::MathFunction(MathFunction::Asinh), + "acosh" => MacroCall::MathFunction(MathFunction::Acosh), + "atanh" => MacroCall::MathFunction(MathFunction::Atanh), + "radians" => MacroCall::MathFunction(MathFunction::Radians), + "degrees" => MacroCall::MathFunction(MathFunction::Degrees), + "floatBitsToInt" => MacroCall::BitCast(Sk::Sint), + "floatBitsToUint" => MacroCall::BitCast(Sk::Uint), + "dFdxCoarse" => MacroCall::Derivate(Axis::X, Ctrl::Coarse), + "dFdyCoarse" => MacroCall::Derivate(Axis::Y, Ctrl::Coarse), + "fwidthCoarse" => MacroCall::Derivate(Axis::Width, Ctrl::Coarse), + "dFdxFine" => MacroCall::Derivate(Axis::X, Ctrl::Fine), + "dFdyFine" => MacroCall::Derivate(Axis::Y, Ctrl::Fine), + "fwidthFine" => MacroCall::Derivate(Axis::Width, Ctrl::Fine), + "dFdx" => MacroCall::Derivate(Axis::X, Ctrl::None), + "dFdy" => MacroCall::Derivate(Axis::Y, Ctrl::None), + "fwidth" => MacroCall::Derivate(Axis::Width, Ctrl::None), + _ => unreachable!(), + }, + )) + } + } + "intBitsToFloat" | "uintBitsToFloat" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = match name { + "intBitsToFloat" => Scalar::I32, + _ => Scalar::U32, + }; + + declaration.overloads.push(module.add_builtin( + vec![match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }], + MacroCall::BitCast(Sk::Float), + )) + } + } + "pow" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = Scalar::F32; + let ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + + declaration.overloads.push( + module + .add_builtin(vec![ty(), ty()], MacroCall::MathFunction(MathFunction::Pow)), + ) + } + } + "abs" | "sign" => { + // bits layout + // bit 0 through 1 - dims + // bit 2 - float/sint + for bits in 0..0b1000 { + let size = match bits & 0b11 { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = match bits >> 2 { + 0b0 => Scalar::F32, + _ => Scalar::I32, + }; + + let args = vec![match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }]; + + declaration.overloads.push(module.add_builtin( + args, + MacroCall::MathFunction(match name { + "abs" => MathFunction::Abs, + "sign" => MathFunction::Sign, + _ => unreachable!(), + }), + )) + } + } + "bitCount" | "bitfieldReverse" | "bitfieldExtract" | "bitfieldInsert" | "findLSB" + | "findMSB" => { + let fun = match name { + "bitCount" => MathFunction::CountOneBits, + "bitfieldReverse" => MathFunction::ReverseBits, + "bitfieldExtract" => MathFunction::ExtractBits, + "bitfieldInsert" => MathFunction::InsertBits, + "findLSB" => MathFunction::FindLsb, + "findMSB" => MathFunction::FindMsb, + _ => unreachable!(), + }; + + let mc = match fun { + MathFunction::ExtractBits => MacroCall::BitfieldExtract, + MathFunction::InsertBits => MacroCall::BitfieldInsert, + _ => MacroCall::MathFunction(fun), + }; + + // bits layout + // bit 0 - int/uint + // bit 1 through 2 - dims + for bits in 0..0b1000 { + let scalar = match bits & 0b1 { + 0b0 => Scalar::I32, + _ => Scalar::U32, + }; + let size = match bits >> 1 { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + let ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + + let mut args = vec![ty()]; + + match fun { + MathFunction::ExtractBits => { + args.push(TypeInner::Scalar(Scalar::I32)); + args.push(TypeInner::Scalar(Scalar::I32)); + } + MathFunction::InsertBits => { + args.push(ty()); + args.push(TypeInner::Scalar(Scalar::I32)); + args.push(TypeInner::Scalar(Scalar::I32)); + } + _ => {} + } + + // we need to cast the return type of findLsb / findMsb + let mc = if scalar.kind == Sk::Uint { + match mc { + MacroCall::MathFunction(MathFunction::FindLsb) => MacroCall::FindLsbUint, + MacroCall::MathFunction(MathFunction::FindMsb) => MacroCall::FindMsbUint, + mc => mc, + } + } else { + mc + }; + + declaration.overloads.push(module.add_builtin(args, mc)) + } + } + "packSnorm4x8" | "packUnorm4x8" | "packSnorm2x16" | "packUnorm2x16" | "packHalf2x16" => { + let fun = match name { + "packSnorm4x8" => MathFunction::Pack4x8snorm, + "packUnorm4x8" => MathFunction::Pack4x8unorm, + "packSnorm2x16" => MathFunction::Pack2x16unorm, + "packUnorm2x16" => MathFunction::Pack2x16snorm, + "packHalf2x16" => MathFunction::Pack2x16float, + _ => unreachable!(), + }; + + let ty = match fun { + MathFunction::Pack4x8snorm | MathFunction::Pack4x8unorm => TypeInner::Vector { + size: crate::VectorSize::Quad, + scalar: Scalar::F32, + }, + MathFunction::Pack2x16unorm + | MathFunction::Pack2x16snorm + | MathFunction::Pack2x16float => TypeInner::Vector { + size: crate::VectorSize::Bi, + scalar: Scalar::F32, + }, + _ => unreachable!(), + }; + + let args = vec![ty]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(fun))); + } + "unpackSnorm4x8" | "unpackUnorm4x8" | "unpackSnorm2x16" | "unpackUnorm2x16" + | "unpackHalf2x16" => { + let fun = match name { + "unpackSnorm4x8" => MathFunction::Unpack4x8snorm, + "unpackUnorm4x8" => MathFunction::Unpack4x8unorm, + "unpackSnorm2x16" => MathFunction::Unpack2x16snorm, + "unpackUnorm2x16" => MathFunction::Unpack2x16unorm, + "unpackHalf2x16" => MathFunction::Unpack2x16float, + _ => unreachable!(), + }; + + let args = vec![TypeInner::Scalar(Scalar::U32)]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(fun))); + } + "atan" => { + // bits layout + // bit 0 - atan/atan2 + // bit 1 through 2 - dims + for bits in 0..0b1000 { + let fun = match bits & 0b1 { + 0b0 => MathFunction::Atan, + _ => MathFunction::Atan2, + }; + let size = match bits >> 1 { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = Scalar::F32; + let ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + + let mut args = vec![ty()]; + + if fun == MathFunction::Atan2 { + args.push(ty()) + } + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(fun))) + } + } + "all" | "any" | "not" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b11 { + let size = match bits { + 0b00 => VectorSize::Bi, + 0b01 => VectorSize::Tri, + _ => VectorSize::Quad, + }; + + let args = vec![TypeInner::Vector { + size, + scalar: Scalar::BOOL, + }]; + + let fun = match name { + "all" => MacroCall::Relational(RelationalFunction::All), + "any" => MacroCall::Relational(RelationalFunction::Any), + "not" => MacroCall::Unary(UnaryOperator::LogicalNot), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "lessThan" | "greaterThan" | "lessThanEqual" | "greaterThanEqual" => { + for bits in 0..0b1001 { + let (size, scalar) = match bits { + 0b0000 => (VectorSize::Bi, Scalar::F32), + 0b0001 => (VectorSize::Tri, Scalar::F32), + 0b0010 => (VectorSize::Quad, Scalar::F32), + 0b0011 => (VectorSize::Bi, Scalar::I32), + 0b0100 => (VectorSize::Tri, Scalar::I32), + 0b0101 => (VectorSize::Quad, Scalar::I32), + 0b0110 => (VectorSize::Bi, Scalar::U32), + 0b0111 => (VectorSize::Tri, Scalar::U32), + _ => (VectorSize::Quad, Scalar::U32), + }; + + let ty = || TypeInner::Vector { size, scalar }; + let args = vec![ty(), ty()]; + + let fun = MacroCall::Binary(match name { + "lessThan" => BinaryOperator::Less, + "greaterThan" => BinaryOperator::Greater, + "lessThanEqual" => BinaryOperator::LessEqual, + "greaterThanEqual" => BinaryOperator::GreaterEqual, + _ => unreachable!(), + }); + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "equal" | "notEqual" => { + for bits in 0..0b1100 { + let (size, scalar) = match bits { + 0b0000 => (VectorSize::Bi, Scalar::F32), + 0b0001 => (VectorSize::Tri, Scalar::F32), + 0b0010 => (VectorSize::Quad, Scalar::F32), + 0b0011 => (VectorSize::Bi, Scalar::I32), + 0b0100 => (VectorSize::Tri, Scalar::I32), + 0b0101 => (VectorSize::Quad, Scalar::I32), + 0b0110 => (VectorSize::Bi, Scalar::U32), + 0b0111 => (VectorSize::Tri, Scalar::U32), + 0b1000 => (VectorSize::Quad, Scalar::U32), + 0b1001 => (VectorSize::Bi, Scalar::BOOL), + 0b1010 => (VectorSize::Tri, Scalar::BOOL), + _ => (VectorSize::Quad, Scalar::BOOL), + }; + + let ty = || TypeInner::Vector { size, scalar }; + let args = vec![ty(), ty()]; + + let fun = MacroCall::Binary(match name { + "equal" => BinaryOperator::Equal, + "notEqual" => BinaryOperator::NotEqual, + _ => unreachable!(), + }); + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "min" | "max" => { + // bits layout + // bit 0 through 1 - scalar kind + // bit 2 through 4 - dims + for bits in 0..0b11100 { + let scalar = match bits & 0b11 { + 0b00 => Scalar::F32, + 0b01 => Scalar::I32, + 0b10 => Scalar::U32, + _ => continue, + }; + let (size, second_size) = match bits >> 2 { + 0b000 => (None, None), + 0b001 => (Some(VectorSize::Bi), None), + 0b010 => (Some(VectorSize::Tri), None), + 0b011 => (Some(VectorSize::Quad), None), + 0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)), + 0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)), + _ => (Some(VectorSize::Quad), Some(VectorSize::Quad)), + }; + + let args = vec![ + match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }, + match second_size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }, + ]; + + let fun = match name { + "max" => MacroCall::Splatted(MathFunction::Max, size, 1), + "min" => MacroCall::Splatted(MathFunction::Min, size, 1), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "mix" => { + // bits layout + // bit 0 through 1 - dims + // bit 2 through 4 - types + // + // 0b10011 is the last element since splatted single elements + // were already added + for bits in 0..0b10011 { + let size = match bits & 0b11 { + 0b00 => Some(VectorSize::Bi), + 0b01 => Some(VectorSize::Tri), + 0b10 => Some(VectorSize::Quad), + _ => None, + }; + let (scalar, splatted, boolean) = match bits >> 2 { + 0b000 => (Scalar::I32, false, true), + 0b001 => (Scalar::U32, false, true), + 0b010 => (Scalar::F32, false, true), + 0b011 => (Scalar::F32, false, false), + _ => (Scalar::F32, true, false), + }; + + let ty = |scalar| match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + let args = vec![ + ty(scalar), + ty(scalar), + match (boolean, splatted) { + (true, _) => ty(Scalar::BOOL), + (_, false) => TypeInner::Scalar(scalar), + _ => ty(scalar), + }, + ]; + + declaration.overloads.push(module.add_builtin( + args, + match boolean { + true => MacroCall::MixBoolean, + false => MacroCall::Splatted(MathFunction::Mix, size, 2), + }, + )) + } + } + "clamp" => { + // bits layout + // bit 0 through 1 - float/int/uint + // bit 2 through 3 - dims + // bit 4 - splatted + // + // 0b11010 is the last element since splatted single elements + // were already added + for bits in 0..0b11011 { + let scalar = match bits & 0b11 { + 0b00 => Scalar::F32, + 0b01 => Scalar::I32, + 0b10 => Scalar::U32, + _ => continue, + }; + let size = match (bits >> 2) & 0b11 { + 0b00 => Some(VectorSize::Bi), + 0b01 => Some(VectorSize::Tri), + 0b10 => Some(VectorSize::Quad), + _ => None, + }; + let splatted = bits & 0b10000 == 0b10000; + + let base_ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + let limit_ty = || match splatted { + true => TypeInner::Scalar(scalar), + false => base_ty(), + }; + + let args = vec![base_ty(), limit_ty(), limit_ty()]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::Clamp(size))) + } + } + "barrier" => declaration + .overloads + .push(module.add_builtin(Vec::new(), MacroCall::Barrier)), + // Add common builtins with floats + _ => inject_common_builtin(declaration, module, name, 4), + } +} + +/// Injects the builtins into declaration that need doubles +fn inject_double_builtin(declaration: &mut FunctionDeclaration, module: &mut Module, name: &str) { + match name { + "abs" | "sign" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let scalar = Scalar::F64; + + let args = vec![match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }]; + + declaration.overloads.push(module.add_builtin( + args, + MacroCall::MathFunction(match name { + "abs" => MathFunction::Abs, + "sign" => MathFunction::Sign, + _ => unreachable!(), + }), + )) + } + } + "min" | "max" => { + // bits layout + // bit 0 through 2 - dims + for bits in 0..0b111 { + let (size, second_size) = match bits { + 0b000 => (None, None), + 0b001 => (Some(VectorSize::Bi), None), + 0b010 => (Some(VectorSize::Tri), None), + 0b011 => (Some(VectorSize::Quad), None), + 0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)), + 0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)), + _ => (Some(VectorSize::Quad), Some(VectorSize::Quad)), + }; + let scalar = Scalar::F64; + + let args = vec![ + match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }, + match second_size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }, + ]; + + let fun = match name { + "max" => MacroCall::Splatted(MathFunction::Max, size, 1), + "min" => MacroCall::Splatted(MathFunction::Min, size, 1), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "mix" => { + // bits layout + // bit 0 through 1 - dims + // bit 2 through 3 - splatted/boolean + // + // 0b1010 is the last element since splatted with single elements + // is equal to normal single elements + for bits in 0..0b1011 { + let size = match bits & 0b11 { + 0b00 => Some(VectorSize::Quad), + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => None, + }; + let scalar = Scalar::F64; + let (splatted, boolean) = match bits >> 2 { + 0b00 => (false, false), + 0b01 => (false, true), + _ => (true, false), + }; + + let ty = |scalar| match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + let args = vec![ + ty(scalar), + ty(scalar), + match (boolean, splatted) { + (true, _) => ty(Scalar::BOOL), + (_, false) => TypeInner::Scalar(scalar), + _ => ty(scalar), + }, + ]; + + declaration.overloads.push(module.add_builtin( + args, + match boolean { + true => MacroCall::MixBoolean, + false => MacroCall::Splatted(MathFunction::Mix, size, 2), + }, + )) + } + } + "clamp" => { + // bits layout + // bit 0 through 1 - dims + // bit 2 - splatted + // + // 0b110 is the last element since splatted with single elements + // is equal to normal single elements + for bits in 0..0b111 { + let scalar = Scalar::F64; + let size = match bits & 0b11 { + 0b00 => Some(VectorSize::Bi), + 0b01 => Some(VectorSize::Tri), + 0b10 => Some(VectorSize::Quad), + _ => None, + }; + let splatted = bits & 0b100 == 0b100; + + let base_ty = || match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + let limit_ty = || match splatted { + true => TypeInner::Scalar(scalar), + false => base_ty(), + }; + + let args = vec![base_ty(), limit_ty(), limit_ty()]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::Clamp(size))) + } + } + "lessThan" | "greaterThan" | "lessThanEqual" | "greaterThanEqual" | "equal" + | "notEqual" => { + let scalar = Scalar::F64; + for bits in 0..0b11 { + let size = match bits { + 0b00 => VectorSize::Bi, + 0b01 => VectorSize::Tri, + _ => VectorSize::Quad, + }; + + let ty = || TypeInner::Vector { size, scalar }; + let args = vec![ty(), ty()]; + + let fun = MacroCall::Binary(match name { + "lessThan" => BinaryOperator::Less, + "greaterThan" => BinaryOperator::Greater, + "lessThanEqual" => BinaryOperator::LessEqual, + "greaterThanEqual" => BinaryOperator::GreaterEqual, + "equal" => BinaryOperator::Equal, + "notEqual" => BinaryOperator::NotEqual, + _ => unreachable!(), + }); + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + // Add common builtins with doubles + _ => inject_common_builtin(declaration, module, name, 8), + } +} + +/// Injects the builtins into declaration that can used either float or doubles +fn inject_common_builtin( + declaration: &mut FunctionDeclaration, + module: &mut Module, + name: &str, + float_width: crate::Bytes, +) { + let float_scalar = Scalar { + kind: Sk::Float, + width: float_width, + }; + match name { + "ceil" | "round" | "roundEven" | "floor" | "fract" | "trunc" | "sqrt" | "inversesqrt" + | "normalize" | "length" | "isinf" | "isnan" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + let args = vec![match size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }]; + + let fun = match name { + "ceil" => MacroCall::MathFunction(MathFunction::Ceil), + "round" | "roundEven" => MacroCall::MathFunction(MathFunction::Round), + "floor" => MacroCall::MathFunction(MathFunction::Floor), + "fract" => MacroCall::MathFunction(MathFunction::Fract), + "trunc" => MacroCall::MathFunction(MathFunction::Trunc), + "sqrt" => MacroCall::MathFunction(MathFunction::Sqrt), + "inversesqrt" => MacroCall::MathFunction(MathFunction::InverseSqrt), + "normalize" => MacroCall::MathFunction(MathFunction::Normalize), + "length" => MacroCall::MathFunction(MathFunction::Length), + "isinf" => MacroCall::Relational(RelationalFunction::IsInf), + "isnan" => MacroCall::Relational(RelationalFunction::IsNan), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "dot" | "reflect" | "distance" | "ldexp" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + let ty = |scalar| match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + + let fun = match name { + "dot" => MacroCall::MathFunction(MathFunction::Dot), + "reflect" => MacroCall::MathFunction(MathFunction::Reflect), + "distance" => MacroCall::MathFunction(MathFunction::Distance), + "ldexp" => MacroCall::MathFunction(MathFunction::Ldexp), + _ => unreachable!(), + }; + + let second_scalar = match fun { + MacroCall::MathFunction(MathFunction::Ldexp) => Scalar::I32, + _ => float_scalar, + }; + + declaration + .overloads + .push(module.add_builtin(vec![ty(float_scalar), ty(second_scalar)], fun)) + } + } + "transpose" => { + // bits layout + // bit 0 through 3 - dims + for bits in 0..0b1001 { + let (rows, columns) = match bits { + 0b0000 => (VectorSize::Bi, VectorSize::Bi), + 0b0001 => (VectorSize::Bi, VectorSize::Tri), + 0b0010 => (VectorSize::Bi, VectorSize::Quad), + 0b0011 => (VectorSize::Tri, VectorSize::Bi), + 0b0100 => (VectorSize::Tri, VectorSize::Tri), + 0b0101 => (VectorSize::Tri, VectorSize::Quad), + 0b0110 => (VectorSize::Quad, VectorSize::Bi), + 0b0111 => (VectorSize::Quad, VectorSize::Tri), + _ => (VectorSize::Quad, VectorSize::Quad), + }; + + declaration.overloads.push(module.add_builtin( + vec![TypeInner::Matrix { + columns, + rows, + scalar: float_scalar, + }], + MacroCall::MathFunction(MathFunction::Transpose), + )) + } + } + "inverse" | "determinant" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b11 { + let (rows, columns) = match bits { + 0b00 => (VectorSize::Bi, VectorSize::Bi), + 0b01 => (VectorSize::Tri, VectorSize::Tri), + _ => (VectorSize::Quad, VectorSize::Quad), + }; + + let args = vec![TypeInner::Matrix { + columns, + rows, + scalar: float_scalar, + }]; + + declaration.overloads.push(module.add_builtin( + args, + MacroCall::MathFunction(match name { + "inverse" => MathFunction::Inverse, + "determinant" => MathFunction::Determinant, + _ => unreachable!(), + }), + )) + } + } + "mod" | "step" => { + // bits layout + // bit 0 through 2 - dims + for bits in 0..0b111 { + let (size, second_size) = match bits { + 0b000 => (None, None), + 0b001 => (Some(VectorSize::Bi), None), + 0b010 => (Some(VectorSize::Tri), None), + 0b011 => (Some(VectorSize::Quad), None), + 0b100 => (Some(VectorSize::Bi), Some(VectorSize::Bi)), + 0b101 => (Some(VectorSize::Tri), Some(VectorSize::Tri)), + _ => (Some(VectorSize::Quad), Some(VectorSize::Quad)), + }; + + let mut args = Vec::with_capacity(2); + let step = name == "step"; + + for i in 0..2 { + let maybe_size = match i == step as u32 { + true => size, + false => second_size, + }; + + args.push(match maybe_size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }) + } + + let fun = match name { + "mod" => MacroCall::Mod(size), + "step" => MacroCall::Splatted(MathFunction::Step, size, 0), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + // TODO: https://github.com/gfx-rs/naga/issues/2526 + // "modf" | "frexp" => { ... } + "cross" => { + let args = vec![ + TypeInner::Vector { + size: VectorSize::Tri, + scalar: float_scalar, + }, + TypeInner::Vector { + size: VectorSize::Tri, + scalar: float_scalar, + }, + ]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Cross))) + } + "outerProduct" => { + // bits layout + // bit 0 through 3 - dims + for bits in 0..0b1001 { + let (size1, size2) = match bits { + 0b0000 => (VectorSize::Bi, VectorSize::Bi), + 0b0001 => (VectorSize::Bi, VectorSize::Tri), + 0b0010 => (VectorSize::Bi, VectorSize::Quad), + 0b0011 => (VectorSize::Tri, VectorSize::Bi), + 0b0100 => (VectorSize::Tri, VectorSize::Tri), + 0b0101 => (VectorSize::Tri, VectorSize::Quad), + 0b0110 => (VectorSize::Quad, VectorSize::Bi), + 0b0111 => (VectorSize::Quad, VectorSize::Tri), + _ => (VectorSize::Quad, VectorSize::Quad), + }; + + let args = vec![ + TypeInner::Vector { + size: size1, + scalar: float_scalar, + }, + TypeInner::Vector { + size: size2, + scalar: float_scalar, + }, + ]; + + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Outer))) + } + } + "faceforward" | "fma" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + let ty = || match size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }; + let args = vec![ty(), ty(), ty()]; + + let fun = match name { + "faceforward" => MacroCall::MathFunction(MathFunction::FaceForward), + "fma" => MacroCall::MathFunction(MathFunction::Fma), + _ => unreachable!(), + }; + + declaration.overloads.push(module.add_builtin(args, fun)) + } + } + "refract" => { + // bits layout + // bit 0 through 1 - dims + for bits in 0..0b100 { + let size = match bits { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + let ty = || match size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }; + let args = vec![ty(), ty(), TypeInner::Scalar(Scalar::F32)]; + declaration + .overloads + .push(module.add_builtin(args, MacroCall::MathFunction(MathFunction::Refract))) + } + } + "smoothstep" => { + // bit 0 - splatted + // bit 1 through 2 - dims + for bits in 0..0b1000 { + let splatted = bits & 0b1 == 0b1; + let size = match bits >> 1 { + 0b00 => None, + 0b01 => Some(VectorSize::Bi), + 0b10 => Some(VectorSize::Tri), + _ => Some(VectorSize::Quad), + }; + + if splatted && size.is_none() { + continue; + } + + let base_ty = || match size { + Some(size) => TypeInner::Vector { + size, + scalar: float_scalar, + }, + None => TypeInner::Scalar(float_scalar), + }; + let ty = || match splatted { + true => TypeInner::Scalar(float_scalar), + false => base_ty(), + }; + declaration.overloads.push(module.add_builtin( + vec![ty(), ty(), base_ty()], + MacroCall::SmoothStep { splatted: size }, + )) + } + } + // The function isn't a builtin or we don't yet support it + _ => {} + } +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum TextureLevelType { + None, + Lod, + Grad, +} + +/// A compiler defined builtin function +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum MacroCall { + Sampler, + SamplerShadow, + Texture { + proj: bool, + offset: bool, + shadow: bool, + level_type: TextureLevelType, + }, + TextureSize { + arrayed: bool, + }, + ImageLoad { + multi: bool, + }, + ImageStore, + MathFunction(MathFunction), + FindLsbUint, + FindMsbUint, + BitfieldExtract, + BitfieldInsert, + Relational(RelationalFunction), + Unary(UnaryOperator), + Binary(BinaryOperator), + Mod(Option), + Splatted(MathFunction, Option, usize), + MixBoolean, + Clamp(Option), + BitCast(Sk), + Derivate(Axis, Ctrl), + Barrier, + /// SmoothStep needs a separate variant because it might need it's inputs + /// to be splatted depending on the overload + SmoothStep { + /// The size of the splat operation if some + splatted: Option, + }, +} + +impl MacroCall { + /// Adds the necessary expressions and statements to the passed body and + /// finally returns the final expression with the correct result + pub fn call( + &self, + frontend: &mut Frontend, + ctx: &mut Context, + args: &mut [Handle], + meta: Span, + ) -> Result>> { + Ok(Some(match *self { + MacroCall::Sampler => { + ctx.samplers.insert(args[0], args[1]); + args[0] + } + MacroCall::SamplerShadow => { + sampled_to_depth(ctx, args[0], meta, &mut frontend.errors); + ctx.invalidate_expression(args[0], meta)?; + ctx.samplers.insert(args[0], args[1]); + args[0] + } + MacroCall::Texture { + proj, + offset, + shadow, + level_type, + } => { + let mut coords = args[1]; + + if proj { + let size = match *ctx.resolve_type(coords, meta)? { + TypeInner::Vector { size, .. } => size, + _ => unreachable!(), + }; + let mut right = ctx.add_expression( + Expression::AccessIndex { + base: coords, + index: size as u32 - 1, + }, + Span::default(), + )?; + let left = if let VectorSize::Bi = size { + ctx.add_expression( + Expression::AccessIndex { + base: coords, + index: 0, + }, + Span::default(), + )? + } else { + let size = match size { + VectorSize::Tri => VectorSize::Bi, + _ => VectorSize::Tri, + }; + right = ctx.add_expression( + Expression::Splat { size, value: right }, + Span::default(), + )?; + ctx.vector_resize(size, coords, Span::default())? + }; + coords = ctx.add_expression( + Expression::Binary { + op: BinaryOperator::Divide, + left, + right, + }, + Span::default(), + )?; + } + + let extra = args.get(2).copied(); + let comps = frontend.coordinate_components(ctx, args[0], coords, extra, meta)?; + + let mut num_args = 2; + + if comps.used_extra { + num_args += 1; + }; + + // Parse out explicit texture level. + let mut level = match level_type { + TextureLevelType::None => SampleLevel::Auto, + + TextureLevelType::Lod => { + num_args += 1; + + if shadow { + log::warn!("Assuming LOD {:?} is zero", args[2],); + + SampleLevel::Zero + } else { + SampleLevel::Exact(args[2]) + } + } + + TextureLevelType::Grad => { + num_args += 2; + + if shadow { + log::warn!( + "Assuming gradients {:?} and {:?} are not greater than 1", + args[2], + args[3], + ); + SampleLevel::Zero + } else { + SampleLevel::Gradient { + x: args[2], + y: args[3], + } + } + } + }; + + let texture_offset = match offset { + true => { + let offset_arg = args[num_args]; + num_args += 1; + match ctx.lift_up_const_expression(offset_arg) { + Ok(v) => Some(v), + Err(e) => { + frontend.errors.push(e); + None + } + } + } + false => None, + }; + + // Now go back and look for optional bias arg (if available) + if let TextureLevelType::None = level_type { + level = args + .get(num_args) + .copied() + .map_or(SampleLevel::Auto, SampleLevel::Bias); + } + + texture_call(ctx, args[0], level, comps, texture_offset, meta)? + } + + MacroCall::TextureSize { arrayed } => { + let mut expr = ctx.add_expression( + Expression::ImageQuery { + image: args[0], + query: ImageQuery::Size { + level: args.get(1).copied(), + }, + }, + Span::default(), + )?; + + if arrayed { + let mut components = Vec::with_capacity(4); + + let size = match *ctx.resolve_type(expr, meta)? { + TypeInner::Vector { size: ori_size, .. } => { + for index in 0..(ori_size as u32) { + components.push(ctx.add_expression( + Expression::AccessIndex { base: expr, index }, + Span::default(), + )?) + } + + match ori_size { + VectorSize::Bi => VectorSize::Tri, + _ => VectorSize::Quad, + } + } + _ => { + components.push(expr); + VectorSize::Bi + } + }; + + components.push(ctx.add_expression( + Expression::ImageQuery { + image: args[0], + query: ImageQuery::NumLayers, + }, + Span::default(), + )?); + + let ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size, + scalar: Scalar::U32, + }, + }, + Span::default(), + ); + + expr = ctx.add_expression(Expression::Compose { components, ty }, meta)? + } + + ctx.add_expression( + Expression::As { + expr, + kind: Sk::Sint, + convert: Some(4), + }, + Span::default(), + )? + } + MacroCall::ImageLoad { multi } => { + let comps = frontend.coordinate_components(ctx, args[0], args[1], None, meta)?; + let (sample, level) = match (multi, args.get(2)) { + (_, None) => (None, None), + (true, Some(&arg)) => (Some(arg), None), + (false, Some(&arg)) => (None, Some(arg)), + }; + ctx.add_expression( + Expression::ImageLoad { + image: args[0], + coordinate: comps.coordinate, + array_index: comps.array_index, + sample, + level, + }, + Span::default(), + )? + } + MacroCall::ImageStore => { + let comps = frontend.coordinate_components(ctx, args[0], args[1], None, meta)?; + ctx.emit_restart(); + ctx.body.push( + crate::Statement::ImageStore { + image: args[0], + coordinate: comps.coordinate, + array_index: comps.array_index, + value: args[2], + }, + meta, + ); + return Ok(None); + } + MacroCall::MathFunction(fun) => ctx.add_expression( + Expression::Math { + fun, + arg: args[0], + arg1: args.get(1).copied(), + arg2: args.get(2).copied(), + arg3: args.get(3).copied(), + }, + Span::default(), + )?, + mc @ (MacroCall::FindLsbUint | MacroCall::FindMsbUint) => { + let fun = match mc { + MacroCall::FindLsbUint => MathFunction::FindLsb, + MacroCall::FindMsbUint => MathFunction::FindMsb, + _ => unreachable!(), + }; + let res = ctx.add_expression( + Expression::Math { + fun, + arg: args[0], + arg1: None, + arg2: None, + arg3: None, + }, + Span::default(), + )?; + ctx.add_expression( + Expression::As { + expr: res, + kind: Sk::Sint, + convert: Some(4), + }, + Span::default(), + )? + } + MacroCall::BitfieldInsert => { + let conv_arg_2 = ctx.add_expression( + Expression::As { + expr: args[2], + kind: Sk::Uint, + convert: Some(4), + }, + Span::default(), + )?; + let conv_arg_3 = ctx.add_expression( + Expression::As { + expr: args[3], + kind: Sk::Uint, + convert: Some(4), + }, + Span::default(), + )?; + ctx.add_expression( + Expression::Math { + fun: MathFunction::InsertBits, + arg: args[0], + arg1: Some(args[1]), + arg2: Some(conv_arg_2), + arg3: Some(conv_arg_3), + }, + Span::default(), + )? + } + MacroCall::BitfieldExtract => { + let conv_arg_1 = ctx.add_expression( + Expression::As { + expr: args[1], + kind: Sk::Uint, + convert: Some(4), + }, + Span::default(), + )?; + let conv_arg_2 = ctx.add_expression( + Expression::As { + expr: args[2], + kind: Sk::Uint, + convert: Some(4), + }, + Span::default(), + )?; + ctx.add_expression( + Expression::Math { + fun: MathFunction::ExtractBits, + arg: args[0], + arg1: Some(conv_arg_1), + arg2: Some(conv_arg_2), + arg3: None, + }, + Span::default(), + )? + } + MacroCall::Relational(fun) => ctx.add_expression( + Expression::Relational { + fun, + argument: args[0], + }, + Span::default(), + )?, + MacroCall::Unary(op) => { + ctx.add_expression(Expression::Unary { op, expr: args[0] }, Span::default())? + } + MacroCall::Binary(op) => ctx.add_expression( + Expression::Binary { + op, + left: args[0], + right: args[1], + }, + Span::default(), + )?, + MacroCall::Mod(size) => { + ctx.implicit_splat(&mut args[1], meta, size)?; + + // x - y * floor(x / y) + + let div = ctx.add_expression( + Expression::Binary { + op: BinaryOperator::Divide, + left: args[0], + right: args[1], + }, + Span::default(), + )?; + let floor = ctx.add_expression( + Expression::Math { + fun: MathFunction::Floor, + arg: div, + arg1: None, + arg2: None, + arg3: None, + }, + Span::default(), + )?; + let mult = ctx.add_expression( + Expression::Binary { + op: BinaryOperator::Multiply, + left: floor, + right: args[1], + }, + Span::default(), + )?; + ctx.add_expression( + Expression::Binary { + op: BinaryOperator::Subtract, + left: args[0], + right: mult, + }, + Span::default(), + )? + } + MacroCall::Splatted(fun, size, i) => { + ctx.implicit_splat(&mut args[i], meta, size)?; + + ctx.add_expression( + Expression::Math { + fun, + arg: args[0], + arg1: args.get(1).copied(), + arg2: args.get(2).copied(), + arg3: args.get(3).copied(), + }, + Span::default(), + )? + } + MacroCall::MixBoolean => ctx.add_expression( + Expression::Select { + condition: args[2], + accept: args[1], + reject: args[0], + }, + Span::default(), + )?, + MacroCall::Clamp(size) => { + ctx.implicit_splat(&mut args[1], meta, size)?; + ctx.implicit_splat(&mut args[2], meta, size)?; + + ctx.add_expression( + Expression::Math { + fun: MathFunction::Clamp, + arg: args[0], + arg1: args.get(1).copied(), + arg2: args.get(2).copied(), + arg3: args.get(3).copied(), + }, + Span::default(), + )? + } + MacroCall::BitCast(kind) => ctx.add_expression( + Expression::As { + expr: args[0], + kind, + convert: None, + }, + Span::default(), + )?, + MacroCall::Derivate(axis, ctrl) => ctx.add_expression( + Expression::Derivative { + axis, + ctrl, + expr: args[0], + }, + Span::default(), + )?, + MacroCall::Barrier => { + ctx.emit_restart(); + ctx.body + .push(crate::Statement::Barrier(crate::Barrier::all()), meta); + return Ok(None); + } + MacroCall::SmoothStep { splatted } => { + ctx.implicit_splat(&mut args[0], meta, splatted)?; + ctx.implicit_splat(&mut args[1], meta, splatted)?; + + ctx.add_expression( + Expression::Math { + fun: MathFunction::SmoothStep, + arg: args[0], + arg1: args.get(1).copied(), + arg2: args.get(2).copied(), + arg3: None, + }, + Span::default(), + )? + } + })) + } +} + +fn texture_call( + ctx: &mut Context, + image: Handle, + level: SampleLevel, + comps: CoordComponents, + offset: Option>, + meta: Span, +) -> Result> { + if let Some(sampler) = ctx.samplers.get(&image).copied() { + let mut array_index = comps.array_index; + + if let Some(ref mut array_index_expr) = array_index { + ctx.conversion(array_index_expr, meta, Scalar::I32)?; + } + + Ok(ctx.add_expression( + Expression::ImageSample { + image, + sampler, + gather: None, //TODO + coordinate: comps.coordinate, + array_index, + offset, + level, + depth_ref: comps.depth_ref, + }, + meta, + )?) + } else { + Err(Error { + kind: ErrorKind::SemanticError("Bad call".into()), + meta, + }) + } +} + +/// Helper struct for texture calls with the separate components from the vector argument +/// +/// Obtained by calling [`coordinate_components`](Frontend::coordinate_components) +#[derive(Debug)] +struct CoordComponents { + coordinate: Handle, + depth_ref: Option>, + array_index: Option>, + used_extra: bool, +} + +impl Frontend { + /// Helper function for texture calls, splits the vector argument into it's components + fn coordinate_components( + &mut self, + ctx: &mut Context, + image: Handle, + coord: Handle, + extra: Option>, + meta: Span, + ) -> Result { + if let TypeInner::Image { + dim, + arrayed, + class, + } = *ctx.resolve_type(image, meta)? + { + let image_size = match dim { + Dim::D1 => None, + Dim::D2 => Some(VectorSize::Bi), + Dim::D3 => Some(VectorSize::Tri), + Dim::Cube => Some(VectorSize::Tri), + }; + let coord_size = match *ctx.resolve_type(coord, meta)? { + TypeInner::Vector { size, .. } => Some(size), + _ => None, + }; + let (shadow, storage) = match class { + ImageClass::Depth { .. } => (true, false), + ImageClass::Storage { .. } => (false, true), + ImageClass::Sampled { .. } => (false, false), + }; + + let coordinate = match (image_size, coord_size) { + (Some(size), Some(coord_s)) if size != coord_s => { + ctx.vector_resize(size, coord, Span::default())? + } + (None, Some(_)) => ctx.add_expression( + Expression::AccessIndex { + base: coord, + index: 0, + }, + Span::default(), + )?, + _ => coord, + }; + + let mut coord_index = image_size.map_or(1, |s| s as u32); + + let array_index = if arrayed && !(storage && dim == Dim::Cube) { + let index = coord_index; + coord_index += 1; + + Some(ctx.add_expression( + Expression::AccessIndex { base: coord, index }, + Span::default(), + )?) + } else { + None + }; + let mut used_extra = false; + let depth_ref = match shadow { + true => { + let index = coord_index; + + if index == 4 { + used_extra = true; + extra + } else { + Some(ctx.add_expression( + Expression::AccessIndex { base: coord, index }, + Span::default(), + )?) + } + } + false => None, + }; + + Ok(CoordComponents { + coordinate, + depth_ref, + array_index, + used_extra, + }) + } else { + self.errors.push(Error { + kind: ErrorKind::SemanticError("Type is not an image".into()), + meta, + }); + + Ok(CoordComponents { + coordinate: coord, + depth_ref: None, + array_index: None, + used_extra: false, + }) + } + } +} + +/// Helper function to cast a expression holding a sampled image to a +/// depth image. +pub fn sampled_to_depth( + ctx: &mut Context, + image: Handle, + meta: Span, + errors: &mut Vec, +) { + // Get the a mutable type handle of the underlying image storage + let ty = match ctx[image] { + Expression::GlobalVariable(handle) => &mut ctx.module.global_variables.get_mut(handle).ty, + Expression::FunctionArgument(i) => { + // Mark the function argument as carrying a depth texture + ctx.parameters_info[i as usize].depth = true; + // NOTE: We need to later also change the parameter type + &mut ctx.arguments[i as usize].ty + } + _ => { + // Only globals and function arguments are allowed to carry an image + return errors.push(Error { + kind: ErrorKind::SemanticError("Not a valid texture expression".into()), + meta, + }); + } + }; + + match ctx.module.types[*ty].inner { + // Update the image class to depth in case it already isn't + TypeInner::Image { + class, + dim, + arrayed, + } => match class { + ImageClass::Sampled { multi, .. } => { + *ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Image { + dim, + arrayed, + class: ImageClass::Depth { multi }, + }, + }, + Span::default(), + ) + } + ImageClass::Depth { .. } => {} + // Other image classes aren't allowed to be transformed to depth + ImageClass::Storage { .. } => errors.push(Error { + kind: ErrorKind::SemanticError("Not a texture".into()), + meta, + }), + }, + _ => errors.push(Error { + kind: ErrorKind::SemanticError("Not a texture".into()), + meta, + }), + }; + + // Copy the handle to allow borrowing the `ctx` again + let ty = *ty; + + // If the image was passed through a function argument we also need to change + // the corresponding parameter + if let Expression::FunctionArgument(i) = ctx[image] { + ctx.parameters[i as usize] = ty; + } +} + +bitflags::bitflags! { + /// Influences the operation `texture_args_generator` + struct TextureArgsOptions: u32 { + /// Generates multisampled variants of images + const MULTI = 1 << 0; + /// Generates shadow variants of images + const SHADOW = 1 << 1; + /// Generates standard images + const STANDARD = 1 << 2; + /// Generates cube arrayed images + const CUBE_ARRAY = 1 << 3; + /// Generates cube arrayed images + const D2_MULTI_ARRAY = 1 << 4; + } +} + +impl From for TextureArgsOptions { + fn from(variations: BuiltinVariations) -> Self { + let mut options = TextureArgsOptions::empty(); + if variations.contains(BuiltinVariations::STANDARD) { + options |= TextureArgsOptions::STANDARD + } + if variations.contains(BuiltinVariations::CUBE_TEXTURES_ARRAY) { + options |= TextureArgsOptions::CUBE_ARRAY + } + if variations.contains(BuiltinVariations::D2_MULTI_TEXTURES_ARRAY) { + options |= TextureArgsOptions::D2_MULTI_ARRAY + } + options + } +} + +/// Helper function to generate the image components for texture/image builtins +/// +/// Calls the passed function `f` with: +/// ```text +/// f(ScalarKind, ImageDimension, arrayed, multi, shadow) +/// ``` +/// +/// `options` controls extra image variants generation like multisampling and depth, +/// see the struct documentation +fn texture_args_generator( + options: TextureArgsOptions, + mut f: impl FnMut(crate::ScalarKind, Dim, bool, bool, bool), +) { + for kind in [Sk::Float, Sk::Uint, Sk::Sint].iter().copied() { + for dim in [Dim::D1, Dim::D2, Dim::D3, Dim::Cube].iter().copied() { + for arrayed in [false, true].iter().copied() { + if dim == Dim::Cube && arrayed { + if !options.contains(TextureArgsOptions::CUBE_ARRAY) { + continue; + } + } else if Dim::D2 == dim + && options.contains(TextureArgsOptions::MULTI) + && arrayed + && options.contains(TextureArgsOptions::D2_MULTI_ARRAY) + { + // multisampling for sampler2DMSArray + f(kind, dim, arrayed, true, false); + } else if !options.contains(TextureArgsOptions::STANDARD) { + continue; + } + + f(kind, dim, arrayed, false, false); + + // 3D images can't be neither arrayed nor shadow + // so we break out early, this way arrayed will always + // be false and we won't hit the shadow branch + if let Dim::D3 = dim { + break; + } + + if Dim::D2 == dim && options.contains(TextureArgsOptions::MULTI) && !arrayed { + // multisampling + f(kind, dim, arrayed, true, false); + } + + if Sk::Float == kind && options.contains(TextureArgsOptions::SHADOW) { + // shadow + f(kind, dim, arrayed, false, true); + } + } + } + } +} + +/// Helper functions used to convert from a image dimension into a integer representing the +/// number of components needed for the coordinates vector (1 means scalar instead of vector) +const fn image_dims_to_coords_size(dim: Dim) -> usize { + match dim { + Dim::D1 => 1, + Dim::D2 => 2, + _ => 3, + } +} diff --git a/naga/src/front/glsl/context.rs b/naga/src/front/glsl/context.rs new file mode 100644 index 0000000000..98ec021a5a --- /dev/null +++ b/naga/src/front/glsl/context.rs @@ -0,0 +1,1506 @@ +use super::{ + ast::{ + GlobalLookup, GlobalLookupKind, HirExpr, HirExprKind, ParameterInfo, ParameterQualifier, + VariableReference, + }, + error::{Error, ErrorKind}, + types::{scalar_components, type_power}, + Frontend, Result, +}; +use crate::{ + front::Typifier, proc::Emitter, AddressSpace, Arena, BinaryOperator, Block, Expression, + FastHashMap, FunctionArgument, Handle, Literal, LocalVariable, RelationalFunction, Scalar, + Span, Statement, Type, TypeInner, VectorSize, +}; +use std::ops::Index; + +/// The position at which an expression is, used while lowering +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ExprPos { + /// The expression is in the left hand side of an assignment + Lhs, + /// The expression is in the right hand side of an assignment + Rhs, + /// The expression is an array being indexed, needed to allow constant + /// arrays to be dynamically indexed + AccessBase { + /// The index is a constant + constant_index: bool, + }, +} + +impl ExprPos { + /// Returns an lhs position if the current position is lhs otherwise AccessBase + const fn maybe_access_base(&self, constant_index: bool) -> Self { + match *self { + ExprPos::Lhs + | ExprPos::AccessBase { + constant_index: false, + } => *self, + _ => ExprPos::AccessBase { constant_index }, + } + } +} + +#[derive(Debug)] +pub struct Context<'a> { + pub expressions: Arena, + pub locals: Arena, + + /// The [`FunctionArgument`]s for the final [`crate::Function`]. + /// + /// Parameters with the `out` and `inout` qualifiers have [`Pointer`] types + /// here. For example, an `inout vec2 a` argument would be a [`Pointer`] to + /// a [`Vector`]. + /// + /// [`Pointer`]: crate::TypeInner::Pointer + /// [`Vector`]: crate::TypeInner::Vector + pub arguments: Vec, + + /// The parameter types given in the source code. + /// + /// The `out` and `inout` qualifiers don't affect the types that appear + /// here. For example, an `inout vec2 a` argument would simply be a + /// [`Vector`], not a pointer to one. + /// + /// [`Vector`]: crate::TypeInner::Vector + pub parameters: Vec>, + pub parameters_info: Vec, + + pub symbol_table: crate::front::SymbolTable, + pub samplers: FastHashMap, Handle>, + + pub const_typifier: Typifier, + pub typifier: Typifier, + emitter: Emitter, + stmt_ctx: Option, + pub body: Block, + pub module: &'a mut crate::Module, + pub is_const: bool, + /// Tracks the constness of `Expression`s residing in `self.expressions` + pub expression_constness: crate::proc::ExpressionConstnessTracker, +} + +impl<'a> Context<'a> { + pub fn new(frontend: &Frontend, module: &'a mut crate::Module, is_const: bool) -> Result { + let mut this = Context { + expressions: Arena::new(), + locals: Arena::new(), + arguments: Vec::new(), + + parameters: Vec::new(), + parameters_info: Vec::new(), + + symbol_table: crate::front::SymbolTable::default(), + samplers: FastHashMap::default(), + + const_typifier: Typifier::new(), + typifier: Typifier::new(), + emitter: Emitter::default(), + stmt_ctx: Some(StmtContext::new()), + body: Block::new(), + module, + is_const: false, + expression_constness: crate::proc::ExpressionConstnessTracker::new(), + }; + + this.emit_start(); + + for &(ref name, lookup) in frontend.global_variables.iter() { + this.add_global(name, lookup)? + } + this.is_const = is_const; + + Ok(this) + } + + pub fn new_body(&mut self, cb: F) -> Result + where + F: FnOnce(&mut Self) -> Result<()>, + { + self.new_body_with_ret(cb).map(|(b, _)| b) + } + + pub fn new_body_with_ret(&mut self, cb: F) -> Result<(Block, R)> + where + F: FnOnce(&mut Self) -> Result, + { + self.emit_restart(); + let old_body = std::mem::replace(&mut self.body, Block::new()); + let res = cb(self); + self.emit_restart(); + let new_body = std::mem::replace(&mut self.body, old_body); + res.map(|r| (new_body, r)) + } + + pub fn with_body(&mut self, body: Block, cb: F) -> Result + where + F: FnOnce(&mut Self) -> Result<()>, + { + self.emit_restart(); + let old_body = std::mem::replace(&mut self.body, body); + let res = cb(self); + self.emit_restart(); + let body = std::mem::replace(&mut self.body, old_body); + res.map(|_| body) + } + + pub fn add_global( + &mut self, + name: &str, + GlobalLookup { + kind, + entry_arg, + mutable, + }: GlobalLookup, + ) -> Result<()> { + let (expr, load, constant) = match kind { + GlobalLookupKind::Variable(v) => { + let span = self.module.global_variables.get_span(v); + ( + self.add_expression(Expression::GlobalVariable(v), span)?, + self.module.global_variables[v].space != AddressSpace::Handle, + None, + ) + } + GlobalLookupKind::BlockSelect(handle, index) => { + let span = self.module.global_variables.get_span(handle); + let base = self.add_expression(Expression::GlobalVariable(handle), span)?; + let expr = self.add_expression(Expression::AccessIndex { base, index }, span)?; + + ( + expr, + { + let ty = self.module.global_variables[handle].ty; + + match self.module.types[ty].inner { + TypeInner::Struct { ref members, .. } => { + if let TypeInner::Array { + size: crate::ArraySize::Dynamic, + .. + } = self.module.types[members[index as usize].ty].inner + { + false + } else { + true + } + } + _ => true, + } + }, + None, + ) + } + GlobalLookupKind::Constant(v, ty) => { + let span = self.module.constants.get_span(v); + ( + self.add_expression(Expression::Constant(v), span)?, + false, + Some((v, ty)), + ) + } + }; + + let var = VariableReference { + expr, + load, + mutable, + constant, + entry_arg, + }; + + self.symbol_table.add(name.into(), var); + + Ok(()) + } + + /// Starts the expression emitter + /// + /// # Panics + /// + /// - If called twice in a row without calling [`emit_end`][Self::emit_end]. + #[inline] + pub fn emit_start(&mut self) { + self.emitter.start(&self.expressions) + } + + /// Emits all the expressions captured by the emitter to the current body + /// + /// # Panics + /// + /// - If called before calling [`emit_start`]. + /// - If called twice in a row without calling [`emit_start`]. + /// + /// [`emit_start`]: Self::emit_start + pub fn emit_end(&mut self) { + self.body.extend(self.emitter.finish(&self.expressions)) + } + + /// Emits all the expressions captured by the emitter to the current body + /// and starts the emitter again + /// + /// # Panics + /// + /// - If called before calling [`emit_start`][Self::emit_start]. + pub fn emit_restart(&mut self) { + self.emit_end(); + self.emit_start() + } + + pub fn add_expression(&mut self, expr: Expression, meta: Span) -> Result> { + let mut eval = if self.is_const { + crate::proc::ConstantEvaluator::for_glsl_module(self.module) + } else { + crate::proc::ConstantEvaluator::for_glsl_function( + self.module, + &mut self.expressions, + &mut self.expression_constness, + &mut self.emitter, + &mut self.body, + ) + }; + + let res = eval.try_eval_and_append(&expr, meta).map_err(|e| Error { + kind: e.into(), + meta, + }); + + match res { + Ok(expr) => Ok(expr), + Err(e) => { + if self.is_const { + Err(e) + } else { + let needs_pre_emit = expr.needs_pre_emit(); + if needs_pre_emit { + self.body.extend(self.emitter.finish(&self.expressions)); + } + let h = self.expressions.append(expr, meta); + if needs_pre_emit { + self.emitter.start(&self.expressions); + } + Ok(h) + } + } + } + } + + /// Add variable to current scope + /// + /// Returns a variable if a variable with the same name was already defined, + /// otherwise returns `None` + pub fn add_local_var( + &mut self, + name: String, + expr: Handle, + mutable: bool, + ) -> Option { + let var = VariableReference { + expr, + load: true, + mutable, + constant: None, + entry_arg: None, + }; + + self.symbol_table.add(name, var) + } + + /// Add function argument to current scope + pub fn add_function_arg( + &mut self, + name_meta: Option<(String, Span)>, + ty: Handle, + qualifier: ParameterQualifier, + ) -> Result<()> { + let index = self.arguments.len(); + let mut arg = FunctionArgument { + name: name_meta.as_ref().map(|&(ref name, _)| name.clone()), + ty, + binding: None, + }; + self.parameters.push(ty); + + let opaque = match self.module.types[ty].inner { + TypeInner::Image { .. } | TypeInner::Sampler { .. } => true, + _ => false, + }; + + if qualifier.is_lhs() { + let span = self.module.types.get_span(arg.ty); + arg.ty = self.module.types.insert( + Type { + name: None, + inner: TypeInner::Pointer { + base: arg.ty, + space: AddressSpace::Function, + }, + }, + span, + ) + } + + self.arguments.push(arg); + + self.parameters_info.push(ParameterInfo { + qualifier, + depth: false, + }); + + if let Some((name, meta)) = name_meta { + let expr = self.add_expression(Expression::FunctionArgument(index as u32), meta)?; + let mutable = qualifier != ParameterQualifier::Const && !opaque; + let load = qualifier.is_lhs(); + + let var = if mutable && !load { + let handle = self.locals.append( + LocalVariable { + name: Some(name.clone()), + ty, + init: None, + }, + meta, + ); + let local_expr = self.add_expression(Expression::LocalVariable(handle), meta)?; + + self.emit_restart(); + + self.body.push( + Statement::Store { + pointer: local_expr, + value: expr, + }, + meta, + ); + + VariableReference { + expr: local_expr, + load: true, + mutable, + constant: None, + entry_arg: None, + } + } else { + VariableReference { + expr, + load, + mutable, + constant: None, + entry_arg: None, + } + }; + + self.symbol_table.add(name, var); + } + + Ok(()) + } + + /// Returns a [`StmtContext`] to be used in parsing and lowering + /// + /// # Panics + /// + /// - If more than one [`StmtContext`] are active at the same time or if the + /// previous call didn't use it in lowering. + #[must_use] + pub fn stmt_ctx(&mut self) -> StmtContext { + self.stmt_ctx.take().unwrap() + } + + /// Lowers a [`HirExpr`] which might produce a [`Expression`]. + /// + /// consumes a [`StmtContext`] returning it to the context so that it can be + /// used again later. + pub fn lower( + &mut self, + mut stmt: StmtContext, + frontend: &mut Frontend, + expr: Handle, + pos: ExprPos, + ) -> Result<(Option>, Span)> { + let res = self.lower_inner(&stmt, frontend, expr, pos); + + stmt.hir_exprs.clear(); + self.stmt_ctx = Some(stmt); + + res + } + + /// Similar to [`lower`](Self::lower) but returns an error if the expression + /// returns void (ie. doesn't produce a [`Expression`]). + /// + /// consumes a [`StmtContext`] returning it to the context so that it can be + /// used again later. + pub fn lower_expect( + &mut self, + mut stmt: StmtContext, + frontend: &mut Frontend, + expr: Handle, + pos: ExprPos, + ) -> Result<(Handle, Span)> { + let res = self.lower_expect_inner(&stmt, frontend, expr, pos); + + stmt.hir_exprs.clear(); + self.stmt_ctx = Some(stmt); + + res + } + + /// internal implementation of [`lower_expect`](Self::lower_expect) + /// + /// this method is only public because it's used in + /// [`function_call`](Frontend::function_call), unless you know what + /// you're doing use [`lower_expect`](Self::lower_expect) + pub fn lower_expect_inner( + &mut self, + stmt: &StmtContext, + frontend: &mut Frontend, + expr: Handle, + pos: ExprPos, + ) -> Result<(Handle, Span)> { + let (maybe_expr, meta) = self.lower_inner(stmt, frontend, expr, pos)?; + + let expr = match maybe_expr { + Some(e) => e, + None => { + return Err(Error { + kind: ErrorKind::SemanticError("Expression returns void".into()), + meta, + }) + } + }; + + Ok((expr, meta)) + } + + fn lower_store( + &mut self, + pointer: Handle, + value: Handle, + meta: Span, + ) -> Result<()> { + if let Expression::Swizzle { + size, + mut vector, + pattern, + } = self.expressions[pointer] + { + // Stores to swizzled values are not directly supported, + // lower them as series of per-component stores. + let size = match size { + VectorSize::Bi => 2, + VectorSize::Tri => 3, + VectorSize::Quad => 4, + }; + + if let Expression::Load { pointer } = self.expressions[vector] { + vector = pointer; + } + + #[allow(clippy::needless_range_loop)] + for index in 0..size { + let dst = self.add_expression( + Expression::AccessIndex { + base: vector, + index: pattern[index].index(), + }, + meta, + )?; + let src = self.add_expression( + Expression::AccessIndex { + base: value, + index: index as u32, + }, + meta, + )?; + + self.emit_restart(); + + self.body.push( + Statement::Store { + pointer: dst, + value: src, + }, + meta, + ); + } + } else { + self.emit_restart(); + + self.body.push(Statement::Store { pointer, value }, meta); + } + + Ok(()) + } + + /// Internal implementation of [`lower`](Self::lower) + fn lower_inner( + &mut self, + stmt: &StmtContext, + frontend: &mut Frontend, + expr: Handle, + pos: ExprPos, + ) -> Result<(Option>, Span)> { + let HirExpr { ref kind, meta } = stmt.hir_exprs[expr]; + + log::debug!("Lowering {:?} (kind {:?}, pos {:?})", expr, kind, pos); + + let handle = match *kind { + HirExprKind::Access { base, index } => { + let (index, _) = self.lower_expect_inner(stmt, frontend, index, ExprPos::Rhs)?; + let maybe_constant_index = match pos { + // Don't try to generate `AccessIndex` if in a LHS position, since it + // wouldn't produce a pointer. + ExprPos::Lhs => None, + _ => self + .module + .to_ctx() + .eval_expr_to_u32_from(index, &self.expressions) + .ok(), + }; + + let base = self + .lower_expect_inner( + stmt, + frontend, + base, + pos.maybe_access_base(maybe_constant_index.is_some()), + )? + .0; + + let pointer = maybe_constant_index + .map(|index| self.add_expression(Expression::AccessIndex { base, index }, meta)) + .unwrap_or_else(|| { + self.add_expression(Expression::Access { base, index }, meta) + })?; + + if ExprPos::Rhs == pos { + let resolved = self.resolve_type(pointer, meta)?; + if resolved.pointer_space().is_some() { + return Ok(( + Some(self.add_expression(Expression::Load { pointer }, meta)?), + meta, + )); + } + } + + pointer + } + HirExprKind::Select { base, ref field } => { + let base = self.lower_expect_inner(stmt, frontend, base, pos)?.0; + + frontend.field_selection(self, pos, base, field, meta)? + } + HirExprKind::Literal(literal) if pos != ExprPos::Lhs => { + self.add_expression(Expression::Literal(literal), meta)? + } + HirExprKind::Binary { left, op, right } if pos != ExprPos::Lhs => { + let (mut left, left_meta) = + self.lower_expect_inner(stmt, frontend, left, ExprPos::Rhs)?; + let (mut right, right_meta) = + self.lower_expect_inner(stmt, frontend, right, ExprPos::Rhs)?; + + match op { + BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight => { + self.implicit_conversion(&mut right, right_meta, Scalar::U32)? + } + _ => self + .binary_implicit_conversion(&mut left, left_meta, &mut right, right_meta)?, + } + + self.typifier_grow(left, left_meta)?; + self.typifier_grow(right, right_meta)?; + + let left_inner = self.get_type(left); + let right_inner = self.get_type(right); + + match (left_inner, right_inner) { + ( + &TypeInner::Matrix { + columns: left_columns, + rows: left_rows, + scalar: left_scalar, + }, + &TypeInner::Matrix { + columns: right_columns, + rows: right_rows, + scalar: right_scalar, + }, + ) => { + let dimensions_ok = if op == BinaryOperator::Multiply { + left_columns == right_rows + } else { + left_columns == right_columns && left_rows == right_rows + }; + + // Check that the two arguments have the same dimensions + if !dimensions_ok || left_scalar != right_scalar { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + format!( + "Cannot apply operation to {left_inner:?} and {right_inner:?}" + ) + .into(), + ), + meta, + }) + } + + match op { + BinaryOperator::Divide => { + // Naga IR doesn't support matrix division so we need to + // divide the columns individually and reassemble the matrix + let mut components = Vec::with_capacity(left_columns as usize); + + for index in 0..left_columns as u32 { + // Get the column vectors + let left_vector = self.add_expression( + Expression::AccessIndex { base: left, index }, + meta, + )?; + let right_vector = self.add_expression( + Expression::AccessIndex { base: right, index }, + meta, + )?; + + // Divide the vectors + let column = self.add_expression( + Expression::Binary { + op, + left: left_vector, + right: right_vector, + }, + meta, + )?; + + components.push(column) + } + + let ty = self.module.types.insert( + Type { + name: None, + inner: TypeInner::Matrix { + columns: left_columns, + rows: left_rows, + scalar: left_scalar, + }, + }, + Span::default(), + ); + + // Rebuild the matrix from the divided vectors + self.add_expression(Expression::Compose { ty, components }, meta)? + } + BinaryOperator::Equal | BinaryOperator::NotEqual => { + // Naga IR doesn't support matrix comparisons so we need to + // compare the columns individually and then fold them together + // + // The folding is done using a logical and for equality and + // a logical or for inequality + let equals = op == BinaryOperator::Equal; + + let (op, combine, fun) = match equals { + true => ( + BinaryOperator::Equal, + BinaryOperator::LogicalAnd, + RelationalFunction::All, + ), + false => ( + BinaryOperator::NotEqual, + BinaryOperator::LogicalOr, + RelationalFunction::Any, + ), + }; + + let mut root = None; + + for index in 0..left_columns as u32 { + // Get the column vectors + let left_vector = self.add_expression( + Expression::AccessIndex { base: left, index }, + meta, + )?; + let right_vector = self.add_expression( + Expression::AccessIndex { base: right, index }, + meta, + )?; + + let argument = self.add_expression( + Expression::Binary { + op, + left: left_vector, + right: right_vector, + }, + meta, + )?; + + // The result of comparing two vectors is a boolean vector + // so use a relational function like all to get a single + // boolean value + let compare = self.add_expression( + Expression::Relational { fun, argument }, + meta, + )?; + + // Fold the result + root = Some(match root { + Some(right) => self.add_expression( + Expression::Binary { + op: combine, + left: compare, + right, + }, + meta, + )?, + None => compare, + }); + } + + root.unwrap() + } + _ => { + self.add_expression(Expression::Binary { left, op, right }, meta)? + } + } + } + (&TypeInner::Vector { .. }, &TypeInner::Vector { .. }) => match op { + BinaryOperator::Equal | BinaryOperator::NotEqual => { + let equals = op == BinaryOperator::Equal; + + let (op, fun) = match equals { + true => (BinaryOperator::Equal, RelationalFunction::All), + false => (BinaryOperator::NotEqual, RelationalFunction::Any), + }; + + let argument = + self.add_expression(Expression::Binary { op, left, right }, meta)?; + + self.add_expression(Expression::Relational { fun, argument }, meta)? + } + _ => self.add_expression(Expression::Binary { left, op, right }, meta)?, + }, + (&TypeInner::Vector { size, .. }, &TypeInner::Scalar { .. }) => match op { + BinaryOperator::Add + | BinaryOperator::Subtract + | BinaryOperator::Divide + | BinaryOperator::And + | BinaryOperator::ExclusiveOr + | BinaryOperator::InclusiveOr + | BinaryOperator::ShiftLeft + | BinaryOperator::ShiftRight => { + let scalar_vector = self + .add_expression(Expression::Splat { size, value: right }, meta)?; + + self.add_expression( + Expression::Binary { + op, + left, + right: scalar_vector, + }, + meta, + )? + } + _ => self.add_expression(Expression::Binary { left, op, right }, meta)?, + }, + (&TypeInner::Scalar { .. }, &TypeInner::Vector { size, .. }) => match op { + BinaryOperator::Add + | BinaryOperator::Subtract + | BinaryOperator::Divide + | BinaryOperator::And + | BinaryOperator::ExclusiveOr + | BinaryOperator::InclusiveOr => { + let scalar_vector = + self.add_expression(Expression::Splat { size, value: left }, meta)?; + + self.add_expression( + Expression::Binary { + op, + left: scalar_vector, + right, + }, + meta, + )? + } + _ => self.add_expression(Expression::Binary { left, op, right }, meta)?, + }, + ( + &TypeInner::Scalar(left_scalar), + &TypeInner::Matrix { + rows, + columns, + scalar: right_scalar, + }, + ) => { + // Check that the two arguments have the same scalar type + if left_scalar != right_scalar { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + format!( + "Cannot apply operation to {left_inner:?} and {right_inner:?}" + ) + .into(), + ), + meta, + }) + } + + match op { + BinaryOperator::Divide + | BinaryOperator::Add + | BinaryOperator::Subtract => { + // Naga IR doesn't support all matrix by scalar operations so + // we need for some to turn the scalar into a vector by + // splatting it and then for each column vector apply the + // operation and finally reconstruct the matrix + let scalar_vector = self.add_expression( + Expression::Splat { + size: rows, + value: left, + }, + meta, + )?; + + let mut components = Vec::with_capacity(columns as usize); + + for index in 0..columns as u32 { + // Get the column vector + let matrix_column = self.add_expression( + Expression::AccessIndex { base: right, index }, + meta, + )?; + + // Apply the operation to the splatted vector and + // the column vector + let column = self.add_expression( + Expression::Binary { + op, + left: scalar_vector, + right: matrix_column, + }, + meta, + )?; + + components.push(column) + } + + let ty = self.module.types.insert( + Type { + name: None, + inner: TypeInner::Matrix { + columns, + rows, + scalar: left_scalar, + }, + }, + Span::default(), + ); + + // Rebuild the matrix from the operation result vectors + self.add_expression(Expression::Compose { ty, components }, meta)? + } + _ => { + self.add_expression(Expression::Binary { left, op, right }, meta)? + } + } + } + ( + &TypeInner::Matrix { + rows, + columns, + scalar: left_scalar, + }, + &TypeInner::Scalar(right_scalar), + ) => { + // Check that the two arguments have the same scalar type + if left_scalar != right_scalar { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + format!( + "Cannot apply operation to {left_inner:?} and {right_inner:?}" + ) + .into(), + ), + meta, + }) + } + + match op { + BinaryOperator::Divide + | BinaryOperator::Add + | BinaryOperator::Subtract => { + // Naga IR doesn't support all matrix by scalar operations so + // we need for some to turn the scalar into a vector by + // splatting it and then for each column vector apply the + // operation and finally reconstruct the matrix + + let scalar_vector = self.add_expression( + Expression::Splat { + size: rows, + value: right, + }, + meta, + )?; + + let mut components = Vec::with_capacity(columns as usize); + + for index in 0..columns as u32 { + // Get the column vector + let matrix_column = self.add_expression( + Expression::AccessIndex { base: left, index }, + meta, + )?; + + // Apply the operation to the splatted vector and + // the column vector + let column = self.add_expression( + Expression::Binary { + op, + left: matrix_column, + right: scalar_vector, + }, + meta, + )?; + + components.push(column) + } + + let ty = self.module.types.insert( + Type { + name: None, + inner: TypeInner::Matrix { + columns, + rows, + scalar: left_scalar, + }, + }, + Span::default(), + ); + + // Rebuild the matrix from the operation result vectors + self.add_expression(Expression::Compose { ty, components }, meta)? + } + _ => { + self.add_expression(Expression::Binary { left, op, right }, meta)? + } + } + } + _ => self.add_expression(Expression::Binary { left, op, right }, meta)?, + } + } + HirExprKind::Unary { op, expr } if pos != ExprPos::Lhs => { + let expr = self + .lower_expect_inner(stmt, frontend, expr, ExprPos::Rhs)? + .0; + + self.add_expression(Expression::Unary { op, expr }, meta)? + } + HirExprKind::Variable(ref var) => match pos { + ExprPos::Lhs => { + if !var.mutable { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "Variable cannot be used in LHS position".into(), + ), + meta, + }) + } + + var.expr + } + ExprPos::AccessBase { constant_index } => { + // If the index isn't constant all accesses backed by a constant base need + // to be done through a proxy local variable, since constants have a non + // pointer type which is required for dynamic indexing + if !constant_index { + if let Some((constant, ty)) = var.constant { + let init = self + .add_expression(Expression::Constant(constant), Span::default())?; + let local = self.locals.append( + LocalVariable { + name: None, + ty, + init: Some(init), + }, + Span::default(), + ); + + self.add_expression(Expression::LocalVariable(local), Span::default())? + } else { + var.expr + } + } else { + var.expr + } + } + _ if var.load => { + self.add_expression(Expression::Load { pointer: var.expr }, meta)? + } + ExprPos::Rhs => { + if let Some((constant, _)) = self.is_const.then_some(var.constant).flatten() { + self.add_expression(Expression::Constant(constant), meta)? + } else { + var.expr + } + } + }, + HirExprKind::Call(ref call) if pos != ExprPos::Lhs => { + let maybe_expr = frontend.function_or_constructor_call( + self, + stmt, + call.kind.clone(), + &call.args, + meta, + )?; + return Ok((maybe_expr, meta)); + } + // `HirExprKind::Conditional` represents the ternary operator in glsl (`:?`) + // + // The ternary operator is defined to only evaluate one of the two possible + // expressions which means that it's behavior is that of an `if` statement, + // and it's merely syntatic sugar for it. + HirExprKind::Conditional { + condition, + accept, + reject, + } if ExprPos::Lhs != pos => { + // Given an expression `a ? b : c`, we need to produce a Naga + // statement roughly like: + // + // var temp; + // if a { + // temp = convert(b); + // } else { + // temp = convert(c); + // } + // + // where `convert` stands for type conversions to bring `b` and `c` to + // the same type, and then use `temp` to represent the value of the whole + // conditional expression in subsequent code. + + // Lower the condition first to the current bodyy + let condition = self + .lower_expect_inner(stmt, frontend, condition, ExprPos::Rhs)? + .0; + + let (mut accept_body, (mut accept, accept_meta)) = + self.new_body_with_ret(|ctx| { + // Lower the `true` branch + ctx.lower_expect_inner(stmt, frontend, accept, pos) + })?; + + let (mut reject_body, (mut reject, reject_meta)) = + self.new_body_with_ret(|ctx| { + // Lower the `false` branch + ctx.lower_expect_inner(stmt, frontend, reject, pos) + })?; + + // We need to do some custom implicit conversions since the two target expressions + // are in different bodies + if let (Some((accept_power, accept_scalar)), Some((reject_power, reject_scalar))) = ( + // Get the components of both branches and calculate the type power + self.expr_scalar_components(accept, accept_meta)? + .and_then(|scalar| Some((type_power(scalar)?, scalar))), + self.expr_scalar_components(reject, reject_meta)? + .and_then(|scalar| Some((type_power(scalar)?, scalar))), + ) { + match accept_power.cmp(&reject_power) { + std::cmp::Ordering::Less => { + accept_body = self.with_body(accept_body, |ctx| { + ctx.conversion(&mut accept, accept_meta, reject_scalar)?; + Ok(()) + })?; + } + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => { + reject_body = self.with_body(reject_body, |ctx| { + ctx.conversion(&mut reject, reject_meta, accept_scalar)?; + Ok(()) + })?; + } + } + } + + // We need to get the type of the resulting expression to create the local, + // this must be done after implicit conversions to ensure both branches have + // the same type. + let ty = self.resolve_type_handle(accept, accept_meta)?; + + // Add the local that will hold the result of our conditional + let local = self.locals.append( + LocalVariable { + name: None, + ty, + init: None, + }, + meta, + ); + + let local_expr = self.add_expression(Expression::LocalVariable(local), meta)?; + + // Add to each the store to the result variable + accept_body.push( + Statement::Store { + pointer: local_expr, + value: accept, + }, + accept_meta, + ); + reject_body.push( + Statement::Store { + pointer: local_expr, + value: reject, + }, + reject_meta, + ); + + // Finally add the `If` to the main body with the `condition` we lowered + // earlier and the branches we prepared. + self.body.push( + Statement::If { + condition, + accept: accept_body, + reject: reject_body, + }, + meta, + ); + + // Note: `Expression::Load` must be emited before it's used so make + // sure the emitter is active here. + self.add_expression( + Expression::Load { + pointer: local_expr, + }, + meta, + )? + } + HirExprKind::Assign { tgt, value } if ExprPos::Lhs != pos => { + let (pointer, ptr_meta) = + self.lower_expect_inner(stmt, frontend, tgt, ExprPos::Lhs)?; + let (mut value, value_meta) = + self.lower_expect_inner(stmt, frontend, value, ExprPos::Rhs)?; + + let ty = match *self.resolve_type(pointer, ptr_meta)? { + TypeInner::Pointer { base, .. } => &self.module.types[base].inner, + ref ty => ty, + }; + + if let Some(scalar) = scalar_components(ty) { + self.implicit_conversion(&mut value, value_meta, scalar)?; + } + + self.lower_store(pointer, value, meta)?; + + value + } + HirExprKind::PrePostfix { op, postfix, expr } if ExprPos::Lhs != pos => { + let (pointer, _) = self.lower_expect_inner(stmt, frontend, expr, ExprPos::Lhs)?; + let left = if let Expression::Swizzle { .. } = self.expressions[pointer] { + pointer + } else { + self.add_expression(Expression::Load { pointer }, meta)? + }; + + let res = match *self.resolve_type(left, meta)? { + TypeInner::Scalar(scalar) => { + let ty = TypeInner::Scalar(scalar); + Literal::one(scalar).map(|i| (ty, i, None, None)) + } + TypeInner::Vector { size, scalar } => { + let ty = TypeInner::Vector { size, scalar }; + Literal::one(scalar).map(|i| (ty, i, Some(size), None)) + } + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + let ty = TypeInner::Matrix { + columns, + rows, + scalar, + }; + Literal::one(scalar).map(|i| (ty, i, Some(rows), Some(columns))) + } + _ => None, + }; + let (ty_inner, literal, rows, columns) = match res { + Some(res) => res, + None => { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "Increment/decrement only works on scalar/vector/matrix".into(), + ), + meta, + }); + return Ok((Some(left), meta)); + } + }; + + let mut right = self.add_expression(Expression::Literal(literal), meta)?; + + // Glsl allows pre/postfixes operations on vectors and matrices, so if the + // target is either of them change the right side of the addition to be splatted + // to the same size as the target, furthermore if the target is a matrix + // use a composed matrix using the splatted value. + if let Some(size) = rows { + right = self.add_expression(Expression::Splat { size, value: right }, meta)?; + + if let Some(cols) = columns { + let ty = self.module.types.insert( + Type { + name: None, + inner: ty_inner, + }, + meta, + ); + + right = self.add_expression( + Expression::Compose { + ty, + components: std::iter::repeat(right).take(cols as usize).collect(), + }, + meta, + )?; + } + } + + let value = self.add_expression(Expression::Binary { op, left, right }, meta)?; + + self.lower_store(pointer, value, meta)?; + + if postfix { + left + } else { + value + } + } + HirExprKind::Method { + expr: object, + ref name, + ref args, + } if ExprPos::Lhs != pos => { + let args = args + .iter() + .map(|e| self.lower_expect_inner(stmt, frontend, *e, ExprPos::Rhs)) + .collect::>>()?; + match name.as_ref() { + "length" => { + if !args.is_empty() { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + ".length() doesn't take any arguments".into(), + ), + meta, + }); + } + let lowered_array = self.lower_expect_inner(stmt, frontend, object, pos)?.0; + let array_type = self.resolve_type(lowered_array, meta)?; + + match *array_type { + TypeInner::Array { + size: crate::ArraySize::Constant(size), + .. + } => { + let mut array_length = self.add_expression( + Expression::Literal(Literal::U32(size.get())), + meta, + )?; + self.forced_conversion(&mut array_length, meta, Scalar::I32)?; + array_length + } + // let the error be handled in type checking if it's not a dynamic array + _ => { + let mut array_length = self + .add_expression(Expression::ArrayLength(lowered_array), meta)?; + self.conversion(&mut array_length, meta, Scalar::I32)?; + array_length + } + } + } + _ => { + return Err(Error { + kind: ErrorKind::SemanticError( + format!("unknown method '{name}'").into(), + ), + meta, + }); + } + } + } + _ => { + return Err(Error { + kind: ErrorKind::SemanticError( + format!("{:?} cannot be in the left hand side", stmt.hir_exprs[expr]) + .into(), + ), + meta, + }) + } + }; + + log::trace!( + "Lowered {:?}\n\tKind = {:?}\n\tPos = {:?}\n\tResult = {:?}", + expr, + kind, + pos, + handle + ); + + Ok((Some(handle), meta)) + } + + pub fn expr_scalar_components( + &mut self, + expr: Handle, + meta: Span, + ) -> Result> { + let ty = self.resolve_type(expr, meta)?; + Ok(scalar_components(ty)) + } + + pub fn expr_power(&mut self, expr: Handle, meta: Span) -> Result> { + Ok(self + .expr_scalar_components(expr, meta)? + .and_then(type_power)) + } + + pub fn conversion( + &mut self, + expr: &mut Handle, + meta: Span, + scalar: Scalar, + ) -> Result<()> { + *expr = self.add_expression( + Expression::As { + expr: *expr, + kind: scalar.kind, + convert: Some(scalar.width), + }, + meta, + )?; + + Ok(()) + } + + pub fn implicit_conversion( + &mut self, + expr: &mut Handle, + meta: Span, + scalar: Scalar, + ) -> Result<()> { + if let (Some(tgt_power), Some(expr_power)) = + (type_power(scalar), self.expr_power(*expr, meta)?) + { + if tgt_power > expr_power { + self.conversion(expr, meta, scalar)?; + } + } + + Ok(()) + } + + pub fn forced_conversion( + &mut self, + expr: &mut Handle, + meta: Span, + scalar: Scalar, + ) -> Result<()> { + if let Some(expr_scalar) = self.expr_scalar_components(*expr, meta)? { + if expr_scalar != scalar { + self.conversion(expr, meta, scalar)?; + } + } + + Ok(()) + } + + pub fn binary_implicit_conversion( + &mut self, + left: &mut Handle, + left_meta: Span, + right: &mut Handle, + right_meta: Span, + ) -> Result<()> { + let left_components = self.expr_scalar_components(*left, left_meta)?; + let right_components = self.expr_scalar_components(*right, right_meta)?; + + if let (Some((left_power, left_scalar)), Some((right_power, right_scalar))) = ( + left_components.and_then(|scalar| Some((type_power(scalar)?, scalar))), + right_components.and_then(|scalar| Some((type_power(scalar)?, scalar))), + ) { + match left_power.cmp(&right_power) { + std::cmp::Ordering::Less => { + self.conversion(left, left_meta, right_scalar)?; + } + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => { + self.conversion(right, right_meta, left_scalar)?; + } + } + } + + Ok(()) + } + + pub fn implicit_splat( + &mut self, + expr: &mut Handle, + meta: Span, + vector_size: Option, + ) -> Result<()> { + let expr_type = self.resolve_type(*expr, meta)?; + + if let (&TypeInner::Scalar { .. }, Some(size)) = (expr_type, vector_size) { + *expr = self.add_expression(Expression::Splat { size, value: *expr }, meta)? + } + + Ok(()) + } + + pub fn vector_resize( + &mut self, + size: VectorSize, + vector: Handle, + meta: Span, + ) -> Result> { + self.add_expression( + Expression::Swizzle { + size, + vector, + pattern: crate::SwizzleComponent::XYZW, + }, + meta, + ) + } +} + +impl Index> for Context<'_> { + type Output = Expression; + + fn index(&self, index: Handle) -> &Self::Output { + if self.is_const { + &self.module.const_expressions[index] + } else { + &self.expressions[index] + } + } +} + +/// Helper struct passed when parsing expressions +/// +/// This struct should only be obtained through [`stmt_ctx`](Context::stmt_ctx) +/// and only one of these may be active at any time per context. +#[derive(Debug)] +pub struct StmtContext { + /// A arena of high level expressions which can be lowered through a + /// [`Context`] to Naga's [`Expression`]s + pub hir_exprs: Arena, +} + +impl StmtContext { + const fn new() -> Self { + StmtContext { + hir_exprs: Arena::new(), + } + } +} diff --git a/naga/src/front/glsl/error.rs b/naga/src/front/glsl/error.rs new file mode 100644 index 0000000000..d6e3586687 --- /dev/null +++ b/naga/src/front/glsl/error.rs @@ -0,0 +1,134 @@ +use super::token::TokenValue; +use crate::{proc::ConstantEvaluatorError, Span}; +use pp_rs::token::PreprocessorError; +use std::borrow::Cow; +use thiserror::Error; + +fn join_with_comma(list: &[ExpectedToken]) -> String { + let mut string = "".to_string(); + for (i, val) in list.iter().enumerate() { + string.push_str(&val.to_string()); + match i { + i if i == list.len() - 1 => {} + i if i == list.len() - 2 => string.push_str(" or "), + _ => string.push_str(", "), + } + } + string +} + +/// One of the expected tokens returned in [`InvalidToken`](ErrorKind::InvalidToken). +#[derive(Debug, PartialEq)] +pub enum ExpectedToken { + /// A specific token was expected. + Token(TokenValue), + /// A type was expected. + TypeName, + /// An identifier was expected. + Identifier, + /// An integer literal was expected. + IntLiteral, + /// A float literal was expected. + FloatLiteral, + /// A boolean literal was expected. + BoolLiteral, + /// The end of file was expected. + Eof, +} +impl From for ExpectedToken { + fn from(token: TokenValue) -> Self { + ExpectedToken::Token(token) + } +} +impl std::fmt::Display for ExpectedToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + ExpectedToken::Token(ref token) => write!(f, "{token:?}"), + ExpectedToken::TypeName => write!(f, "a type"), + ExpectedToken::Identifier => write!(f, "identifier"), + ExpectedToken::IntLiteral => write!(f, "integer literal"), + ExpectedToken::FloatLiteral => write!(f, "float literal"), + ExpectedToken::BoolLiteral => write!(f, "bool literal"), + ExpectedToken::Eof => write!(f, "end of file"), + } + } +} + +/// Information about the cause of an error. +#[derive(Debug, Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum ErrorKind { + /// Whilst parsing as encountered an unexpected EOF. + #[error("Unexpected end of file")] + EndOfFile, + /// The shader specified an unsupported or invalid profile. + #[error("Invalid profile: {0}")] + InvalidProfile(String), + /// The shader requested an unsupported or invalid version. + #[error("Invalid version: {0}")] + InvalidVersion(u64), + /// Whilst parsing an unexpected token was encountered. + /// + /// A list of expected tokens is also returned. + #[error("Expected {}, found {0:?}", join_with_comma(.1))] + InvalidToken(TokenValue, Vec), + /// A specific feature is not yet implemented. + /// + /// To help prioritize work please open an issue in the github issue tracker + /// if none exist already or react to the already existing one. + #[error("Not implemented: {0}")] + NotImplemented(&'static str), + /// A reference to a variable that wasn't declared was used. + #[error("Unknown variable: {0}")] + UnknownVariable(String), + /// A reference to a type that wasn't declared was used. + #[error("Unknown type: {0}")] + UnknownType(String), + /// A reference to a non existent member of a type was made. + #[error("Unknown field: {0}")] + UnknownField(String), + /// An unknown layout qualifier was used. + /// + /// If the qualifier does exist please open an issue in the github issue tracker + /// if none exist already or react to the already existing one to help + /// prioritize work. + #[error("Unknown layout qualifier: {0}")] + UnknownLayoutQualifier(String), + /// Unsupported matrix of the form matCx2 + /// + /// Our IR expects matrices of the form matCx2 to have a stride of 8 however + /// matrices in the std140 layout have a stride of at least 16 + #[error("unsupported matrix of the form matCx2 in std140 block layout")] + UnsupportedMatrixTypeInStd140, + /// A variable with the same name already exists in the current scope. + #[error("Variable already declared: {0}")] + VariableAlreadyDeclared(String), + /// A semantic error was detected in the shader. + #[error("{0}")] + SemanticError(Cow<'static, str>), + /// An error was returned by the preprocessor. + #[error("{0:?}")] + PreprocessorError(PreprocessorError), + /// The parser entered an illegal state and exited + /// + /// This obviously is a bug and as such should be reported in the github issue tracker + #[error("Internal error: {0}")] + InternalError(&'static str), +} + +impl From for ErrorKind { + fn from(err: ConstantEvaluatorError) -> Self { + ErrorKind::SemanticError(err.to_string().into()) + } +} + +/// Error returned during shader parsing. +#[derive(Debug, Error)] +#[error("{kind}")] +#[cfg_attr(test, derive(PartialEq))] +pub struct Error { + /// Holds the information about the error itself. + pub kind: ErrorKind, + /// Holds information about the range of the source code where the error happened. + pub meta: Span, +} diff --git a/naga/src/front/glsl/functions.rs b/naga/src/front/glsl/functions.rs new file mode 100644 index 0000000000..df8cc8a30e --- /dev/null +++ b/naga/src/front/glsl/functions.rs @@ -0,0 +1,1602 @@ +use super::{ + ast::*, + builtins::{inject_builtin, sampled_to_depth}, + context::{Context, ExprPos, StmtContext}, + error::{Error, ErrorKind}, + types::scalar_components, + Frontend, Result, +}; +use crate::{ + front::glsl::types::type_power, proc::ensure_block_returns, AddressSpace, Block, EntryPoint, + Expression, Function, FunctionArgument, FunctionResult, Handle, Literal, LocalVariable, Scalar, + ScalarKind, Span, Statement, StructMember, Type, TypeInner, +}; +use std::iter; + +/// Struct detailing a store operation that must happen after a function call +struct ProxyWrite { + /// The store target + target: Handle, + /// A pointer to read the value of the store + value: Handle, + /// An optional conversion to be applied + convert: Option, +} + +impl Frontend { + pub(crate) fn function_or_constructor_call( + &mut self, + ctx: &mut Context, + stmt: &StmtContext, + fc: FunctionCallKind, + raw_args: &[Handle], + meta: Span, + ) -> Result>> { + let args: Vec<_> = raw_args + .iter() + .map(|e| ctx.lower_expect_inner(stmt, self, *e, ExprPos::Rhs)) + .collect::>()?; + + match fc { + FunctionCallKind::TypeConstructor(ty) => { + if args.len() == 1 { + self.constructor_single(ctx, ty, args[0], meta).map(Some) + } else { + self.constructor_many(ctx, ty, args, meta).map(Some) + } + } + FunctionCallKind::Function(name) => { + self.function_call(ctx, stmt, name, args, raw_args, meta) + } + } + } + + fn constructor_single( + &mut self, + ctx: &mut Context, + ty: Handle, + (mut value, expr_meta): (Handle, Span), + meta: Span, + ) -> Result> { + let expr_type = ctx.resolve_type(value, expr_meta)?; + + let vector_size = match *expr_type { + TypeInner::Vector { size, .. } => Some(size), + _ => None, + }; + + let expr_is_bool = expr_type.scalar_kind() == Some(ScalarKind::Bool); + + // Special case: if casting from a bool, we need to use Select and not As. + match ctx.module.types[ty].inner.scalar() { + Some(result_scalar) if expr_is_bool && result_scalar.kind != ScalarKind::Bool => { + let result_scalar = Scalar { + width: 4, + ..result_scalar + }; + let l0 = Literal::zero(result_scalar).unwrap(); + let l1 = Literal::one(result_scalar).unwrap(); + let mut reject = ctx.add_expression(Expression::Literal(l0), expr_meta)?; + let mut accept = ctx.add_expression(Expression::Literal(l1), expr_meta)?; + + ctx.implicit_splat(&mut reject, meta, vector_size)?; + ctx.implicit_splat(&mut accept, meta, vector_size)?; + + let h = ctx.add_expression( + Expression::Select { + accept, + reject, + condition: value, + }, + expr_meta, + )?; + + return Ok(h); + } + _ => {} + } + + Ok(match ctx.module.types[ty].inner { + TypeInner::Vector { size, scalar } if vector_size.is_none() => { + ctx.forced_conversion(&mut value, expr_meta, scalar)?; + + if let TypeInner::Scalar { .. } = *ctx.resolve_type(value, expr_meta)? { + ctx.add_expression(Expression::Splat { size, value }, meta)? + } else { + self.vector_constructor(ctx, ty, size, scalar, &[(value, expr_meta)], meta)? + } + } + TypeInner::Scalar(scalar) => { + let mut expr = value; + if let TypeInner::Vector { .. } | TypeInner::Matrix { .. } = + *ctx.resolve_type(value, expr_meta)? + { + expr = ctx.add_expression( + Expression::AccessIndex { + base: expr, + index: 0, + }, + meta, + )?; + } + + if let TypeInner::Matrix { .. } = *ctx.resolve_type(value, expr_meta)? { + expr = ctx.add_expression( + Expression::AccessIndex { + base: expr, + index: 0, + }, + meta, + )?; + } + + ctx.add_expression( + Expression::As { + kind: scalar.kind, + expr, + convert: Some(scalar.width), + }, + meta, + )? + } + TypeInner::Vector { size, scalar } => { + if vector_size.map_or(true, |s| s != size) { + value = ctx.vector_resize(size, value, expr_meta)?; + } + + ctx.add_expression( + Expression::As { + kind: scalar.kind, + expr: value, + convert: Some(scalar.width), + }, + meta, + )? + } + TypeInner::Matrix { + columns, + rows, + scalar, + } => self.matrix_one_arg(ctx, ty, columns, rows, scalar, (value, expr_meta), meta)?, + TypeInner::Struct { ref members, .. } => { + let scalar_components = members + .get(0) + .and_then(|member| scalar_components(&ctx.module.types[member.ty].inner)); + if let Some(scalar) = scalar_components { + ctx.implicit_conversion(&mut value, expr_meta, scalar)?; + } + + ctx.add_expression( + Expression::Compose { + ty, + components: vec![value], + }, + meta, + )? + } + + TypeInner::Array { base, .. } => { + let scalar_components = scalar_components(&ctx.module.types[base].inner); + if let Some(scalar) = scalar_components { + ctx.implicit_conversion(&mut value, expr_meta, scalar)?; + } + + ctx.add_expression( + Expression::Compose { + ty, + components: vec![value], + }, + meta, + )? + } + _ => { + self.errors.push(Error { + kind: ErrorKind::SemanticError("Bad type constructor".into()), + meta, + }); + + value + } + }) + } + + #[allow(clippy::too_many_arguments)] + fn matrix_one_arg( + &mut self, + ctx: &mut Context, + ty: Handle, + columns: crate::VectorSize, + rows: crate::VectorSize, + element_scalar: Scalar, + (mut value, expr_meta): (Handle, Span), + meta: Span, + ) -> Result> { + let mut components = Vec::with_capacity(columns as usize); + // TODO: casts + // `Expression::As` doesn't support matrix width + // casts so we need to do some extra work for casts + + ctx.forced_conversion(&mut value, expr_meta, element_scalar)?; + match *ctx.resolve_type(value, expr_meta)? { + TypeInner::Scalar(_) => { + // If a matrix is constructed with a single scalar value, then that + // value is used to initialize all the values along the diagonal of + // the matrix; the rest are given zeros. + let vector_ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: rows, + scalar: element_scalar, + }, + }, + meta, + ); + + let zero_literal = Literal::zero(element_scalar).unwrap(); + let zero = ctx.add_expression(Expression::Literal(zero_literal), meta)?; + + for i in 0..columns as u32 { + components.push( + ctx.add_expression( + Expression::Compose { + ty: vector_ty, + components: (0..rows as u32) + .map(|r| match r == i { + true => value, + false => zero, + }) + .collect(), + }, + meta, + )?, + ) + } + } + TypeInner::Matrix { + rows: ori_rows, + columns: ori_cols, + .. + } => { + // If a matrix is constructed from a matrix, then each component + // (column i, row j) in the result that has a corresponding component + // (column i, row j) in the argument will be initialized from there. All + // other components will be initialized to the identity matrix. + + let zero_literal = Literal::zero(element_scalar).unwrap(); + let one_literal = Literal::one(element_scalar).unwrap(); + + let zero = ctx.add_expression(Expression::Literal(zero_literal), meta)?; + let one = ctx.add_expression(Expression::Literal(one_literal), meta)?; + + let vector_ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: rows, + scalar: element_scalar, + }, + }, + meta, + ); + + for i in 0..columns as u32 { + if i < ori_cols as u32 { + use std::cmp::Ordering; + + let vector = ctx.add_expression( + Expression::AccessIndex { + base: value, + index: i, + }, + meta, + )?; + + components.push(match ori_rows.cmp(&rows) { + Ordering::Less => { + let components = (0..rows as u32) + .map(|r| { + if r < ori_rows as u32 { + ctx.add_expression( + Expression::AccessIndex { + base: vector, + index: r, + }, + meta, + ) + } else if r == i { + Ok(one) + } else { + Ok(zero) + } + }) + .collect::>()?; + + ctx.add_expression( + Expression::Compose { + ty: vector_ty, + components, + }, + meta, + )? + } + Ordering::Equal => vector, + Ordering::Greater => ctx.vector_resize(rows, vector, meta)?, + }) + } else { + let compose_expr = Expression::Compose { + ty: vector_ty, + components: (0..rows as u32) + .map(|r| match r == i { + true => one, + false => zero, + }) + .collect(), + }; + + let vec = ctx.add_expression(compose_expr, meta)?; + + components.push(vec) + } + } + } + _ => { + components = iter::repeat(value).take(columns as usize).collect(); + } + } + + ctx.add_expression(Expression::Compose { ty, components }, meta) + } + + #[allow(clippy::too_many_arguments)] + fn vector_constructor( + &mut self, + ctx: &mut Context, + ty: Handle, + size: crate::VectorSize, + scalar: Scalar, + args: &[(Handle, Span)], + meta: Span, + ) -> Result> { + let mut components = Vec::with_capacity(size as usize); + + for (mut arg, expr_meta) in args.iter().copied() { + ctx.forced_conversion(&mut arg, expr_meta, scalar)?; + + if components.len() >= size as usize { + break; + } + + match *ctx.resolve_type(arg, expr_meta)? { + TypeInner::Scalar { .. } => components.push(arg), + TypeInner::Matrix { rows, columns, .. } => { + components.reserve(rows as usize * columns as usize); + for c in 0..(columns as u32) { + let base = ctx.add_expression( + Expression::AccessIndex { + base: arg, + index: c, + }, + expr_meta, + )?; + for r in 0..(rows as u32) { + components.push(ctx.add_expression( + Expression::AccessIndex { base, index: r }, + expr_meta, + )?) + } + } + } + TypeInner::Vector { size: ori_size, .. } => { + components.reserve(ori_size as usize); + for index in 0..(ori_size as u32) { + components.push(ctx.add_expression( + Expression::AccessIndex { base: arg, index }, + expr_meta, + )?) + } + } + _ => components.push(arg), + } + } + + components.truncate(size as usize); + + ctx.add_expression(Expression::Compose { ty, components }, meta) + } + + fn constructor_many( + &mut self, + ctx: &mut Context, + ty: Handle, + args: Vec<(Handle, Span)>, + meta: Span, + ) -> Result> { + let mut components = Vec::with_capacity(args.len()); + + let struct_member_data = match ctx.module.types[ty].inner { + TypeInner::Matrix { + columns, + rows, + scalar: element_scalar, + } => { + let mut flattened = Vec::with_capacity(columns as usize * rows as usize); + + for (mut arg, meta) in args.iter().copied() { + ctx.forced_conversion(&mut arg, meta, element_scalar)?; + + match *ctx.resolve_type(arg, meta)? { + TypeInner::Vector { size, .. } => { + for i in 0..(size as u32) { + flattened.push(ctx.add_expression( + Expression::AccessIndex { + base: arg, + index: i, + }, + meta, + )?) + } + } + _ => flattened.push(arg), + } + } + + let ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: rows, + scalar: element_scalar, + }, + }, + meta, + ); + + for chunk in flattened.chunks(rows as usize) { + components.push(ctx.add_expression( + Expression::Compose { + ty, + components: Vec::from(chunk), + }, + meta, + )?) + } + None + } + TypeInner::Vector { size, scalar } => { + return self.vector_constructor(ctx, ty, size, scalar, &args, meta) + } + TypeInner::Array { base, .. } => { + for (mut arg, meta) in args.iter().copied() { + let scalar_components = scalar_components(&ctx.module.types[base].inner); + if let Some(scalar) = scalar_components { + ctx.implicit_conversion(&mut arg, meta, scalar)?; + } + + components.push(arg) + } + None + } + TypeInner::Struct { ref members, .. } => Some( + members + .iter() + .map(|member| scalar_components(&ctx.module.types[member.ty].inner)) + .collect::>(), + ), + _ => { + return Err(Error { + kind: ErrorKind::SemanticError("Constructor: Too many arguments".into()), + meta, + }) + } + }; + + if let Some(struct_member_data) = struct_member_data { + for ((mut arg, meta), scalar_components) in + args.iter().copied().zip(struct_member_data.iter().copied()) + { + if let Some(scalar) = scalar_components { + ctx.implicit_conversion(&mut arg, meta, scalar)?; + } + + components.push(arg) + } + } + + ctx.add_expression(Expression::Compose { ty, components }, meta) + } + + #[allow(clippy::too_many_arguments)] + fn function_call( + &mut self, + ctx: &mut Context, + stmt: &StmtContext, + name: String, + args: Vec<(Handle, Span)>, + raw_args: &[Handle], + meta: Span, + ) -> Result>> { + // Grow the typifier to be able to index it later without needing + // to hold the context mutably + for &(expr, span) in args.iter() { + ctx.typifier_grow(expr, span)?; + } + + // Check if the passed arguments require any special variations + let mut variations = + builtin_required_variations(args.iter().map(|&(expr, _)| ctx.get_type(expr))); + + // Initiate the declaration if it wasn't previously initialized and inject builtins + let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| { + variations |= BuiltinVariations::STANDARD; + Default::default() + }); + inject_builtin(declaration, ctx.module, &name, variations); + + // Borrow again but without mutability, at this point a declaration is guaranteed + let declaration = self.lookup_function.get(&name).unwrap(); + + // Possibly contains the overload to be used in the call + let mut maybe_overload = None; + // The conversions needed for the best analyzed overload, this is initialized all to + // `NONE` to make sure that conversions always pass the first time without ambiguity + let mut old_conversions = vec![Conversion::None; args.len()]; + // Tracks whether the comparison between overloads lead to an ambiguity + let mut ambiguous = false; + + // Iterate over all the available overloads to select either an exact match or a + // overload which has suitable implicit conversions + 'outer: for (overload_idx, overload) in declaration.overloads.iter().enumerate() { + // If the overload and the function call don't have the same number of arguments + // continue to the next overload + if args.len() != overload.parameters.len() { + continue; + } + + log::trace!("Testing overload {}", overload_idx); + + // Stores whether the current overload matches exactly the function call + let mut exact = true; + // State of the selection + // If None we still don't know what is the best overload + // If Some(true) the new overload is better + // If Some(false) the old overload is better + let mut superior = None; + // Store the conversions for the current overload so that later they can replace the + // conversions used for querying the best overload + let mut new_conversions = vec![Conversion::None; args.len()]; + + // Loop through the overload parameters and check if the current overload is better + // compared to the previous best overload. + for (i, overload_parameter) in overload.parameters.iter().enumerate() { + let call_argument = &args[i]; + let parameter_info = &overload.parameters_info[i]; + + // If the image is used in the overload as a depth texture convert it + // before comparing, otherwise exact matches wouldn't be reported + if parameter_info.depth { + sampled_to_depth(ctx, call_argument.0, call_argument.1, &mut self.errors); + ctx.invalidate_expression(call_argument.0, call_argument.1)? + } + + ctx.typifier_grow(call_argument.0, call_argument.1)?; + + let overload_param_ty = &ctx.module.types[*overload_parameter].inner; + let call_arg_ty = ctx.get_type(call_argument.0); + + log::trace!( + "Testing parameter {}\n\tOverload = {:?}\n\tCall = {:?}", + i, + overload_param_ty, + call_arg_ty + ); + + // Storage images cannot be directly compared since while the access is part of the + // type in naga's IR, in glsl they are a qualifier and don't enter in the match as + // long as the access needed is satisfied. + if let ( + &TypeInner::Image { + class: + crate::ImageClass::Storage { + format: overload_format, + access: overload_access, + }, + dim: overload_dim, + arrayed: overload_arrayed, + }, + &TypeInner::Image { + class: + crate::ImageClass::Storage { + format: call_format, + access: call_access, + }, + dim: call_dim, + arrayed: call_arrayed, + }, + ) = (overload_param_ty, call_arg_ty) + { + // Images size must match otherwise the overload isn't what we want + let good_size = call_dim == overload_dim && call_arrayed == overload_arrayed; + // Glsl requires the formats to strictly match unless you are builtin + // function overload and have not been replaced, in which case we only + // check that the format scalar kind matches + let good_format = overload_format == call_format + || (overload.internal + && ScalarKind::from(overload_format) == ScalarKind::from(call_format)); + if !(good_size && good_format) { + continue 'outer; + } + + // While storage access mismatch is an error it isn't one that causes + // the overload matching to fail so we defer the error and consider + // that the images match exactly + if !call_access.contains(overload_access) { + self.errors.push(Error { + kind: ErrorKind::SemanticError( + format!( + "'{name}': image needs {overload_access:?} access but only {call_access:?} was provided" + ) + .into(), + ), + meta, + }); + } + + // The images satisfy the conditions to be considered as an exact match + new_conversions[i] = Conversion::Exact; + continue; + } else if overload_param_ty == call_arg_ty { + // If the types match there's no need to check for conversions so continue + new_conversions[i] = Conversion::Exact; + continue; + } + + // Glsl defines that inout follows both the conversions for input parameters and + // output parameters, this means that the type must have a conversion from both the + // call argument to the function parameter and the function parameter to the call + // argument, the only way this is possible is for the conversion to be an identity + // (i.e. call argument = function parameter) + if let ParameterQualifier::InOut = parameter_info.qualifier { + continue 'outer; + } + + // The function call argument and the function definition + // parameter are not equal at this point, so we need to try + // implicit conversions. + // + // Now there are two cases, the argument is defined as a normal + // parameter (`in` or `const`), in this case an implicit + // conversion is made from the calling argument to the + // definition argument. If the parameter is `out` the + // opposite needs to be done, so the implicit conversion is made + // from the definition argument to the calling argument. + let maybe_conversion = if parameter_info.qualifier.is_lhs() { + conversion(call_arg_ty, overload_param_ty) + } else { + conversion(overload_param_ty, call_arg_ty) + }; + + let conversion = match maybe_conversion { + Some(info) => info, + None => continue 'outer, + }; + + // At this point a conversion will be needed so the overload no longer + // exactly matches the call arguments + exact = false; + + // Compare the conversions needed for this overload parameter to that of the + // last overload analyzed respective parameter, the value is: + // - `true` when the new overload argument has a better conversion + // - `false` when the old overload argument has a better conversion + let best_arg = match (conversion, old_conversions[i]) { + // An exact match is always better, we don't need to check this for the + // current overload since it was checked earlier + (_, Conversion::Exact) => false, + // No overload was yet analyzed so this one is the best yet + (_, Conversion::None) => true, + // A conversion from a float to a double is the best possible conversion + (Conversion::FloatToDouble, _) => true, + (_, Conversion::FloatToDouble) => false, + // A conversion from a float to an integer is preferred than one + // from double to an integer + (Conversion::IntToFloat, Conversion::IntToDouble) => true, + (Conversion::IntToDouble, Conversion::IntToFloat) => false, + // This case handles things like no conversion and exact which were already + // treated and other cases which no conversion is better than the other + _ => continue, + }; + + // Check if the best parameter corresponds to the current selected overload + // to pass to the next comparison, if this isn't true mark it as ambiguous + match best_arg { + true => match superior { + Some(false) => ambiguous = true, + _ => { + superior = Some(true); + new_conversions[i] = conversion + } + }, + false => match superior { + Some(true) => ambiguous = true, + _ => superior = Some(false), + }, + } + } + + // The overload matches exactly the function call so there's no ambiguity (since + // repeated overload aren't allowed) and the current overload is selected, no + // further querying is needed. + if exact { + maybe_overload = Some(overload); + ambiguous = false; + break; + } + + match superior { + // New overload is better keep it + Some(true) => { + maybe_overload = Some(overload); + // Replace the conversions + old_conversions = new_conversions; + } + // Old overload is better do nothing + Some(false) => {} + // No overload was better than the other this can be caused + // when all conversions are ambiguous in which the overloads themselves are + // ambiguous. + None => { + ambiguous = true; + // Assign the new overload, this helps ensures that in this case of + // ambiguity the parsing won't end immediately and allow for further + // collection of errors. + maybe_overload = Some(overload); + } + } + } + + if ambiguous { + self.errors.push(Error { + kind: ErrorKind::SemanticError( + format!("Ambiguous best function for '{name}'").into(), + ), + meta, + }) + } + + let overload = maybe_overload.ok_or_else(|| Error { + kind: ErrorKind::SemanticError(format!("Unknown function '{name}'").into()), + meta, + })?; + + let parameters_info = overload.parameters_info.clone(); + let parameters = overload.parameters.clone(); + let is_void = overload.void; + let kind = overload.kind; + + let mut arguments = Vec::with_capacity(args.len()); + let mut proxy_writes = Vec::new(); + + // Iterate through the function call arguments applying transformations as needed + for (((parameter_info, call_argument), expr), parameter) in parameters_info + .iter() + .zip(&args) + .zip(raw_args) + .zip(¶meters) + { + let (mut handle, meta) = + ctx.lower_expect_inner(stmt, self, *expr, parameter_info.qualifier.as_pos())?; + + if parameter_info.qualifier.is_lhs() { + self.process_lhs_argument( + ctx, + meta, + *parameter, + parameter_info, + handle, + call_argument, + &mut proxy_writes, + &mut arguments, + )?; + + continue; + } + + let scalar_comps = scalar_components(&ctx.module.types[*parameter].inner); + + // Apply implicit conversions as needed + if let Some(scalar) = scalar_comps { + ctx.implicit_conversion(&mut handle, meta, scalar)?; + } + + arguments.push(handle) + } + + match kind { + FunctionKind::Call(function) => { + ctx.emit_end(); + + let result = if !is_void { + Some(ctx.add_expression(Expression::CallResult(function), meta)?) + } else { + None + }; + + ctx.body.push( + crate::Statement::Call { + function, + arguments, + result, + }, + meta, + ); + + ctx.emit_start(); + + // Write back all the variables that were scheduled to their original place + for proxy_write in proxy_writes { + let mut value = ctx.add_expression( + Expression::Load { + pointer: proxy_write.value, + }, + meta, + )?; + + if let Some(scalar) = proxy_write.convert { + ctx.conversion(&mut value, meta, scalar)?; + } + + ctx.emit_restart(); + + ctx.body.push( + Statement::Store { + pointer: proxy_write.target, + value, + }, + meta, + ); + } + + Ok(result) + } + FunctionKind::Macro(builtin) => builtin.call(self, ctx, arguments.as_mut_slice(), meta), + } + } + + /// Processes a function call argument that appears in place of an output + /// parameter. + #[allow(clippy::too_many_arguments)] + fn process_lhs_argument( + &mut self, + ctx: &mut Context, + meta: Span, + parameter_ty: Handle, + parameter_info: &ParameterInfo, + original: Handle, + call_argument: &(Handle, Span), + proxy_writes: &mut Vec, + arguments: &mut Vec>, + ) -> Result<()> { + let original_ty = ctx.resolve_type(original, meta)?; + let original_pointer_space = original_ty.pointer_space(); + + // The type of a possible spill variable needed for a proxy write + let mut maybe_ty = match *original_ty { + // If the argument is to be passed as a pointer but the type of the + // expression returns a vector it must mean that it was for example + // swizzled and it must be spilled into a local before calling + TypeInner::Vector { size, scalar } => Some(ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Vector { size, scalar }, + }, + Span::default(), + )), + // If the argument is a pointer whose address space isn't `Function`, an + // indirection through a local variable is needed to align the address + // spaces of the call argument and the overload parameter. + TypeInner::Pointer { base, space } if space != AddressSpace::Function => Some(base), + TypeInner::ValuePointer { + size, + scalar, + space, + } if space != AddressSpace::Function => { + let inner = match size { + Some(size) => TypeInner::Vector { size, scalar }, + None => TypeInner::Scalar(scalar), + }; + + Some( + ctx.module + .types + .insert(Type { name: None, inner }, Span::default()), + ) + } + _ => None, + }; + + // Since the original expression might be a pointer and we want a value + // for the proxy writes, we might need to load the pointer. + let value = if original_pointer_space.is_some() { + ctx.add_expression(Expression::Load { pointer: original }, Span::default())? + } else { + original + }; + + ctx.typifier_grow(call_argument.0, call_argument.1)?; + + let overload_param_ty = &ctx.module.types[parameter_ty].inner; + let call_arg_ty = ctx.get_type(call_argument.0); + let needs_conversion = call_arg_ty != overload_param_ty; + + let arg_scalar_comps = scalar_components(call_arg_ty); + + // Since output parameters also allow implicit conversions from the + // parameter to the argument, we need to spill the conversion to a + // variable and create a proxy write for the original variable. + if needs_conversion { + maybe_ty = Some(parameter_ty); + } + + if let Some(ty) = maybe_ty { + // Create the spill variable + let spill_var = ctx.locals.append( + LocalVariable { + name: None, + ty, + init: None, + }, + Span::default(), + ); + let spill_expr = + ctx.add_expression(Expression::LocalVariable(spill_var), Span::default())?; + + // If the argument is also copied in we must store the value of the + // original variable to the spill variable. + if let ParameterQualifier::InOut = parameter_info.qualifier { + ctx.body.push( + Statement::Store { + pointer: spill_expr, + value, + }, + Span::default(), + ); + } + + // Add the spill variable as an argument to the function call + arguments.push(spill_expr); + + let convert = if needs_conversion { + arg_scalar_comps + } else { + None + }; + + // Register the temporary local to be written back to it's original + // place after the function call + if let Expression::Swizzle { + size, + mut vector, + pattern, + } = ctx.expressions[original] + { + if let Expression::Load { pointer } = ctx.expressions[vector] { + vector = pointer; + } + + for (i, component) in pattern.iter().take(size as usize).enumerate() { + let original = ctx.add_expression( + Expression::AccessIndex { + base: vector, + index: *component as u32, + }, + Span::default(), + )?; + + let spill_component = ctx.add_expression( + Expression::AccessIndex { + base: spill_expr, + index: i as u32, + }, + Span::default(), + )?; + + proxy_writes.push(ProxyWrite { + target: original, + value: spill_component, + convert, + }); + } + } else { + proxy_writes.push(ProxyWrite { + target: original, + value: spill_expr, + convert, + }); + } + } else { + arguments.push(original); + } + + Ok(()) + } + + pub(crate) fn add_function( + &mut self, + mut ctx: Context, + name: String, + result: Option, + meta: Span, + ) { + ensure_block_returns(&mut ctx.body); + + let void = result.is_none(); + + // Check if the passed arguments require any special variations + let mut variations = builtin_required_variations( + ctx.parameters + .iter() + .map(|&arg| &ctx.module.types[arg].inner), + ); + + // Initiate the declaration if it wasn't previously initialized and inject builtins + let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| { + variations |= BuiltinVariations::STANDARD; + Default::default() + }); + inject_builtin(declaration, ctx.module, &name, variations); + + let Context { + expressions, + locals, + arguments, + parameters, + parameters_info, + body, + module, + .. + } = ctx; + + let function = Function { + name: Some(name), + arguments, + result, + local_variables: locals, + expressions, + named_expressions: crate::NamedExpressions::default(), + body, + }; + + 'outer: for decl in declaration.overloads.iter_mut() { + if parameters.len() != decl.parameters.len() { + continue; + } + + for (new_parameter, old_parameter) in parameters.iter().zip(decl.parameters.iter()) { + let new_inner = &module.types[*new_parameter].inner; + let old_inner = &module.types[*old_parameter].inner; + + if new_inner != old_inner { + continue 'outer; + } + } + + if decl.defined { + return self.errors.push(Error { + kind: ErrorKind::SemanticError("Function already defined".into()), + meta, + }); + } + + decl.defined = true; + decl.parameters_info = parameters_info; + match decl.kind { + FunctionKind::Call(handle) => *module.functions.get_mut(handle) = function, + FunctionKind::Macro(_) => { + let handle = module.functions.append(function, meta); + decl.kind = FunctionKind::Call(handle) + } + } + return; + } + + let handle = module.functions.append(function, meta); + declaration.overloads.push(Overload { + parameters, + parameters_info, + kind: FunctionKind::Call(handle), + defined: true, + internal: false, + void, + }); + } + + pub(crate) fn add_prototype( + &mut self, + ctx: Context, + name: String, + result: Option, + meta: Span, + ) { + let void = result.is_none(); + + // Check if the passed arguments require any special variations + let mut variations = builtin_required_variations( + ctx.parameters + .iter() + .map(|&arg| &ctx.module.types[arg].inner), + ); + + // Initiate the declaration if it wasn't previously initialized and inject builtins + let declaration = self.lookup_function.entry(name.clone()).or_insert_with(|| { + variations |= BuiltinVariations::STANDARD; + Default::default() + }); + inject_builtin(declaration, ctx.module, &name, variations); + + let Context { + arguments, + parameters, + parameters_info, + module, + .. + } = ctx; + + let function = Function { + name: Some(name), + arguments, + result, + ..Default::default() + }; + + 'outer: for decl in declaration.overloads.iter() { + if parameters.len() != decl.parameters.len() { + continue; + } + + for (new_parameter, old_parameter) in parameters.iter().zip(decl.parameters.iter()) { + let new_inner = &module.types[*new_parameter].inner; + let old_inner = &module.types[*old_parameter].inner; + + if new_inner != old_inner { + continue 'outer; + } + } + + return self.errors.push(Error { + kind: ErrorKind::SemanticError("Prototype already defined".into()), + meta, + }); + } + + let handle = module.functions.append(function, meta); + declaration.overloads.push(Overload { + parameters, + parameters_info, + kind: FunctionKind::Call(handle), + defined: false, + internal: false, + void, + }); + } + + /// Create a Naga [`EntryPoint`] that calls the GLSL `main` function. + /// + /// We compile the GLSL `main` function as an ordinary Naga [`Function`]. + /// This function synthesizes a Naga [`EntryPoint`] to call that. + /// + /// Each GLSL input and output variable (including builtins) becomes a Naga + /// [`GlobalVariable`]s in the [`Private`] address space, which `main` can + /// access in the usual way. + /// + /// The `EntryPoint` we synthesize here has an argument for each GLSL input + /// variable, and returns a struct with a member for each GLSL output + /// variable. The entry point contains code to: + /// + /// - copy its arguments into the Naga globals representing the GLSL input + /// variables, + /// + /// - call the Naga `Function` representing the GLSL `main` function, and then + /// + /// - build its return value from whatever values the GLSL `main` left in + /// the Naga globals representing GLSL `output` variables. + /// + /// Upon entry, [`ctx.body`] should contain code, accumulated by prior calls + /// to [`ParsingContext::parse_external_declaration`][pxd], to initialize + /// private global variables as needed. This code gets spliced into the + /// entry point before the call to `main`. + /// + /// [`GlobalVariable`]: crate::GlobalVariable + /// [`Private`]: crate::AddressSpace::Private + /// [`ctx.body`]: Context::body + /// [pxd]: super::ParsingContext::parse_external_declaration + pub(crate) fn add_entry_point( + &mut self, + function: Handle, + mut ctx: Context, + ) -> Result<()> { + let mut arguments = Vec::new(); + + let body = Block::with_capacity( + // global init body + ctx.body.len() + + // prologue and epilogue + self.entry_args.len() * 2 + // Call, Emit for composing struct and return + + 3, + ); + + let global_init_body = std::mem::replace(&mut ctx.body, body); + + for arg in self.entry_args.iter() { + if arg.storage != StorageQualifier::Input { + continue; + } + + let pointer = ctx + .expressions + .append(Expression::GlobalVariable(arg.handle), Default::default()); + + let ty = ctx.module.global_variables[arg.handle].ty; + + ctx.arg_type_walker( + arg.name.clone(), + arg.binding.clone(), + pointer, + ty, + &mut |ctx, name, pointer, ty, binding| { + let idx = arguments.len() as u32; + + arguments.push(FunctionArgument { + name, + ty, + binding: Some(binding), + }); + + let value = ctx + .expressions + .append(Expression::FunctionArgument(idx), Default::default()); + ctx.body + .push(Statement::Store { pointer, value }, Default::default()); + }, + )? + } + + ctx.body.extend_block(global_init_body); + + ctx.body.push( + Statement::Call { + function, + arguments: Vec::new(), + result: None, + }, + Default::default(), + ); + + let mut span = 0; + let mut members = Vec::new(); + let mut components = Vec::new(); + + for arg in self.entry_args.iter() { + if arg.storage != StorageQualifier::Output { + continue; + } + + let pointer = ctx + .expressions + .append(Expression::GlobalVariable(arg.handle), Default::default()); + + let ty = ctx.module.global_variables[arg.handle].ty; + + ctx.arg_type_walker( + arg.name.clone(), + arg.binding.clone(), + pointer, + ty, + &mut |ctx, name, pointer, ty, binding| { + members.push(StructMember { + name, + ty, + binding: Some(binding), + offset: span, + }); + + span += ctx.module.types[ty].inner.size(ctx.module.to_ctx()); + + let len = ctx.expressions.len(); + let load = ctx + .expressions + .append(Expression::Load { pointer }, Default::default()); + ctx.body.push( + Statement::Emit(ctx.expressions.range_from(len)), + Default::default(), + ); + components.push(load) + }, + )? + } + + let (ty, value) = if !components.is_empty() { + let ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Struct { members, span }, + }, + Default::default(), + ); + + let len = ctx.expressions.len(); + let res = ctx + .expressions + .append(Expression::Compose { ty, components }, Default::default()); + ctx.body.push( + Statement::Emit(ctx.expressions.range_from(len)), + Default::default(), + ); + + (Some(ty), Some(res)) + } else { + (None, None) + }; + + ctx.body + .push(Statement::Return { value }, Default::default()); + + let Context { + body, expressions, .. + } = ctx; + + ctx.module.entry_points.push(EntryPoint { + name: "main".to_string(), + stage: self.meta.stage, + early_depth_test: Some(crate::EarlyDepthTest { conservative: None }) + .filter(|_| self.meta.early_fragment_tests), + workgroup_size: self.meta.workgroup_size, + function: Function { + arguments, + expressions, + body, + result: ty.map(|ty| FunctionResult { ty, binding: None }), + ..Default::default() + }, + }); + + Ok(()) + } +} + +impl Context<'_> { + /// Helper function for building the input/output interface of the entry point + /// + /// Calls `f` with the data of the entry point argument, flattening composite types + /// recursively + /// + /// The passed arguments to the callback are: + /// - The ctx + /// - The name + /// - The pointer expression to the global storage + /// - The handle to the type of the entry point argument + /// - The binding of the entry point argument + fn arg_type_walker( + &mut self, + name: Option, + binding: crate::Binding, + pointer: Handle, + ty: Handle, + f: &mut impl FnMut( + &mut Context, + Option, + Handle, + Handle, + crate::Binding, + ), + ) -> Result<()> { + match self.module.types[ty].inner { + // TODO: Better error reporting + // right now we just don't walk the array if the size isn't known at + // compile time and let validation catch it + TypeInner::Array { + base, + size: crate::ArraySize::Constant(size), + .. + } => { + let mut location = match binding { + crate::Binding::Location { location, .. } => location, + crate::Binding::BuiltIn(_) => return Ok(()), + }; + + let interpolation = + self.module.types[base] + .inner + .scalar_kind() + .map(|kind| match kind { + ScalarKind::Float => crate::Interpolation::Perspective, + _ => crate::Interpolation::Flat, + }); + + for index in 0..size.get() { + let member_pointer = self.add_expression( + Expression::AccessIndex { + base: pointer, + index, + }, + crate::Span::default(), + )?; + + let binding = crate::Binding::Location { + location, + interpolation, + sampling: None, + second_blend_source: false, + }; + location += 1; + + self.arg_type_walker(name.clone(), binding, member_pointer, base, f)? + } + } + TypeInner::Struct { ref members, .. } => { + let mut location = match binding { + crate::Binding::Location { location, .. } => location, + crate::Binding::BuiltIn(_) => return Ok(()), + }; + + for (i, member) in members.clone().into_iter().enumerate() { + let member_pointer = self.add_expression( + Expression::AccessIndex { + base: pointer, + index: i as u32, + }, + crate::Span::default(), + )?; + + let binding = match member.binding { + Some(binding) => binding, + None => { + let interpolation = self.module.types[member.ty] + .inner + .scalar_kind() + .map(|kind| match kind { + ScalarKind::Float => crate::Interpolation::Perspective, + _ => crate::Interpolation::Flat, + }); + let binding = crate::Binding::Location { + location, + interpolation, + sampling: None, + second_blend_source: false, + }; + location += 1; + binding + } + }; + + self.arg_type_walker(member.name, binding, member_pointer, member.ty, f)? + } + } + _ => f(self, name, pointer, ty, binding), + } + + Ok(()) + } +} + +/// Helper enum containing the type of conversion need for a call +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +enum Conversion { + /// No conversion needed + Exact, + /// Float to double conversion needed + FloatToDouble, + /// Int or uint to float conversion needed + IntToFloat, + /// Int or uint to double conversion needed + IntToDouble, + /// Other type of conversion needed + Other, + /// No conversion was yet registered + None, +} + +/// Helper function, returns the type of conversion from `source` to `target`, if a +/// conversion is not possible returns None. +fn conversion(target: &TypeInner, source: &TypeInner) -> Option { + use ScalarKind::*; + + // Gather the `ScalarKind` and scalar width from both the target and the source + let (target_scalar, source_scalar) = match (target, source) { + // Conversions between scalars are allowed + (&TypeInner::Scalar(tgt_scalar), &TypeInner::Scalar(src_scalar)) => { + (tgt_scalar, src_scalar) + } + // Conversions between vectors of the same size are allowed + ( + &TypeInner::Vector { + size: tgt_size, + scalar: tgt_scalar, + }, + &TypeInner::Vector { + size: src_size, + scalar: src_scalar, + }, + ) if tgt_size == src_size => (tgt_scalar, src_scalar), + // Conversions between matrices of the same size are allowed + ( + &TypeInner::Matrix { + rows: tgt_rows, + columns: tgt_cols, + scalar: tgt_scalar, + }, + &TypeInner::Matrix { + rows: src_rows, + columns: src_cols, + scalar: src_scalar, + }, + ) if tgt_cols == src_cols && tgt_rows == src_rows => (tgt_scalar, src_scalar), + _ => return None, + }; + + // Check if source can be converted into target, if this is the case then the type + // power of target must be higher than that of source + let target_power = type_power(target_scalar); + let source_power = type_power(source_scalar); + if target_power < source_power { + return None; + } + + Some(match (target_scalar, source_scalar) { + // A conversion from a float to a double is special + (Scalar::F64, Scalar::F32) => Conversion::FloatToDouble, + // A conversion from an integer to a float is special + ( + Scalar::F32, + Scalar { + kind: Sint | Uint, + width: _, + }, + ) => Conversion::IntToFloat, + // A conversion from an integer to a double is special + ( + Scalar::F64, + Scalar { + kind: Sint | Uint, + width: _, + }, + ) => Conversion::IntToDouble, + _ => Conversion::Other, + }) +} + +/// Helper method returning all the non standard builtin variations needed +/// to process the function call with the passed arguments +fn builtin_required_variations<'a>(args: impl Iterator) -> BuiltinVariations { + let mut variations = BuiltinVariations::empty(); + + for ty in args { + match *ty { + TypeInner::ValuePointer { scalar, .. } + | TypeInner::Scalar(scalar) + | TypeInner::Vector { scalar, .. } + | TypeInner::Matrix { scalar, .. } => { + if scalar == Scalar::F64 { + variations |= BuiltinVariations::DOUBLE + } + } + TypeInner::Image { + dim, + arrayed, + class, + } => { + if dim == crate::ImageDimension::Cube && arrayed { + variations |= BuiltinVariations::CUBE_TEXTURES_ARRAY + } + + if dim == crate::ImageDimension::D2 && arrayed && class.is_multisampled() { + variations |= BuiltinVariations::D2_MULTI_TEXTURES_ARRAY + } + } + _ => {} + } + } + + variations +} diff --git a/naga/src/front/glsl/lex.rs b/naga/src/front/glsl/lex.rs new file mode 100644 index 0000000000..1b59a9bf3e --- /dev/null +++ b/naga/src/front/glsl/lex.rs @@ -0,0 +1,301 @@ +use super::{ + ast::Precision, + token::{Directive, DirectiveKind, Token, TokenValue}, + types::parse_type, +}; +use crate::{FastHashMap, Span, StorageAccess}; +use pp_rs::{ + pp::Preprocessor, + token::{PreprocessorError, Punct, TokenValue as PPTokenValue}, +}; + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub struct LexerResult { + pub kind: LexerResultKind, + pub meta: Span, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub enum LexerResultKind { + Token(Token), + Directive(Directive), + Error(PreprocessorError), +} + +pub struct Lexer<'a> { + pp: Preprocessor<'a>, +} + +impl<'a> Lexer<'a> { + pub fn new(input: &'a str, defines: &'a FastHashMap) -> Self { + let mut pp = Preprocessor::new(input); + for (define, value) in defines { + pp.add_define(define, value).unwrap(); //TODO: handle error + } + Lexer { pp } + } +} + +impl<'a> Iterator for Lexer<'a> { + type Item = LexerResult; + fn next(&mut self) -> Option { + let pp_token = match self.pp.next()? { + Ok(t) => t, + Err((err, loc)) => { + return Some(LexerResult { + kind: LexerResultKind::Error(err), + meta: loc.into(), + }); + } + }; + + let meta = pp_token.location.into(); + let value = match pp_token.value { + PPTokenValue::Extension(extension) => { + return Some(LexerResult { + kind: LexerResultKind::Directive(Directive { + kind: DirectiveKind::Extension, + tokens: extension.tokens, + }), + meta, + }) + } + PPTokenValue::Float(float) => TokenValue::FloatConstant(float), + PPTokenValue::Ident(ident) => { + match ident.as_str() { + // Qualifiers + "layout" => TokenValue::Layout, + "in" => TokenValue::In, + "out" => TokenValue::Out, + "uniform" => TokenValue::Uniform, + "buffer" => TokenValue::Buffer, + "shared" => TokenValue::Shared, + "invariant" => TokenValue::Invariant, + "flat" => TokenValue::Interpolation(crate::Interpolation::Flat), + "noperspective" => TokenValue::Interpolation(crate::Interpolation::Linear), + "smooth" => TokenValue::Interpolation(crate::Interpolation::Perspective), + "centroid" => TokenValue::Sampling(crate::Sampling::Centroid), + "sample" => TokenValue::Sampling(crate::Sampling::Sample), + "const" => TokenValue::Const, + "inout" => TokenValue::InOut, + "precision" => TokenValue::Precision, + "highp" => TokenValue::PrecisionQualifier(Precision::High), + "mediump" => TokenValue::PrecisionQualifier(Precision::Medium), + "lowp" => TokenValue::PrecisionQualifier(Precision::Low), + "restrict" => TokenValue::Restrict, + "readonly" => TokenValue::MemoryQualifier(StorageAccess::LOAD), + "writeonly" => TokenValue::MemoryQualifier(StorageAccess::STORE), + // values + "true" => TokenValue::BoolConstant(true), + "false" => TokenValue::BoolConstant(false), + // jump statements + "continue" => TokenValue::Continue, + "break" => TokenValue::Break, + "return" => TokenValue::Return, + "discard" => TokenValue::Discard, + // selection statements + "if" => TokenValue::If, + "else" => TokenValue::Else, + "switch" => TokenValue::Switch, + "case" => TokenValue::Case, + "default" => TokenValue::Default, + // iteration statements + "while" => TokenValue::While, + "do" => TokenValue::Do, + "for" => TokenValue::For, + // types + "void" => TokenValue::Void, + "struct" => TokenValue::Struct, + word => match parse_type(word) { + Some(t) => TokenValue::TypeName(t), + None => TokenValue::Identifier(String::from(word)), + }, + } + } + PPTokenValue::Integer(integer) => TokenValue::IntConstant(integer), + PPTokenValue::Punct(punct) => match punct { + // Compound assignments + Punct::AddAssign => TokenValue::AddAssign, + Punct::SubAssign => TokenValue::SubAssign, + Punct::MulAssign => TokenValue::MulAssign, + Punct::DivAssign => TokenValue::DivAssign, + Punct::ModAssign => TokenValue::ModAssign, + Punct::LeftShiftAssign => TokenValue::LeftShiftAssign, + Punct::RightShiftAssign => TokenValue::RightShiftAssign, + Punct::AndAssign => TokenValue::AndAssign, + Punct::XorAssign => TokenValue::XorAssign, + Punct::OrAssign => TokenValue::OrAssign, + + // Two character punctuation + Punct::Increment => TokenValue::Increment, + Punct::Decrement => TokenValue::Decrement, + Punct::LogicalAnd => TokenValue::LogicalAnd, + Punct::LogicalOr => TokenValue::LogicalOr, + Punct::LogicalXor => TokenValue::LogicalXor, + Punct::LessEqual => TokenValue::LessEqual, + Punct::GreaterEqual => TokenValue::GreaterEqual, + Punct::EqualEqual => TokenValue::Equal, + Punct::NotEqual => TokenValue::NotEqual, + Punct::LeftShift => TokenValue::LeftShift, + Punct::RightShift => TokenValue::RightShift, + + // Parenthesis or similar + Punct::LeftBrace => TokenValue::LeftBrace, + Punct::RightBrace => TokenValue::RightBrace, + Punct::LeftParen => TokenValue::LeftParen, + Punct::RightParen => TokenValue::RightParen, + Punct::LeftBracket => TokenValue::LeftBracket, + Punct::RightBracket => TokenValue::RightBracket, + + // Other one character punctuation + Punct::LeftAngle => TokenValue::LeftAngle, + Punct::RightAngle => TokenValue::RightAngle, + Punct::Semicolon => TokenValue::Semicolon, + Punct::Comma => TokenValue::Comma, + Punct::Colon => TokenValue::Colon, + Punct::Dot => TokenValue::Dot, + Punct::Equal => TokenValue::Assign, + Punct::Bang => TokenValue::Bang, + Punct::Minus => TokenValue::Dash, + Punct::Tilde => TokenValue::Tilde, + Punct::Plus => TokenValue::Plus, + Punct::Star => TokenValue::Star, + Punct::Slash => TokenValue::Slash, + Punct::Percent => TokenValue::Percent, + Punct::Pipe => TokenValue::VerticalBar, + Punct::Caret => TokenValue::Caret, + Punct::Ampersand => TokenValue::Ampersand, + Punct::Question => TokenValue::Question, + }, + PPTokenValue::Pragma(pragma) => { + return Some(LexerResult { + kind: LexerResultKind::Directive(Directive { + kind: DirectiveKind::Pragma, + tokens: pragma.tokens, + }), + meta, + }) + } + PPTokenValue::Version(version) => { + return Some(LexerResult { + kind: LexerResultKind::Directive(Directive { + kind: DirectiveKind::Version { + is_first_directive: version.is_first_directive, + }, + tokens: version.tokens, + }), + meta, + }) + } + }; + + Some(LexerResult { + kind: LexerResultKind::Token(Token { value, meta }), + meta, + }) + } +} + +#[cfg(test)] +mod tests { + use pp_rs::token::{Integer, Location, Token as PPToken, TokenValue as PPTokenValue}; + + use super::{ + super::token::{Directive, DirectiveKind, Token, TokenValue}, + Lexer, LexerResult, LexerResultKind, + }; + use crate::Span; + + #[test] + fn lex_tokens() { + let defines = crate::FastHashMap::default(); + + // line comments + let mut lex = Lexer::new("#version 450\nvoid main () {}", &defines); + let mut location = Location::default(); + location.start = 9; + location.end = 12; + assert_eq!( + lex.next().unwrap(), + LexerResult { + kind: LexerResultKind::Directive(Directive { + kind: DirectiveKind::Version { + is_first_directive: true + }, + tokens: vec![PPToken { + value: PPTokenValue::Integer(Integer { + signed: true, + value: 450, + width: 32 + }), + location + }] + }), + meta: Span::new(1, 8) + } + ); + assert_eq!( + lex.next().unwrap(), + LexerResult { + kind: LexerResultKind::Token(Token { + value: TokenValue::Void, + meta: Span::new(13, 17) + }), + meta: Span::new(13, 17) + } + ); + assert_eq!( + lex.next().unwrap(), + LexerResult { + kind: LexerResultKind::Token(Token { + value: TokenValue::Identifier("main".into()), + meta: Span::new(18, 22) + }), + meta: Span::new(18, 22) + } + ); + assert_eq!( + lex.next().unwrap(), + LexerResult { + kind: LexerResultKind::Token(Token { + value: TokenValue::LeftParen, + meta: Span::new(23, 24) + }), + meta: Span::new(23, 24) + } + ); + assert_eq!( + lex.next().unwrap(), + LexerResult { + kind: LexerResultKind::Token(Token { + value: TokenValue::RightParen, + meta: Span::new(24, 25) + }), + meta: Span::new(24, 25) + } + ); + assert_eq!( + lex.next().unwrap(), + LexerResult { + kind: LexerResultKind::Token(Token { + value: TokenValue::LeftBrace, + meta: Span::new(26, 27) + }), + meta: Span::new(26, 27) + } + ); + assert_eq!( + lex.next().unwrap(), + LexerResult { + kind: LexerResultKind::Token(Token { + value: TokenValue::RightBrace, + meta: Span::new(27, 28) + }), + meta: Span::new(27, 28) + } + ); + assert_eq!(lex.next(), None); + } +} diff --git a/naga/src/front/glsl/mod.rs b/naga/src/front/glsl/mod.rs new file mode 100644 index 0000000000..49624a9433 --- /dev/null +++ b/naga/src/front/glsl/mod.rs @@ -0,0 +1,232 @@ +/*! +Frontend for [GLSL][glsl] (OpenGL Shading Language). + +To begin, take a look at the documentation for the [`Frontend`]. + +# Supported versions +## Vulkan +- 440 (partial) +- 450 +- 460 + +[glsl]: https://www.khronos.org/registry/OpenGL/index_gl.php +*/ + +pub use ast::{Precision, Profile}; +pub use error::{Error, ErrorKind, ExpectedToken}; +pub use token::TokenValue; + +use crate::{proc::Layouter, FastHashMap, FastHashSet, Handle, Module, ShaderStage, Span, Type}; +use ast::{EntryArg, FunctionDeclaration, GlobalLookup}; +use parser::ParsingContext; + +mod ast; +mod builtins; +mod context; +mod error; +mod functions; +mod lex; +mod offset; +mod parser; +#[cfg(test)] +mod parser_tests; +mod token; +mod types; +mod variables; + +type Result = std::result::Result; + +/// Per-shader options passed to [`parse`](Frontend::parse). +/// +/// The [`From`] trait is implemented for [`ShaderStage`] to provide a quick way +/// to create an `Options` instance. +/// +/// ```rust +/// # use naga::ShaderStage; +/// # use naga::front::glsl::Options; +/// Options::from(ShaderStage::Vertex); +/// ``` +#[derive(Debug)] +pub struct Options { + /// The shader stage in the pipeline. + pub stage: ShaderStage, + /// Preprocesor definitions to be used, akin to having + /// ```glsl + /// #define key value + /// ``` + /// for each key value pair in the map. + pub defines: FastHashMap, +} + +impl From for Options { + fn from(stage: ShaderStage) -> Self { + Options { + stage, + defines: FastHashMap::default(), + } + } +} + +/// Additional information about the GLSL shader. +/// +/// Stores additional information about the GLSL shader which might not be +/// stored in the shader [`Module`]. +#[derive(Debug)] +pub struct ShaderMetadata { + /// The GLSL version specified in the shader through the use of the + /// `#version` preprocessor directive. + pub version: u16, + /// The GLSL profile specified in the shader through the use of the + /// `#version` preprocessor directive. + pub profile: Profile, + /// The shader stage in the pipeline, passed to the [`parse`](Frontend::parse) + /// method via the [`Options`] struct. + pub stage: ShaderStage, + + /// The workgroup size for compute shaders, defaults to `[1; 3]` for + /// compute shaders and `[0; 3]` for non compute shaders. + pub workgroup_size: [u32; 3], + /// Whether or not early fragment tests where requested by the shader. + /// Defaults to `false`. + pub early_fragment_tests: bool, + + /// The shader can request extensions via the + /// `#extension` preprocessor directive, in the directive a behavior + /// parameter is used to control whether the extension should be disabled, + /// warn on usage, enabled if possible or required. + /// + /// This field only stores extensions which were required or requested to + /// be enabled if possible and they are supported. + pub extensions: FastHashSet, +} + +impl ShaderMetadata { + fn reset(&mut self, stage: ShaderStage) { + self.version = 0; + self.profile = Profile::Core; + self.stage = stage; + self.workgroup_size = [u32::from(stage == ShaderStage::Compute); 3]; + self.early_fragment_tests = false; + self.extensions.clear(); + } +} + +impl Default for ShaderMetadata { + fn default() -> Self { + ShaderMetadata { + version: 0, + profile: Profile::Core, + stage: ShaderStage::Vertex, + workgroup_size: [0; 3], + early_fragment_tests: false, + extensions: FastHashSet::default(), + } + } +} + +/// The `Frontend` is the central structure of the GLSL frontend. +/// +/// To instantiate a new `Frontend` the [`Default`] trait is used, so a +/// call to the associated function [`Frontend::default`](Frontend::default) will +/// return a new `Frontend` instance. +/// +/// To parse a shader simply call the [`parse`](Frontend::parse) method with a +/// [`Options`] struct and a [`&str`](str) holding the glsl code. +/// +/// The `Frontend` also provides the [`metadata`](Frontend::metadata) to get some +/// further information about the previously parsed shader, like version and +/// extensions used (see the documentation for +/// [`ShaderMetadata`] to see all the returned information) +/// +/// # Example usage +/// ```rust +/// use naga::ShaderStage; +/// use naga::front::glsl::{Frontend, Options}; +/// +/// let glsl = r#" +/// #version 450 core +/// +/// void main() {} +/// "#; +/// +/// let mut frontend = Frontend::default(); +/// let options = Options::from(ShaderStage::Vertex); +/// frontend.parse(&options, glsl); +/// ``` +/// +/// # Reusability +/// +/// If there's a need to parse more than one shader reusing the same `Frontend` +/// instance may be beneficial since internal allocations will be reused. +/// +/// Calling the [`parse`](Frontend::parse) method multiple times will reset the +/// `Frontend` so no extra care is needed when reusing. +#[derive(Debug, Default)] +pub struct Frontend { + meta: ShaderMetadata, + + lookup_function: FastHashMap, + lookup_type: FastHashMap>, + + global_variables: Vec<(String, GlobalLookup)>, + + entry_args: Vec, + + layouter: Layouter, + + errors: Vec, +} + +impl Frontend { + fn reset(&mut self, stage: ShaderStage) { + self.meta.reset(stage); + + self.lookup_function.clear(); + self.lookup_type.clear(); + self.global_variables.clear(); + self.entry_args.clear(); + self.layouter.clear(); + } + + /// Parses a shader either outputting a shader [`Module`] or a list of + /// [`Error`]s. + /// + /// Multiple calls using the same `Frontend` and different shaders are supported. + pub fn parse( + &mut self, + options: &Options, + source: &str, + ) -> std::result::Result> { + self.reset(options.stage); + + let lexer = lex::Lexer::new(source, &options.defines); + let mut ctx = ParsingContext::new(lexer); + + match ctx.parse(self) { + Ok(module) => { + if self.errors.is_empty() { + Ok(module) + } else { + Err(std::mem::take(&mut self.errors)) + } + } + Err(e) => { + self.errors.push(e); + Err(std::mem::take(&mut self.errors)) + } + } + } + + /// Returns additional information about the parsed shader which might not + /// be stored in the [`Module`], see the documentation for + /// [`ShaderMetadata`] for more information about the returned data. + /// + /// # Notes + /// + /// Following an unsuccessful parsing the state of the returned information + /// is undefined, it might contain only partial information about the + /// current shader, the previous shader or both. + pub const fn metadata(&self) -> &ShaderMetadata { + &self.meta + } +} diff --git a/naga/src/front/glsl/offset.rs b/naga/src/front/glsl/offset.rs new file mode 100644 index 0000000000..c88c46598d --- /dev/null +++ b/naga/src/front/glsl/offset.rs @@ -0,0 +1,173 @@ +/*! +Module responsible for calculating the offset and span for types. + +There exists two types of layouts std140 and std430 (there's technically +two more layouts, shared and packed. Shared is not supported by spirv. Packed is +implementation dependent and for now it's just implemented as an alias to +std140). + +The OpenGl spec (the layout rules are defined by the OpenGl spec in section +7.6.2.2 as opposed to the GLSL spec) uses the term basic machine units which are +equivalent to bytes. +*/ + +use super::{ + ast::StructLayout, + error::{Error, ErrorKind}, + Span, +}; +use crate::{proc::Alignment, Handle, Scalar, Type, TypeInner, UniqueArena}; + +/// Struct with information needed for defining a struct member. +/// +/// Returned by [`calculate_offset`]. +#[derive(Debug)] +pub struct TypeAlignSpan { + /// The handle to the type, this might be the same handle passed to + /// [`calculate_offset`] or a new such a new array type with a different + /// stride set. + pub ty: Handle, + /// The alignment required by the type. + pub align: Alignment, + /// The size of the type. + pub span: u32, +} + +/// Returns the type, alignment and span of a struct member according to a [`StructLayout`]. +/// +/// The functions returns a [`TypeAlignSpan`] which has a `ty` member this +/// should be used as the struct member type because for example arrays may have +/// to change the stride and as such need to have a different type. +pub fn calculate_offset( + mut ty: Handle, + meta: Span, + layout: StructLayout, + types: &mut UniqueArena, + errors: &mut Vec, +) -> TypeAlignSpan { + // When using the std430 storage layout, shader storage blocks will be laid out in buffer storage + // identically to uniform and shader storage blocks using the std140 layout, except + // that the base alignment and stride of arrays of scalars and vectors in rule 4 and of + // structures in rule 9 are not rounded up a multiple of the base alignment of a vec4. + + let (align, span) = match types[ty].inner { + // 1. If the member is a scalar consuming N basic machine units, + // the base alignment is N. + TypeInner::Scalar(Scalar { width, .. }) => (Alignment::from_width(width), width as u32), + // 2. If the member is a two- or four-component vector with components + // consuming N basic machine units, the base alignment is 2N or 4N, respectively. + // 3. If the member is a three-component vector with components consuming N + // basic machine units, the base alignment is 4N. + TypeInner::Vector { + size, + scalar: Scalar { width, .. }, + } => ( + Alignment::from(size) * Alignment::from_width(width), + size as u32 * width as u32, + ), + // 4. If the member is an array of scalars or vectors, the base alignment and array + // stride are set to match the base alignment of a single array element, according + // to rules (1), (2), and (3), and rounded up to the base alignment of a vec4. + // TODO: Matrices array + TypeInner::Array { base, size, .. } => { + let info = calculate_offset(base, meta, layout, types, errors); + + let name = types[ty].name.clone(); + + // See comment at the beginning of the function + let (align, stride) = if StructLayout::Std430 == layout { + (info.align, info.align.round_up(info.span)) + } else { + let align = info.align.max(Alignment::MIN_UNIFORM); + (align, align.round_up(info.span)) + }; + + let span = match size { + crate::ArraySize::Constant(size) => size.get() * stride, + crate::ArraySize::Dynamic => stride, + }; + + let ty_span = types.get_span(ty); + ty = types.insert( + Type { + name, + inner: TypeInner::Array { + base: info.ty, + size, + stride, + }, + }, + ty_span, + ); + + (align, span) + } + // 5. If the member is a column-major matrix with C columns and R rows, the + // matrix is stored identically to an array of C column vectors with R + // components each, according to rule (4) + // TODO: Row major matrices + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + let mut align = Alignment::from(rows) * Alignment::from_width(scalar.width); + + // See comment at the beginning of the function + if StructLayout::Std430 != layout { + align = align.max(Alignment::MIN_UNIFORM); + } + + // See comment on the error kind + if StructLayout::Std140 == layout && rows == crate::VectorSize::Bi { + errors.push(Error { + kind: ErrorKind::UnsupportedMatrixTypeInStd140, + meta, + }); + } + + (align, align * columns as u32) + } + TypeInner::Struct { ref members, .. } => { + let mut span = 0; + let mut align = Alignment::ONE; + let mut members = members.clone(); + let name = types[ty].name.clone(); + + for member in members.iter_mut() { + let info = calculate_offset(member.ty, meta, layout, types, errors); + + let member_alignment = info.align; + span = member_alignment.round_up(span); + align = member_alignment.max(align); + + member.ty = info.ty; + member.offset = span; + + span += info.span; + } + + span = align.round_up(span); + + let ty_span = types.get_span(ty); + ty = types.insert( + Type { + name, + inner: TypeInner::Struct { members, span }, + }, + ty_span, + ); + + (align, span) + } + _ => { + errors.push(Error { + kind: ErrorKind::SemanticError("Invalid struct member type".into()), + meta, + }); + (Alignment::ONE, 0) + } + }; + + TypeAlignSpan { ty, align, span } +} diff --git a/naga/src/front/glsl/parser.rs b/naga/src/front/glsl/parser.rs new file mode 100644 index 0000000000..851d2e1d79 --- /dev/null +++ b/naga/src/front/glsl/parser.rs @@ -0,0 +1,431 @@ +use super::{ + ast::{FunctionKind, Profile, TypeQualifiers}, + context::{Context, ExprPos}, + error::ExpectedToken, + error::{Error, ErrorKind}, + lex::{Lexer, LexerResultKind}, + token::{Directive, DirectiveKind}, + token::{Token, TokenValue}, + variables::{GlobalOrConstant, VarDeclaration}, + Frontend, Result, +}; +use crate::{arena::Handle, proc::U32EvalError, Expression, Module, Span, Type}; +use pp_rs::token::{PreprocessorError, Token as PPToken, TokenValue as PPTokenValue}; +use std::iter::Peekable; + +mod declarations; +mod expressions; +mod functions; +mod types; + +pub struct ParsingContext<'source> { + lexer: Peekable>, + /// Used to store tokens already consumed by the parser but that need to be backtracked + backtracked_token: Option, + last_meta: Span, +} + +impl<'source> ParsingContext<'source> { + pub fn new(lexer: Lexer<'source>) -> Self { + ParsingContext { + lexer: lexer.peekable(), + backtracked_token: None, + last_meta: Span::default(), + } + } + + /// Helper method for backtracking from a consumed token + /// + /// This method should always be used instead of assigning to `backtracked_token` since + /// it validates that backtracking hasn't occurred more than one time in a row + /// + /// # Panics + /// - If the parser already backtracked without bumping in between + pub fn backtrack(&mut self, token: Token) -> Result<()> { + // This should never happen + if let Some(ref prev_token) = self.backtracked_token { + return Err(Error { + kind: ErrorKind::InternalError("The parser tried to backtrack twice in a row"), + meta: prev_token.meta, + }); + } + + self.backtracked_token = Some(token); + + Ok(()) + } + + pub fn expect_ident(&mut self, frontend: &mut Frontend) -> Result<(String, Span)> { + let token = self.bump(frontend)?; + + match token.value { + TokenValue::Identifier(name) => Ok((name, token.meta)), + _ => Err(Error { + kind: ErrorKind::InvalidToken(token.value, vec![ExpectedToken::Identifier]), + meta: token.meta, + }), + } + } + + pub fn expect(&mut self, frontend: &mut Frontend, value: TokenValue) -> Result { + let token = self.bump(frontend)?; + + if token.value != value { + Err(Error { + kind: ErrorKind::InvalidToken(token.value, vec![value.into()]), + meta: token.meta, + }) + } else { + Ok(token) + } + } + + pub fn next(&mut self, frontend: &mut Frontend) -> Option { + loop { + if let Some(token) = self.backtracked_token.take() { + self.last_meta = token.meta; + break Some(token); + } + + let res = self.lexer.next()?; + + match res.kind { + LexerResultKind::Token(token) => { + self.last_meta = token.meta; + break Some(token); + } + LexerResultKind::Directive(directive) => { + frontend.handle_directive(directive, res.meta) + } + LexerResultKind::Error(error) => frontend.errors.push(Error { + kind: ErrorKind::PreprocessorError(error), + meta: res.meta, + }), + } + } + } + + pub fn bump(&mut self, frontend: &mut Frontend) -> Result { + self.next(frontend).ok_or(Error { + kind: ErrorKind::EndOfFile, + meta: self.last_meta, + }) + } + + /// Returns None on the end of the file rather than an error like other methods + pub fn bump_if(&mut self, frontend: &mut Frontend, value: TokenValue) -> Option { + if self.peek(frontend).filter(|t| t.value == value).is_some() { + self.bump(frontend).ok() + } else { + None + } + } + + pub fn peek(&mut self, frontend: &mut Frontend) -> Option<&Token> { + loop { + if let Some(ref token) = self.backtracked_token { + break Some(token); + } + + match self.lexer.peek()?.kind { + LexerResultKind::Token(_) => { + let res = self.lexer.peek()?; + + match res.kind { + LexerResultKind::Token(ref token) => break Some(token), + _ => unreachable!(), + } + } + LexerResultKind::Error(_) | LexerResultKind::Directive(_) => { + let res = self.lexer.next()?; + + match res.kind { + LexerResultKind::Directive(directive) => { + frontend.handle_directive(directive, res.meta) + } + LexerResultKind::Error(error) => frontend.errors.push(Error { + kind: ErrorKind::PreprocessorError(error), + meta: res.meta, + }), + LexerResultKind::Token(_) => unreachable!(), + } + } + } + } + } + + pub fn expect_peek(&mut self, frontend: &mut Frontend) -> Result<&Token> { + let meta = self.last_meta; + self.peek(frontend).ok_or(Error { + kind: ErrorKind::EndOfFile, + meta, + }) + } + + pub fn parse(&mut self, frontend: &mut Frontend) -> Result { + let mut module = Module::default(); + + // Body and expression arena for global initialization + let mut ctx = Context::new(frontend, &mut module, false)?; + + while self.peek(frontend).is_some() { + self.parse_external_declaration(frontend, &mut ctx)?; + } + + // Add an `EntryPoint` to `parser.module` for `main`, if a + // suitable overload exists. Error out if we can't find one. + if let Some(declaration) = frontend.lookup_function.get("main") { + for decl in declaration.overloads.iter() { + if let FunctionKind::Call(handle) = decl.kind { + if decl.defined && decl.parameters.is_empty() { + frontend.add_entry_point(handle, ctx)?; + return Ok(module); + } + } + } + } + + Err(Error { + kind: ErrorKind::SemanticError("Missing entry point".into()), + meta: Span::default(), + }) + } + + fn parse_uint_constant( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + ) -> Result<(u32, Span)> { + let (const_expr, meta) = self.parse_constant_expression(frontend, ctx.module)?; + + let res = ctx.module.to_ctx().eval_expr_to_u32(const_expr); + + let int = match res { + Ok(value) => Ok(value), + Err(U32EvalError::Negative) => Err(Error { + kind: ErrorKind::SemanticError("int constant overflows".into()), + meta, + }), + Err(U32EvalError::NonConst) => Err(Error { + kind: ErrorKind::SemanticError("Expected a uint constant".into()), + meta, + }), + }?; + + Ok((int, meta)) + } + + fn parse_constant_expression( + &mut self, + frontend: &mut Frontend, + module: &mut Module, + ) -> Result<(Handle, Span)> { + let mut ctx = Context::new(frontend, module, true)?; + + let mut stmt_ctx = ctx.stmt_ctx(); + let expr = self.parse_conditional(frontend, &mut ctx, &mut stmt_ctx, None)?; + let (root, meta) = ctx.lower_expect(stmt_ctx, frontend, expr, ExprPos::Rhs)?; + + Ok((root, meta)) + } +} + +impl Frontend { + fn handle_directive(&mut self, directive: Directive, meta: Span) { + let mut tokens = directive.tokens.into_iter(); + + match directive.kind { + DirectiveKind::Version { is_first_directive } => { + if !is_first_directive { + self.errors.push(Error { + kind: ErrorKind::SemanticError( + "#version must occur first in shader".into(), + ), + meta, + }) + } + + match tokens.next() { + Some(PPToken { + value: PPTokenValue::Integer(int), + location, + }) => match int.value { + 440 | 450 | 460 => self.meta.version = int.value as u16, + _ => self.errors.push(Error { + kind: ErrorKind::InvalidVersion(int.value), + meta: location.into(), + }), + }, + Some(PPToken { value, location }) => self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( + value, + )), + meta: location.into(), + }), + None => self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedNewLine), + meta, + }), + }; + + match tokens.next() { + Some(PPToken { + value: PPTokenValue::Ident(name), + location, + }) => match name.as_str() { + "core" => self.meta.profile = Profile::Core, + _ => self.errors.push(Error { + kind: ErrorKind::InvalidProfile(name), + meta: location.into(), + }), + }, + Some(PPToken { value, location }) => self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( + value, + )), + meta: location.into(), + }), + None => {} + }; + + if let Some(PPToken { value, location }) = tokens.next() { + self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( + value, + )), + meta: location.into(), + }) + } + } + DirectiveKind::Extension => { + // TODO: Proper extension handling + // - Checking for extension support in the compiler + // - Handle behaviors such as warn + // - Handle the all extension + let name = match tokens.next() { + Some(PPToken { + value: PPTokenValue::Ident(name), + .. + }) => Some(name), + Some(PPToken { value, location }) => { + self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( + value, + )), + meta: location.into(), + }); + + None + } + None => { + self.errors.push(Error { + kind: ErrorKind::PreprocessorError( + PreprocessorError::UnexpectedNewLine, + ), + meta, + }); + + None + } + }; + + match tokens.next() { + Some(PPToken { + value: PPTokenValue::Punct(pp_rs::token::Punct::Colon), + .. + }) => {} + Some(PPToken { value, location }) => self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( + value, + )), + meta: location.into(), + }), + None => self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedNewLine), + meta, + }), + }; + + match tokens.next() { + Some(PPToken { + value: PPTokenValue::Ident(behavior), + location, + }) => match behavior.as_str() { + "require" | "enable" | "warn" | "disable" => { + if let Some(name) = name { + self.meta.extensions.insert(name); + } + } + _ => self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( + PPTokenValue::Ident(behavior), + )), + meta: location.into(), + }), + }, + Some(PPToken { value, location }) => self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( + value, + )), + meta: location.into(), + }), + None => self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedNewLine), + meta, + }), + } + + if let Some(PPToken { value, location }) = tokens.next() { + self.errors.push(Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedToken( + value, + )), + meta: location.into(), + }) + } + } + DirectiveKind::Pragma => { + // TODO: handle some common pragmas? + } + } + } +} + +pub struct DeclarationContext<'ctx, 'qualifiers, 'a> { + qualifiers: TypeQualifiers<'qualifiers>, + /// Indicates a global declaration + external: bool, + is_inside_loop: bool, + ctx: &'ctx mut Context<'a>, +} + +impl<'ctx, 'qualifiers, 'a> DeclarationContext<'ctx, 'qualifiers, 'a> { + fn add_var( + &mut self, + frontend: &mut Frontend, + ty: Handle, + name: String, + init: Option>, + meta: Span, + ) -> Result> { + let decl = VarDeclaration { + qualifiers: &mut self.qualifiers, + ty, + name: Some(name), + init, + meta, + }; + + match self.external { + true => { + let global = frontend.add_global_var(self.ctx, decl)?; + let expr = match global { + GlobalOrConstant::Global(handle) => Expression::GlobalVariable(handle), + GlobalOrConstant::Constant(handle) => Expression::Constant(handle), + }; + Ok(self.ctx.add_expression(expr, meta)?) + } + false => frontend.add_local_var(self.ctx, decl), + } + } +} diff --git a/naga/src/front/glsl/parser/declarations.rs b/naga/src/front/glsl/parser/declarations.rs new file mode 100644 index 0000000000..f5e38fb016 --- /dev/null +++ b/naga/src/front/glsl/parser/declarations.rs @@ -0,0 +1,677 @@ +use crate::{ + front::glsl::{ + ast::{ + GlobalLookup, GlobalLookupKind, Precision, QualifierKey, QualifierValue, + StorageQualifier, StructLayout, TypeQualifiers, + }, + context::{Context, ExprPos}, + error::ExpectedToken, + offset, + token::{Token, TokenValue}, + types::scalar_components, + variables::{GlobalOrConstant, VarDeclaration}, + Error, ErrorKind, Frontend, Span, + }, + proc::Alignment, + AddressSpace, Expression, FunctionResult, Handle, Scalar, ScalarKind, Statement, StructMember, + Type, TypeInner, +}; + +use super::{DeclarationContext, ParsingContext, Result}; + +/// Helper method used to retrieve the child type of `ty` at +/// index `i`. +/// +/// # Note +/// +/// Does not check if the index is valid and returns the same type +/// when indexing out-of-bounds a struct or indexing a non indexable +/// type. +fn element_or_member_type( + ty: Handle, + i: usize, + types: &mut crate::UniqueArena, +) -> Handle { + match types[ty].inner { + // The child type of a vector is a scalar of the same kind and width + TypeInner::Vector { scalar, .. } => types.insert( + Type { + name: None, + inner: TypeInner::Scalar(scalar), + }, + Default::default(), + ), + // The child type of a matrix is a vector of floats with the same + // width and the size of the matrix rows. + TypeInner::Matrix { rows, scalar, .. } => types.insert( + Type { + name: None, + inner: TypeInner::Vector { size: rows, scalar }, + }, + Default::default(), + ), + // The child type of an array is the base type of the array + TypeInner::Array { base, .. } => base, + // The child type of a struct at index `i` is the type of it's + // member at that same index. + // + // In case the index is out of bounds the same type is returned + TypeInner::Struct { ref members, .. } => { + members.get(i).map(|member| member.ty).unwrap_or(ty) + } + // The type isn't indexable, the same type is returned + _ => ty, + } +} + +impl<'source> ParsingContext<'source> { + pub fn parse_external_declaration( + &mut self, + frontend: &mut Frontend, + global_ctx: &mut Context, + ) -> Result<()> { + if self + .parse_declaration(frontend, global_ctx, true, false)? + .is_none() + { + let token = self.bump(frontend)?; + match token.value { + TokenValue::Semicolon if frontend.meta.version == 460 => Ok(()), + _ => { + let expected = match frontend.meta.version { + 460 => vec![TokenValue::Semicolon.into(), ExpectedToken::Eof], + _ => vec![ExpectedToken::Eof], + }; + Err(Error { + kind: ErrorKind::InvalidToken(token.value, expected), + meta: token.meta, + }) + } + } + } else { + Ok(()) + } + } + + pub fn parse_initializer( + &mut self, + frontend: &mut Frontend, + ty: Handle, + ctx: &mut Context, + ) -> Result<(Handle, Span)> { + // initializer: + // assignment_expression + // LEFT_BRACE initializer_list RIGHT_BRACE + // LEFT_BRACE initializer_list COMMA RIGHT_BRACE + // + // initializer_list: + // initializer + // initializer_list COMMA initializer + if let Some(Token { mut meta, .. }) = self.bump_if(frontend, TokenValue::LeftBrace) { + // initializer_list + let mut components = Vec::new(); + loop { + // The type expected to be parsed inside the initializer list + let new_ty = element_or_member_type(ty, components.len(), &mut ctx.module.types); + + components.push(self.parse_initializer(frontend, new_ty, ctx)?.0); + + let token = self.bump(frontend)?; + match token.value { + TokenValue::Comma => { + if let Some(Token { meta: end_meta, .. }) = + self.bump_if(frontend, TokenValue::RightBrace) + { + meta.subsume(end_meta); + break; + } + } + TokenValue::RightBrace => { + meta.subsume(token.meta); + break; + } + _ => { + return Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![TokenValue::Comma.into(), TokenValue::RightBrace.into()], + ), + meta: token.meta, + }) + } + } + } + + Ok(( + ctx.add_expression(Expression::Compose { ty, components }, meta)?, + meta, + )) + } else { + let mut stmt = ctx.stmt_ctx(); + let expr = self.parse_assignment(frontend, ctx, &mut stmt)?; + let (mut init, init_meta) = ctx.lower_expect(stmt, frontend, expr, ExprPos::Rhs)?; + + let scalar_components = scalar_components(&ctx.module.types[ty].inner); + if let Some(scalar) = scalar_components { + ctx.implicit_conversion(&mut init, init_meta, scalar)?; + } + + Ok((init, init_meta)) + } + } + + // Note: caller preparsed the type and qualifiers + // Note: caller skips this if the fallthrough token is not expected to be consumed here so this + // produced Error::InvalidToken if it isn't consumed + pub fn parse_init_declarator_list( + &mut self, + frontend: &mut Frontend, + mut ty: Handle, + ctx: &mut DeclarationContext, + ) -> Result<()> { + // init_declarator_list: + // single_declaration + // init_declarator_list COMMA IDENTIFIER + // init_declarator_list COMMA IDENTIFIER array_specifier + // init_declarator_list COMMA IDENTIFIER array_specifier EQUAL initializer + // init_declarator_list COMMA IDENTIFIER EQUAL initializer + // + // single_declaration: + // fully_specified_type + // fully_specified_type IDENTIFIER + // fully_specified_type IDENTIFIER array_specifier + // fully_specified_type IDENTIFIER array_specifier EQUAL initializer + // fully_specified_type IDENTIFIER EQUAL initializer + + // Consume any leading comma, e.g. this is valid: `float, a=1;` + if self + .peek(frontend) + .map_or(false, |t| t.value == TokenValue::Comma) + { + self.next(frontend); + } + + loop { + let token = self.bump(frontend)?; + let name = match token.value { + TokenValue::Semicolon => break, + TokenValue::Identifier(name) => name, + _ => { + return Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![ExpectedToken::Identifier, TokenValue::Semicolon.into()], + ), + meta: token.meta, + }) + } + }; + let mut meta = token.meta; + + // array_specifier + // array_specifier EQUAL initializer + // EQUAL initializer + + // parse an array specifier if it exists + // NOTE: unlike other parse methods this one doesn't expect an array specifier and + // returns Ok(None) rather than an error if there is not one + self.parse_array_specifier(frontend, ctx.ctx, &mut meta, &mut ty)?; + + let is_global_const = + ctx.qualifiers.storage.0 == StorageQualifier::Const && ctx.external; + + let init = self + .bump_if(frontend, TokenValue::Assign) + .map::, _>(|_| { + let prev_const = ctx.ctx.is_const; + ctx.ctx.is_const = is_global_const; + + let (mut expr, init_meta) = self.parse_initializer(frontend, ty, ctx.ctx)?; + + let scalar_components = scalar_components(&ctx.ctx.module.types[ty].inner); + if let Some(scalar) = scalar_components { + ctx.ctx.implicit_conversion(&mut expr, init_meta, scalar)?; + } + + ctx.ctx.is_const = prev_const; + + meta.subsume(init_meta); + + Ok(expr) + }) + .transpose()?; + + let decl_initializer; + let late_initializer; + if is_global_const { + decl_initializer = init; + late_initializer = None; + } else if ctx.external { + decl_initializer = + init.and_then(|expr| ctx.ctx.lift_up_const_expression(expr).ok()); + late_initializer = None; + } else if let Some(init) = init { + if ctx.is_inside_loop || !ctx.ctx.expression_constness.is_const(init) { + decl_initializer = None; + late_initializer = Some(init); + } else { + decl_initializer = Some(init); + late_initializer = None; + } + } else { + decl_initializer = None; + late_initializer = None; + }; + + let pointer = ctx.add_var(frontend, ty, name, decl_initializer, meta)?; + + if let Some(value) = late_initializer { + ctx.ctx.emit_restart(); + ctx.ctx.body.push(Statement::Store { pointer, value }, meta); + } + + let token = self.bump(frontend)?; + match token.value { + TokenValue::Semicolon => break, + TokenValue::Comma => {} + _ => { + return Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![TokenValue::Comma.into(), TokenValue::Semicolon.into()], + ), + meta: token.meta, + }) + } + } + } + + Ok(()) + } + + /// `external` whether or not we are in a global or local context + pub fn parse_declaration( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + external: bool, + is_inside_loop: bool, + ) -> Result> { + //declaration: + // function_prototype SEMICOLON + // + // init_declarator_list SEMICOLON + // PRECISION precision_qualifier type_specifier SEMICOLON + // + // type_qualifier IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE SEMICOLON + // type_qualifier IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE IDENTIFIER SEMICOLON + // type_qualifier IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE IDENTIFIER array_specifier SEMICOLON + // type_qualifier SEMICOLON type_qualifier IDENTIFIER SEMICOLON + // type_qualifier IDENTIFIER identifier_list SEMICOLON + + if self.peek_type_qualifier(frontend) || self.peek_type_name(frontend) { + let mut qualifiers = self.parse_type_qualifiers(frontend, ctx)?; + + if self.peek_type_name(frontend) { + // This branch handles variables and function prototypes and if + // external is true also function definitions + let (ty, mut meta) = self.parse_type(frontend, ctx)?; + + let token = self.bump(frontend)?; + let token_fallthrough = match token.value { + TokenValue::Identifier(name) => match self.expect_peek(frontend)?.value { + TokenValue::LeftParen => { + // This branch handles function definition and prototypes + self.bump(frontend)?; + + let result = ty.map(|ty| FunctionResult { ty, binding: None }); + + let mut context = Context::new(frontend, ctx.module, false)?; + + self.parse_function_args(frontend, &mut context)?; + + let end_meta = self.expect(frontend, TokenValue::RightParen)?.meta; + meta.subsume(end_meta); + + let token = self.bump(frontend)?; + return match token.value { + TokenValue::Semicolon => { + // This branch handles function prototypes + frontend.add_prototype(context, name, result, meta); + + Ok(Some(meta)) + } + TokenValue::LeftBrace if external => { + // This branch handles function definitions + // as you can see by the guard this branch + // only happens if external is also true + + // parse the body + self.parse_compound_statement( + token.meta, + frontend, + &mut context, + &mut None, + false, + )?; + + frontend.add_function(context, name, result, meta); + + Ok(Some(meta)) + } + _ if external => Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![ + TokenValue::LeftBrace.into(), + TokenValue::Semicolon.into(), + ], + ), + meta: token.meta, + }), + _ => Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![TokenValue::Semicolon.into()], + ), + meta: token.meta, + }), + }; + } + // Pass the token to the init_declarator_list parser + _ => Token { + value: TokenValue::Identifier(name), + meta: token.meta, + }, + }, + // Pass the token to the init_declarator_list parser + _ => token, + }; + + // If program execution has reached here then this will be a + // init_declarator_list + // token_fallthrough will have a token that was already bumped + if let Some(ty) = ty { + let mut ctx = DeclarationContext { + qualifiers, + external, + is_inside_loop, + ctx, + }; + + self.backtrack(token_fallthrough)?; + self.parse_init_declarator_list(frontend, ty, &mut ctx)?; + } else { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError("Declaration cannot have void type".into()), + meta, + }) + } + + Ok(Some(meta)) + } else { + // This branch handles struct definitions and modifiers like + // ```glsl + // layout(early_fragment_tests); + // ``` + let token = self.bump(frontend)?; + match token.value { + TokenValue::Identifier(ty_name) => { + if self.bump_if(frontend, TokenValue::LeftBrace).is_some() { + self.parse_block_declaration( + frontend, + ctx, + &mut qualifiers, + ty_name, + token.meta, + ) + .map(Some) + } else { + if qualifiers.invariant.take().is_some() { + frontend.make_variable_invariant(ctx, &ty_name, token.meta)?; + + qualifiers.unused_errors(&mut frontend.errors); + self.expect(frontend, TokenValue::Semicolon)?; + return Ok(Some(qualifiers.span)); + } + + //TODO: declaration + // type_qualifier IDENTIFIER SEMICOLON + // type_qualifier IDENTIFIER identifier_list SEMICOLON + Err(Error { + kind: ErrorKind::NotImplemented("variable qualifier"), + meta: token.meta, + }) + } + } + TokenValue::Semicolon => { + if let Some(value) = + qualifiers.uint_layout_qualifier("local_size_x", &mut frontend.errors) + { + frontend.meta.workgroup_size[0] = value; + } + if let Some(value) = + qualifiers.uint_layout_qualifier("local_size_y", &mut frontend.errors) + { + frontend.meta.workgroup_size[1] = value; + } + if let Some(value) = + qualifiers.uint_layout_qualifier("local_size_z", &mut frontend.errors) + { + frontend.meta.workgroup_size[2] = value; + } + + frontend.meta.early_fragment_tests |= qualifiers + .none_layout_qualifier("early_fragment_tests", &mut frontend.errors); + + qualifiers.unused_errors(&mut frontend.errors); + + Ok(Some(qualifiers.span)) + } + _ => Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![ExpectedToken::Identifier, TokenValue::Semicolon.into()], + ), + meta: token.meta, + }), + } + } + } else { + match self.peek(frontend).map(|t| &t.value) { + Some(&TokenValue::Precision) => { + // PRECISION precision_qualifier type_specifier SEMICOLON + self.bump(frontend)?; + + let token = self.bump(frontend)?; + let _ = match token.value { + TokenValue::PrecisionQualifier(p) => p, + _ => { + return Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![ + TokenValue::PrecisionQualifier(Precision::High).into(), + TokenValue::PrecisionQualifier(Precision::Medium).into(), + TokenValue::PrecisionQualifier(Precision::Low).into(), + ], + ), + meta: token.meta, + }) + } + }; + + let (ty, meta) = self.parse_type_non_void(frontend, ctx)?; + + match ctx.module.types[ty].inner { + TypeInner::Scalar(Scalar { + kind: ScalarKind::Float | ScalarKind::Sint, + .. + }) => {} + _ => frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "Precision statement can only work on floats and ints".into(), + ), + meta, + }), + } + + self.expect(frontend, TokenValue::Semicolon)?; + + Ok(Some(meta)) + } + _ => Ok(None), + } + } + } + + pub fn parse_block_declaration( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + qualifiers: &mut TypeQualifiers, + ty_name: String, + mut meta: Span, + ) -> Result { + let layout = match qualifiers.layout_qualifiers.remove(&QualifierKey::Layout) { + Some((QualifierValue::Layout(l), _)) => l, + None => { + if let StorageQualifier::AddressSpace(AddressSpace::Storage { .. }) = + qualifiers.storage.0 + { + StructLayout::Std430 + } else { + StructLayout::Std140 + } + } + _ => unreachable!(), + }; + + let mut members = Vec::new(); + let span = self.parse_struct_declaration_list(frontend, ctx, &mut members, layout)?; + self.expect(frontend, TokenValue::RightBrace)?; + + let mut ty = ctx.module.types.insert( + Type { + name: Some(ty_name), + inner: TypeInner::Struct { + members: members.clone(), + span, + }, + }, + Default::default(), + ); + + let token = self.bump(frontend)?; + let name = match token.value { + TokenValue::Semicolon => None, + TokenValue::Identifier(name) => { + self.parse_array_specifier(frontend, ctx, &mut meta, &mut ty)?; + + self.expect(frontend, TokenValue::Semicolon)?; + + Some(name) + } + _ => { + return Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![ExpectedToken::Identifier, TokenValue::Semicolon.into()], + ), + meta: token.meta, + }) + } + }; + + let global = frontend.add_global_var( + ctx, + VarDeclaration { + qualifiers, + ty, + name, + init: None, + meta, + }, + )?; + + for (i, k, ty) in members.into_iter().enumerate().filter_map(|(i, m)| { + let ty = m.ty; + m.name.map(|s| (i as u32, s, ty)) + }) { + let lookup = GlobalLookup { + kind: match global { + GlobalOrConstant::Global(handle) => GlobalLookupKind::BlockSelect(handle, i), + GlobalOrConstant::Constant(handle) => GlobalLookupKind::Constant(handle, ty), + }, + entry_arg: None, + mutable: true, + }; + ctx.add_global(&k, lookup)?; + + frontend.global_variables.push((k, lookup)); + } + + Ok(meta) + } + + // TODO: Accept layout arguments + pub fn parse_struct_declaration_list( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + members: &mut Vec, + layout: StructLayout, + ) -> Result { + let mut span = 0; + let mut align = Alignment::ONE; + + loop { + // TODO: type_qualifier + + let (base_ty, mut meta) = self.parse_type_non_void(frontend, ctx)?; + + loop { + let (name, name_meta) = self.expect_ident(frontend)?; + let mut ty = base_ty; + self.parse_array_specifier(frontend, ctx, &mut meta, &mut ty)?; + + meta.subsume(name_meta); + + let info = offset::calculate_offset( + ty, + meta, + layout, + &mut ctx.module.types, + &mut frontend.errors, + ); + + let member_alignment = info.align; + span = member_alignment.round_up(span); + align = member_alignment.max(align); + + members.push(StructMember { + name: Some(name), + ty: info.ty, + binding: None, + offset: span, + }); + + span += info.span; + + if self.bump_if(frontend, TokenValue::Comma).is_none() { + break; + } + } + + self.expect(frontend, TokenValue::Semicolon)?; + + if let TokenValue::RightBrace = self.expect_peek(frontend)?.value { + break; + } + } + + span = align.round_up(span); + + Ok(span) + } +} diff --git a/naga/src/front/glsl/parser/expressions.rs b/naga/src/front/glsl/parser/expressions.rs new file mode 100644 index 0000000000..1b8febce90 --- /dev/null +++ b/naga/src/front/glsl/parser/expressions.rs @@ -0,0 +1,542 @@ +use std::num::NonZeroU32; + +use crate::{ + front::glsl::{ + ast::{FunctionCall, FunctionCallKind, HirExpr, HirExprKind}, + context::{Context, StmtContext}, + error::{ErrorKind, ExpectedToken}, + parser::ParsingContext, + token::{Token, TokenValue}, + Error, Frontend, Result, Span, + }, + ArraySize, BinaryOperator, Handle, Literal, Type, TypeInner, UnaryOperator, +}; + +impl<'source> ParsingContext<'source> { + pub fn parse_primary( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + stmt: &mut StmtContext, + ) -> Result> { + let mut token = self.bump(frontend)?; + + let literal = match token.value { + TokenValue::IntConstant(int) => { + if int.width != 32 { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError("Unsupported non-32bit integer".into()), + meta: token.meta, + }); + } + if int.signed { + Literal::I32(int.value as i32) + } else { + Literal::U32(int.value as u32) + } + } + TokenValue::FloatConstant(float) => { + if float.width != 32 { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError("Unsupported floating-point value (expected single-precision floating-point number)".into()), + meta: token.meta, + }); + } + Literal::F32(float.value) + } + TokenValue::BoolConstant(value) => Literal::Bool(value), + TokenValue::LeftParen => { + let expr = self.parse_expression(frontend, ctx, stmt)?; + let meta = self.expect(frontend, TokenValue::RightParen)?.meta; + + token.meta.subsume(meta); + + return Ok(expr); + } + _ => { + return Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![ + TokenValue::LeftParen.into(), + ExpectedToken::IntLiteral, + ExpectedToken::FloatLiteral, + ExpectedToken::BoolLiteral, + ], + ), + meta: token.meta, + }); + } + }; + + Ok(stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::Literal(literal), + meta: token.meta, + }, + Default::default(), + )) + } + + pub fn parse_function_call_args( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + stmt: &mut StmtContext, + meta: &mut Span, + ) -> Result>> { + let mut args = Vec::new(); + if let Some(token) = self.bump_if(frontend, TokenValue::RightParen) { + meta.subsume(token.meta); + } else { + loop { + args.push(self.parse_assignment(frontend, ctx, stmt)?); + + let token = self.bump(frontend)?; + match token.value { + TokenValue::Comma => {} + TokenValue::RightParen => { + meta.subsume(token.meta); + break; + } + _ => { + return Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![TokenValue::Comma.into(), TokenValue::RightParen.into()], + ), + meta: token.meta, + }); + } + } + } + } + + Ok(args) + } + + pub fn parse_postfix( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + stmt: &mut StmtContext, + ) -> Result> { + let mut base = if self.peek_type_name(frontend) { + let (mut handle, mut meta) = self.parse_type_non_void(frontend, ctx)?; + + self.expect(frontend, TokenValue::LeftParen)?; + let args = self.parse_function_call_args(frontend, ctx, stmt, &mut meta)?; + + if let TypeInner::Array { + size: ArraySize::Dynamic, + stride, + base, + } = ctx.module.types[handle].inner + { + let span = ctx.module.types.get_span(handle); + + let size = u32::try_from(args.len()) + .ok() + .and_then(NonZeroU32::new) + .ok_or(Error { + kind: ErrorKind::SemanticError( + "There must be at least one argument".into(), + ), + meta, + })?; + + handle = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Array { + stride, + base, + size: ArraySize::Constant(size), + }, + }, + span, + ) + } + + stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::Call(FunctionCall { + kind: FunctionCallKind::TypeConstructor(handle), + args, + }), + meta, + }, + Default::default(), + ) + } else if let TokenValue::Identifier(_) = self.expect_peek(frontend)?.value { + let (name, mut meta) = self.expect_ident(frontend)?; + + let expr = if self.bump_if(frontend, TokenValue::LeftParen).is_some() { + let args = self.parse_function_call_args(frontend, ctx, stmt, &mut meta)?; + + let kind = match frontend.lookup_type.get(&name) { + Some(ty) => FunctionCallKind::TypeConstructor(*ty), + None => FunctionCallKind::Function(name), + }; + + HirExpr { + kind: HirExprKind::Call(FunctionCall { kind, args }), + meta, + } + } else { + let var = match frontend.lookup_variable(ctx, &name, meta)? { + Some(var) => var, + None => { + return Err(Error { + kind: ErrorKind::UnknownVariable(name), + meta, + }) + } + }; + + HirExpr { + kind: HirExprKind::Variable(var), + meta, + } + }; + + stmt.hir_exprs.append(expr, Default::default()) + } else { + self.parse_primary(frontend, ctx, stmt)? + }; + + while let TokenValue::LeftBracket + | TokenValue::Dot + | TokenValue::Increment + | TokenValue::Decrement = self.expect_peek(frontend)?.value + { + let Token { value, mut meta } = self.bump(frontend)?; + + match value { + TokenValue::LeftBracket => { + let index = self.parse_expression(frontend, ctx, stmt)?; + let end_meta = self.expect(frontend, TokenValue::RightBracket)?.meta; + + meta.subsume(end_meta); + base = stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::Access { base, index }, + meta, + }, + Default::default(), + ) + } + TokenValue::Dot => { + let (field, end_meta) = self.expect_ident(frontend)?; + + if self.bump_if(frontend, TokenValue::LeftParen).is_some() { + let args = self.parse_function_call_args(frontend, ctx, stmt, &mut meta)?; + + base = stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::Method { + expr: base, + name: field, + args, + }, + meta, + }, + Default::default(), + ); + continue; + } + + meta.subsume(end_meta); + base = stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::Select { base, field }, + meta, + }, + Default::default(), + ) + } + TokenValue::Increment | TokenValue::Decrement => { + base = stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::PrePostfix { + op: match value { + TokenValue::Increment => crate::BinaryOperator::Add, + _ => crate::BinaryOperator::Subtract, + }, + postfix: true, + expr: base, + }, + meta, + }, + Default::default(), + ) + } + _ => unreachable!(), + } + } + + Ok(base) + } + + pub fn parse_unary( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + stmt: &mut StmtContext, + ) -> Result> { + Ok(match self.expect_peek(frontend)?.value { + TokenValue::Plus | TokenValue::Dash | TokenValue::Bang | TokenValue::Tilde => { + let Token { value, mut meta } = self.bump(frontend)?; + + let expr = self.parse_unary(frontend, ctx, stmt)?; + let end_meta = stmt.hir_exprs[expr].meta; + + let kind = match value { + TokenValue::Dash => HirExprKind::Unary { + op: UnaryOperator::Negate, + expr, + }, + TokenValue::Bang => HirExprKind::Unary { + op: UnaryOperator::LogicalNot, + expr, + }, + TokenValue::Tilde => HirExprKind::Unary { + op: UnaryOperator::BitwiseNot, + expr, + }, + _ => return Ok(expr), + }; + + meta.subsume(end_meta); + stmt.hir_exprs + .append(HirExpr { kind, meta }, Default::default()) + } + TokenValue::Increment | TokenValue::Decrement => { + let Token { value, meta } = self.bump(frontend)?; + + let expr = self.parse_unary(frontend, ctx, stmt)?; + + stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::PrePostfix { + op: match value { + TokenValue::Increment => crate::BinaryOperator::Add, + _ => crate::BinaryOperator::Subtract, + }, + postfix: false, + expr, + }, + meta, + }, + Default::default(), + ) + } + _ => self.parse_postfix(frontend, ctx, stmt)?, + }) + } + + pub fn parse_binary( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + stmt: &mut StmtContext, + passthrough: Option>, + min_bp: u8, + ) -> Result> { + let mut left = passthrough + .ok_or(ErrorKind::EndOfFile /* Dummy error */) + .or_else(|_| self.parse_unary(frontend, ctx, stmt))?; + let mut meta = stmt.hir_exprs[left].meta; + + while let Some((l_bp, r_bp)) = binding_power(&self.expect_peek(frontend)?.value) { + if l_bp < min_bp { + break; + } + + let Token { value, .. } = self.bump(frontend)?; + + let right = self.parse_binary(frontend, ctx, stmt, None, r_bp)?; + let end_meta = stmt.hir_exprs[right].meta; + + meta.subsume(end_meta); + left = stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::Binary { + left, + op: match value { + TokenValue::LogicalOr => BinaryOperator::LogicalOr, + TokenValue::LogicalXor => BinaryOperator::NotEqual, + TokenValue::LogicalAnd => BinaryOperator::LogicalAnd, + TokenValue::VerticalBar => BinaryOperator::InclusiveOr, + TokenValue::Caret => BinaryOperator::ExclusiveOr, + TokenValue::Ampersand => BinaryOperator::And, + TokenValue::Equal => BinaryOperator::Equal, + TokenValue::NotEqual => BinaryOperator::NotEqual, + TokenValue::GreaterEqual => BinaryOperator::GreaterEqual, + TokenValue::LessEqual => BinaryOperator::LessEqual, + TokenValue::LeftAngle => BinaryOperator::Less, + TokenValue::RightAngle => BinaryOperator::Greater, + TokenValue::LeftShift => BinaryOperator::ShiftLeft, + TokenValue::RightShift => BinaryOperator::ShiftRight, + TokenValue::Plus => BinaryOperator::Add, + TokenValue::Dash => BinaryOperator::Subtract, + TokenValue::Star => BinaryOperator::Multiply, + TokenValue::Slash => BinaryOperator::Divide, + TokenValue::Percent => BinaryOperator::Modulo, + _ => unreachable!(), + }, + right, + }, + meta, + }, + Default::default(), + ) + } + + Ok(left) + } + + pub fn parse_conditional( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + stmt: &mut StmtContext, + passthrough: Option>, + ) -> Result> { + let mut condition = self.parse_binary(frontend, ctx, stmt, passthrough, 0)?; + let mut meta = stmt.hir_exprs[condition].meta; + + if self.bump_if(frontend, TokenValue::Question).is_some() { + let accept = self.parse_expression(frontend, ctx, stmt)?; + self.expect(frontend, TokenValue::Colon)?; + let reject = self.parse_assignment(frontend, ctx, stmt)?; + let end_meta = stmt.hir_exprs[reject].meta; + + meta.subsume(end_meta); + condition = stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::Conditional { + condition, + accept, + reject, + }, + meta, + }, + Default::default(), + ) + } + + Ok(condition) + } + + pub fn parse_assignment( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + stmt: &mut StmtContext, + ) -> Result> { + let tgt = self.parse_unary(frontend, ctx, stmt)?; + let mut meta = stmt.hir_exprs[tgt].meta; + + Ok(match self.expect_peek(frontend)?.value { + TokenValue::Assign => { + self.bump(frontend)?; + let value = self.parse_assignment(frontend, ctx, stmt)?; + let end_meta = stmt.hir_exprs[value].meta; + + meta.subsume(end_meta); + stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::Assign { tgt, value }, + meta, + }, + Default::default(), + ) + } + TokenValue::OrAssign + | TokenValue::AndAssign + | TokenValue::AddAssign + | TokenValue::DivAssign + | TokenValue::ModAssign + | TokenValue::SubAssign + | TokenValue::MulAssign + | TokenValue::LeftShiftAssign + | TokenValue::RightShiftAssign + | TokenValue::XorAssign => { + let token = self.bump(frontend)?; + let right = self.parse_assignment(frontend, ctx, stmt)?; + let end_meta = stmt.hir_exprs[right].meta; + + meta.subsume(end_meta); + let value = stmt.hir_exprs.append( + HirExpr { + meta, + kind: HirExprKind::Binary { + left: tgt, + op: match token.value { + TokenValue::OrAssign => BinaryOperator::InclusiveOr, + TokenValue::AndAssign => BinaryOperator::And, + TokenValue::AddAssign => BinaryOperator::Add, + TokenValue::DivAssign => BinaryOperator::Divide, + TokenValue::ModAssign => BinaryOperator::Modulo, + TokenValue::SubAssign => BinaryOperator::Subtract, + TokenValue::MulAssign => BinaryOperator::Multiply, + TokenValue::LeftShiftAssign => BinaryOperator::ShiftLeft, + TokenValue::RightShiftAssign => BinaryOperator::ShiftRight, + TokenValue::XorAssign => BinaryOperator::ExclusiveOr, + _ => unreachable!(), + }, + right, + }, + }, + Default::default(), + ); + + stmt.hir_exprs.append( + HirExpr { + kind: HirExprKind::Assign { tgt, value }, + meta, + }, + Default::default(), + ) + } + _ => self.parse_conditional(frontend, ctx, stmt, Some(tgt))?, + }) + } + + pub fn parse_expression( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + stmt: &mut StmtContext, + ) -> Result> { + let mut expr = self.parse_assignment(frontend, ctx, stmt)?; + + while let TokenValue::Comma = self.expect_peek(frontend)?.value { + self.bump(frontend)?; + expr = self.parse_assignment(frontend, ctx, stmt)?; + } + + Ok(expr) + } +} + +const fn binding_power(value: &TokenValue) -> Option<(u8, u8)> { + Some(match *value { + TokenValue::LogicalOr => (1, 2), + TokenValue::LogicalXor => (3, 4), + TokenValue::LogicalAnd => (5, 6), + TokenValue::VerticalBar => (7, 8), + TokenValue::Caret => (9, 10), + TokenValue::Ampersand => (11, 12), + TokenValue::Equal | TokenValue::NotEqual => (13, 14), + TokenValue::GreaterEqual + | TokenValue::LessEqual + | TokenValue::LeftAngle + | TokenValue::RightAngle => (15, 16), + TokenValue::LeftShift | TokenValue::RightShift => (17, 18), + TokenValue::Plus | TokenValue::Dash => (19, 20), + TokenValue::Star | TokenValue::Slash | TokenValue::Percent => (21, 22), + _ => return None, + }) +} diff --git a/naga/src/front/glsl/parser/functions.rs b/naga/src/front/glsl/parser/functions.rs new file mode 100644 index 0000000000..b63b13a4a3 --- /dev/null +++ b/naga/src/front/glsl/parser/functions.rs @@ -0,0 +1,656 @@ +use crate::front::glsl::context::ExprPos; +use crate::front::glsl::Span; +use crate::Literal; +use crate::{ + front::glsl::{ + ast::ParameterQualifier, + context::Context, + parser::ParsingContext, + token::{Token, TokenValue}, + variables::VarDeclaration, + Error, ErrorKind, Frontend, Result, + }, + Block, Expression, Statement, SwitchCase, UnaryOperator, +}; + +impl<'source> ParsingContext<'source> { + pub fn peek_parameter_qualifier(&mut self, frontend: &mut Frontend) -> bool { + self.peek(frontend).map_or(false, |t| match t.value { + TokenValue::In | TokenValue::Out | TokenValue::InOut | TokenValue::Const => true, + _ => false, + }) + } + + /// Returns the parsed `ParameterQualifier` or `ParameterQualifier::In` + pub fn parse_parameter_qualifier(&mut self, frontend: &mut Frontend) -> ParameterQualifier { + if self.peek_parameter_qualifier(frontend) { + match self.bump(frontend).unwrap().value { + TokenValue::In => ParameterQualifier::In, + TokenValue::Out => ParameterQualifier::Out, + TokenValue::InOut => ParameterQualifier::InOut, + TokenValue::Const => ParameterQualifier::Const, + _ => unreachable!(), + } + } else { + ParameterQualifier::In + } + } + + pub fn parse_statement( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + terminator: &mut Option, + is_inside_loop: bool, + ) -> Result> { + // Type qualifiers always identify a declaration statement + if self.peek_type_qualifier(frontend) { + return self.parse_declaration(frontend, ctx, false, is_inside_loop); + } + + // Type names can identify either declaration statements or type constructors + // depending on wether the token following the type name is a `(` (LeftParen) + if self.peek_type_name(frontend) { + // Start by consuming the type name so that we can peek the token after it + let token = self.bump(frontend)?; + // Peek the next token and check if it's a `(` (LeftParen) if so the statement + // is a constructor, otherwise it's a declaration. We need to do the check + // beforehand and not in the if since we will backtrack before the if + let declaration = TokenValue::LeftParen != self.expect_peek(frontend)?.value; + + self.backtrack(token)?; + + if declaration { + return self.parse_declaration(frontend, ctx, false, is_inside_loop); + } + } + + let new_break = || { + let mut block = Block::new(); + block.push(Statement::Break, crate::Span::default()); + block + }; + + let &Token { + ref value, + mut meta, + } = self.expect_peek(frontend)?; + + let meta_rest = match *value { + TokenValue::Continue => { + let meta = self.bump(frontend)?.meta; + ctx.body.push(Statement::Continue, meta); + terminator.get_or_insert(ctx.body.len()); + self.expect(frontend, TokenValue::Semicolon)?.meta + } + TokenValue::Break => { + let meta = self.bump(frontend)?.meta; + ctx.body.push(Statement::Break, meta); + terminator.get_or_insert(ctx.body.len()); + self.expect(frontend, TokenValue::Semicolon)?.meta + } + TokenValue::Return => { + self.bump(frontend)?; + let (value, meta) = match self.expect_peek(frontend)?.value { + TokenValue::Semicolon => (None, self.bump(frontend)?.meta), + _ => { + // TODO: Implicit conversions + let mut stmt = ctx.stmt_ctx(); + let expr = self.parse_expression(frontend, ctx, &mut stmt)?; + self.expect(frontend, TokenValue::Semicolon)?; + let (handle, meta) = + ctx.lower_expect(stmt, frontend, expr, ExprPos::Rhs)?; + (Some(handle), meta) + } + }; + + ctx.emit_restart(); + + ctx.body.push(Statement::Return { value }, meta); + terminator.get_or_insert(ctx.body.len()); + + meta + } + TokenValue::Discard => { + let meta = self.bump(frontend)?.meta; + ctx.body.push(Statement::Kill, meta); + terminator.get_or_insert(ctx.body.len()); + + self.expect(frontend, TokenValue::Semicolon)?.meta + } + TokenValue::If => { + let mut meta = self.bump(frontend)?.meta; + + self.expect(frontend, TokenValue::LeftParen)?; + let condition = { + let mut stmt = ctx.stmt_ctx(); + let expr = self.parse_expression(frontend, ctx, &mut stmt)?; + let (handle, more_meta) = + ctx.lower_expect(stmt, frontend, expr, ExprPos::Rhs)?; + meta.subsume(more_meta); + handle + }; + self.expect(frontend, TokenValue::RightParen)?; + + let accept = ctx.new_body(|ctx| { + if let Some(more_meta) = + self.parse_statement(frontend, ctx, &mut None, is_inside_loop)? + { + meta.subsume(more_meta); + } + Ok(()) + })?; + + let reject = ctx.new_body(|ctx| { + if self.bump_if(frontend, TokenValue::Else).is_some() { + if let Some(more_meta) = + self.parse_statement(frontend, ctx, &mut None, is_inside_loop)? + { + meta.subsume(more_meta); + } + } + Ok(()) + })?; + + ctx.body.push( + Statement::If { + condition, + accept, + reject, + }, + meta, + ); + + meta + } + TokenValue::Switch => { + let mut meta = self.bump(frontend)?.meta; + let end_meta; + + self.expect(frontend, TokenValue::LeftParen)?; + + let (selector, uint) = { + let mut stmt = ctx.stmt_ctx(); + let expr = self.parse_expression(frontend, ctx, &mut stmt)?; + let (root, meta) = ctx.lower_expect(stmt, frontend, expr, ExprPos::Rhs)?; + let uint = ctx.resolve_type(root, meta)?.scalar_kind() + == Some(crate::ScalarKind::Uint); + (root, uint) + }; + + self.expect(frontend, TokenValue::RightParen)?; + + ctx.emit_restart(); + + let mut cases = Vec::new(); + // Track if any default case is present in the switch statement. + let mut default_present = false; + + self.expect(frontend, TokenValue::LeftBrace)?; + loop { + let value = match self.expect_peek(frontend)?.value { + TokenValue::Case => { + self.bump(frontend)?; + + let (const_expr, meta) = + self.parse_constant_expression(frontend, ctx.module)?; + + match ctx.module.const_expressions[const_expr] { + Expression::Literal(Literal::I32(value)) => match uint { + // This unchecked cast isn't good, but since + // we only reach this code when the selector + // is unsigned but the case label is signed, + // verification will reject the module + // anyway (which also matches GLSL's rules). + true => crate::SwitchValue::U32(value as u32), + false => crate::SwitchValue::I32(value), + }, + Expression::Literal(Literal::U32(value)) => { + crate::SwitchValue::U32(value) + } + _ => { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "Case values can only be integers".into(), + ), + meta, + }); + + crate::SwitchValue::I32(0) + } + } + } + TokenValue::Default => { + self.bump(frontend)?; + default_present = true; + crate::SwitchValue::Default + } + TokenValue::RightBrace => { + end_meta = self.bump(frontend)?.meta; + break; + } + _ => { + let Token { value, meta } = self.bump(frontend)?; + return Err(Error { + kind: ErrorKind::InvalidToken( + value, + vec![ + TokenValue::Case.into(), + TokenValue::Default.into(), + TokenValue::RightBrace.into(), + ], + ), + meta, + }); + } + }; + + self.expect(frontend, TokenValue::Colon)?; + + let mut fall_through = true; + + let body = ctx.new_body(|ctx| { + let mut case_terminator = None; + loop { + match self.expect_peek(frontend)?.value { + TokenValue::Case | TokenValue::Default | TokenValue::RightBrace => { + break + } + _ => { + self.parse_statement( + frontend, + ctx, + &mut case_terminator, + is_inside_loop, + )?; + } + } + } + + if let Some(mut idx) = case_terminator { + if let Statement::Break = ctx.body[idx - 1] { + fall_through = false; + idx -= 1; + } + + ctx.body.cull(idx..) + } + + Ok(()) + })?; + + cases.push(SwitchCase { + value, + body, + fall_through, + }) + } + + meta.subsume(end_meta); + + // NOTE: do not unwrap here since a switch statement isn't required + // to have any cases. + if let Some(case) = cases.last_mut() { + // GLSL requires that the last case not be empty, so we check + // that here and produce an error otherwise (fall_through must + // also be checked because `break`s count as statements but + // they aren't added to the body) + if case.body.is_empty() && case.fall_through { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "last case/default label must be followed by statements".into(), + ), + meta, + }) + } + + // GLSL allows the last case to not have any `break` statement, + // this would mark it as fall through but naga's IR requires that + // the last case must not be fall through, so we mark need to mark + // the last case as not fall through always. + case.fall_through = false; + } + + // Add an empty default case in case non was present, this is needed because + // naga's IR requires that all switch statements must have a default case but + // GLSL doesn't require that, so we might need to add an empty default case. + if !default_present { + cases.push(SwitchCase { + value: crate::SwitchValue::Default, + body: Block::new(), + fall_through: false, + }) + } + + ctx.body.push(Statement::Switch { selector, cases }, meta); + + meta + } + TokenValue::While => { + let mut meta = self.bump(frontend)?.meta; + + let loop_body = ctx.new_body(|ctx| { + let mut stmt = ctx.stmt_ctx(); + self.expect(frontend, TokenValue::LeftParen)?; + let root = self.parse_expression(frontend, ctx, &mut stmt)?; + meta.subsume(self.expect(frontend, TokenValue::RightParen)?.meta); + + let (expr, expr_meta) = ctx.lower_expect(stmt, frontend, root, ExprPos::Rhs)?; + let condition = ctx.add_expression( + Expression::Unary { + op: UnaryOperator::LogicalNot, + expr, + }, + expr_meta, + )?; + + ctx.emit_restart(); + + ctx.body.push( + Statement::If { + condition, + accept: new_break(), + reject: Block::new(), + }, + crate::Span::default(), + ); + + meta.subsume(expr_meta); + + if let Some(body_meta) = self.parse_statement(frontend, ctx, &mut None, true)? { + meta.subsume(body_meta); + } + Ok(()) + })?; + + ctx.body.push( + Statement::Loop { + body: loop_body, + continuing: Block::new(), + break_if: None, + }, + meta, + ); + + meta + } + TokenValue::Do => { + let mut meta = self.bump(frontend)?.meta; + + let loop_body = ctx.new_body(|ctx| { + let mut terminator = None; + self.parse_statement(frontend, ctx, &mut terminator, true)?; + + let mut stmt = ctx.stmt_ctx(); + + self.expect(frontend, TokenValue::While)?; + self.expect(frontend, TokenValue::LeftParen)?; + let root = self.parse_expression(frontend, ctx, &mut stmt)?; + let end_meta = self.expect(frontend, TokenValue::RightParen)?.meta; + + meta.subsume(end_meta); + + let (expr, expr_meta) = ctx.lower_expect(stmt, frontend, root, ExprPos::Rhs)?; + let condition = ctx.add_expression( + Expression::Unary { + op: UnaryOperator::LogicalNot, + expr, + }, + expr_meta, + )?; + + ctx.emit_restart(); + + ctx.body.push( + Statement::If { + condition, + accept: new_break(), + reject: Block::new(), + }, + crate::Span::default(), + ); + + if let Some(idx) = terminator { + ctx.body.cull(idx..) + } + Ok(()) + })?; + + ctx.body.push( + Statement::Loop { + body: loop_body, + continuing: Block::new(), + break_if: None, + }, + meta, + ); + + meta + } + TokenValue::For => { + let mut meta = self.bump(frontend)?.meta; + + ctx.symbol_table.push_scope(); + self.expect(frontend, TokenValue::LeftParen)?; + + if self.bump_if(frontend, TokenValue::Semicolon).is_none() { + if self.peek_type_name(frontend) || self.peek_type_qualifier(frontend) { + self.parse_declaration(frontend, ctx, false, false)?; + } else { + let mut stmt = ctx.stmt_ctx(); + let expr = self.parse_expression(frontend, ctx, &mut stmt)?; + ctx.lower(stmt, frontend, expr, ExprPos::Rhs)?; + self.expect(frontend, TokenValue::Semicolon)?; + } + } + + let loop_body = ctx.new_body(|ctx| { + if self.bump_if(frontend, TokenValue::Semicolon).is_none() { + let (expr, expr_meta) = if self.peek_type_name(frontend) + || self.peek_type_qualifier(frontend) + { + let mut qualifiers = self.parse_type_qualifiers(frontend, ctx)?; + let (ty, mut meta) = self.parse_type_non_void(frontend, ctx)?; + let name = self.expect_ident(frontend)?.0; + + self.expect(frontend, TokenValue::Assign)?; + + let (value, end_meta) = self.parse_initializer(frontend, ty, ctx)?; + meta.subsume(end_meta); + + let decl = VarDeclaration { + qualifiers: &mut qualifiers, + ty, + name: Some(name), + init: None, + meta, + }; + + let pointer = frontend.add_local_var(ctx, decl)?; + + ctx.emit_restart(); + + ctx.body.push(Statement::Store { pointer, value }, meta); + + (value, end_meta) + } else { + let mut stmt = ctx.stmt_ctx(); + let root = self.parse_expression(frontend, ctx, &mut stmt)?; + ctx.lower_expect(stmt, frontend, root, ExprPos::Rhs)? + }; + + let condition = ctx.add_expression( + Expression::Unary { + op: UnaryOperator::LogicalNot, + expr, + }, + expr_meta, + )?; + + ctx.emit_restart(); + + ctx.body.push( + Statement::If { + condition, + accept: new_break(), + reject: Block::new(), + }, + crate::Span::default(), + ); + + self.expect(frontend, TokenValue::Semicolon)?; + } + Ok(()) + })?; + + let continuing = ctx.new_body(|ctx| { + match self.expect_peek(frontend)?.value { + TokenValue::RightParen => {} + _ => { + let mut stmt = ctx.stmt_ctx(); + let rest = self.parse_expression(frontend, ctx, &mut stmt)?; + ctx.lower(stmt, frontend, rest, ExprPos::Rhs)?; + } + } + Ok(()) + })?; + + meta.subsume(self.expect(frontend, TokenValue::RightParen)?.meta); + + let loop_body = ctx.with_body(loop_body, |ctx| { + if let Some(stmt_meta) = self.parse_statement(frontend, ctx, &mut None, true)? { + meta.subsume(stmt_meta); + } + Ok(()) + })?; + + ctx.body.push( + Statement::Loop { + body: loop_body, + continuing, + break_if: None, + }, + meta, + ); + + ctx.symbol_table.pop_scope(); + + meta + } + TokenValue::LeftBrace => { + let mut meta = self.bump(frontend)?.meta; + + let mut block_terminator = None; + + let block = ctx.new_body(|ctx| { + let block_meta = self.parse_compound_statement( + meta, + frontend, + ctx, + &mut block_terminator, + is_inside_loop, + )?; + meta.subsume(block_meta); + Ok(()) + })?; + + ctx.body.push(Statement::Block(block), meta); + if block_terminator.is_some() { + terminator.get_or_insert(ctx.body.len()); + } + + meta + } + TokenValue::Semicolon => self.bump(frontend)?.meta, + _ => { + // Attempt to force expression parsing for remainder of the + // tokens. Unknown or invalid tokens will be caught there and + // turned into an error. + let mut stmt = ctx.stmt_ctx(); + let expr = self.parse_expression(frontend, ctx, &mut stmt)?; + ctx.lower(stmt, frontend, expr, ExprPos::Rhs)?; + self.expect(frontend, TokenValue::Semicolon)?.meta + } + }; + + meta.subsume(meta_rest); + Ok(Some(meta)) + } + + pub fn parse_compound_statement( + &mut self, + mut meta: Span, + frontend: &mut Frontend, + ctx: &mut Context, + terminator: &mut Option, + is_inside_loop: bool, + ) -> Result { + ctx.symbol_table.push_scope(); + + loop { + if let Some(Token { + meta: brace_meta, .. + }) = self.bump_if(frontend, TokenValue::RightBrace) + { + meta.subsume(brace_meta); + break; + } + + let stmt = self.parse_statement(frontend, ctx, terminator, is_inside_loop)?; + + if let Some(stmt_meta) = stmt { + meta.subsume(stmt_meta); + } + } + + if let Some(idx) = *terminator { + ctx.body.cull(idx..) + } + + ctx.symbol_table.pop_scope(); + + Ok(meta) + } + + pub fn parse_function_args( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + ) -> Result<()> { + if self.bump_if(frontend, TokenValue::Void).is_some() { + return Ok(()); + } + + loop { + if self.peek_type_name(frontend) || self.peek_parameter_qualifier(frontend) { + let qualifier = self.parse_parameter_qualifier(frontend); + let mut ty = self.parse_type_non_void(frontend, ctx)?.0; + + match self.expect_peek(frontend)?.value { + TokenValue::Comma => { + self.bump(frontend)?; + ctx.add_function_arg(None, ty, qualifier)?; + continue; + } + TokenValue::Identifier(_) => { + let mut name = self.expect_ident(frontend)?; + self.parse_array_specifier(frontend, ctx, &mut name.1, &mut ty)?; + + ctx.add_function_arg(Some(name), ty, qualifier)?; + + if self.bump_if(frontend, TokenValue::Comma).is_some() { + continue; + } + + break; + } + _ => break, + } + } + + break; + } + + Ok(()) + } +} diff --git a/naga/src/front/glsl/parser/types.rs b/naga/src/front/glsl/parser/types.rs new file mode 100644 index 0000000000..6316677022 --- /dev/null +++ b/naga/src/front/glsl/parser/types.rs @@ -0,0 +1,443 @@ +use std::num::NonZeroU32; + +use crate::{ + front::glsl::{ + ast::{QualifierKey, QualifierValue, StorageQualifier, StructLayout, TypeQualifiers}, + context::Context, + error::ExpectedToken, + parser::ParsingContext, + token::{Token, TokenValue}, + Error, ErrorKind, Frontend, Result, + }, + AddressSpace, ArraySize, Handle, Span, Type, TypeInner, +}; + +impl<'source> ParsingContext<'source> { + /// Parses an optional array_specifier returning wether or not it's present + /// and modifying the type handle if it exists + pub fn parse_array_specifier( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + span: &mut Span, + ty: &mut Handle, + ) -> Result<()> { + while self.parse_array_specifier_single(frontend, ctx, span, ty)? {} + Ok(()) + } + + /// Implementation of [`Self::parse_array_specifier`] for a single array_specifier + fn parse_array_specifier_single( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + span: &mut Span, + ty: &mut Handle, + ) -> Result { + if self.bump_if(frontend, TokenValue::LeftBracket).is_some() { + let size = if let Some(Token { meta, .. }) = + self.bump_if(frontend, TokenValue::RightBracket) + { + span.subsume(meta); + ArraySize::Dynamic + } else { + let (value, constant_span) = self.parse_uint_constant(frontend, ctx)?; + let size = NonZeroU32::new(value).ok_or(Error { + kind: ErrorKind::SemanticError("Array size must be greater than zero".into()), + meta: constant_span, + })?; + let end_span = self.expect(frontend, TokenValue::RightBracket)?.meta; + span.subsume(end_span); + ArraySize::Constant(size) + }; + + frontend.layouter.update(ctx.module.to_ctx()).unwrap(); + let stride = frontend.layouter[*ty].to_stride(); + *ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Array { + base: *ty, + size, + stride, + }, + }, + *span, + ); + + Ok(true) + } else { + Ok(false) + } + } + + pub fn parse_type( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + ) -> Result<(Option>, Span)> { + let token = self.bump(frontend)?; + let mut handle = match token.value { + TokenValue::Void => return Ok((None, token.meta)), + TokenValue::TypeName(ty) => ctx.module.types.insert(ty, token.meta), + TokenValue::Struct => { + let mut meta = token.meta; + let ty_name = self.expect_ident(frontend)?.0; + self.expect(frontend, TokenValue::LeftBrace)?; + let mut members = Vec::new(); + let span = self.parse_struct_declaration_list( + frontend, + ctx, + &mut members, + StructLayout::Std140, + )?; + let end_meta = self.expect(frontend, TokenValue::RightBrace)?.meta; + meta.subsume(end_meta); + let ty = ctx.module.types.insert( + Type { + name: Some(ty_name.clone()), + inner: TypeInner::Struct { members, span }, + }, + meta, + ); + frontend.lookup_type.insert(ty_name, ty); + ty + } + TokenValue::Identifier(ident) => match frontend.lookup_type.get(&ident) { + Some(ty) => *ty, + None => { + return Err(Error { + kind: ErrorKind::UnknownType(ident), + meta: token.meta, + }) + } + }, + _ => { + return Err(Error { + kind: ErrorKind::InvalidToken( + token.value, + vec![ + TokenValue::Void.into(), + TokenValue::Struct.into(), + ExpectedToken::TypeName, + ], + ), + meta: token.meta, + }); + } + }; + + let mut span = token.meta; + self.parse_array_specifier(frontend, ctx, &mut span, &mut handle)?; + Ok((Some(handle), span)) + } + + pub fn parse_type_non_void( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + ) -> Result<(Handle, Span)> { + let (maybe_ty, meta) = self.parse_type(frontend, ctx)?; + let ty = maybe_ty.ok_or_else(|| Error { + kind: ErrorKind::SemanticError("Type can't be void".into()), + meta, + })?; + + Ok((ty, meta)) + } + + pub fn peek_type_qualifier(&mut self, frontend: &mut Frontend) -> bool { + self.peek(frontend).map_or(false, |t| match t.value { + TokenValue::Invariant + | TokenValue::Interpolation(_) + | TokenValue::Sampling(_) + | TokenValue::PrecisionQualifier(_) + | TokenValue::Const + | TokenValue::In + | TokenValue::Out + | TokenValue::Uniform + | TokenValue::Shared + | TokenValue::Buffer + | TokenValue::Restrict + | TokenValue::MemoryQualifier(_) + | TokenValue::Layout => true, + _ => false, + }) + } + + pub fn parse_type_qualifiers<'a>( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + ) -> Result> { + let mut qualifiers = TypeQualifiers::default(); + + while self.peek_type_qualifier(frontend) { + let token = self.bump(frontend)?; + + // Handle layout qualifiers outside the match since this can push multiple values + if token.value == TokenValue::Layout { + self.parse_layout_qualifier_id_list(frontend, ctx, &mut qualifiers)?; + continue; + } + + qualifiers.span.subsume(token.meta); + + match token.value { + TokenValue::Invariant => { + if qualifiers.invariant.is_some() { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "Cannot use more than one invariant qualifier per declaration" + .into(), + ), + meta: token.meta, + }) + } + + qualifiers.invariant = Some(token.meta); + } + TokenValue::Interpolation(i) => { + if qualifiers.interpolation.is_some() { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "Cannot use more than one interpolation qualifier per declaration" + .into(), + ), + meta: token.meta, + }) + } + + qualifiers.interpolation = Some((i, token.meta)); + } + TokenValue::Const + | TokenValue::In + | TokenValue::Out + | TokenValue::Uniform + | TokenValue::Shared + | TokenValue::Buffer => { + let storage = match token.value { + TokenValue::Const => StorageQualifier::Const, + TokenValue::In => StorageQualifier::Input, + TokenValue::Out => StorageQualifier::Output, + TokenValue::Uniform => { + StorageQualifier::AddressSpace(AddressSpace::Uniform) + } + TokenValue::Shared => { + StorageQualifier::AddressSpace(AddressSpace::WorkGroup) + } + TokenValue::Buffer => { + StorageQualifier::AddressSpace(AddressSpace::Storage { + access: crate::StorageAccess::all(), + }) + } + _ => unreachable!(), + }; + + if StorageQualifier::AddressSpace(AddressSpace::Function) + != qualifiers.storage.0 + { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "Cannot use more than one storage qualifier per declaration".into(), + ), + meta: token.meta, + }); + } + + qualifiers.storage = (storage, token.meta); + } + TokenValue::Sampling(s) => { + if qualifiers.sampling.is_some() { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "Cannot use more than one sampling qualifier per declaration" + .into(), + ), + meta: token.meta, + }) + } + + qualifiers.sampling = Some((s, token.meta)); + } + TokenValue::PrecisionQualifier(p) => { + if qualifiers.precision.is_some() { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "Cannot use more than one precision qualifier per declaration" + .into(), + ), + meta: token.meta, + }) + } + + qualifiers.precision = Some((p, token.meta)); + } + TokenValue::MemoryQualifier(access) => { + let storage_access = qualifiers + .storage_access + .get_or_insert((crate::StorageAccess::all(), Span::default())); + if !storage_access.0.contains(!access) { + frontend.errors.push(Error { + kind: ErrorKind::SemanticError( + "The same memory qualifier can only be used once".into(), + ), + meta: token.meta, + }) + } + + storage_access.0 &= access; + storage_access.1.subsume(token.meta); + } + TokenValue::Restrict => continue, + _ => unreachable!(), + }; + } + + Ok(qualifiers) + } + + pub fn parse_layout_qualifier_id_list( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + qualifiers: &mut TypeQualifiers, + ) -> Result<()> { + self.expect(frontend, TokenValue::LeftParen)?; + loop { + self.parse_layout_qualifier_id(frontend, ctx, &mut qualifiers.layout_qualifiers)?; + + if self.bump_if(frontend, TokenValue::Comma).is_some() { + continue; + } + + break; + } + let token = self.expect(frontend, TokenValue::RightParen)?; + qualifiers.span.subsume(token.meta); + + Ok(()) + } + + pub fn parse_layout_qualifier_id( + &mut self, + frontend: &mut Frontend, + ctx: &mut Context, + qualifiers: &mut crate::FastHashMap, + ) -> Result<()> { + // layout_qualifier_id: + // IDENTIFIER + // IDENTIFIER EQUAL constant_expression + // SHARED + let mut token = self.bump(frontend)?; + match token.value { + TokenValue::Identifier(name) => { + let (key, value) = match name.as_str() { + "std140" => ( + QualifierKey::Layout, + QualifierValue::Layout(StructLayout::Std140), + ), + "std430" => ( + QualifierKey::Layout, + QualifierValue::Layout(StructLayout::Std430), + ), + word => { + if let Some(format) = map_image_format(word) { + (QualifierKey::Format, QualifierValue::Format(format)) + } else { + let key = QualifierKey::String(name.into()); + let value = if self.bump_if(frontend, TokenValue::Assign).is_some() { + let (value, end_meta) = + match self.parse_uint_constant(frontend, ctx) { + Ok(v) => v, + Err(e) => { + frontend.errors.push(e); + (0, Span::default()) + } + }; + token.meta.subsume(end_meta); + + QualifierValue::Uint(value) + } else { + QualifierValue::None + }; + + (key, value) + } + } + }; + + qualifiers.insert(key, (value, token.meta)); + } + _ => frontend.errors.push(Error { + kind: ErrorKind::InvalidToken(token.value, vec![ExpectedToken::Identifier]), + meta: token.meta, + }), + } + + Ok(()) + } + + pub fn peek_type_name(&mut self, frontend: &mut Frontend) -> bool { + self.peek(frontend).map_or(false, |t| match t.value { + TokenValue::TypeName(_) | TokenValue::Void => true, + TokenValue::Struct => true, + TokenValue::Identifier(ref ident) => frontend.lookup_type.contains_key(ident), + _ => false, + }) + } +} + +fn map_image_format(word: &str) -> Option { + use crate::StorageFormat as Sf; + + let format = match word { + // float-image-format-qualifier: + "rgba32f" => Sf::Rgba32Float, + "rgba16f" => Sf::Rgba16Float, + "rg32f" => Sf::Rg32Float, + "rg16f" => Sf::Rg16Float, + "r11f_g11f_b10f" => Sf::Rg11b10Float, + "r32f" => Sf::R32Float, + "r16f" => Sf::R16Float, + "rgba16" => Sf::Rgba16Unorm, + "rgb10_a2ui" => Sf::Rgb10a2Uint, + "rgb10_a2" => Sf::Rgb10a2Unorm, + "rgba8" => Sf::Rgba8Unorm, + "rg16" => Sf::Rg16Unorm, + "rg8" => Sf::Rg8Unorm, + "r16" => Sf::R16Unorm, + "r8" => Sf::R8Unorm, + "rgba16_snorm" => Sf::Rgba16Snorm, + "rgba8_snorm" => Sf::Rgba8Snorm, + "rg16_snorm" => Sf::Rg16Snorm, + "rg8_snorm" => Sf::Rg8Snorm, + "r16_snorm" => Sf::R16Snorm, + "r8_snorm" => Sf::R8Snorm, + // int-image-format-qualifier: + "rgba32i" => Sf::Rgba32Sint, + "rgba16i" => Sf::Rgba16Sint, + "rgba8i" => Sf::Rgba8Sint, + "rg32i" => Sf::Rg32Sint, + "rg16i" => Sf::Rg16Sint, + "rg8i" => Sf::Rg8Sint, + "r32i" => Sf::R32Sint, + "r16i" => Sf::R16Sint, + "r8i" => Sf::R8Sint, + // uint-image-format-qualifier: + "rgba32ui" => Sf::Rgba32Uint, + "rgba16ui" => Sf::Rgba16Uint, + "rgba8ui" => Sf::Rgba8Uint, + "rg32ui" => Sf::Rg32Uint, + "rg16ui" => Sf::Rg16Uint, + "rg8ui" => Sf::Rg8Uint, + "r32ui" => Sf::R32Uint, + "r16ui" => Sf::R16Uint, + "r8ui" => Sf::R8Uint, + // TODO: These next ones seem incorrect to me + // "rgb10_a2ui" => Sf::Rgb10a2Unorm, + _ => return None, + }; + + Some(format) +} diff --git a/naga/src/front/glsl/parser_tests.rs b/naga/src/front/glsl/parser_tests.rs new file mode 100644 index 0000000000..0f4fbab22f --- /dev/null +++ b/naga/src/front/glsl/parser_tests.rs @@ -0,0 +1,844 @@ +use super::{ + ast::Profile, + error::ExpectedToken, + error::{Error, ErrorKind}, + token::TokenValue, + Frontend, Options, Span, +}; +use crate::ShaderStage; +use pp_rs::token::PreprocessorError; + +#[test] +fn version() { + let mut frontend = Frontend::default(); + + // invalid versions + assert_eq!( + frontend + .parse( + &Options::from(ShaderStage::Vertex), + "#version 99000\n void main(){}", + ) + .err() + .unwrap(), + vec![Error { + kind: ErrorKind::InvalidVersion(99000), + meta: Span::new(9, 14) + }], + ); + + assert_eq!( + frontend + .parse( + &Options::from(ShaderStage::Vertex), + "#version 449\n void main(){}", + ) + .err() + .unwrap(), + vec![Error { + kind: ErrorKind::InvalidVersion(449), + meta: Span::new(9, 12) + }] + ); + + assert_eq!( + frontend + .parse( + &Options::from(ShaderStage::Vertex), + "#version 450 smart\n void main(){}", + ) + .err() + .unwrap(), + vec![Error { + kind: ErrorKind::InvalidProfile("smart".into()), + meta: Span::new(13, 18), + }] + ); + + assert_eq!( + frontend + .parse( + &Options::from(ShaderStage::Vertex), + "#version 450\nvoid main(){} #version 450", + ) + .err() + .unwrap(), + vec![ + Error { + kind: ErrorKind::PreprocessorError(PreprocessorError::UnexpectedHash,), + meta: Span::new(27, 28), + }, + Error { + kind: ErrorKind::InvalidToken( + TokenValue::Identifier("version".into()), + vec![ExpectedToken::Eof] + ), + meta: Span::new(28, 35) + } + ] + ); + + // valid versions + frontend + .parse( + &Options::from(ShaderStage::Vertex), + " # version 450\nvoid main() {}", + ) + .unwrap(); + assert_eq!( + (frontend.metadata().version, frontend.metadata().profile), + (450, Profile::Core) + ); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + "#version 450\nvoid main() {}", + ) + .unwrap(); + assert_eq!( + (frontend.metadata().version, frontend.metadata().profile), + (450, Profile::Core) + ); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + "#version 450 core\nvoid main(void) {}", + ) + .unwrap(); + assert_eq!( + (frontend.metadata().version, frontend.metadata().profile), + (450, Profile::Core) + ); +} + +#[test] +fn control_flow() { + let mut frontend = Frontend::default(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + if (true) { + return 1; + } else { + return 2; + } + } + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + if (true) { + return 1; + } + } + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + int x; + int y = 3; + switch (5) { + case 2: + x = 2; + case 5: + x = 5; + y = 2; + break; + default: + x = 0; + } + } + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + int x = 0; + while(x < 5) { + x = x + 1; + } + do { + x = x - 1; + } while(x >= 4) + } + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + int x = 0; + for(int i = 0; i < 10;) { + x = x + 2; + } + for(;;); + return x; + } + "#, + ) + .unwrap(); +} + +#[test] +fn declarations() { + let mut frontend = Frontend::default(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + #version 450 + layout(location = 0) in vec2 v_uv; + layout(location = 0) out vec4 o_color; + layout(set = 1, binding = 1) uniform texture2D tex; + layout(set = 1, binding = 2) uniform sampler tex_sampler; + + layout(early_fragment_tests) in; + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + #version 450 + layout(std140, set = 2, binding = 0) + uniform u_locals { + vec3 model_offs; + float load_time; + ivec4 atlas_offs; + }; + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + #version 450 + layout(push_constant) + uniform u_locals { + vec3 model_offs; + float load_time; + ivec4 atlas_offs; + }; + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + #version 450 + layout(std430, set = 2, binding = 0) + uniform u_locals { + vec3 model_offs; + float load_time; + ivec4 atlas_offs; + }; + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + #version 450 + layout(std140, set = 2, binding = 0) + uniform u_locals { + vec3 model_offs; + float load_time; + } block_var; + + void main() { + load_time * model_offs; + block_var.load_time * block_var.model_offs; + } + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + #version 450 + float vector = vec4(1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0); + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + #version 450 + precision highp float; + + void main() {} + "#, + ) + .unwrap(); +} + +#[test] +fn textures() { + let mut frontend = Frontend::default(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + #version 450 + layout(location = 0) in vec2 v_uv; + layout(location = 0) out vec4 o_color; + layout(set = 1, binding = 1) uniform texture2D tex; + layout(set = 1, binding = 2) uniform sampler tex_sampler; + void main() { + o_color = texture(sampler2D(tex, tex_sampler), v_uv); + o_color.a = texture(sampler2D(tex, tex_sampler), v_uv, 2.0).a; + } + "#, + ) + .unwrap(); +} + +#[test] +fn functions() { + let mut frontend = Frontend::default(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void test1(float); + void test1(float) {} + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void test2(float a) {} + void test3(float a, float b) {} + void test4(float, float) {} + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + float test(float a) { return a; } + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + float test(vec4 p) { + return p.x; + } + + void main() {} + "#, + ) + .unwrap(); + + // Function overloading + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + float test(vec2 p); + float test(vec3 p); + float test(vec4 p); + + float test(vec2 p) { + return p.x; + } + + float test(vec3 p) { + return p.x; + } + + float test(vec4 p) { + return p.x; + } + + void main() {} + "#, + ) + .unwrap(); + + assert_eq!( + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + int test(vec4 p) { + return p.x; + } + + float test(vec4 p) { + return p.x; + } + + void main() {} + "#, + ) + .err() + .unwrap(), + vec![Error { + kind: ErrorKind::SemanticError("Function already defined".into()), + meta: Span::new(134, 152), + }] + ); + + println!(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + float callee(uint q) { + return float(q); + } + + float caller() { + callee(1u); + } + + void main() {} + "#, + ) + .unwrap(); + + // Nested function call + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + layout(set = 0, binding = 1) uniform texture2D t_noise; + layout(set = 0, binding = 2) uniform sampler s_noise; + + void main() { + textureLod(sampler2D(t_noise, s_noise), vec2(1.0), 0); + } + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void fun(vec2 in_parameter, out float out_parameter) { + ivec2 _ = ivec2(in_parameter); + } + + void main() { + float a; + fun(vec2(1.0), a); + } + "#, + ) + .unwrap(); +} + +#[test] +fn constants() { + use crate::{Constant, Expression, Type, TypeInner}; + + let mut frontend = Frontend::default(); + + let module = frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + const float a = 1.0; + float global = a; + const float b = a; + + void main() {} + "#, + ) + .unwrap(); + + let mut types = module.types.iter(); + let mut constants = module.constants.iter(); + let mut const_expressions = module.const_expressions.iter(); + + let (ty_handle, ty) = types.next().unwrap(); + assert_eq!( + ty, + &Type { + name: None, + inner: TypeInner::Scalar(crate::Scalar::F32) + } + ); + + let (init_handle, init) = const_expressions.next().unwrap(); + assert_eq!(init, &Expression::Literal(crate::Literal::F32(1.0))); + + assert_eq!( + constants.next().unwrap().1, + &Constant { + name: Some("a".to_owned()), + r#override: crate::Override::None, + ty: ty_handle, + init: init_handle + } + ); + + assert_eq!( + constants.next().unwrap().1, + &Constant { + name: Some("b".to_owned()), + r#override: crate::Override::None, + ty: ty_handle, + init: init_handle + } + ); + + assert!(constants.next().is_none()); +} + +#[test] +fn function_overloading() { + let mut frontend = Frontend::default(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + + float saturate(float v) { return clamp(v, 0.0, 1.0); } + vec2 saturate(vec2 v) { return clamp(v, vec2(0.0), vec2(1.0)); } + vec3 saturate(vec3 v) { return clamp(v, vec3(0.0), vec3(1.0)); } + vec4 saturate(vec4 v) { return clamp(v, vec4(0.0), vec4(1.0)); } + + void main() { + float v1 = saturate(1.5); + vec2 v2 = saturate(vec2(0.5, 1.5)); + vec3 v3 = saturate(vec3(0.5, 1.5, 2.5)); + vec3 v4 = saturate(vec4(0.5, 1.5, 2.5, 3.5)); + } + "#, + ) + .unwrap(); +} + +#[test] +fn implicit_conversions() { + let mut frontend = Frontend::default(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + mat4 a = mat4(1); + float b = 1u; + float c = 1 + 2.0; + } + "#, + ) + .unwrap(); + + assert_eq!( + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void test(int a) {} + void test(uint a) {} + + void main() { + test(1.0); + } + "#, + ) + .err() + .unwrap(), + vec![Error { + kind: ErrorKind::SemanticError("Unknown function \'test\'".into()), + meta: Span::new(156, 165), + }] + ); + + assert_eq!( + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void test(float a) {} + void test(uint a) {} + + void main() { + test(1); + } + "#, + ) + .err() + .unwrap(), + vec![Error { + kind: ErrorKind::SemanticError("Ambiguous best function for \'test\'".into()), + meta: Span::new(158, 165), + }] + ); +} + +#[test] +fn structs() { + let mut frontend = Frontend::default(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + Test { + vec4 pos; + } xx; + + void main() {} + "#, + ) + .unwrap_err(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + struct Test { + vec4 pos; + }; + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + const int NUM_VECS = 42; + struct Test { + vec4 vecs[NUM_VECS]; + }; + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + struct Hello { + vec4 test; + } test() { + return Hello( vec4(1.0) ); + } + + void main() {} + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + struct Test {}; + + void main() {} + "#, + ) + .unwrap_err(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + inout struct Test { + vec4 x; + }; + + void main() {} + "#, + ) + .unwrap_err(); +} + +#[test] +fn swizzles() { + let mut frontend = Frontend::default(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + vec4 v = vec4(1); + v.xyz = vec3(2); + v.x = 5.0; + v.xyz.zxy.yx.xy = vec2(5.0, 1.0); + } + "#, + ) + .unwrap(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + vec4 v = vec4(1); + v.xx = vec2(5.0); + } + "#, + ) + .unwrap_err(); + + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + vec3 v = vec3(1); + v.w = 2.0; + } + "#, + ) + .unwrap_err(); +} + +#[test] +fn expressions() { + let mut frontend = Frontend::default(); + + // Vector indexing + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + float test(int index) { + vec4 v = vec4(1.0, 2.0, 3.0, 4.0); + return v[index] + 1.0; + } + + void main() {} + "#, + ) + .unwrap(); + + // Prefix increment/decrement + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + uint index = 0; + + --index; + ++index; + } + "#, + ) + .unwrap(); + + // Dynamic indexing of array + frontend + .parse( + &Options::from(ShaderStage::Vertex), + r#" + # version 450 + void main() { + const vec4 positions[1] = { vec4(0) }; + + gl_Position = positions[gl_VertexIndex]; + } + "#, + ) + .unwrap(); +} diff --git a/naga/src/front/glsl/token.rs b/naga/src/front/glsl/token.rs new file mode 100644 index 0000000000..4c3b5d4a25 --- /dev/null +++ b/naga/src/front/glsl/token.rs @@ -0,0 +1,137 @@ +pub use pp_rs::token::{Float, Integer, Location, Token as PPToken}; + +use super::ast::Precision; +use crate::{Interpolation, Sampling, Span, Type}; + +impl From for Span { + fn from(loc: Location) -> Self { + Span::new(loc.start, loc.end) + } +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub struct Token { + pub value: TokenValue, + pub meta: Span, +} + +/// A token passed from the lexing used in the parsing. +/// +/// This type is exported since it's returned in the +/// [`InvalidToken`](super::ErrorKind::InvalidToken) error. +#[derive(Debug, PartialEq)] +pub enum TokenValue { + Identifier(String), + + FloatConstant(Float), + IntConstant(Integer), + BoolConstant(bool), + + Layout, + In, + Out, + InOut, + Uniform, + Buffer, + Const, + Shared, + + Restrict, + /// A `glsl` memory qualifier such as `writeonly` + /// + /// The associated [`crate::StorageAccess`] is the access being allowed + /// (for example `writeonly` has an associated value of [`crate::StorageAccess::STORE`]) + MemoryQualifier(crate::StorageAccess), + + Invariant, + Interpolation(Interpolation), + Sampling(Sampling), + Precision, + PrecisionQualifier(Precision), + + Continue, + Break, + Return, + Discard, + + If, + Else, + Switch, + Case, + Default, + While, + Do, + For, + + Void, + Struct, + TypeName(Type), + + Assign, + AddAssign, + SubAssign, + MulAssign, + DivAssign, + ModAssign, + LeftShiftAssign, + RightShiftAssign, + AndAssign, + XorAssign, + OrAssign, + + Increment, + Decrement, + + LogicalOr, + LogicalAnd, + LogicalXor, + + LessEqual, + GreaterEqual, + Equal, + NotEqual, + + LeftShift, + RightShift, + + LeftBrace, + RightBrace, + LeftParen, + RightParen, + LeftBracket, + RightBracket, + LeftAngle, + RightAngle, + + Comma, + Semicolon, + Colon, + Dot, + Bang, + Dash, + Tilde, + Plus, + Star, + Slash, + Percent, + VerticalBar, + Caret, + Ampersand, + Question, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub struct Directive { + pub kind: DirectiveKind, + pub tokens: Vec, +} + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub enum DirectiveKind { + Version { is_first_directive: bool }, + Extension, + Pragma, +} diff --git a/naga/src/front/glsl/types.rs b/naga/src/front/glsl/types.rs new file mode 100644 index 0000000000..e87d76fffc --- /dev/null +++ b/naga/src/front/glsl/types.rs @@ -0,0 +1,360 @@ +use super::{context::Context, Error, ErrorKind, Result, Span}; +use crate::{ + proc::ResolveContext, Expression, Handle, ImageClass, ImageDimension, Scalar, ScalarKind, Type, + TypeInner, VectorSize, +}; + +pub fn parse_type(type_name: &str) -> Option { + match type_name { + "bool" => Some(Type { + name: None, + inner: TypeInner::Scalar(Scalar::BOOL), + }), + "float" => Some(Type { + name: None, + inner: TypeInner::Scalar(Scalar::F32), + }), + "double" => Some(Type { + name: None, + inner: TypeInner::Scalar(Scalar::F64), + }), + "int" => Some(Type { + name: None, + inner: TypeInner::Scalar(Scalar::I32), + }), + "uint" => Some(Type { + name: None, + inner: TypeInner::Scalar(Scalar::U32), + }), + "sampler" | "samplerShadow" => Some(Type { + name: None, + inner: TypeInner::Sampler { + comparison: type_name == "samplerShadow", + }, + }), + word => { + fn kind_width_parse(ty: &str) -> Option { + Some(match ty { + "" => Scalar::F32, + "b" => Scalar::BOOL, + "i" => Scalar::I32, + "u" => Scalar::U32, + "d" => Scalar::F64, + _ => return None, + }) + } + + fn size_parse(n: &str) -> Option { + Some(match n { + "2" => VectorSize::Bi, + "3" => VectorSize::Tri, + "4" => VectorSize::Quad, + _ => return None, + }) + } + + let vec_parse = |word: &str| { + let mut iter = word.split("vec"); + + let kind = iter.next()?; + let size = iter.next()?; + let scalar = kind_width_parse(kind)?; + let size = size_parse(size)?; + + Some(Type { + name: None, + inner: TypeInner::Vector { size, scalar }, + }) + }; + + let mat_parse = |word: &str| { + let mut iter = word.split("mat"); + + let kind = iter.next()?; + let size = iter.next()?; + let scalar = kind_width_parse(kind)?; + + let (columns, rows) = if let Some(size) = size_parse(size) { + (size, size) + } else { + let mut iter = size.split('x'); + match (iter.next()?, iter.next()?, iter.next()) { + (col, row, None) => (size_parse(col)?, size_parse(row)?), + _ => return None, + } + }; + + Some(Type { + name: None, + inner: TypeInner::Matrix { + columns, + rows, + scalar, + }, + }) + }; + + let texture_parse = |word: &str| { + let mut iter = word.split("texture"); + + let texture_kind = |ty| { + Some(match ty { + "" => ScalarKind::Float, + "i" => ScalarKind::Sint, + "u" => ScalarKind::Uint, + _ => return None, + }) + }; + + let kind = iter.next()?; + let size = iter.next()?; + let kind = texture_kind(kind)?; + + let sampled = |multi| ImageClass::Sampled { kind, multi }; + + let (dim, arrayed, class) = match size { + "1D" => (ImageDimension::D1, false, sampled(false)), + "1DArray" => (ImageDimension::D1, true, sampled(false)), + "2D" => (ImageDimension::D2, false, sampled(false)), + "2DArray" => (ImageDimension::D2, true, sampled(false)), + "2DMS" => (ImageDimension::D2, false, sampled(true)), + "2DMSArray" => (ImageDimension::D2, true, sampled(true)), + "3D" => (ImageDimension::D3, false, sampled(false)), + "Cube" => (ImageDimension::Cube, false, sampled(false)), + "CubeArray" => (ImageDimension::Cube, true, sampled(false)), + _ => return None, + }; + + Some(Type { + name: None, + inner: TypeInner::Image { + dim, + arrayed, + class, + }, + }) + }; + + let image_parse = |word: &str| { + let mut iter = word.split("image"); + + let texture_kind = |ty| { + Some(match ty { + "" => ScalarKind::Float, + "i" => ScalarKind::Sint, + "u" => ScalarKind::Uint, + _ => return None, + }) + }; + + let kind = iter.next()?; + let size = iter.next()?; + // TODO: Check that the texture format and the kind match + let _ = texture_kind(kind)?; + + let class = ImageClass::Storage { + format: crate::StorageFormat::R8Uint, + access: crate::StorageAccess::all(), + }; + + // TODO: glsl support multisampled storage images, naga doesn't + let (dim, arrayed) = match size { + "1D" => (ImageDimension::D1, false), + "1DArray" => (ImageDimension::D1, true), + "2D" => (ImageDimension::D2, false), + "2DArray" => (ImageDimension::D2, true), + "3D" => (ImageDimension::D3, false), + // Naga doesn't support cube images and it's usefulness + // is questionable, so they won't be supported for now + // "Cube" => (ImageDimension::Cube, false), + // "CubeArray" => (ImageDimension::Cube, true), + _ => return None, + }; + + Some(Type { + name: None, + inner: TypeInner::Image { + dim, + arrayed, + class, + }, + }) + }; + + vec_parse(word) + .or_else(|| mat_parse(word)) + .or_else(|| texture_parse(word)) + .or_else(|| image_parse(word)) + } + } +} + +pub const fn scalar_components(ty: &TypeInner) -> Option { + match *ty { + TypeInner::Scalar(scalar) + | TypeInner::Vector { scalar, .. } + | TypeInner::ValuePointer { scalar, .. } + | TypeInner::Matrix { scalar, .. } => Some(scalar), + _ => None, + } +} + +pub const fn type_power(scalar: Scalar) -> Option { + Some(match scalar.kind { + ScalarKind::Sint => 0, + ScalarKind::Uint => 1, + ScalarKind::Float if scalar.width == 4 => 2, + ScalarKind::Float => 3, + ScalarKind::Bool | ScalarKind::AbstractInt | ScalarKind::AbstractFloat => return None, + }) +} + +impl Context<'_> { + /// Resolves the types of the expressions until `expr` (inclusive) + /// + /// This needs to be done before the [`typifier`] can be queried for + /// the types of the expressions in the range between the last grow and `expr`. + /// + /// # Note + /// + /// The `resolve_type*` methods (like [`resolve_type`]) automatically + /// grow the [`typifier`] so calling this method is not necessary when using + /// them. + /// + /// [`typifier`]: Context::typifier + /// [`resolve_type`]: Self::resolve_type + pub(crate) fn typifier_grow(&mut self, expr: Handle, meta: Span) -> Result<()> { + let resolve_ctx = ResolveContext::with_locals(self.module, &self.locals, &self.arguments); + + let typifier = if self.is_const { + &mut self.const_typifier + } else { + &mut self.typifier + }; + + let expressions = if self.is_const { + &self.module.const_expressions + } else { + &self.expressions + }; + + typifier + .grow(expr, expressions, &resolve_ctx) + .map_err(|error| Error { + kind: ErrorKind::SemanticError(format!("Can't resolve type: {error:?}").into()), + meta, + }) + } + + pub(crate) fn get_type(&self, expr: Handle) -> &TypeInner { + let typifier = if self.is_const { + &self.const_typifier + } else { + &self.typifier + }; + + typifier.get(expr, &self.module.types) + } + + /// Gets the type for the result of the `expr` expression + /// + /// Automatically grows the [`typifier`] to `expr` so calling + /// [`typifier_grow`] is not necessary + /// + /// [`typifier`]: Context::typifier + /// [`typifier_grow`]: Self::typifier_grow + pub(crate) fn resolve_type( + &mut self, + expr: Handle, + meta: Span, + ) -> Result<&TypeInner> { + self.typifier_grow(expr, meta)?; + Ok(self.get_type(expr)) + } + + /// Gets the type handle for the result of the `expr` expression + /// + /// Automatically grows the [`typifier`] to `expr` so calling + /// [`typifier_grow`] is not necessary + /// + /// # Note + /// + /// Consider using [`resolve_type`] whenever possible + /// since it doesn't require adding each type to the [`types`] arena + /// and it doesn't need to mutably borrow the [`Parser`][Self] + /// + /// [`types`]: crate::Module::types + /// [`typifier`]: Context::typifier + /// [`typifier_grow`]: Self::typifier_grow + /// [`resolve_type`]: Self::resolve_type + pub(crate) fn resolve_type_handle( + &mut self, + expr: Handle, + meta: Span, + ) -> Result> { + self.typifier_grow(expr, meta)?; + + let typifier = if self.is_const { + &mut self.const_typifier + } else { + &mut self.typifier + }; + + Ok(typifier.register_type(expr, &mut self.module.types)) + } + + /// Invalidates the cached type resolution for `expr` forcing a recomputation + pub(crate) fn invalidate_expression( + &mut self, + expr: Handle, + meta: Span, + ) -> Result<()> { + let resolve_ctx = ResolveContext::with_locals(self.module, &self.locals, &self.arguments); + + let typifier = if self.is_const { + &mut self.const_typifier + } else { + &mut self.typifier + }; + + typifier + .invalidate(expr, &self.expressions, &resolve_ctx) + .map_err(|error| Error { + kind: ErrorKind::SemanticError(format!("Can't resolve type: {error:?}").into()), + meta, + }) + } + + pub(crate) fn lift_up_const_expression( + &mut self, + expr: Handle, + ) -> Result> { + let meta = self.expressions.get_span(expr); + Ok(match self.expressions[expr] { + ref expr @ (Expression::Literal(_) + | Expression::Constant(_) + | Expression::ZeroValue(_)) => self.module.const_expressions.append(expr.clone(), meta), + Expression::Compose { ty, ref components } => { + let mut components = components.clone(); + for component in &mut components { + *component = self.lift_up_const_expression(*component)?; + } + self.module + .const_expressions + .append(Expression::Compose { ty, components }, meta) + } + Expression::Splat { size, value } => { + let value = self.lift_up_const_expression(value)?; + self.module + .const_expressions + .append(Expression::Splat { size, value }, meta) + } + _ => { + return Err(Error { + kind: ErrorKind::SemanticError("Expression is not const-expression".into()), + meta, + }) + } + }) + } +} diff --git a/naga/src/front/glsl/variables.rs b/naga/src/front/glsl/variables.rs new file mode 100644 index 0000000000..5af2b228f0 --- /dev/null +++ b/naga/src/front/glsl/variables.rs @@ -0,0 +1,646 @@ +use super::{ + ast::*, + context::{Context, ExprPos}, + error::{Error, ErrorKind}, + Frontend, Result, Span, +}; +use crate::{ + AddressSpace, Binding, BuiltIn, Constant, Expression, GlobalVariable, Handle, Interpolation, + LocalVariable, ResourceBinding, Scalar, ScalarKind, ShaderStage, SwizzleComponent, Type, + TypeInner, VectorSize, +}; + +pub struct VarDeclaration<'a, 'key> { + pub qualifiers: &'a mut TypeQualifiers<'key>, + pub ty: Handle, + pub name: Option, + pub init: Option>, + pub meta: Span, +} + +/// Information about a builtin used in [`add_builtin`](Frontend::add_builtin). +struct BuiltInData { + /// The type of the builtin. + inner: TypeInner, + /// The associated builtin class. + builtin: BuiltIn, + /// Whether the builtin can be written to or not. + mutable: bool, + /// The storage used for the builtin. + storage: StorageQualifier, +} + +pub enum GlobalOrConstant { + Global(Handle), + Constant(Handle), +} + +impl Frontend { + /// Adds a builtin and returns a variable reference to it + fn add_builtin( + &mut self, + ctx: &mut Context, + name: &str, + data: BuiltInData, + meta: Span, + ) -> Result> { + let ty = ctx.module.types.insert( + Type { + name: None, + inner: data.inner, + }, + meta, + ); + + let handle = ctx.module.global_variables.append( + GlobalVariable { + name: Some(name.into()), + space: AddressSpace::Private, + binding: None, + ty, + init: None, + }, + meta, + ); + + let idx = self.entry_args.len(); + self.entry_args.push(EntryArg { + name: None, + binding: Binding::BuiltIn(data.builtin), + handle, + storage: data.storage, + }); + + self.global_variables.push(( + name.into(), + GlobalLookup { + kind: GlobalLookupKind::Variable(handle), + entry_arg: Some(idx), + mutable: data.mutable, + }, + )); + + let expr = ctx.add_expression(Expression::GlobalVariable(handle), meta)?; + + let var = VariableReference { + expr, + load: true, + mutable: data.mutable, + constant: None, + entry_arg: Some(idx), + }; + + ctx.symbol_table.add_root(name.into(), var.clone()); + + Ok(Some(var)) + } + + pub(crate) fn lookup_variable( + &mut self, + ctx: &mut Context, + name: &str, + meta: Span, + ) -> Result> { + if let Some(var) = ctx.symbol_table.lookup(name).cloned() { + return Ok(Some(var)); + } + + let data = match name { + "gl_Position" => BuiltInData { + inner: TypeInner::Vector { + size: VectorSize::Quad, + scalar: Scalar::F32, + }, + builtin: BuiltIn::Position { invariant: false }, + mutable: true, + storage: StorageQualifier::Output, + }, + "gl_FragCoord" => BuiltInData { + inner: TypeInner::Vector { + size: VectorSize::Quad, + scalar: Scalar::F32, + }, + builtin: BuiltIn::Position { invariant: false }, + mutable: false, + storage: StorageQualifier::Input, + }, + "gl_PointCoord" => BuiltInData { + inner: TypeInner::Vector { + size: VectorSize::Bi, + scalar: Scalar::F32, + }, + builtin: BuiltIn::PointCoord, + mutable: false, + storage: StorageQualifier::Input, + }, + "gl_GlobalInvocationID" + | "gl_NumWorkGroups" + | "gl_WorkGroupSize" + | "gl_WorkGroupID" + | "gl_LocalInvocationID" => BuiltInData { + inner: TypeInner::Vector { + size: VectorSize::Tri, + scalar: Scalar::U32, + }, + builtin: match name { + "gl_GlobalInvocationID" => BuiltIn::GlobalInvocationId, + "gl_NumWorkGroups" => BuiltIn::NumWorkGroups, + "gl_WorkGroupSize" => BuiltIn::WorkGroupSize, + "gl_WorkGroupID" => BuiltIn::WorkGroupId, + "gl_LocalInvocationID" => BuiltIn::LocalInvocationId, + _ => unreachable!(), + }, + mutable: false, + storage: StorageQualifier::Input, + }, + "gl_FrontFacing" => BuiltInData { + inner: TypeInner::Scalar(Scalar::BOOL), + builtin: BuiltIn::FrontFacing, + mutable: false, + storage: StorageQualifier::Input, + }, + "gl_PointSize" | "gl_FragDepth" => BuiltInData { + inner: TypeInner::Scalar(Scalar::F32), + builtin: match name { + "gl_PointSize" => BuiltIn::PointSize, + "gl_FragDepth" => BuiltIn::FragDepth, + _ => unreachable!(), + }, + mutable: true, + storage: StorageQualifier::Output, + }, + "gl_ClipDistance" | "gl_CullDistance" => { + let base = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Scalar(Scalar::F32), + }, + meta, + ); + + BuiltInData { + inner: TypeInner::Array { + base, + size: crate::ArraySize::Dynamic, + stride: 4, + }, + builtin: match name { + "gl_ClipDistance" => BuiltIn::ClipDistance, + "gl_CullDistance" => BuiltIn::CullDistance, + _ => unreachable!(), + }, + mutable: self.meta.stage == ShaderStage::Vertex, + storage: StorageQualifier::Output, + } + } + _ => { + let builtin = match name { + "gl_BaseVertex" => BuiltIn::BaseVertex, + "gl_BaseInstance" => BuiltIn::BaseInstance, + "gl_PrimitiveID" => BuiltIn::PrimitiveIndex, + "gl_InstanceIndex" => BuiltIn::InstanceIndex, + "gl_VertexIndex" => BuiltIn::VertexIndex, + "gl_SampleID" => BuiltIn::SampleIndex, + "gl_LocalInvocationIndex" => BuiltIn::LocalInvocationIndex, + _ => return Ok(None), + }; + + BuiltInData { + inner: TypeInner::Scalar(Scalar::U32), + builtin, + mutable: false, + storage: StorageQualifier::Input, + } + } + }; + + self.add_builtin(ctx, name, data, meta) + } + + pub(crate) fn make_variable_invariant( + &mut self, + ctx: &mut Context, + name: &str, + meta: Span, + ) -> Result<()> { + if let Some(var) = self.lookup_variable(ctx, name, meta)? { + if let Some(index) = var.entry_arg { + if let Binding::BuiltIn(BuiltIn::Position { ref mut invariant }) = + self.entry_args[index].binding + { + *invariant = true; + } + } + } + Ok(()) + } + + pub(crate) fn field_selection( + &mut self, + ctx: &mut Context, + pos: ExprPos, + expression: Handle, + name: &str, + meta: Span, + ) -> Result> { + let (ty, is_pointer) = match *ctx.resolve_type(expression, meta)? { + TypeInner::Pointer { base, .. } => (&ctx.module.types[base].inner, true), + ref ty => (ty, false), + }; + match *ty { + TypeInner::Struct { ref members, .. } => { + let index = members + .iter() + .position(|m| m.name == Some(name.into())) + .ok_or_else(|| Error { + kind: ErrorKind::UnknownField(name.into()), + meta, + })?; + let pointer = ctx.add_expression( + Expression::AccessIndex { + base: expression, + index: index as u32, + }, + meta, + )?; + + Ok(match pos { + ExprPos::Rhs if is_pointer => { + ctx.add_expression(Expression::Load { pointer }, meta)? + } + _ => pointer, + }) + } + // swizzles (xyzw, rgba, stpq) + TypeInner::Vector { size, .. } => { + let check_swizzle_components = |comps: &str| { + name.chars() + .map(|c| { + comps + .find(c) + .filter(|i| *i < size as usize) + .map(|i| SwizzleComponent::from_index(i as u32)) + }) + .collect::>>() + }; + + let components = check_swizzle_components("xyzw") + .or_else(|| check_swizzle_components("rgba")) + .or_else(|| check_swizzle_components("stpq")); + + if let Some(components) = components { + if let ExprPos::Lhs = pos { + let not_unique = (1..components.len()) + .any(|i| components[i..].contains(&components[i - 1])); + if not_unique { + self.errors.push(Error { + kind: + ErrorKind::SemanticError( + format!( + "swizzle cannot have duplicate components in left-hand-side expression for \"{name:?}\"" + ) + .into(), + ), + meta , + }) + } + } + + let mut pattern = [SwizzleComponent::X; 4]; + for (pat, component) in pattern.iter_mut().zip(&components) { + *pat = *component; + } + + // flatten nested swizzles (vec.zyx.xy.x => vec.z) + let mut expression = expression; + if let Expression::Swizzle { + size: _, + vector, + pattern: ref src_pattern, + } = ctx[expression] + { + expression = vector; + for pat in &mut pattern { + *pat = src_pattern[pat.index() as usize]; + } + } + + let size = match components.len() { + // Swizzles with just one component are accesses and not swizzles + 1 => { + match pos { + // If the position is in the right hand side and the base + // vector is a pointer, load it, otherwise the swizzle would + // produce a pointer + ExprPos::Rhs if is_pointer => { + expression = ctx.add_expression( + Expression::Load { + pointer: expression, + }, + meta, + )?; + } + _ => {} + }; + return ctx.add_expression( + Expression::AccessIndex { + base: expression, + index: pattern[0].index(), + }, + meta, + ); + } + 2 => VectorSize::Bi, + 3 => VectorSize::Tri, + 4 => VectorSize::Quad, + _ => { + self.errors.push(Error { + kind: ErrorKind::SemanticError( + format!("Bad swizzle size for \"{name:?}\"").into(), + ), + meta, + }); + + VectorSize::Quad + } + }; + + if is_pointer { + // NOTE: for lhs expression, this extra load ends up as an unused expr, because the + // assignment will extract the pointer and use it directly anyway. Unfortunately we + // need it for validation to pass, as swizzles cannot operate on pointer values. + expression = ctx.add_expression( + Expression::Load { + pointer: expression, + }, + meta, + )?; + } + + Ok(ctx.add_expression( + Expression::Swizzle { + size, + vector: expression, + pattern, + }, + meta, + )?) + } else { + Err(Error { + kind: ErrorKind::SemanticError( + format!("Invalid swizzle for vector \"{name}\"").into(), + ), + meta, + }) + } + } + _ => Err(Error { + kind: ErrorKind::SemanticError( + format!("Can't lookup field on this type \"{name}\"").into(), + ), + meta, + }), + } + } + + pub(crate) fn add_global_var( + &mut self, + ctx: &mut Context, + VarDeclaration { + qualifiers, + mut ty, + name, + init, + meta, + }: VarDeclaration, + ) -> Result { + let storage = qualifiers.storage.0; + let (ret, lookup) = match storage { + StorageQualifier::Input | StorageQualifier::Output => { + let input = storage == StorageQualifier::Input; + // TODO: glslang seems to use a counter for variables without + // explicit location (even if that causes collisions) + let location = qualifiers + .uint_layout_qualifier("location", &mut self.errors) + .unwrap_or(0); + let interpolation = qualifiers.interpolation.take().map(|(i, _)| i).or_else(|| { + let kind = ctx.module.types[ty].inner.scalar_kind()?; + Some(match kind { + ScalarKind::Float => Interpolation::Perspective, + _ => Interpolation::Flat, + }) + }); + let sampling = qualifiers.sampling.take().map(|(s, _)| s); + + let handle = ctx.module.global_variables.append( + GlobalVariable { + name: name.clone(), + space: AddressSpace::Private, + binding: None, + ty, + init, + }, + meta, + ); + + let idx = self.entry_args.len(); + self.entry_args.push(EntryArg { + name: name.clone(), + binding: Binding::Location { + location, + interpolation, + sampling, + second_blend_source: false, + }, + handle, + storage, + }); + + let lookup = GlobalLookup { + kind: GlobalLookupKind::Variable(handle), + entry_arg: Some(idx), + mutable: !input, + }; + + (GlobalOrConstant::Global(handle), lookup) + } + StorageQualifier::Const => { + let init = init.ok_or_else(|| Error { + kind: ErrorKind::SemanticError("const values must have an initializer".into()), + meta, + })?; + + let constant = Constant { + name: name.clone(), + r#override: crate::Override::None, + ty, + init, + }; + let handle = ctx.module.constants.fetch_or_append(constant, meta); + + let lookup = GlobalLookup { + kind: GlobalLookupKind::Constant(handle, ty), + entry_arg: None, + mutable: false, + }; + + (GlobalOrConstant::Constant(handle), lookup) + } + StorageQualifier::AddressSpace(mut space) => { + match space { + AddressSpace::Storage { ref mut access } => { + if let Some((allowed_access, _)) = qualifiers.storage_access.take() { + *access = allowed_access; + } + } + AddressSpace::Uniform => match ctx.module.types[ty].inner { + TypeInner::Image { + class, + dim, + arrayed, + } => { + if let crate::ImageClass::Storage { + mut access, + mut format, + } = class + { + if let Some((allowed_access, _)) = qualifiers.storage_access.take() + { + access = allowed_access; + } + + match qualifiers.layout_qualifiers.remove(&QualifierKey::Format) { + Some((QualifierValue::Format(f), _)) => format = f, + // TODO: glsl supports images without format qualifier + // if they are `writeonly` + None => self.errors.push(Error { + kind: ErrorKind::SemanticError( + "image types require a format layout qualifier".into(), + ), + meta, + }), + _ => unreachable!(), + } + + ty = ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Image { + dim, + arrayed, + class: crate::ImageClass::Storage { format, access }, + }, + }, + meta, + ); + } + + space = AddressSpace::Handle + } + TypeInner::Sampler { .. } => space = AddressSpace::Handle, + _ => { + if qualifiers.none_layout_qualifier("push_constant", &mut self.errors) { + space = AddressSpace::PushConstant + } + } + }, + AddressSpace::Function => space = AddressSpace::Private, + _ => {} + }; + + let binding = match space { + AddressSpace::Uniform | AddressSpace::Storage { .. } | AddressSpace::Handle => { + let binding = qualifiers.uint_layout_qualifier("binding", &mut self.errors); + if binding.is_none() { + self.errors.push(Error { + kind: ErrorKind::SemanticError( + "uniform/buffer blocks require layout(binding=X)".into(), + ), + meta, + }); + } + let set = qualifiers.uint_layout_qualifier("set", &mut self.errors); + binding.map(|binding| ResourceBinding { + group: set.unwrap_or(0), + binding, + }) + } + _ => None, + }; + + let handle = ctx.module.global_variables.append( + GlobalVariable { + name: name.clone(), + space, + binding, + ty, + init, + }, + meta, + ); + + let lookup = GlobalLookup { + kind: GlobalLookupKind::Variable(handle), + entry_arg: None, + mutable: true, + }; + + (GlobalOrConstant::Global(handle), lookup) + } + }; + + if let Some(name) = name { + ctx.add_global(&name, lookup)?; + + self.global_variables.push((name, lookup)); + } + + qualifiers.unused_errors(&mut self.errors); + + Ok(ret) + } + + pub(crate) fn add_local_var( + &mut self, + ctx: &mut Context, + decl: VarDeclaration, + ) -> Result> { + let storage = decl.qualifiers.storage; + let mutable = match storage.0 { + StorageQualifier::AddressSpace(AddressSpace::Function) => true, + StorageQualifier::Const => false, + _ => { + self.errors.push(Error { + kind: ErrorKind::SemanticError("Locals cannot have a storage qualifier".into()), + meta: storage.1, + }); + true + } + }; + + let handle = ctx.locals.append( + LocalVariable { + name: decl.name.clone(), + ty: decl.ty, + init: decl.init, + }, + decl.meta, + ); + let expr = ctx.add_expression(Expression::LocalVariable(handle), decl.meta)?; + + if let Some(name) = decl.name { + let maybe_var = ctx.add_local_var(name.clone(), expr, mutable); + + if maybe_var.is_some() { + self.errors.push(Error { + kind: ErrorKind::VariableAlreadyDeclared(name), + meta: decl.meta, + }) + } + } + + decl.qualifiers.unused_errors(&mut self.errors); + + Ok(expr) + } +} diff --git a/naga/src/front/interpolator.rs b/naga/src/front/interpolator.rs new file mode 100644 index 0000000000..0196a2254d --- /dev/null +++ b/naga/src/front/interpolator.rs @@ -0,0 +1,62 @@ +/*! +Interpolation defaults. +*/ + +impl crate::Binding { + /// Apply the usual default interpolation for `ty` to `binding`. + /// + /// This function is a utility front ends may use to satisfy the Naga IR's + /// requirement, meant to ensure that input languages' policies have been + /// applied appropriately, that all I/O `Binding`s from the vertex shader to the + /// fragment shader must have non-`None` `interpolation` values. + /// + /// All the shader languages Naga supports have similar rules: + /// perspective-correct, center-sampled interpolation is the default for any + /// binding that can vary, and everything else either defaults to flat, or + /// requires an explicit flat qualifier/attribute/what-have-you. + /// + /// If `binding` is not a [`Location`] binding, or if its [`interpolation`] is + /// already set, then make no changes. Otherwise, set `binding`'s interpolation + /// and sampling to reasonable defaults depending on `ty`, the type of the value + /// being interpolated: + /// + /// - If `ty` is a floating-point scalar, vector, or matrix type, then + /// default to [`Perspective`] interpolation and [`Center`] sampling. + /// + /// - If `ty` is an integral scalar or vector, then default to [`Flat`] + /// interpolation, which has no associated sampling. + /// + /// - For any other types, make no change. Such types are not permitted as + /// user-defined IO values, and will probably be flagged by the verifier + /// + /// When structs appear in input or output types, each member ought to have its + /// own [`Binding`], so structs are simply covered by the third case. + /// + /// [`Binding`]: crate::Binding + /// [`Location`]: crate::Binding::Location + /// [`interpolation`]: crate::Binding::Location::interpolation + /// [`Perspective`]: crate::Interpolation::Perspective + /// [`Flat`]: crate::Interpolation::Flat + /// [`Center`]: crate::Sampling::Center + pub fn apply_default_interpolation(&mut self, ty: &crate::TypeInner) { + if let crate::Binding::Location { + location: _, + interpolation: ref mut interpolation @ None, + ref mut sampling, + second_blend_source: _, + } = *self + { + match ty.scalar_kind() { + Some(crate::ScalarKind::Float) => { + *interpolation = Some(crate::Interpolation::Perspective); + *sampling = Some(crate::Sampling::Center); + } + Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) => { + *interpolation = Some(crate::Interpolation::Flat); + *sampling = None; + } + Some(_) | None => {} + } + } + } +} diff --git a/naga/src/front/mod.rs b/naga/src/front/mod.rs new file mode 100644 index 0000000000..634c809fb9 --- /dev/null +++ b/naga/src/front/mod.rs @@ -0,0 +1,328 @@ +/*! +Frontend parsers that consume binary and text shaders and load them into [`Module`](super::Module)s. +*/ + +mod interpolator; +mod type_gen; + +#[cfg(feature = "glsl-in")] +pub mod glsl; +#[cfg(feature = "spv-in")] +pub mod spv; +#[cfg(feature = "wgsl-in")] +pub mod wgsl; + +use crate::{ + arena::{Arena, Handle, UniqueArena}, + proc::{ResolveContext, ResolveError, TypeResolution}, + FastHashMap, +}; +use std::ops; + +/// A table of types for an `Arena`. +/// +/// A front end can use a `Typifier` to get types for an arena's expressions +/// while it is still contributing expressions to it. At any point, you can call +/// [`typifier.grow(expr, arena, ctx)`], where `expr` is a `Handle` +/// referring to something in `arena`, and the `Typifier` will resolve the types +/// of all the expressions up to and including `expr`. Then you can write +/// `typifier[handle]` to get the type of any handle at or before `expr`. +/// +/// Note that `Typifier` does *not* build an `Arena` as a part of its +/// usual operation. Ideally, a module's type arena should only contain types +/// actually needed by `Handle`s elsewhere in the module — functions, +/// variables, [`Compose`] expressions, other types, and so on — so we don't +/// want every little thing that occurs as the type of some intermediate +/// expression to show up there. +/// +/// Instead, `Typifier` accumulates a [`TypeResolution`] for each expression, +/// which refers to the `Arena` in the [`ResolveContext`] passed to `grow` +/// as needed. [`TypeResolution`] is a lightweight representation for +/// intermediate types like this; see its documentation for details. +/// +/// If you do need to register a `Typifier`'s conclusion in an `Arena` +/// (say, for a [`LocalVariable`] whose type you've inferred), you can use +/// [`register_type`] to do so. +/// +/// [`typifier.grow(expr, arena)`]: Typifier::grow +/// [`register_type`]: Typifier::register_type +/// [`Compose`]: crate::Expression::Compose +/// [`LocalVariable`]: crate::LocalVariable +#[derive(Debug, Default)] +pub struct Typifier { + resolutions: Vec, +} + +impl Typifier { + pub const fn new() -> Self { + Typifier { + resolutions: Vec::new(), + } + } + + pub fn reset(&mut self) { + self.resolutions.clear() + } + + pub fn get<'a>( + &'a self, + expr_handle: Handle, + types: &'a UniqueArena, + ) -> &'a crate::TypeInner { + self.resolutions[expr_handle.index()].inner_with(types) + } + + /// Add an expression's type to an `Arena`. + /// + /// Add the type of `expr_handle` to `types`, and return a `Handle` + /// referring to it. + /// + /// # Note + /// + /// If you just need a [`TypeInner`] for `expr_handle`'s type, consider + /// using `typifier[expression].inner_with(types)` instead. Calling + /// [`TypeResolution::inner_with`] often lets us avoid adding anything to + /// the arena, which can significantly reduce the number of types that end + /// up in the final module. + /// + /// [`TypeInner`]: crate::TypeInner + pub fn register_type( + &self, + expr_handle: Handle, + types: &mut UniqueArena, + ) -> Handle { + match self[expr_handle].clone() { + TypeResolution::Handle(handle) => handle, + TypeResolution::Value(inner) => { + types.insert(crate::Type { name: None, inner }, crate::Span::UNDEFINED) + } + } + } + + /// Grow this typifier until it contains a type for `expr_handle`. + pub fn grow( + &mut self, + expr_handle: Handle, + expressions: &Arena, + ctx: &ResolveContext, + ) -> Result<(), ResolveError> { + if self.resolutions.len() <= expr_handle.index() { + for (eh, expr) in expressions.iter().skip(self.resolutions.len()) { + //Note: the closure can't `Err` by construction + let resolution = ctx.resolve(expr, |h| Ok(&self.resolutions[h.index()]))?; + log::debug!("Resolving {:?} = {:?} : {:?}", eh, expr, resolution); + self.resolutions.push(resolution); + } + } + Ok(()) + } + + /// Recompute the type resolution for `expr_handle`. + /// + /// If the type of `expr_handle` hasn't yet been calculated, call + /// [`grow`](Self::grow) to ensure it is covered. + /// + /// In either case, when this returns, `self[expr_handle]` should be an + /// updated type resolution for `expr_handle`. + pub fn invalidate( + &mut self, + expr_handle: Handle, + expressions: &Arena, + ctx: &ResolveContext, + ) -> Result<(), ResolveError> { + if self.resolutions.len() <= expr_handle.index() { + self.grow(expr_handle, expressions, ctx) + } else { + let expr = &expressions[expr_handle]; + //Note: the closure can't `Err` by construction + let resolution = ctx.resolve(expr, |h| Ok(&self.resolutions[h.index()]))?; + self.resolutions[expr_handle.index()] = resolution; + Ok(()) + } + } +} + +impl ops::Index> for Typifier { + type Output = TypeResolution; + fn index(&self, handle: Handle) -> &Self::Output { + &self.resolutions[handle.index()] + } +} + +/// Type representing a lexical scope, associating a name to a single variable +/// +/// The scope is generic over the variable representation and name representaion +/// in order to allow larger flexibility on the frontends on how they might +/// represent them. +type Scope = FastHashMap; + +/// Structure responsible for managing variable lookups and keeping track of +/// lexical scopes +/// +/// The symbol table is generic over the variable representation and its name +/// to allow larger flexibility on the frontends on how they might represent them. +/// +/// ``` +/// use naga::front::SymbolTable; +/// +/// // Create a new symbol table with `u32`s representing the variable +/// let mut symbol_table: SymbolTable<&str, u32> = SymbolTable::default(); +/// +/// // Add two variables named `var1` and `var2` with 0 and 2 respectively +/// symbol_table.add("var1", 0); +/// symbol_table.add("var2", 2); +/// +/// // Check that `var1` exists and is `0` +/// assert_eq!(symbol_table.lookup("var1"), Some(&0)); +/// +/// // Push a new scope and add a variable to it named `var1` shadowing the +/// // variable of our previous scope +/// symbol_table.push_scope(); +/// symbol_table.add("var1", 1); +/// +/// // Check that `var1` now points to the new value of `1` and `var2` still +/// // exists with its value of `2` +/// assert_eq!(symbol_table.lookup("var1"), Some(&1)); +/// assert_eq!(symbol_table.lookup("var2"), Some(&2)); +/// +/// // Pop the scope +/// symbol_table.pop_scope(); +/// +/// // Check that `var1` now refers to our initial variable with value `0` +/// assert_eq!(symbol_table.lookup("var1"), Some(&0)); +/// ``` +/// +/// Scopes are ordered as a LIFO stack so a variable defined in a later scope +/// with the same name as another variable defined in a earlier scope will take +/// precedence in the lookup. Scopes can be added with [`push_scope`] and +/// removed with [`pop_scope`]. +/// +/// A root scope is added when the symbol table is created and must always be +/// present. Trying to pop it will result in a panic. +/// +/// Variables can be added with [`add`] and looked up with [`lookup`]. Adding a +/// variable will do so in the currently active scope and as mentioned +/// previously a lookup will search from the current scope to the root scope. +/// +/// [`push_scope`]: Self::push_scope +/// [`pop_scope`]: Self::push_scope +/// [`add`]: Self::add +/// [`lookup`]: Self::lookup +pub struct SymbolTable { + /// Stack of lexical scopes. Not all scopes are active; see [`cursor`]. + /// + /// [`cursor`]: Self::cursor + scopes: Vec>, + /// Limit of the [`scopes`] stack (exclusive). By using a separate value for + /// the stack length instead of `Vec`'s own internal length, the scopes can + /// be reused to cache memory allocations. + /// + /// [`scopes`]: Self::scopes + cursor: usize, +} + +impl SymbolTable { + /// Adds a new lexical scope. + /// + /// All variables declared after this point will be added to this scope + /// until another scope is pushed or [`pop_scope`] is called, causing this + /// scope to be removed along with all variables added to it. + /// + /// [`pop_scope`]: Self::pop_scope + pub fn push_scope(&mut self) { + // If the cursor is equal to the scope's stack length then we need to + // push another empty scope. Otherwise we can reuse the already existing + // scope. + if self.scopes.len() == self.cursor { + self.scopes.push(FastHashMap::default()) + } else { + self.scopes[self.cursor].clear(); + } + + self.cursor += 1; + } + + /// Removes the current lexical scope and all its variables + /// + /// # PANICS + /// - If the current lexical scope is the root scope + pub fn pop_scope(&mut self) { + // Despite the method title, the variables are only deleted when the + // scope is reused. This is because while a clear is inevitable if the + // scope needs to be reused, there are cases where the scope might be + // popped and not reused, i.e. if another scope with the same nesting + // level is never pushed again. + assert!(self.cursor != 1, "Tried to pop the root scope"); + + self.cursor -= 1; + } +} + +impl SymbolTable +where + Name: std::hash::Hash + Eq, +{ + /// Perform a lookup for a variable named `name`. + /// + /// As stated in the struct level documentation the lookup will proceed from + /// the current scope to the root scope, returning `Some` when a variable is + /// found or `None` if there doesn't exist a variable with `name` in any + /// scope. + pub fn lookup(&self, name: &Q) -> Option<&Var> + where + Name: std::borrow::Borrow, + Q: std::hash::Hash + Eq, + { + // Iterate backwards trough the scopes and try to find the variable + for scope in self.scopes[..self.cursor].iter().rev() { + if let Some(var) = scope.get(name) { + return Some(var); + } + } + + None + } + + /// Adds a new variable to the current scope. + /// + /// Returns the previous variable with the same name in this scope if it + /// exists, so that the frontend might handle it in case variable shadowing + /// is disallowed. + pub fn add(&mut self, name: Name, var: Var) -> Option { + self.scopes[self.cursor - 1].insert(name, var) + } + + /// Adds a new variable to the root scope. + /// + /// This is used in GLSL for builtins which aren't known in advance and only + /// when used for the first time, so there must be a way to add those + /// declarations to the root unconditionally from the current scope. + /// + /// Returns the previous variable with the same name in the root scope if it + /// exists, so that the frontend might handle it in case variable shadowing + /// is disallowed. + pub fn add_root(&mut self, name: Name, var: Var) -> Option { + self.scopes[0].insert(name, var) + } +} + +impl Default for SymbolTable { + /// Constructs a new symbol table with a root scope + fn default() -> Self { + Self { + scopes: vec![FastHashMap::default()], + cursor: 1, + } + } +} + +use std::fmt; + +impl fmt::Debug for SymbolTable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("SymbolTable ")?; + f.debug_list() + .entries(self.scopes[..self.cursor].iter()) + .finish() + } +} diff --git a/naga/src/front/spv/convert.rs b/naga/src/front/spv/convert.rs new file mode 100644 index 0000000000..f0a714fbeb --- /dev/null +++ b/naga/src/front/spv/convert.rs @@ -0,0 +1,179 @@ +use super::error::Error; +use std::convert::TryInto; + +pub(super) const fn map_binary_operator(word: spirv::Op) -> Result { + use crate::BinaryOperator; + use spirv::Op; + + match word { + // Arithmetic Instructions +, -, *, /, % + Op::IAdd | Op::FAdd => Ok(BinaryOperator::Add), + Op::ISub | Op::FSub => Ok(BinaryOperator::Subtract), + Op::IMul | Op::FMul => Ok(BinaryOperator::Multiply), + Op::UDiv | Op::SDiv | Op::FDiv => Ok(BinaryOperator::Divide), + Op::SRem => Ok(BinaryOperator::Modulo), + // Relational and Logical Instructions + Op::IEqual | Op::FOrdEqual | Op::FUnordEqual | Op::LogicalEqual => { + Ok(BinaryOperator::Equal) + } + Op::INotEqual | Op::FOrdNotEqual | Op::FUnordNotEqual | Op::LogicalNotEqual => { + Ok(BinaryOperator::NotEqual) + } + Op::ULessThan | Op::SLessThan | Op::FOrdLessThan | Op::FUnordLessThan => { + Ok(BinaryOperator::Less) + } + Op::ULessThanEqual + | Op::SLessThanEqual + | Op::FOrdLessThanEqual + | Op::FUnordLessThanEqual => Ok(BinaryOperator::LessEqual), + Op::UGreaterThan | Op::SGreaterThan | Op::FOrdGreaterThan | Op::FUnordGreaterThan => { + Ok(BinaryOperator::Greater) + } + Op::UGreaterThanEqual + | Op::SGreaterThanEqual + | Op::FOrdGreaterThanEqual + | Op::FUnordGreaterThanEqual => Ok(BinaryOperator::GreaterEqual), + Op::BitwiseOr => Ok(BinaryOperator::InclusiveOr), + Op::BitwiseXor => Ok(BinaryOperator::ExclusiveOr), + Op::BitwiseAnd => Ok(BinaryOperator::And), + _ => Err(Error::UnknownBinaryOperator(word)), + } +} + +pub(super) const fn map_relational_fun( + word: spirv::Op, +) -> Result { + use crate::RelationalFunction as Rf; + use spirv::Op; + + match word { + Op::All => Ok(Rf::All), + Op::Any => Ok(Rf::Any), + Op::IsNan => Ok(Rf::IsNan), + Op::IsInf => Ok(Rf::IsInf), + _ => Err(Error::UnknownRelationalFunction(word)), + } +} + +pub(super) const fn map_vector_size(word: spirv::Word) -> Result { + match word { + 2 => Ok(crate::VectorSize::Bi), + 3 => Ok(crate::VectorSize::Tri), + 4 => Ok(crate::VectorSize::Quad), + _ => Err(Error::InvalidVectorSize(word)), + } +} + +pub(super) fn map_image_dim(word: spirv::Word) -> Result { + use spirv::Dim as D; + match D::from_u32(word) { + Some(D::Dim1D) => Ok(crate::ImageDimension::D1), + Some(D::Dim2D) => Ok(crate::ImageDimension::D2), + Some(D::Dim3D) => Ok(crate::ImageDimension::D3), + Some(D::DimCube) => Ok(crate::ImageDimension::Cube), + _ => Err(Error::UnsupportedImageDim(word)), + } +} + +pub(super) fn map_image_format(word: spirv::Word) -> Result { + match spirv::ImageFormat::from_u32(word) { + Some(spirv::ImageFormat::R8) => Ok(crate::StorageFormat::R8Unorm), + Some(spirv::ImageFormat::R8Snorm) => Ok(crate::StorageFormat::R8Snorm), + Some(spirv::ImageFormat::R8ui) => Ok(crate::StorageFormat::R8Uint), + Some(spirv::ImageFormat::R8i) => Ok(crate::StorageFormat::R8Sint), + Some(spirv::ImageFormat::R16) => Ok(crate::StorageFormat::R16Unorm), + Some(spirv::ImageFormat::R16Snorm) => Ok(crate::StorageFormat::R16Snorm), + Some(spirv::ImageFormat::R16ui) => Ok(crate::StorageFormat::R16Uint), + Some(spirv::ImageFormat::R16i) => Ok(crate::StorageFormat::R16Sint), + Some(spirv::ImageFormat::R16f) => Ok(crate::StorageFormat::R16Float), + Some(spirv::ImageFormat::Rg8) => Ok(crate::StorageFormat::Rg8Unorm), + Some(spirv::ImageFormat::Rg8Snorm) => Ok(crate::StorageFormat::Rg8Snorm), + Some(spirv::ImageFormat::Rg8ui) => Ok(crate::StorageFormat::Rg8Uint), + Some(spirv::ImageFormat::Rg8i) => Ok(crate::StorageFormat::Rg8Sint), + Some(spirv::ImageFormat::R32ui) => Ok(crate::StorageFormat::R32Uint), + Some(spirv::ImageFormat::R32i) => Ok(crate::StorageFormat::R32Sint), + Some(spirv::ImageFormat::R32f) => Ok(crate::StorageFormat::R32Float), + Some(spirv::ImageFormat::Rg16) => Ok(crate::StorageFormat::Rg16Unorm), + Some(spirv::ImageFormat::Rg16Snorm) => Ok(crate::StorageFormat::Rg16Snorm), + Some(spirv::ImageFormat::Rg16ui) => Ok(crate::StorageFormat::Rg16Uint), + Some(spirv::ImageFormat::Rg16i) => Ok(crate::StorageFormat::Rg16Sint), + Some(spirv::ImageFormat::Rg16f) => Ok(crate::StorageFormat::Rg16Float), + Some(spirv::ImageFormat::Rgba8) => Ok(crate::StorageFormat::Rgba8Unorm), + Some(spirv::ImageFormat::Rgba8Snorm) => Ok(crate::StorageFormat::Rgba8Snorm), + Some(spirv::ImageFormat::Rgba8ui) => Ok(crate::StorageFormat::Rgba8Uint), + Some(spirv::ImageFormat::Rgba8i) => Ok(crate::StorageFormat::Rgba8Sint), + Some(spirv::ImageFormat::Rgb10a2ui) => Ok(crate::StorageFormat::Rgb10a2Uint), + Some(spirv::ImageFormat::Rgb10A2) => Ok(crate::StorageFormat::Rgb10a2Unorm), + Some(spirv::ImageFormat::R11fG11fB10f) => Ok(crate::StorageFormat::Rg11b10Float), + Some(spirv::ImageFormat::Rg32ui) => Ok(crate::StorageFormat::Rg32Uint), + Some(spirv::ImageFormat::Rg32i) => Ok(crate::StorageFormat::Rg32Sint), + Some(spirv::ImageFormat::Rg32f) => Ok(crate::StorageFormat::Rg32Float), + Some(spirv::ImageFormat::Rgba16) => Ok(crate::StorageFormat::Rgba16Unorm), + Some(spirv::ImageFormat::Rgba16Snorm) => Ok(crate::StorageFormat::Rgba16Snorm), + Some(spirv::ImageFormat::Rgba16ui) => Ok(crate::StorageFormat::Rgba16Uint), + Some(spirv::ImageFormat::Rgba16i) => Ok(crate::StorageFormat::Rgba16Sint), + Some(spirv::ImageFormat::Rgba16f) => Ok(crate::StorageFormat::Rgba16Float), + Some(spirv::ImageFormat::Rgba32ui) => Ok(crate::StorageFormat::Rgba32Uint), + Some(spirv::ImageFormat::Rgba32i) => Ok(crate::StorageFormat::Rgba32Sint), + Some(spirv::ImageFormat::Rgba32f) => Ok(crate::StorageFormat::Rgba32Float), + _ => Err(Error::UnsupportedImageFormat(word)), + } +} + +pub(super) fn map_width(word: spirv::Word) -> Result { + (word >> 3) // bits to bytes + .try_into() + .map_err(|_| Error::InvalidTypeWidth(word)) +} + +pub(super) fn map_builtin(word: spirv::Word, invariant: bool) -> Result { + use spirv::BuiltIn as Bi; + Ok(match spirv::BuiltIn::from_u32(word) { + Some(Bi::Position | Bi::FragCoord) => crate::BuiltIn::Position { invariant }, + Some(Bi::ViewIndex) => crate::BuiltIn::ViewIndex, + // vertex + Some(Bi::BaseInstance) => crate::BuiltIn::BaseInstance, + Some(Bi::BaseVertex) => crate::BuiltIn::BaseVertex, + Some(Bi::ClipDistance) => crate::BuiltIn::ClipDistance, + Some(Bi::CullDistance) => crate::BuiltIn::CullDistance, + Some(Bi::InstanceIndex) => crate::BuiltIn::InstanceIndex, + Some(Bi::PointSize) => crate::BuiltIn::PointSize, + Some(Bi::VertexIndex) => crate::BuiltIn::VertexIndex, + // fragment + Some(Bi::FragDepth) => crate::BuiltIn::FragDepth, + Some(Bi::PointCoord) => crate::BuiltIn::PointCoord, + Some(Bi::FrontFacing) => crate::BuiltIn::FrontFacing, + Some(Bi::PrimitiveId) => crate::BuiltIn::PrimitiveIndex, + Some(Bi::SampleId) => crate::BuiltIn::SampleIndex, + Some(Bi::SampleMask) => crate::BuiltIn::SampleMask, + // compute + Some(Bi::GlobalInvocationId) => crate::BuiltIn::GlobalInvocationId, + Some(Bi::LocalInvocationId) => crate::BuiltIn::LocalInvocationId, + Some(Bi::LocalInvocationIndex) => crate::BuiltIn::LocalInvocationIndex, + Some(Bi::WorkgroupId) => crate::BuiltIn::WorkGroupId, + Some(Bi::WorkgroupSize) => crate::BuiltIn::WorkGroupSize, + Some(Bi::NumWorkgroups) => crate::BuiltIn::NumWorkGroups, + _ => return Err(Error::UnsupportedBuiltIn(word)), + }) +} + +pub(super) fn map_storage_class(word: spirv::Word) -> Result { + use super::ExtendedClass as Ec; + use spirv::StorageClass as Sc; + Ok(match Sc::from_u32(word) { + Some(Sc::Function) => Ec::Global(crate::AddressSpace::Function), + Some(Sc::Input) => Ec::Input, + Some(Sc::Output) => Ec::Output, + Some(Sc::Private) => Ec::Global(crate::AddressSpace::Private), + Some(Sc::UniformConstant) => Ec::Global(crate::AddressSpace::Handle), + Some(Sc::StorageBuffer) => Ec::Global(crate::AddressSpace::Storage { + //Note: this is restricted by decorations later + access: crate::StorageAccess::all(), + }), + // we expect the `Storage` case to be filtered out before calling this function. + Some(Sc::Uniform) => Ec::Global(crate::AddressSpace::Uniform), + Some(Sc::Workgroup) => Ec::Global(crate::AddressSpace::WorkGroup), + Some(Sc::PushConstant) => Ec::Global(crate::AddressSpace::PushConstant), + _ => return Err(Error::UnsupportedStorageClass(word)), + }) +} diff --git a/naga/src/front/spv/error.rs b/naga/src/front/spv/error.rs new file mode 100644 index 0000000000..2f9bf2d1bc --- /dev/null +++ b/naga/src/front/spv/error.rs @@ -0,0 +1,129 @@ +use super::ModuleState; +use crate::arena::Handle; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("invalid header")] + InvalidHeader, + #[error("invalid word count")] + InvalidWordCount, + #[error("unknown instruction {0}")] + UnknownInstruction(u16), + #[error("unknown capability %{0}")] + UnknownCapability(spirv::Word), + #[error("unsupported instruction {1:?} at {0:?}")] + UnsupportedInstruction(ModuleState, spirv::Op), + #[error("unsupported capability {0:?}")] + UnsupportedCapability(spirv::Capability), + #[error("unsupported extension {0}")] + UnsupportedExtension(String), + #[error("unsupported extension set {0}")] + UnsupportedExtSet(String), + #[error("unsupported extension instantiation set %{0}")] + UnsupportedExtInstSet(spirv::Word), + #[error("unsupported extension instantiation %{0}")] + UnsupportedExtInst(spirv::Word), + #[error("unsupported type {0:?}")] + UnsupportedType(Handle), + #[error("unsupported execution model %{0}")] + UnsupportedExecutionModel(spirv::Word), + #[error("unsupported execution mode %{0}")] + UnsupportedExecutionMode(spirv::Word), + #[error("unsupported storage class %{0}")] + UnsupportedStorageClass(spirv::Word), + #[error("unsupported image dimension %{0}")] + UnsupportedImageDim(spirv::Word), + #[error("unsupported image format %{0}")] + UnsupportedImageFormat(spirv::Word), + #[error("unsupported builtin %{0}")] + UnsupportedBuiltIn(spirv::Word), + #[error("unsupported control flow %{0}")] + UnsupportedControlFlow(spirv::Word), + #[error("unsupported binary operator %{0}")] + UnsupportedBinaryOperator(spirv::Word), + #[error("Naga supports OpTypeRuntimeArray in the StorageBuffer storage class only")] + UnsupportedRuntimeArrayStorageClass, + #[error("unsupported matrix stride {stride} for a {columns}x{rows} matrix with scalar width={width}")] + UnsupportedMatrixStride { + stride: u32, + columns: u8, + rows: u8, + width: u8, + }, + #[error("unknown binary operator {0:?}")] + UnknownBinaryOperator(spirv::Op), + #[error("unknown relational function {0:?}")] + UnknownRelationalFunction(spirv::Op), + #[error("invalid parameter {0:?}")] + InvalidParameter(spirv::Op), + #[error("invalid operand count {1} for {0:?}")] + InvalidOperandCount(spirv::Op, u16), + #[error("invalid operand")] + InvalidOperand, + #[error("invalid id %{0}")] + InvalidId(spirv::Word), + #[error("invalid decoration %{0}")] + InvalidDecoration(spirv::Word), + #[error("invalid type width %{0}")] + InvalidTypeWidth(spirv::Word), + #[error("invalid sign %{0}")] + InvalidSign(spirv::Word), + #[error("invalid inner type %{0}")] + InvalidInnerType(spirv::Word), + #[error("invalid vector size %{0}")] + InvalidVectorSize(spirv::Word), + #[error("invalid access type %{0}")] + InvalidAccessType(spirv::Word), + #[error("invalid access {0:?}")] + InvalidAccess(crate::Expression), + #[error("invalid access index %{0}")] + InvalidAccessIndex(spirv::Word), + #[error("invalid index type %{0}")] + InvalidIndexType(spirv::Word), + #[error("invalid binding %{0}")] + InvalidBinding(spirv::Word), + #[error("invalid global var {0:?}")] + InvalidGlobalVar(crate::Expression), + #[error("invalid image/sampler expression {0:?}")] + InvalidImageExpression(crate::Expression), + #[error("invalid image base type {0:?}")] + InvalidImageBaseType(Handle), + #[error("invalid image {0:?}")] + InvalidImage(Handle), + #[error("invalid as type {0:?}")] + InvalidAsType(Handle), + #[error("invalid vector type {0:?}")] + InvalidVectorType(Handle), + #[error("inconsistent comparison sampling {0:?}")] + InconsistentComparisonSampling(Handle), + #[error("wrong function result type %{0}")] + WrongFunctionResultType(spirv::Word), + #[error("wrong function argument type %{0}")] + WrongFunctionArgumentType(spirv::Word), + #[error("missing decoration {0:?}")] + MissingDecoration(spirv::Decoration), + #[error("bad string")] + BadString, + #[error("incomplete data")] + IncompleteData, + #[error("invalid terminator")] + InvalidTerminator, + #[error("invalid edge classification")] + InvalidEdgeClassification, + #[error("cycle detected in the CFG during traversal at {0}")] + ControlFlowGraphCycle(crate::front::spv::BlockId), + #[error("recursive function call %{0}")] + FunctionCallCycle(spirv::Word), + #[error("invalid array size {0:?}")] + InvalidArraySize(Handle), + #[error("invalid barrier scope %{0}")] + InvalidBarrierScope(spirv::Word), + #[error("invalid barrier memory semantics %{0}")] + InvalidBarrierMemorySemantics(spirv::Word), + #[error( + "arrays of images / samplers are supported only through bindings for \ + now (i.e. you can't create an array of images or samplers that doesn't \ + come from a binding)" + )] + NonBindingArrayOfImageOrSamplers, +} diff --git a/naga/src/front/spv/function.rs b/naga/src/front/spv/function.rs new file mode 100644 index 0000000000..198d9c52dd --- /dev/null +++ b/naga/src/front/spv/function.rs @@ -0,0 +1,674 @@ +use crate::{ + arena::{Arena, Handle}, + front::spv::{BlockContext, BodyIndex}, +}; + +use super::{Error, Instruction, LookupExpression, LookupHelper as _}; +use crate::proc::Emitter; + +pub type BlockId = u32; + +#[derive(Copy, Clone, Debug)] +pub struct MergeInstruction { + pub merge_block_id: BlockId, + pub continue_block_id: Option, +} + +impl> super::Frontend { + // Registers a function call. It will generate a dummy handle to call, which + // gets resolved after all the functions are processed. + pub(super) fn add_call( + &mut self, + from: spirv::Word, + to: spirv::Word, + ) -> Handle { + let dummy_handle = self + .dummy_functions + .append(crate::Function::default(), Default::default()); + self.deferred_function_calls.push(to); + self.function_call_graph.add_edge(from, to, ()); + dummy_handle + } + + pub(super) fn parse_function(&mut self, module: &mut crate::Module) -> Result<(), Error> { + let start = self.data_offset; + self.lookup_expression.clear(); + self.lookup_load_override.clear(); + self.lookup_sampled_image.clear(); + + let result_type_id = self.next()?; + let fun_id = self.next()?; + let _fun_control = self.next()?; + let fun_type_id = self.next()?; + + let mut fun = { + let ft = self.lookup_function_type.lookup(fun_type_id)?; + if ft.return_type_id != result_type_id { + return Err(Error::WrongFunctionResultType(result_type_id)); + } + crate::Function { + name: self.future_decor.remove(&fun_id).and_then(|dec| dec.name), + arguments: Vec::with_capacity(ft.parameter_type_ids.len()), + result: if self.lookup_void_type == Some(result_type_id) { + None + } else { + let lookup_result_ty = self.lookup_type.lookup(result_type_id)?; + Some(crate::FunctionResult { + ty: lookup_result_ty.handle, + binding: None, + }) + }, + local_variables: Arena::new(), + expressions: self + .make_expression_storage(&module.global_variables, &module.constants), + named_expressions: crate::NamedExpressions::default(), + body: crate::Block::new(), + } + }; + + // read parameters + for i in 0..fun.arguments.capacity() { + let start = self.data_offset; + match self.next_inst()? { + Instruction { + op: spirv::Op::FunctionParameter, + wc: 3, + } => { + let type_id = self.next()?; + let id = self.next()?; + let handle = fun.expressions.append( + crate::Expression::FunctionArgument(i as u32), + self.span_from(start), + ); + self.lookup_expression.insert( + id, + LookupExpression { + handle, + type_id, + // Setting this to an invalid id will cause get_expr_handle + // to default to the main body making sure no load/stores + // are added. + block_id: 0, + }, + ); + //Note: we redo the lookup in order to work around `self` borrowing + + if type_id + != self + .lookup_function_type + .lookup(fun_type_id)? + .parameter_type_ids[i] + { + return Err(Error::WrongFunctionArgumentType(type_id)); + } + let ty = self.lookup_type.lookup(type_id)?.handle; + let decor = self.future_decor.remove(&id).unwrap_or_default(); + fun.arguments.push(crate::FunctionArgument { + name: decor.name, + ty, + binding: None, + }); + } + Instruction { op, .. } => return Err(Error::InvalidParameter(op)), + } + } + + // Read body + self.function_call_graph.add_node(fun_id); + let mut parameters_sampling = + vec![super::image::SamplingFlags::empty(); fun.arguments.len()]; + + let mut block_ctx = BlockContext { + phis: Default::default(), + blocks: Default::default(), + body_for_label: Default::default(), + mergers: Default::default(), + bodies: Default::default(), + function_id: fun_id, + expressions: &mut fun.expressions, + local_arena: &mut fun.local_variables, + const_arena: &mut module.constants, + const_expressions: &mut module.const_expressions, + type_arena: &module.types, + global_arena: &module.global_variables, + arguments: &fun.arguments, + parameter_sampling: &mut parameters_sampling, + }; + // Insert the main body whose parent is also himself + block_ctx.bodies.push(super::Body::with_parent(0)); + + // Scan the blocks and add them as nodes + loop { + let fun_inst = self.next_inst()?; + log::debug!("{:?}", fun_inst.op); + match fun_inst.op { + spirv::Op::Line => { + fun_inst.expect(4)?; + let _file_id = self.next()?; + let _row_id = self.next()?; + let _col_id = self.next()?; + } + spirv::Op::Label => { + // Read the label ID + fun_inst.expect(2)?; + let block_id = self.next()?; + + self.next_block(block_id, &mut block_ctx)?; + } + spirv::Op::FunctionEnd => { + fun_inst.expect(1)?; + break; + } + _ => { + return Err(Error::UnsupportedInstruction(self.state, fun_inst.op)); + } + } + } + + if let Some(ref prefix) = self.options.block_ctx_dump_prefix { + let dump_suffix = match self.lookup_entry_point.get(&fun_id) { + Some(ep) => format!("block_ctx.{:?}-{}.txt", ep.stage, ep.name), + None => format!("block_ctx.Fun-{}.txt", module.functions.len()), + }; + let dest = prefix.join(dump_suffix); + let dump = format!("{block_ctx:#?}"); + if let Err(e) = std::fs::write(&dest, dump) { + log::error!("Unable to dump the block context into {:?}: {}", dest, e); + } + } + + // Emit `Store` statements to properly initialize all the local variables we + // created for `phi` expressions. + // + // Note that get_expr_handle also contributes slightly odd entries to this table, + // to get the spill. + for phi in block_ctx.phis.iter() { + // Get a pointer to the local variable for the phi's value. + let phi_pointer = block_ctx.expressions.append( + crate::Expression::LocalVariable(phi.local), + crate::Span::default(), + ); + + // At the end of each of `phi`'s predecessor blocks, store the corresponding + // source value in the phi's local variable. + for &(source, predecessor) in phi.expressions.iter() { + let source_lexp = &self.lookup_expression[&source]; + let predecessor_body_idx = block_ctx.body_for_label[&predecessor]; + // If the expression is a global/argument it will have a 0 block + // id so we must use a default value instead of panicking + let source_body_idx = block_ctx + .body_for_label + .get(&source_lexp.block_id) + .copied() + .unwrap_or(0); + + // If the Naga `Expression` generated for `source` is in scope, then we + // can simply store that in the phi's local variable. + // + // Otherwise, spill the source value to a local variable in the block that + // defines it. (We know this store dominates the predecessor; otherwise, + // the phi wouldn't have been able to refer to that source expression in + // the first place.) Then, the predecessor block can count on finding the + // source's value in that local variable. + let value = if super::is_parent(predecessor_body_idx, source_body_idx, &block_ctx) { + source_lexp.handle + } else { + // The source SPIR-V expression is not defined in the phi's + // predecessor block, nor is it a globally available expression. So it + // must be defined off in some other block that merely dominates the + // predecessor. This means that the corresponding Naga `Expression` + // may not be in scope in the predecessor block. + // + // In the block that defines `source`, spill it to a fresh local + // variable, to ensure we can still use it at the end of the + // predecessor. + let ty = self.lookup_type[&source_lexp.type_id].handle; + let local = block_ctx.local_arena.append( + crate::LocalVariable { + name: None, + ty, + init: None, + }, + crate::Span::default(), + ); + + let pointer = block_ctx.expressions.append( + crate::Expression::LocalVariable(local), + crate::Span::default(), + ); + + // Get the spilled value of the source expression. + let start = block_ctx.expressions.len(); + let expr = block_ctx + .expressions + .append(crate::Expression::Load { pointer }, crate::Span::default()); + let range = block_ctx.expressions.range_from(start); + + block_ctx + .blocks + .get_mut(&predecessor) + .unwrap() + .push(crate::Statement::Emit(range), crate::Span::default()); + + // At the end of the block that defines it, spill the source + // expression's value. + block_ctx + .blocks + .get_mut(&source_lexp.block_id) + .unwrap() + .push( + crate::Statement::Store { + pointer, + value: source_lexp.handle, + }, + crate::Span::default(), + ); + + expr + }; + + // At the end of the phi predecessor block, store the source + // value in the phi's value. + block_ctx.blocks.get_mut(&predecessor).unwrap().push( + crate::Statement::Store { + pointer: phi_pointer, + value, + }, + crate::Span::default(), + ) + } + } + + fun.body = block_ctx.lower(); + + // done + let fun_handle = module.functions.append(fun, self.span_from_with_op(start)); + self.lookup_function.insert( + fun_id, + super::LookupFunction { + handle: fun_handle, + parameters_sampling, + }, + ); + + if let Some(ep) = self.lookup_entry_point.remove(&fun_id) { + // create a wrapping function + let mut function = crate::Function { + name: Some(format!("{}_wrap", ep.name)), + arguments: Vec::new(), + result: None, + local_variables: Arena::new(), + expressions: Arena::new(), + named_expressions: crate::NamedExpressions::default(), + body: crate::Block::new(), + }; + + // 1. copy the inputs from arguments to privates + for &v_id in ep.variable_ids.iter() { + let lvar = self.lookup_variable.lookup(v_id)?; + if let super::Variable::Input(ref arg) = lvar.inner { + let span = module.global_variables.get_span(lvar.handle); + let arg_expr = function.expressions.append( + crate::Expression::FunctionArgument(function.arguments.len() as u32), + span, + ); + let load_expr = if arg.ty == module.global_variables[lvar.handle].ty { + arg_expr + } else { + // The only case where the type is different is if we need to treat + // unsigned integer as signed. + let mut emitter = Emitter::default(); + emitter.start(&function.expressions); + let handle = function.expressions.append( + crate::Expression::As { + expr: arg_expr, + kind: crate::ScalarKind::Sint, + convert: Some(4), + }, + span, + ); + function.body.extend(emitter.finish(&function.expressions)); + handle + }; + function.body.push( + crate::Statement::Store { + pointer: function + .expressions + .append(crate::Expression::GlobalVariable(lvar.handle), span), + value: load_expr, + }, + span, + ); + + let mut arg = arg.clone(); + if ep.stage == crate::ShaderStage::Fragment { + if let Some(ref mut binding) = arg.binding { + binding.apply_default_interpolation(&module.types[arg.ty].inner); + } + } + function.arguments.push(arg); + } + } + // 2. call the wrapped function + let fake_id = !(module.entry_points.len() as u32); // doesn't matter, as long as it's not a collision + let dummy_handle = self.add_call(fake_id, fun_id); + function.body.push( + crate::Statement::Call { + function: dummy_handle, + arguments: Vec::new(), + result: None, + }, + crate::Span::default(), + ); + + // 3. copy the outputs from privates to the result + let mut members = Vec::new(); + let mut components = Vec::new(); + for &v_id in ep.variable_ids.iter() { + let lvar = self.lookup_variable.lookup(v_id)?; + if let super::Variable::Output(ref result) = lvar.inner { + let span = module.global_variables.get_span(lvar.handle); + let expr_handle = function + .expressions + .append(crate::Expression::GlobalVariable(lvar.handle), span); + + // Cull problematic builtins of gl_PerVertex. + // See the docs for `Frontend::gl_per_vertex_builtin_access`. + { + let ty = &module.types[result.ty]; + match ty.inner { + crate::TypeInner::Struct { + members: ref original_members, + span, + } if ty.name.as_deref() == Some("gl_PerVertex") => { + let mut new_members = original_members.clone(); + for member in &mut new_members { + if let Some(crate::Binding::BuiltIn(built_in)) = member.binding + { + if !self.gl_per_vertex_builtin_access.contains(&built_in) { + member.binding = None + } + } + } + if &new_members != original_members { + module.types.replace( + result.ty, + crate::Type { + name: ty.name.clone(), + inner: crate::TypeInner::Struct { + members: new_members, + span, + }, + }, + ); + } + } + _ => {} + } + } + + match module.types[result.ty].inner { + crate::TypeInner::Struct { + members: ref sub_members, + .. + } => { + for (index, sm) in sub_members.iter().enumerate() { + if sm.binding.is_none() { + continue; + } + let mut sm = sm.clone(); + + if let Some(ref mut binding) = sm.binding { + if ep.stage == crate::ShaderStage::Vertex { + binding.apply_default_interpolation( + &module.types[sm.ty].inner, + ); + } + } + + members.push(sm); + + components.push(function.expressions.append( + crate::Expression::AccessIndex { + base: expr_handle, + index: index as u32, + }, + span, + )); + } + } + ref inner => { + let mut binding = result.binding.clone(); + if let Some(ref mut binding) = binding { + if ep.stage == crate::ShaderStage::Vertex { + binding.apply_default_interpolation(inner); + } + } + + members.push(crate::StructMember { + name: None, + ty: result.ty, + binding, + offset: 0, + }); + // populate just the globals first, then do `Load` in a + // separate step, so that we can get a range. + components.push(expr_handle); + } + } + } + } + + for (member_index, member) in members.iter().enumerate() { + match member.binding { + Some(crate::Binding::BuiltIn(crate::BuiltIn::Position { .. })) + if self.options.adjust_coordinate_space => + { + let mut emitter = Emitter::default(); + emitter.start(&function.expressions); + let global_expr = components[member_index]; + let span = function.expressions.get_span(global_expr); + let access_expr = function.expressions.append( + crate::Expression::AccessIndex { + base: global_expr, + index: 1, + }, + span, + ); + let load_expr = function.expressions.append( + crate::Expression::Load { + pointer: access_expr, + }, + span, + ); + let neg_expr = function.expressions.append( + crate::Expression::Unary { + op: crate::UnaryOperator::Negate, + expr: load_expr, + }, + span, + ); + function.body.extend(emitter.finish(&function.expressions)); + function.body.push( + crate::Statement::Store { + pointer: access_expr, + value: neg_expr, + }, + span, + ); + } + _ => {} + } + } + + let mut emitter = Emitter::default(); + emitter.start(&function.expressions); + for component in components.iter_mut() { + let load_expr = crate::Expression::Load { + pointer: *component, + }; + let span = function.expressions.get_span(*component); + *component = function.expressions.append(load_expr, span); + } + + match members[..] { + [] => {} + [ref member] => { + function.body.extend(emitter.finish(&function.expressions)); + let span = function.expressions.get_span(components[0]); + function.body.push( + crate::Statement::Return { + value: components.first().cloned(), + }, + span, + ); + function.result = Some(crate::FunctionResult { + ty: member.ty, + binding: member.binding.clone(), + }); + } + _ => { + let span = crate::Span::total_span( + components.iter().map(|h| function.expressions.get_span(*h)), + ); + let ty = module.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Struct { + members, + span: 0xFFFF, // shouldn't matter + }, + }, + span, + ); + let result_expr = function + .expressions + .append(crate::Expression::Compose { ty, components }, span); + function.body.extend(emitter.finish(&function.expressions)); + function.body.push( + crate::Statement::Return { + value: Some(result_expr), + }, + span, + ); + function.result = Some(crate::FunctionResult { ty, binding: None }); + } + } + + module.entry_points.push(crate::EntryPoint { + name: ep.name, + stage: ep.stage, + early_depth_test: ep.early_depth_test, + workgroup_size: ep.workgroup_size, + function, + }); + } + + Ok(()) + } +} + +impl<'function> BlockContext<'function> { + pub(super) fn gctx(&self) -> crate::proc::GlobalCtx { + crate::proc::GlobalCtx { + types: self.type_arena, + constants: self.const_arena, + const_expressions: self.const_expressions, + } + } + + /// Consumes the `BlockContext` producing a Ir [`Block`](crate::Block) + fn lower(mut self) -> crate::Block { + fn lower_impl( + blocks: &mut crate::FastHashMap, + bodies: &[super::Body], + body_idx: BodyIndex, + ) -> crate::Block { + let mut block = crate::Block::new(); + + for item in bodies[body_idx].data.iter() { + match *item { + super::BodyFragment::BlockId(id) => block.append(blocks.get_mut(&id).unwrap()), + super::BodyFragment::If { + condition, + accept, + reject, + } => { + let accept = lower_impl(blocks, bodies, accept); + let reject = lower_impl(blocks, bodies, reject); + + block.push( + crate::Statement::If { + condition, + accept, + reject, + }, + crate::Span::default(), + ) + } + super::BodyFragment::Loop { + body, + continuing, + break_if, + } => { + let body = lower_impl(blocks, bodies, body); + let continuing = lower_impl(blocks, bodies, continuing); + + block.push( + crate::Statement::Loop { + body, + continuing, + break_if, + }, + crate::Span::default(), + ) + } + super::BodyFragment::Switch { + selector, + ref cases, + default, + } => { + let mut ir_cases: Vec<_> = cases + .iter() + .map(|&(value, body_idx)| { + let body = lower_impl(blocks, bodies, body_idx); + + // Handle simple cases that would make a fallthrough statement unreachable code + let fall_through = body.last().map_or(true, |s| !s.is_terminator()); + + crate::SwitchCase { + value: crate::SwitchValue::I32(value), + body, + fall_through, + } + }) + .collect(); + ir_cases.push(crate::SwitchCase { + value: crate::SwitchValue::Default, + body: lower_impl(blocks, bodies, default), + fall_through: false, + }); + + block.push( + crate::Statement::Switch { + selector, + cases: ir_cases, + }, + crate::Span::default(), + ) + } + super::BodyFragment::Break => { + block.push(crate::Statement::Break, crate::Span::default()) + } + super::BodyFragment::Continue => { + block.push(crate::Statement::Continue, crate::Span::default()) + } + } + } + + block + } + + lower_impl(&mut self.blocks, &self.bodies, 0) + } +} diff --git a/naga/src/front/spv/image.rs b/naga/src/front/spv/image.rs new file mode 100644 index 0000000000..0f25dd626b --- /dev/null +++ b/naga/src/front/spv/image.rs @@ -0,0 +1,767 @@ +use crate::{ + arena::{Handle, UniqueArena}, + Scalar, +}; + +use super::{Error, LookupExpression, LookupHelper as _}; + +#[derive(Clone, Debug)] +pub(super) struct LookupSampledImage { + image: Handle, + sampler: Handle, +} + +bitflags::bitflags! { + /// Flags describing sampling method. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct SamplingFlags: u32 { + /// Regular sampling. + const REGULAR = 0x1; + /// Comparison sampling. + const COMPARISON = 0x2; + } +} + +impl<'function> super::BlockContext<'function> { + fn get_image_expr_ty( + &self, + handle: Handle, + ) -> Result, Error> { + match self.expressions[handle] { + crate::Expression::GlobalVariable(handle) => Ok(self.global_arena[handle].ty), + crate::Expression::FunctionArgument(i) => Ok(self.arguments[i as usize].ty), + ref other => Err(Error::InvalidImageExpression(other.clone())), + } + } +} + +/// Options of a sampling operation. +#[derive(Debug)] +pub struct SamplingOptions { + /// Projection sampling: the division by W is expected to happen + /// in the texture unit. + pub project: bool, + /// Depth comparison sampling with a reference value. + pub compare: bool, +} + +enum ExtraCoordinate { + ArrayLayer, + Projection, + Garbage, +} + +/// Return the texture coordinates separated from the array layer, +/// and/or divided by the projection term. +/// +/// The Proj sampling ops expect an extra coordinate for the W. +/// The arrayed (can't be Proj!) images expect an extra coordinate for the layer. +fn extract_image_coordinates( + image_dim: crate::ImageDimension, + extra_coordinate: ExtraCoordinate, + base: Handle, + coordinate_ty: Handle, + ctx: &mut super::BlockContext, +) -> (Handle, Option>) { + let (given_size, kind) = match ctx.type_arena[coordinate_ty].inner { + crate::TypeInner::Scalar(Scalar { kind, .. }) => (None, kind), + crate::TypeInner::Vector { + size, + scalar: Scalar { kind, .. }, + } => (Some(size), kind), + ref other => unreachable!("Unexpected texture coordinate {:?}", other), + }; + + let required_size = image_dim.required_coordinate_size(); + let required_ty = required_size.map(|size| { + ctx.type_arena + .get(&crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size, + scalar: Scalar { kind, width: 4 }, + }, + }) + .expect("Required coordinate type should have been set up by `parse_type_image`!") + }); + let extra_expr = crate::Expression::AccessIndex { + base, + index: required_size.map_or(1, |size| size as u32), + }; + + let base_span = ctx.expressions.get_span(base); + + match extra_coordinate { + ExtraCoordinate::ArrayLayer => { + let extracted = match required_size { + None => ctx + .expressions + .append(crate::Expression::AccessIndex { base, index: 0 }, base_span), + Some(size) => { + let mut components = Vec::with_capacity(size as usize); + for index in 0..size as u32 { + let comp = ctx + .expressions + .append(crate::Expression::AccessIndex { base, index }, base_span); + components.push(comp); + } + ctx.expressions.append( + crate::Expression::Compose { + ty: required_ty.unwrap(), + components, + }, + base_span, + ) + } + }; + let array_index_f32 = ctx.expressions.append(extra_expr, base_span); + let array_index = ctx.expressions.append( + crate::Expression::As { + kind: crate::ScalarKind::Sint, + expr: array_index_f32, + convert: Some(4), + }, + base_span, + ); + (extracted, Some(array_index)) + } + ExtraCoordinate::Projection => { + let projection = ctx.expressions.append(extra_expr, base_span); + let divided = match required_size { + None => { + let temp = ctx + .expressions + .append(crate::Expression::AccessIndex { base, index: 0 }, base_span); + ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Divide, + left: temp, + right: projection, + }, + base_span, + ) + } + Some(size) => { + let mut components = Vec::with_capacity(size as usize); + for index in 0..size as u32 { + let temp = ctx + .expressions + .append(crate::Expression::AccessIndex { base, index }, base_span); + let comp = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Divide, + left: temp, + right: projection, + }, + base_span, + ); + components.push(comp); + } + ctx.expressions.append( + crate::Expression::Compose { + ty: required_ty.unwrap(), + components, + }, + base_span, + ) + } + }; + (divided, None) + } + ExtraCoordinate::Garbage if given_size == required_size => (base, None), + ExtraCoordinate::Garbage => { + use crate::SwizzleComponent as Sc; + let cut_expr = match required_size { + None => crate::Expression::AccessIndex { base, index: 0 }, + Some(size) => crate::Expression::Swizzle { + size, + vector: base, + pattern: [Sc::X, Sc::Y, Sc::Z, Sc::W], + }, + }; + (ctx.expressions.append(cut_expr, base_span), None) + } + } +} + +pub(super) fn patch_comparison_type( + flags: SamplingFlags, + var: &mut crate::GlobalVariable, + arena: &mut UniqueArena, +) -> bool { + if !flags.contains(SamplingFlags::COMPARISON) { + return true; + } + if flags == SamplingFlags::all() { + return false; + } + + log::debug!("Flipping comparison for {:?}", var); + let original_ty = &arena[var.ty]; + let original_ty_span = arena.get_span(var.ty); + let ty_inner = match original_ty.inner { + crate::TypeInner::Image { + class: crate::ImageClass::Sampled { multi, .. }, + dim, + arrayed, + } => crate::TypeInner::Image { + class: crate::ImageClass::Depth { multi }, + dim, + arrayed, + }, + crate::TypeInner::Sampler { .. } => crate::TypeInner::Sampler { comparison: true }, + ref other => unreachable!("Unexpected type for comparison mutation: {:?}", other), + }; + + let name = original_ty.name.clone(); + var.ty = arena.insert( + crate::Type { + name, + inner: ty_inner, + }, + original_ty_span, + ); + true +} + +impl> super::Frontend { + pub(super) fn parse_image_couple(&mut self) -> Result<(), Error> { + let _result_type_id = self.next()?; + let result_id = self.next()?; + let image_id = self.next()?; + let sampler_id = self.next()?; + let image_lexp = self.lookup_expression.lookup(image_id)?; + let sampler_lexp = self.lookup_expression.lookup(sampler_id)?; + self.lookup_sampled_image.insert( + result_id, + LookupSampledImage { + image: image_lexp.handle, + sampler: sampler_lexp.handle, + }, + ); + Ok(()) + } + + pub(super) fn parse_image_uncouple(&mut self, block_id: spirv::Word) -> Result<(), Error> { + let result_type_id = self.next()?; + let result_id = self.next()?; + let sampled_image_id = self.next()?; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: self.lookup_sampled_image.lookup(sampled_image_id)?.image, + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + pub(super) fn parse_image_write( + &mut self, + words_left: u16, + ctx: &mut super::BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + body_idx: usize, + ) -> Result { + let image_id = self.next()?; + let coordinate_id = self.next()?; + let value_id = self.next()?; + + let image_ops = if words_left != 0 { self.next()? } else { 0 }; + + if image_ops != 0 { + let other = spirv::ImageOperands::from_bits_truncate(image_ops); + log::warn!("Unknown image write ops {:?}", other); + for _ in 1..words_left { + self.next()?; + } + } + + let image_lexp = self.lookup_expression.lookup(image_id)?; + let image_ty = ctx.get_image_expr_ty(image_lexp.handle)?; + + let coord_lexp = self.lookup_expression.lookup(coordinate_id)?; + let coord_handle = + self.get_expr_handle(coordinate_id, coord_lexp, ctx, emitter, block, body_idx); + let coord_type_handle = self.lookup_type.lookup(coord_lexp.type_id)?.handle; + let (coordinate, array_index) = match ctx.type_arena[image_ty].inner { + crate::TypeInner::Image { + dim, + arrayed, + class: _, + } => extract_image_coordinates( + dim, + if arrayed { + ExtraCoordinate::ArrayLayer + } else { + ExtraCoordinate::Garbage + }, + coord_handle, + coord_type_handle, + ctx, + ), + _ => return Err(Error::InvalidImage(image_ty)), + }; + + let value_lexp = self.lookup_expression.lookup(value_id)?; + let value = self.get_expr_handle(value_id, value_lexp, ctx, emitter, block, body_idx); + + Ok(crate::Statement::ImageStore { + image: image_lexp.handle, + coordinate, + array_index, + value, + }) + } + + pub(super) fn parse_image_load( + &mut self, + mut words_left: u16, + ctx: &mut super::BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let image_id = self.next()?; + let coordinate_id = self.next()?; + + let mut image_ops = if words_left != 0 { + words_left -= 1; + self.next()? + } else { + 0 + }; + + let mut sample = None; + let mut level = None; + while image_ops != 0 { + let bit = 1 << image_ops.trailing_zeros(); + match spirv::ImageOperands::from_bits_truncate(bit) { + spirv::ImageOperands::LOD => { + let lod_expr = self.next()?; + let lod_lexp = self.lookup_expression.lookup(lod_expr)?; + let lod_handle = + self.get_expr_handle(lod_expr, lod_lexp, ctx, emitter, block, body_idx); + level = Some(lod_handle); + words_left -= 1; + } + spirv::ImageOperands::SAMPLE => { + let sample_expr = self.next()?; + let sample_handle = self.lookup_expression.lookup(sample_expr)?.handle; + sample = Some(sample_handle); + words_left -= 1; + } + other => { + log::warn!("Unknown image load op {:?}", other); + for _ in 0..words_left { + self.next()?; + } + break; + } + } + image_ops ^= bit; + } + + // No need to call get_expr_handle here since only globals/arguments are + // allowed as images and they are always in the root scope + let image_lexp = self.lookup_expression.lookup(image_id)?; + let image_ty = ctx.get_image_expr_ty(image_lexp.handle)?; + + let coord_lexp = self.lookup_expression.lookup(coordinate_id)?; + let coord_handle = + self.get_expr_handle(coordinate_id, coord_lexp, ctx, emitter, block, body_idx); + let coord_type_handle = self.lookup_type.lookup(coord_lexp.type_id)?.handle; + let (coordinate, array_index) = match ctx.type_arena[image_ty].inner { + crate::TypeInner::Image { + dim, + arrayed, + class: _, + } => extract_image_coordinates( + dim, + if arrayed { + ExtraCoordinate::ArrayLayer + } else { + ExtraCoordinate::Garbage + }, + coord_handle, + coord_type_handle, + ctx, + ), + _ => return Err(Error::InvalidImage(image_ty)), + }; + + let expr = crate::Expression::ImageLoad { + image: image_lexp.handle, + coordinate, + array_index, + sample, + level, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, self.span_from_with_op(start)), + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + pub(super) fn parse_image_sample( + &mut self, + mut words_left: u16, + options: SamplingOptions, + ctx: &mut super::BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let sampled_image_id = self.next()?; + let coordinate_id = self.next()?; + let dref_id = if options.compare { + Some(self.next()?) + } else { + None + }; + + let mut image_ops = if words_left != 0 { + words_left -= 1; + self.next()? + } else { + 0 + }; + + let mut level = crate::SampleLevel::Auto; + let mut offset = None; + while image_ops != 0 { + let bit = 1 << image_ops.trailing_zeros(); + match spirv::ImageOperands::from_bits_truncate(bit) { + spirv::ImageOperands::BIAS => { + let bias_expr = self.next()?; + let bias_lexp = self.lookup_expression.lookup(bias_expr)?; + let bias_handle = + self.get_expr_handle(bias_expr, bias_lexp, ctx, emitter, block, body_idx); + level = crate::SampleLevel::Bias(bias_handle); + words_left -= 1; + } + spirv::ImageOperands::LOD => { + let lod_expr = self.next()?; + let lod_lexp = self.lookup_expression.lookup(lod_expr)?; + let lod_handle = + self.get_expr_handle(lod_expr, lod_lexp, ctx, emitter, block, body_idx); + level = if options.compare { + log::debug!("Assuming {:?} is zero", lod_handle); + crate::SampleLevel::Zero + } else { + crate::SampleLevel::Exact(lod_handle) + }; + words_left -= 1; + } + spirv::ImageOperands::GRAD => { + let grad_x_expr = self.next()?; + let grad_x_lexp = self.lookup_expression.lookup(grad_x_expr)?; + let grad_x_handle = self.get_expr_handle( + grad_x_expr, + grad_x_lexp, + ctx, + emitter, + block, + body_idx, + ); + let grad_y_expr = self.next()?; + let grad_y_lexp = self.lookup_expression.lookup(grad_y_expr)?; + let grad_y_handle = self.get_expr_handle( + grad_y_expr, + grad_y_lexp, + ctx, + emitter, + block, + body_idx, + ); + level = if options.compare { + log::debug!( + "Assuming gradients {:?} and {:?} are not greater than 1", + grad_x_handle, + grad_y_handle + ); + crate::SampleLevel::Zero + } else { + crate::SampleLevel::Gradient { + x: grad_x_handle, + y: grad_y_handle, + } + }; + words_left -= 2; + } + spirv::ImageOperands::CONST_OFFSET => { + let offset_constant = self.next()?; + let offset_handle = self.lookup_constant.lookup(offset_constant)?.handle; + let offset_handle = ctx.const_expressions.append( + crate::Expression::Constant(offset_handle), + Default::default(), + ); + offset = Some(offset_handle); + words_left -= 1; + } + other => { + log::warn!("Unknown image sample operand {:?}", other); + for _ in 0..words_left { + self.next()?; + } + break; + } + } + image_ops ^= bit; + } + + let si_lexp = self.lookup_sampled_image.lookup(sampled_image_id)?; + let coord_lexp = self.lookup_expression.lookup(coordinate_id)?; + let coord_handle = + self.get_expr_handle(coordinate_id, coord_lexp, ctx, emitter, block, body_idx); + let coord_type_handle = self.lookup_type.lookup(coord_lexp.type_id)?.handle; + + let sampling_bit = if options.compare { + SamplingFlags::COMPARISON + } else { + SamplingFlags::REGULAR + }; + + let image_ty = match ctx.expressions[si_lexp.image] { + crate::Expression::GlobalVariable(handle) => { + if let Some(flags) = self.handle_sampling.get_mut(&handle) { + *flags |= sampling_bit; + } + + ctx.global_arena[handle].ty + } + + crate::Expression::FunctionArgument(i) => { + ctx.parameter_sampling[i as usize] |= sampling_bit; + ctx.arguments[i as usize].ty + } + + crate::Expression::Access { base, .. } => match ctx.expressions[base] { + crate::Expression::GlobalVariable(handle) => { + if let Some(flags) = self.handle_sampling.get_mut(&handle) { + *flags |= sampling_bit; + } + + match ctx.type_arena[ctx.global_arena[handle].ty].inner { + crate::TypeInner::BindingArray { base, .. } => base, + _ => return Err(Error::InvalidGlobalVar(ctx.expressions[base].clone())), + } + } + + ref other => return Err(Error::InvalidGlobalVar(other.clone())), + }, + + ref other => return Err(Error::InvalidGlobalVar(other.clone())), + }; + + match ctx.expressions[si_lexp.sampler] { + crate::Expression::GlobalVariable(handle) => { + *self.handle_sampling.get_mut(&handle).unwrap() |= sampling_bit; + } + + crate::Expression::FunctionArgument(i) => { + ctx.parameter_sampling[i as usize] |= sampling_bit; + } + + crate::Expression::Access { base, .. } => match ctx.expressions[base] { + crate::Expression::GlobalVariable(handle) => { + *self.handle_sampling.get_mut(&handle).unwrap() |= sampling_bit; + } + + ref other => return Err(Error::InvalidGlobalVar(other.clone())), + }, + + ref other => return Err(Error::InvalidGlobalVar(other.clone())), + } + + let ((coordinate, array_index), depth_ref) = match ctx.type_arena[image_ty].inner { + crate::TypeInner::Image { + dim, + arrayed, + class: _, + } => ( + extract_image_coordinates( + dim, + if options.project { + ExtraCoordinate::Projection + } else if arrayed { + ExtraCoordinate::ArrayLayer + } else { + ExtraCoordinate::Garbage + }, + coord_handle, + coord_type_handle, + ctx, + ), + { + match dref_id { + Some(id) => { + let expr_lexp = self.lookup_expression.lookup(id)?; + let mut expr = + self.get_expr_handle(id, expr_lexp, ctx, emitter, block, body_idx); + + if options.project { + let required_size = dim.required_coordinate_size(); + let right = ctx.expressions.append( + crate::Expression::AccessIndex { + base: coord_handle, + index: required_size.map_or(1, |size| size as u32), + }, + crate::Span::default(), + ); + expr = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Divide, + left: expr, + right, + }, + crate::Span::default(), + ) + }; + Some(expr) + } + None => None, + } + }, + ), + _ => return Err(Error::InvalidImage(image_ty)), + }; + + let expr = crate::Expression::ImageSample { + image: si_lexp.image, + sampler: si_lexp.sampler, + gather: None, //TODO + coordinate, + array_index, + offset, + level, + depth_ref, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, self.span_from_with_op(start)), + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + pub(super) fn parse_image_query_size( + &mut self, + at_level: bool, + ctx: &mut super::BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let image_id = self.next()?; + let level = if at_level { + let level_id = self.next()?; + let level_lexp = self.lookup_expression.lookup(level_id)?; + Some(self.get_expr_handle(level_id, level_lexp, ctx, emitter, block, body_idx)) + } else { + None + }; + + // No need to call get_expr_handle here since only globals/arguments are + // allowed as images and they are always in the root scope + //TODO: handle arrays and cubes + let image_lexp = self.lookup_expression.lookup(image_id)?; + + let expr = crate::Expression::ImageQuery { + image: image_lexp.handle, + query: crate::ImageQuery::Size { level }, + }; + + let result_type_handle = self.lookup_type.lookup(result_type_id)?.handle; + let maybe_scalar_kind = ctx.type_arena[result_type_handle].inner.scalar_kind(); + + let expr = if maybe_scalar_kind == Some(crate::ScalarKind::Sint) { + crate::Expression::As { + expr: ctx.expressions.append(expr, self.span_from_with_op(start)), + kind: crate::ScalarKind::Sint, + convert: Some(4), + } + } else { + expr + }; + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, self.span_from_with_op(start)), + type_id: result_type_id, + block_id, + }, + ); + + Ok(()) + } + + pub(super) fn parse_image_query_other( + &mut self, + query: crate::ImageQuery, + ctx: &mut super::BlockContext, + block_id: spirv::Word, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let image_id = self.next()?; + + // No need to call get_expr_handle here since only globals/arguments are + // allowed as images and they are always in the root scope + let image_lexp = self.lookup_expression.lookup(image_id)?.clone(); + + let expr = crate::Expression::ImageQuery { + image: image_lexp.handle, + query, + }; + + let result_type_handle = self.lookup_type.lookup(result_type_id)?.handle; + let maybe_scalar_kind = ctx.type_arena[result_type_handle].inner.scalar_kind(); + + let expr = if maybe_scalar_kind == Some(crate::ScalarKind::Sint) { + crate::Expression::As { + expr: ctx.expressions.append(expr, self.span_from_with_op(start)), + kind: crate::ScalarKind::Sint, + convert: Some(4), + } + } else { + expr + }; + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, self.span_from_with_op(start)), + type_id: result_type_id, + block_id, + }, + ); + + Ok(()) + } +} diff --git a/naga/src/front/spv/mod.rs b/naga/src/front/spv/mod.rs new file mode 100644 index 0000000000..d347302825 --- /dev/null +++ b/naga/src/front/spv/mod.rs @@ -0,0 +1,5356 @@ +/*! +Frontend for [SPIR-V][spv] (Standard Portable Intermediate Representation). + +## ID lookups + +Our IR links to everything with `Handle`, while SPIR-V uses IDs. +In order to keep track of the associations, the parser has many lookup tables. +There map `spv::Word` into a specific IR handle, plus potentially a bit of +extra info, such as the related SPIR-V type ID. +TODO: would be nice to find ways that avoid looking up as much + +## Inputs/Outputs + +We create a private variable for each input/output. The relevant inputs are +populated at the start of an entry point. The outputs are saved at the end. + +The function associated with an entry point is wrapped in another function, +such that we can handle any `Return` statements without problems. + +## Row-major matrices + +We don't handle them natively, since the IR only expects column majority. +Instead, we detect when such matrix is accessed in the `OpAccessChain`, +and we generate a parallel expression that loads the value, but transposed. +This value then gets used instead of `OpLoad` result later on. + +[spv]: https://www.khronos.org/registry/SPIR-V/ +*/ + +mod convert; +mod error; +mod function; +mod image; +mod null; + +use convert::*; +pub use error::Error; +use function::*; + +use crate::{ + arena::{Arena, Handle, UniqueArena}, + proc::{Alignment, Layouter}, + FastHashMap, FastHashSet, FastIndexMap, +}; + +use petgraph::graphmap::GraphMap; +use std::{convert::TryInto, mem, num::NonZeroU32, path::PathBuf}; + +pub const SUPPORTED_CAPABILITIES: &[spirv::Capability] = &[ + spirv::Capability::Shader, + spirv::Capability::VulkanMemoryModel, + spirv::Capability::ClipDistance, + spirv::Capability::CullDistance, + spirv::Capability::SampleRateShading, + spirv::Capability::DerivativeControl, + spirv::Capability::Matrix, + spirv::Capability::ImageQuery, + spirv::Capability::Sampled1D, + spirv::Capability::Image1D, + spirv::Capability::SampledCubeArray, + spirv::Capability::ImageCubeArray, + spirv::Capability::StorageImageExtendedFormats, + spirv::Capability::Int8, + spirv::Capability::Int16, + spirv::Capability::Int64, + spirv::Capability::Float16, + spirv::Capability::Float64, + spirv::Capability::Geometry, + spirv::Capability::MultiView, + // tricky ones + spirv::Capability::UniformBufferArrayDynamicIndexing, + spirv::Capability::StorageBufferArrayDynamicIndexing, +]; +pub const SUPPORTED_EXTENSIONS: &[&str] = &[ + "SPV_KHR_storage_buffer_storage_class", + "SPV_KHR_vulkan_memory_model", + "SPV_KHR_multiview", +]; +pub const SUPPORTED_EXT_SETS: &[&str] = &["GLSL.std.450"]; + +#[derive(Copy, Clone)] +pub struct Instruction { + op: spirv::Op, + wc: u16, +} + +impl Instruction { + const fn expect(self, count: u16) -> Result<(), Error> { + if self.wc == count { + Ok(()) + } else { + Err(Error::InvalidOperandCount(self.op, self.wc)) + } + } + + fn expect_at_least(self, count: u16) -> Result { + self.wc + .checked_sub(count) + .ok_or(Error::InvalidOperandCount(self.op, self.wc)) + } +} + +impl crate::TypeInner { + fn can_comparison_sample(&self, module: &crate::Module) -> bool { + match *self { + crate::TypeInner::Image { + class: + crate::ImageClass::Sampled { + kind: crate::ScalarKind::Float, + multi: false, + }, + .. + } => true, + crate::TypeInner::Sampler { .. } => true, + crate::TypeInner::BindingArray { base, .. } => { + module.types[base].inner.can_comparison_sample(module) + } + _ => false, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub enum ModuleState { + Empty, + Capability, + Extension, + ExtInstImport, + MemoryModel, + EntryPoint, + ExecutionMode, + Source, + Name, + ModuleProcessed, + Annotation, + Type, + Function, +} + +trait LookupHelper { + type Target; + fn lookup(&self, key: spirv::Word) -> Result<&Self::Target, Error>; +} + +impl LookupHelper for FastHashMap { + type Target = T; + fn lookup(&self, key: spirv::Word) -> Result<&T, Error> { + self.get(&key).ok_or(Error::InvalidId(key)) + } +} + +impl crate::ImageDimension { + const fn required_coordinate_size(&self) -> Option { + match *self { + crate::ImageDimension::D1 => None, + crate::ImageDimension::D2 => Some(crate::VectorSize::Bi), + crate::ImageDimension::D3 => Some(crate::VectorSize::Tri), + crate::ImageDimension::Cube => Some(crate::VectorSize::Tri), + } + } +} + +type MemberIndex = u32; + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Default)] + struct DecorationFlags: u32 { + const NON_READABLE = 0x1; + const NON_WRITABLE = 0x2; + } +} + +impl DecorationFlags { + fn to_storage_access(self) -> crate::StorageAccess { + let mut access = crate::StorageAccess::all(); + if self.contains(DecorationFlags::NON_READABLE) { + access &= !crate::StorageAccess::LOAD; + } + if self.contains(DecorationFlags::NON_WRITABLE) { + access &= !crate::StorageAccess::STORE; + } + access + } +} + +#[derive(Debug, PartialEq)] +enum Majority { + Column, + Row, +} + +#[derive(Debug, Default)] +struct Decoration { + name: Option, + built_in: Option, + location: Option, + desc_set: Option, + desc_index: Option, + specialization: Option, + storage_buffer: bool, + offset: Option, + array_stride: Option, + matrix_stride: Option, + matrix_major: Option, + invariant: bool, + interpolation: Option, + sampling: Option, + flags: DecorationFlags, +} + +impl Decoration { + fn debug_name(&self) -> &str { + match self.name { + Some(ref name) => name.as_str(), + None => "?", + } + } + + fn specialization(&self) -> crate::Override { + self.specialization + .map_or(crate::Override::None, crate::Override::ByNameOrId) + } + + const fn resource_binding(&self) -> Option { + match *self { + Decoration { + desc_set: Some(group), + desc_index: Some(binding), + .. + } => Some(crate::ResourceBinding { group, binding }), + _ => None, + } + } + + fn io_binding(&self) -> Result { + match *self { + Decoration { + built_in: Some(built_in), + location: None, + invariant, + .. + } => Ok(crate::Binding::BuiltIn(map_builtin(built_in, invariant)?)), + Decoration { + built_in: None, + location: Some(location), + interpolation, + sampling, + .. + } => Ok(crate::Binding::Location { + location, + interpolation, + sampling, + second_blend_source: false, + }), + _ => Err(Error::MissingDecoration(spirv::Decoration::Location)), + } + } +} + +#[derive(Debug)] +struct LookupFunctionType { + parameter_type_ids: Vec, + return_type_id: spirv::Word, +} + +struct LookupFunction { + handle: Handle, + parameters_sampling: Vec, +} + +#[derive(Debug)] +struct EntryPoint { + stage: crate::ShaderStage, + name: String, + early_depth_test: Option, + workgroup_size: [u32; 3], + variable_ids: Vec, +} + +#[derive(Clone, Debug)] +struct LookupType { + handle: Handle, + base_id: Option, +} + +#[derive(Debug)] +struct LookupConstant { + handle: Handle, + type_id: spirv::Word, +} + +#[derive(Debug)] +enum Variable { + Global, + Input(crate::FunctionArgument), + Output(crate::FunctionResult), +} + +#[derive(Debug)] +struct LookupVariable { + inner: Variable, + handle: Handle, + type_id: spirv::Word, +} + +/// Information about SPIR-V result ids, stored in `Parser::lookup_expression`. +#[derive(Clone, Debug)] +struct LookupExpression { + /// The `Expression` constructed for this result. + /// + /// Note that, while a SPIR-V result id can be used in any block dominated + /// by its definition, a Naga `Expression` is only in scope for the rest of + /// its subtree. `Parser::get_expr_handle` takes care of spilling the result + /// to a `LocalVariable` which can then be used anywhere. + handle: Handle, + + /// The SPIR-V type of this result. + type_id: spirv::Word, + + /// The label id of the block that defines this expression. + /// + /// This is zero for globals, constants, and function parameters, since they + /// originate outside any function's block. + block_id: spirv::Word, +} + +#[derive(Debug)] +struct LookupMember { + type_id: spirv::Word, + // This is true for either matrices, or arrays of matrices (yikes). + row_major: bool, +} + +#[derive(Clone, Debug)] +enum LookupLoadOverride { + /// For arrays of matrices, we track them but not loading yet. + Pending, + /// For matrices, vectors, and scalars, we pre-load the data. + Loaded(Handle), +} + +#[derive(PartialEq)] +enum ExtendedClass { + Global(crate::AddressSpace), + Input, + Output, +} + +#[derive(Clone, Debug)] +pub struct Options { + /// The IR coordinate space matches all the APIs except SPIR-V, + /// so by default we flip the Y coordinate of the `BuiltIn::Position`. + /// This flag can be used to avoid this. + pub adjust_coordinate_space: bool, + /// Only allow shaders with the known set of capabilities. + pub strict_capabilities: bool, + pub block_ctx_dump_prefix: Option, +} + +impl Default for Options { + fn default() -> Self { + Options { + adjust_coordinate_space: true, + strict_capabilities: false, + block_ctx_dump_prefix: None, + } + } +} + +/// An index into the `BlockContext::bodies` table. +type BodyIndex = usize; + +/// An intermediate representation of a Naga [`Statement`]. +/// +/// `Body` and `BodyFragment` values form a tree: the `BodyIndex` fields of the +/// variants are indices of the child `Body` values in [`BlockContext::bodies`]. +/// The `lower` function assembles the final `Statement` tree from this `Body` +/// tree. See [`BlockContext`] for details. +/// +/// [`Statement`]: crate::Statement +#[derive(Debug)] +enum BodyFragment { + BlockId(spirv::Word), + If { + condition: Handle, + accept: BodyIndex, + reject: BodyIndex, + }, + Loop { + /// The body of the loop. Its [`Body::parent`] is the block containing + /// this `Loop` fragment. + body: BodyIndex, + + /// The loop's continuing block. This is a grandchild: its + /// [`Body::parent`] is the loop body block, whose index is above. + continuing: BodyIndex, + + /// If the SPIR-V loop's back-edge branch is conditional, this is the + /// expression that must be `false` for the back-edge to be taken, with + /// `true` being for the "loop merge" (which breaks out of the loop). + break_if: Option>, + }, + Switch { + selector: Handle, + cases: Vec<(i32, BodyIndex)>, + default: BodyIndex, + }, + Break, + Continue, +} + +/// An intermediate representation of a Naga [`Block`]. +/// +/// This will be assembled into a `Block` once we've added spills for phi nodes +/// and out-of-scope expressions. See [`BlockContext`] for details. +/// +/// [`Block`]: crate::Block +#[derive(Debug)] +struct Body { + /// The index of the direct parent of this body + parent: usize, + data: Vec, +} + +impl Body { + /// Creates a new empty `Body` with the specified `parent` + pub const fn with_parent(parent: usize) -> Self { + Body { + parent, + data: Vec::new(), + } + } +} + +#[derive(Debug)] +struct PhiExpression { + /// The local variable used for the phi node + local: Handle, + /// List of (expression, block) + expressions: Vec<(spirv::Word, spirv::Word)>, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum MergeBlockInformation { + LoopMerge, + LoopContinue, + SelectionMerge, + SwitchMerge, +} + +/// Fragments of Naga IR, to be assembled into `Statements` once data flow is +/// resolved. +/// +/// We can't build a Naga `Statement` tree directly from SPIR-V blocks for three +/// main reasons: +/// +/// - We parse a function's SPIR-V blocks in the order they appear in the file. +/// Within a function, SPIR-V requires that a block must precede any blocks it +/// structurally dominates, but doesn't say much else about the order in which +/// they must appear. So while we know we'll see control flow header blocks +/// before their child constructs and merge blocks, those children and the +/// merge blocks may appear in any order - perhaps even intermingled with +/// children of other constructs. +/// +/// - A SPIR-V expression can be used in any SPIR-V block dominated by its +/// definition, whereas Naga expressions are scoped to the rest of their +/// subtree. This means that discovering an expression use later in the +/// function retroactively requires us to have spilled that expression into a +/// local variable back before we left its scope. +/// +/// - We translate SPIR-V OpPhi expressions as Naga local variables in which we +/// store the appropriate value before jumping to the OpPhi's block. +/// +/// All these cases require us to go back and amend previously generated Naga IR +/// based on things we discover later. But modifying old blocks in arbitrary +/// spots in a `Statement` tree is awkward. +/// +/// Instead, as we iterate through the function's body, we accumulate +/// control-flow-free fragments of Naga IR in the [`blocks`] table, while +/// building a skeleton of the Naga `Statement` tree in [`bodies`]. We note any +/// spills and temporaries we must introduce in [`phis`]. +/// +/// Finally, once we've processed the entire function, we add temporaries and +/// spills to the fragmentary `Blocks` as directed by `phis`, and assemble them +/// into the final Naga `Statement` tree as directed by `bodies`. +/// +/// [`blocks`]: BlockContext::blocks +/// [`bodies`]: BlockContext::bodies +/// [`phis`]: BlockContext::phis +/// [`lower`]: function::lower +#[derive(Debug)] +struct BlockContext<'function> { + /// Phi nodes encountered when parsing the function, used to generate spills + /// to local variables. + phis: Vec, + + /// Fragments of control-flow-free Naga IR. + /// + /// These will be stitched together into a proper [`Statement`] tree according + /// to `bodies`, once parsing is complete. + /// + /// [`Statement`]: crate::Statement + blocks: FastHashMap, + + /// Map from each SPIR-V block's label id to the index of the [`Body`] in + /// [`bodies`] the block should append its contents to. + /// + /// Since each statement in a Naga [`Block`] dominates the next, we are sure + /// to encounter their SPIR-V blocks in order. Thus, by having this table + /// map a SPIR-V structured control flow construct's merge block to the same + /// body index as its header block, when we encounter the merge block, we + /// will simply pick up building the [`Body`] where the header left off. + /// + /// A function's first block is special: it is the only block we encounter + /// without having seen its label mentioned in advance. (It's simply the + /// first `OpLabel` after the `OpFunction`.) We thus assume that any block + /// missing an entry here must be the first block, which always has body + /// index zero. + /// + /// [`bodies`]: BlockContext::bodies + /// [`Block`]: crate::Block + body_for_label: FastHashMap, + + /// SPIR-V metadata about merge/continue blocks. + mergers: FastHashMap, + + /// A table of `Body` values, each representing a block in the final IR. + /// + /// The first element is always the function's top-level block. + bodies: Vec, + + /// Id of the function currently being processed + function_id: spirv::Word, + /// Expression arena of the function currently being processed + expressions: &'function mut Arena, + /// Local variables arena of the function currently being processed + local_arena: &'function mut Arena, + /// Constants arena of the module being processed + const_arena: &'function mut Arena, + const_expressions: &'function mut Arena, + /// Type arena of the module being processed + type_arena: &'function UniqueArena, + /// Global arena of the module being processed + global_arena: &'function Arena, + /// Arguments of the function currently being processed + arguments: &'function [crate::FunctionArgument], + /// Metadata about the usage of function parameters as sampling objects + parameter_sampling: &'function mut [image::SamplingFlags], +} + +enum SignAnchor { + Result, + Operand, +} + +pub struct Frontend { + data: I, + data_offset: usize, + state: ModuleState, + layouter: Layouter, + temp_bytes: Vec, + ext_glsl_id: Option, + future_decor: FastHashMap, + future_member_decor: FastHashMap<(spirv::Word, MemberIndex), Decoration>, + lookup_member: FastHashMap<(Handle, MemberIndex), LookupMember>, + handle_sampling: FastHashMap, image::SamplingFlags>, + lookup_type: FastHashMap, + lookup_void_type: Option, + lookup_storage_buffer_types: FastHashMap, crate::StorageAccess>, + // Lookup for samplers and sampled images, storing flags on how they are used. + lookup_constant: FastHashMap, + lookup_variable: FastHashMap, + lookup_expression: FastHashMap, + // Load overrides are used to work around row-major matrices + lookup_load_override: FastHashMap, + lookup_sampled_image: FastHashMap, + lookup_function_type: FastHashMap, + lookup_function: FastHashMap, + lookup_entry_point: FastHashMap, + //Note: each `OpFunctionCall` gets a single entry here, indexed by the + // dummy `Handle` of the call site. + deferred_function_calls: Vec, + dummy_functions: Arena, + // Graph of all function calls through the module. + // It's used to sort the functions (as nodes) topologically, + // so that in the IR any called function is already known. + function_call_graph: GraphMap, + options: Options, + + /// Maps for a switch from a case target to the respective body and associated literals that + /// use that target block id. + /// + /// Used to preserve allocations between instruction parsing. + switch_cases: FastIndexMap)>, + + /// Tracks access to gl_PerVertex's builtins, it is used to cull unused builtins since initializing those can + /// affect performance and the mere presence of some of these builtins might cause backends to error since they + /// might be unsupported. + /// + /// The problematic builtins are: PointSize, ClipDistance and CullDistance. + /// + /// glslang declares those by default even though they are never written to + /// (see ) + gl_per_vertex_builtin_access: FastHashSet, +} + +impl> Frontend { + pub fn new(data: I, options: &Options) -> Self { + Frontend { + data, + data_offset: 0, + state: ModuleState::Empty, + layouter: Layouter::default(), + temp_bytes: Vec::new(), + ext_glsl_id: None, + future_decor: FastHashMap::default(), + future_member_decor: FastHashMap::default(), + handle_sampling: FastHashMap::default(), + lookup_member: FastHashMap::default(), + lookup_type: FastHashMap::default(), + lookup_void_type: None, + lookup_storage_buffer_types: FastHashMap::default(), + lookup_constant: FastHashMap::default(), + lookup_variable: FastHashMap::default(), + lookup_expression: FastHashMap::default(), + lookup_load_override: FastHashMap::default(), + lookup_sampled_image: FastHashMap::default(), + lookup_function_type: FastHashMap::default(), + lookup_function: FastHashMap::default(), + lookup_entry_point: FastHashMap::default(), + deferred_function_calls: Vec::default(), + dummy_functions: Arena::new(), + function_call_graph: GraphMap::new(), + options: options.clone(), + switch_cases: FastIndexMap::default(), + gl_per_vertex_builtin_access: FastHashSet::default(), + } + } + + fn span_from(&self, from: usize) -> crate::Span { + crate::Span::from(from..self.data_offset) + } + + fn span_from_with_op(&self, from: usize) -> crate::Span { + crate::Span::from((from - 4)..self.data_offset) + } + + fn next(&mut self) -> Result { + if let Some(res) = self.data.next() { + self.data_offset += 4; + Ok(res) + } else { + Err(Error::IncompleteData) + } + } + + fn next_inst(&mut self) -> Result { + let word = self.next()?; + let (wc, opcode) = ((word >> 16) as u16, (word & 0xffff) as u16); + if wc == 0 { + return Err(Error::InvalidWordCount); + } + let op = spirv::Op::from_u32(opcode as u32).ok_or(Error::UnknownInstruction(opcode))?; + + Ok(Instruction { op, wc }) + } + + fn next_string(&mut self, mut count: u16) -> Result<(String, u16), Error> { + self.temp_bytes.clear(); + loop { + if count == 0 { + return Err(Error::BadString); + } + count -= 1; + let chars = self.next()?.to_le_bytes(); + let pos = chars.iter().position(|&c| c == 0).unwrap_or(4); + self.temp_bytes.extend_from_slice(&chars[..pos]); + if pos < 4 { + break; + } + } + std::str::from_utf8(&self.temp_bytes) + .map(|s| (s.to_owned(), count)) + .map_err(|_| Error::BadString) + } + + fn next_decoration( + &mut self, + inst: Instruction, + base_words: u16, + dec: &mut Decoration, + ) -> Result<(), Error> { + let raw = self.next()?; + let dec_typed = spirv::Decoration::from_u32(raw).ok_or(Error::InvalidDecoration(raw))?; + log::trace!("\t\t{}: {:?}", dec.debug_name(), dec_typed); + match dec_typed { + spirv::Decoration::BuiltIn => { + inst.expect(base_words + 2)?; + dec.built_in = Some(self.next()?); + } + spirv::Decoration::Location => { + inst.expect(base_words + 2)?; + dec.location = Some(self.next()?); + } + spirv::Decoration::DescriptorSet => { + inst.expect(base_words + 2)?; + dec.desc_set = Some(self.next()?); + } + spirv::Decoration::Binding => { + inst.expect(base_words + 2)?; + dec.desc_index = Some(self.next()?); + } + spirv::Decoration::BufferBlock => { + dec.storage_buffer = true; + } + spirv::Decoration::Offset => { + inst.expect(base_words + 2)?; + dec.offset = Some(self.next()?); + } + spirv::Decoration::ArrayStride => { + inst.expect(base_words + 2)?; + dec.array_stride = NonZeroU32::new(self.next()?); + } + spirv::Decoration::MatrixStride => { + inst.expect(base_words + 2)?; + dec.matrix_stride = NonZeroU32::new(self.next()?); + } + spirv::Decoration::Invariant => { + dec.invariant = true; + } + spirv::Decoration::NoPerspective => { + dec.interpolation = Some(crate::Interpolation::Linear); + } + spirv::Decoration::Flat => { + dec.interpolation = Some(crate::Interpolation::Flat); + } + spirv::Decoration::Centroid => { + dec.sampling = Some(crate::Sampling::Centroid); + } + spirv::Decoration::Sample => { + dec.sampling = Some(crate::Sampling::Sample); + } + spirv::Decoration::NonReadable => { + dec.flags |= DecorationFlags::NON_READABLE; + } + spirv::Decoration::NonWritable => { + dec.flags |= DecorationFlags::NON_WRITABLE; + } + spirv::Decoration::ColMajor => { + dec.matrix_major = Some(Majority::Column); + } + spirv::Decoration::RowMajor => { + dec.matrix_major = Some(Majority::Row); + } + spirv::Decoration::SpecId => { + dec.specialization = Some(self.next()?); + } + other => { + log::warn!("Unknown decoration {:?}", other); + for _ in base_words + 1..inst.wc { + let _var = self.next()?; + } + } + } + Ok(()) + } + + /// Return the Naga `Expression` for a given SPIR-V result `id`. + /// + /// `lookup` must be the `LookupExpression` for `id`. + /// + /// SPIR-V result ids can be used by any block dominated by the id's + /// definition, but Naga `Expressions` are only in scope for the remainder + /// of their `Statement` subtree. This means that the `Expression` generated + /// for `id` may no longer be in scope. In such cases, this function takes + /// care of spilling the value of `id` to a `LocalVariable` which can then + /// be used anywhere. The SPIR-V domination rule ensures that the + /// `LocalVariable` has been initialized before it is used. + /// + /// The `body_idx` argument should be the index of the `Body` that hopes to + /// use `id`'s `Expression`. + fn get_expr_handle( + &self, + id: spirv::Word, + lookup: &LookupExpression, + ctx: &mut BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + body_idx: BodyIndex, + ) -> Handle { + // What `Body` was `id` defined in? + let expr_body_idx = ctx + .body_for_label + .get(&lookup.block_id) + .copied() + .unwrap_or(0); + + // Don't need to do a load/store if the expression is in the main body + // or if the expression is in the same body as where the query was + // requested. The body_idx might actually not be the final one if a loop + // or conditional occurs but in those cases we know that the new body + // will be a subscope of the body that was passed so we can still reuse + // the handle and not issue a load/store. + if is_parent(body_idx, expr_body_idx, ctx) { + lookup.handle + } else { + // Add a temporary variable of the same type which will be used to + // store the original expression and used in the current block + let ty = self.lookup_type[&lookup.type_id].handle; + let local = ctx.local_arena.append( + crate::LocalVariable { + name: None, + ty, + init: None, + }, + crate::Span::default(), + ); + + block.extend(emitter.finish(ctx.expressions)); + let pointer = ctx.expressions.append( + crate::Expression::LocalVariable(local), + crate::Span::default(), + ); + emitter.start(ctx.expressions); + let expr = ctx + .expressions + .append(crate::Expression::Load { pointer }, crate::Span::default()); + + // Add a slightly odd entry to the phi table, so that while `id`'s + // `Expression` is still in scope, the usual phi processing will + // spill its value to `local`, where we can find it later. + // + // This pretends that the block in which `id` is defined is the + // predecessor of some other block with a phi in it that cites id as + // one of its sources, and uses `local` as its variable. There is no + // such phi, but nobody needs to know that. + ctx.phis.push(PhiExpression { + local, + expressions: vec![(id, lookup.block_id)], + }); + + expr + } + } + + fn parse_expr_unary_op( + &mut self, + ctx: &mut BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + op: crate::UnaryOperator, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let p_id = self.next()?; + + let p_lexp = self.lookup_expression.lookup(p_id)?; + let handle = self.get_expr_handle(p_id, p_lexp, ctx, emitter, block, body_idx); + + let expr = crate::Expression::Unary { op, expr: handle }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, self.span_from_with_op(start)), + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + fn parse_expr_binary_op( + &mut self, + ctx: &mut BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + op: crate::BinaryOperator, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let p1_id = self.next()?; + let p2_id = self.next()?; + + let p1_lexp = self.lookup_expression.lookup(p1_id)?; + let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); + let p2_lexp = self.lookup_expression.lookup(p2_id)?; + let right = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx); + + let expr = crate::Expression::Binary { op, left, right }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, self.span_from_with_op(start)), + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + /// A more complicated version of the unary op, + /// where we force the operand to have the same type as the result. + fn parse_expr_unary_op_sign_adjusted( + &mut self, + ctx: &mut BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + op: crate::UnaryOperator, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let p1_id = self.next()?; + let span = self.span_from_with_op(start); + + let p1_lexp = self.lookup_expression.lookup(p1_id)?; + let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); + + let result_lookup_ty = self.lookup_type.lookup(result_type_id)?; + let kind = ctx.type_arena[result_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + + let expr = crate::Expression::Unary { + op, + expr: if p1_lexp.type_id == result_type_id { + left + } else { + ctx.expressions.append( + crate::Expression::As { + expr: left, + kind, + convert: None, + }, + span, + ) + }, + }; + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + /// A more complicated version of the binary op, + /// where we force the operand to have the same type as the result. + /// This is mostly needed for "i++" and "i--" coming from GLSL. + #[allow(clippy::too_many_arguments)] + fn parse_expr_binary_op_sign_adjusted( + &mut self, + ctx: &mut BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + op: crate::BinaryOperator, + // For arithmetic operations, we need the sign of operands to match the result. + // For boolean operations, however, the operands need to match the signs, but + // result is always different - a boolean. + anchor: SignAnchor, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let p1_id = self.next()?; + let p2_id = self.next()?; + let span = self.span_from_with_op(start); + + let p1_lexp = self.lookup_expression.lookup(p1_id)?; + let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); + let p2_lexp = self.lookup_expression.lookup(p2_id)?; + let right = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx); + + let expected_type_id = match anchor { + SignAnchor::Result => result_type_id, + SignAnchor::Operand => p1_lexp.type_id, + }; + let expected_lookup_ty = self.lookup_type.lookup(expected_type_id)?; + let kind = ctx.type_arena[expected_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + + let expr = crate::Expression::Binary { + op, + left: if p1_lexp.type_id == expected_type_id { + left + } else { + ctx.expressions.append( + crate::Expression::As { + expr: left, + kind, + convert: None, + }, + span, + ) + }, + right: if p2_lexp.type_id == expected_type_id { + right + } else { + ctx.expressions.append( + crate::Expression::As { + expr: right, + kind, + convert: None, + }, + span, + ) + }, + }; + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + /// A version of the binary op where one or both of the arguments might need to be casted to a + /// specific integer kind (unsigned or signed), used for operations like OpINotEqual or + /// OpUGreaterThan. + #[allow(clippy::too_many_arguments)] + fn parse_expr_int_comparison( + &mut self, + ctx: &mut BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + op: crate::BinaryOperator, + kind: crate::ScalarKind, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let p1_id = self.next()?; + let p2_id = self.next()?; + let span = self.span_from_with_op(start); + + let p1_lexp = self.lookup_expression.lookup(p1_id)?; + let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); + let p1_lookup_ty = self.lookup_type.lookup(p1_lexp.type_id)?; + let p1_kind = ctx.type_arena[p1_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + let p2_lexp = self.lookup_expression.lookup(p2_id)?; + let right = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx); + let p2_lookup_ty = self.lookup_type.lookup(p2_lexp.type_id)?; + let p2_kind = ctx.type_arena[p2_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + + let expr = crate::Expression::Binary { + op, + left: if p1_kind == kind { + left + } else { + ctx.expressions.append( + crate::Expression::As { + expr: left, + kind, + convert: None, + }, + span, + ) + }, + right: if p2_kind == kind { + right + } else { + ctx.expressions.append( + crate::Expression::As { + expr: right, + kind, + convert: None, + }, + span, + ) + }, + }; + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + fn parse_expr_shift_op( + &mut self, + ctx: &mut BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + op: crate::BinaryOperator, + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let p1_id = self.next()?; + let p2_id = self.next()?; + + let span = self.span_from_with_op(start); + + let p1_lexp = self.lookup_expression.lookup(p1_id)?; + let left = self.get_expr_handle(p1_id, p1_lexp, ctx, emitter, block, body_idx); + let p2_lexp = self.lookup_expression.lookup(p2_id)?; + let p2_handle = self.get_expr_handle(p2_id, p2_lexp, ctx, emitter, block, body_idx); + // convert the shift to Uint + let right = ctx.expressions.append( + crate::Expression::As { + expr: p2_handle, + kind: crate::ScalarKind::Uint, + convert: None, + }, + span, + ); + + let expr = crate::Expression::Binary { op, left, right }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + fn parse_expr_derivative( + &mut self, + ctx: &mut BlockContext, + emitter: &mut crate::proc::Emitter, + block: &mut crate::Block, + block_id: spirv::Word, + body_idx: usize, + (axis, ctrl): (crate::DerivativeAxis, crate::DerivativeControl), + ) -> Result<(), Error> { + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let arg_id = self.next()?; + + let arg_lexp = self.lookup_expression.lookup(arg_id)?; + let arg_handle = self.get_expr_handle(arg_id, arg_lexp, ctx, emitter, block, body_idx); + + let expr = crate::Expression::Derivative { + axis, + ctrl, + expr: arg_handle, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, self.span_from_with_op(start)), + type_id: result_type_id, + block_id, + }, + ); + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn insert_composite( + &self, + root_expr: Handle, + root_type_id: spirv::Word, + object_expr: Handle, + selections: &[spirv::Word], + type_arena: &UniqueArena, + expressions: &mut Arena, + span: crate::Span, + ) -> Result, Error> { + let selection = match selections.first() { + Some(&index) => index, + None => return Ok(object_expr), + }; + let root_span = expressions.get_span(root_expr); + let root_lookup = self.lookup_type.lookup(root_type_id)?; + + let (count, child_type_id) = match type_arena[root_lookup.handle].inner { + crate::TypeInner::Struct { ref members, .. } => { + let child_member = self + .lookup_member + .get(&(root_lookup.handle, selection)) + .ok_or(Error::InvalidAccessType(root_type_id))?; + (members.len(), child_member.type_id) + } + crate::TypeInner::Array { size, .. } => { + let size = match size { + crate::ArraySize::Constant(size) => size.get(), + // A runtime sized array is not a composite type + crate::ArraySize::Dynamic => { + return Err(Error::InvalidAccessType(root_type_id)) + } + }; + + let child_type_id = root_lookup + .base_id + .ok_or(Error::InvalidAccessType(root_type_id))?; + + (size as usize, child_type_id) + } + crate::TypeInner::Vector { size, .. } + | crate::TypeInner::Matrix { columns: size, .. } => { + let child_type_id = root_lookup + .base_id + .ok_or(Error::InvalidAccessType(root_type_id))?; + (size as usize, child_type_id) + } + _ => return Err(Error::InvalidAccessType(root_type_id)), + }; + + let mut components = Vec::with_capacity(count); + for index in 0..count as u32 { + let expr = expressions.append( + crate::Expression::AccessIndex { + base: root_expr, + index, + }, + if index == selection { span } else { root_span }, + ); + components.push(expr); + } + components[selection as usize] = self.insert_composite( + components[selection as usize], + child_type_id, + object_expr, + &selections[1..], + type_arena, + expressions, + span, + )?; + + Ok(expressions.append( + crate::Expression::Compose { + ty: root_lookup.handle, + components, + }, + span, + )) + } + + /// Add the next SPIR-V block's contents to `block_ctx`. + /// + /// Except for the function's entry block, `block_id` should be the label of + /// a block we've seen mentioned before, with an entry in + /// `block_ctx.body_for_label` to tell us which `Body` it contributes to. + fn next_block(&mut self, block_id: spirv::Word, ctx: &mut BlockContext) -> Result<(), Error> { + // Extend `body` with the correct form for a branch to `target`. + fn merger(body: &mut Body, target: &MergeBlockInformation) { + body.data.push(match *target { + MergeBlockInformation::LoopContinue => BodyFragment::Continue, + MergeBlockInformation::LoopMerge | MergeBlockInformation::SwitchMerge => { + BodyFragment::Break + } + + // Finishing a selection merge means just falling off the end of + // the `accept` or `reject` block of the `If` statement. + MergeBlockInformation::SelectionMerge => return, + }) + } + + let mut emitter = crate::proc::Emitter::default(); + emitter.start(ctx.expressions); + + // Find the `Body` to which this block contributes. + // + // If this is some SPIR-V structured control flow construct's merge + // block, then `body_idx` will refer to the same `Body` as the header, + // so that we simply pick up accumulating the `Body` where the header + // left off. Each of the statements in a block dominates the next, so + // we're sure to encounter their SPIR-V blocks in order, ensuring that + // the `Body` will be assembled in the proper order. + // + // Note that, unlike every other kind of SPIR-V block, we don't know the + // function's first block's label in advance. Thus, we assume that if + // this block has no entry in `ctx.body_for_label`, it must be the + // function's first block. This always has body index zero. + let mut body_idx = *ctx.body_for_label.entry(block_id).or_default(); + + // The Naga IR block this call builds. This will end up as + // `ctx.blocks[&block_id]`, and `ctx.bodies[body_idx]` will refer to it + // via a `BodyFragment::BlockId`. + let mut block = crate::Block::new(); + + // Stores the merge block as defined by a `OpSelectionMerge` otherwise is `None` + // + // This is used in `OpSwitch` to promote the `MergeBlockInformation` from + // `SelectionMerge` to `SwitchMerge` to allow `Break`s this isn't desirable for + // `LoopMerge`s because otherwise `Continue`s wouldn't be allowed + let mut selection_merge_block = None; + + macro_rules! get_expr_handle { + ($id:expr, $lexp:expr) => { + self.get_expr_handle($id, $lexp, ctx, &mut emitter, &mut block, body_idx) + }; + } + macro_rules! parse_expr_op { + ($op:expr, BINARY) => { + self.parse_expr_binary_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) + }; + + ($op:expr, SHIFT) => { + self.parse_expr_shift_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) + }; + ($op:expr, UNARY) => { + self.parse_expr_unary_op(ctx, &mut emitter, &mut block, block_id, body_idx, $op) + }; + ($axis:expr, $ctrl:expr, DERIVATIVE) => { + self.parse_expr_derivative( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + ($axis, $ctrl), + ) + }; + } + + let terminator = loop { + use spirv::Op; + let start = self.data_offset; + let inst = self.next_inst()?; + let span = crate::Span::from(start..(start + 4 * (inst.wc as usize))); + log::debug!("\t\t{:?} [{}]", inst.op, inst.wc); + + match inst.op { + Op::Line => { + inst.expect(4)?; + let _file_id = self.next()?; + let _row_id = self.next()?; + let _col_id = self.next()?; + } + Op::NoLine => inst.expect(1)?, + Op::Undef => { + inst.expect(3)?; + let type_id = self.next()?; + let id = self.next()?; + let type_lookup = self.lookup_type.lookup(type_id)?; + let ty = type_lookup.handle; + + self.lookup_expression.insert( + id, + LookupExpression { + handle: ctx + .expressions + .append(crate::Expression::ZeroValue(ty), span), + type_id, + block_id, + }, + ); + } + Op::Variable => { + inst.expect_at_least(4)?; + block.extend(emitter.finish(ctx.expressions)); + + let result_type_id = self.next()?; + let result_id = self.next()?; + let _storage_class = self.next()?; + let init = if inst.wc > 4 { + inst.expect(5)?; + let init_id = self.next()?; + let lconst = self.lookup_constant.lookup(init_id)?; + Some( + ctx.expressions + .append(crate::Expression::Constant(lconst.handle), span), + ) + } else { + None + }; + + let name = self + .future_decor + .remove(&result_id) + .and_then(|decor| decor.name); + if let Some(ref name) = name { + log::debug!("\t\t\tid={} name={}", result_id, name); + } + let lookup_ty = self.lookup_type.lookup(result_type_id)?; + let var_handle = ctx.local_arena.append( + crate::LocalVariable { + name, + ty: match ctx.type_arena[lookup_ty.handle].inner { + crate::TypeInner::Pointer { base, .. } => base, + _ => lookup_ty.handle, + }, + init, + }, + span, + ); + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx + .expressions + .append(crate::Expression::LocalVariable(var_handle), span), + type_id: result_type_id, + block_id, + }, + ); + emitter.start(ctx.expressions); + } + Op::Phi => { + inst.expect_at_least(3)?; + block.extend(emitter.finish(ctx.expressions)); + + let result_type_id = self.next()?; + let result_id = self.next()?; + + let name = format!("phi_{result_id}"); + let local = ctx.local_arena.append( + crate::LocalVariable { + name: Some(name), + ty: self.lookup_type.lookup(result_type_id)?.handle, + init: None, + }, + self.span_from(start), + ); + let pointer = ctx + .expressions + .append(crate::Expression::LocalVariable(local), span); + + let in_count = (inst.wc - 3) / 2; + let mut phi = PhiExpression { + local, + expressions: Vec::with_capacity(in_count as usize), + }; + for _ in 0..in_count { + let expr = self.next()?; + let block = self.next()?; + phi.expressions.push((expr, block)); + } + + ctx.phis.push(phi); + emitter.start(ctx.expressions); + + // Associate the lookup with an actual value, which is emitted + // into the current block. + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx + .expressions + .append(crate::Expression::Load { pointer }, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::AccessChain | Op::InBoundsAccessChain => { + struct AccessExpression { + base_handle: Handle, + type_id: spirv::Word, + load_override: Option, + } + + inst.expect_at_least(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + log::trace!("\t\t\tlooking up expr {:?}", base_id); + + let mut acex = { + let lexp = self.lookup_expression.lookup(base_id)?; + let lty = self.lookup_type.lookup(lexp.type_id)?; + + // HACK `OpAccessChain` and `OpInBoundsAccessChain` + // require for the result type to be a pointer, but if + // we're given a pointer to an image / sampler, it will + // be *already* dereferenced, since we do that early + // during `parse_type_pointer()`. + // + // This can happen only through `BindingArray`, since + // that's the only case where one can obtain a pointer + // to an image / sampler, and so let's match on that: + let dereference = match ctx.type_arena[lty.handle].inner { + crate::TypeInner::BindingArray { .. } => false, + _ => true, + }; + + let type_id = if dereference { + lty.base_id.ok_or(Error::InvalidAccessType(lexp.type_id))? + } else { + lexp.type_id + }; + + AccessExpression { + base_handle: get_expr_handle!(base_id, lexp), + type_id, + load_override: self.lookup_load_override.get(&base_id).cloned(), + } + }; + + for _ in 4..inst.wc { + let access_id = self.next()?; + log::trace!("\t\t\tlooking up index expr {:?}", access_id); + let index_expr = self.lookup_expression.lookup(access_id)?.clone(); + let index_expr_handle = get_expr_handle!(access_id, &index_expr); + let index_expr_data = &ctx.expressions[index_expr.handle]; + let index_maybe = match *index_expr_data { + crate::Expression::Constant(const_handle) => Some( + ctx.gctx() + .eval_expr_to_u32(ctx.const_arena[const_handle].init) + .map_err(|_| { + Error::InvalidAccess(crate::Expression::Constant( + const_handle, + )) + })?, + ), + _ => None, + }; + + log::trace!("\t\t\tlooking up type {:?}", acex.type_id); + let type_lookup = self.lookup_type.lookup(acex.type_id)?; + let ty = &ctx.type_arena[type_lookup.handle]; + acex = match ty.inner { + // can only index a struct with a constant + crate::TypeInner::Struct { ref members, .. } => { + let index = index_maybe + .ok_or_else(|| Error::InvalidAccess(index_expr_data.clone()))?; + + let lookup_member = self + .lookup_member + .get(&(type_lookup.handle, index)) + .ok_or(Error::InvalidAccessType(acex.type_id))?; + let base_handle = ctx.expressions.append( + crate::Expression::AccessIndex { + base: acex.base_handle, + index, + }, + span, + ); + + if ty.name.as_deref() == Some("gl_PerVertex") { + if let Some(crate::Binding::BuiltIn(built_in)) = + members[index as usize].binding + { + self.gl_per_vertex_builtin_access.insert(built_in); + } + } + + AccessExpression { + base_handle, + type_id: lookup_member.type_id, + load_override: if lookup_member.row_major { + debug_assert!(acex.load_override.is_none()); + let sub_type_lookup = + self.lookup_type.lookup(lookup_member.type_id)?; + Some(match ctx.type_arena[sub_type_lookup.handle].inner { + // load it transposed, to match column major expectations + crate::TypeInner::Matrix { .. } => { + let loaded = ctx.expressions.append( + crate::Expression::Load { + pointer: base_handle, + }, + span, + ); + let transposed = ctx.expressions.append( + crate::Expression::Math { + fun: crate::MathFunction::Transpose, + arg: loaded, + arg1: None, + arg2: None, + arg3: None, + }, + span, + ); + LookupLoadOverride::Loaded(transposed) + } + _ => LookupLoadOverride::Pending, + }) + } else { + None + }, + } + } + crate::TypeInner::Matrix { .. } => { + let load_override = match acex.load_override { + // We are indexing inside a row-major matrix + Some(LookupLoadOverride::Loaded(load_expr)) => { + let index = index_maybe.ok_or_else(|| { + Error::InvalidAccess(index_expr_data.clone()) + })?; + let sub_handle = ctx.expressions.append( + crate::Expression::AccessIndex { + base: load_expr, + index, + }, + span, + ); + Some(LookupLoadOverride::Loaded(sub_handle)) + } + _ => None, + }; + let sub_expr = match index_maybe { + Some(index) => crate::Expression::AccessIndex { + base: acex.base_handle, + index, + }, + None => crate::Expression::Access { + base: acex.base_handle, + index: index_expr_handle, + }, + }; + AccessExpression { + base_handle: ctx.expressions.append(sub_expr, span), + type_id: type_lookup + .base_id + .ok_or(Error::InvalidAccessType(acex.type_id))?, + load_override, + } + } + // This must be a vector or an array. + _ => { + let base_handle = ctx.expressions.append( + crate::Expression::Access { + base: acex.base_handle, + index: index_expr_handle, + }, + span, + ); + let load_override = match acex.load_override { + // If there is a load override in place, then we always end up + // with a side-loaded value here. + Some(lookup_load_override) => { + let sub_expr = match lookup_load_override { + // We must be indexing into the array of row-major matrices. + // Let's load the result of indexing and transpose it. + LookupLoadOverride::Pending => { + let loaded = ctx.expressions.append( + crate::Expression::Load { + pointer: base_handle, + }, + span, + ); + ctx.expressions.append( + crate::Expression::Math { + fun: crate::MathFunction::Transpose, + arg: loaded, + arg1: None, + arg2: None, + arg3: None, + }, + span, + ) + } + // We are indexing inside a row-major matrix. + LookupLoadOverride::Loaded(load_expr) => { + ctx.expressions.append( + crate::Expression::Access { + base: load_expr, + index: index_expr_handle, + }, + span, + ) + } + }; + Some(LookupLoadOverride::Loaded(sub_expr)) + } + None => None, + }; + AccessExpression { + base_handle, + type_id: type_lookup + .base_id + .ok_or(Error::InvalidAccessType(acex.type_id))?, + load_override, + } + } + }; + } + + if let Some(load_expr) = acex.load_override { + self.lookup_load_override.insert(result_id, load_expr); + } + let lookup_expression = LookupExpression { + handle: acex.base_handle, + type_id: result_type_id, + block_id, + }; + self.lookup_expression.insert(result_id, lookup_expression); + } + Op::VectorExtractDynamic => { + inst.expect(5)?; + + let result_type_id = self.next()?; + let id = self.next()?; + let composite_id = self.next()?; + let index_id = self.next()?; + + let root_lexp = self.lookup_expression.lookup(composite_id)?; + let root_handle = get_expr_handle!(composite_id, root_lexp); + let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; + let index_lexp = self.lookup_expression.lookup(index_id)?; + let index_handle = get_expr_handle!(index_id, index_lexp); + let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle; + + let num_components = match ctx.type_arena[root_type_lookup.handle].inner { + crate::TypeInner::Vector { size, .. } => size as u32, + _ => return Err(Error::InvalidVectorType(root_type_lookup.handle)), + }; + + let mut make_index = |ctx: &mut BlockContext, index: u32| { + make_index_literal( + ctx, + index, + &mut block, + &mut emitter, + index_type, + index_lexp.type_id, + span, + ) + }; + + let index_expr = make_index(ctx, 0)?; + let mut handle = ctx.expressions.append( + crate::Expression::Access { + base: root_handle, + index: index_expr, + }, + span, + ); + for index in 1..num_components { + let index_expr = make_index(ctx, index)?; + let access_expr = ctx.expressions.append( + crate::Expression::Access { + base: root_handle, + index: index_expr, + }, + span, + ); + let cond = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Equal, + left: index_expr, + right: index_handle, + }, + span, + ); + handle = ctx.expressions.append( + crate::Expression::Select { + condition: cond, + accept: access_expr, + reject: handle, + }, + span, + ); + } + + self.lookup_expression.insert( + id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::VectorInsertDynamic => { + inst.expect(6)?; + + let result_type_id = self.next()?; + let id = self.next()?; + let composite_id = self.next()?; + let object_id = self.next()?; + let index_id = self.next()?; + + let object_lexp = self.lookup_expression.lookup(object_id)?; + let object_handle = get_expr_handle!(object_id, object_lexp); + let root_lexp = self.lookup_expression.lookup(composite_id)?; + let root_handle = get_expr_handle!(composite_id, root_lexp); + let root_type_lookup = self.lookup_type.lookup(root_lexp.type_id)?; + let index_lexp = self.lookup_expression.lookup(index_id)?; + let index_handle = get_expr_handle!(index_id, index_lexp); + let index_type = self.lookup_type.lookup(index_lexp.type_id)?.handle; + + let num_components = match ctx.type_arena[root_type_lookup.handle].inner { + crate::TypeInner::Vector { size, .. } => size as u32, + _ => return Err(Error::InvalidVectorType(root_type_lookup.handle)), + }; + + let mut components = Vec::with_capacity(num_components as usize); + for index in 0..num_components { + let index_expr = make_index_literal( + ctx, + index, + &mut block, + &mut emitter, + index_type, + index_lexp.type_id, + span, + )?; + let access_expr = ctx.expressions.append( + crate::Expression::Access { + base: root_handle, + index: index_expr, + }, + span, + ); + let cond = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Equal, + left: index_expr, + right: index_handle, + }, + span, + ); + let handle = ctx.expressions.append( + crate::Expression::Select { + condition: cond, + accept: object_handle, + reject: access_expr, + }, + span, + ); + components.push(handle); + } + let handle = ctx.expressions.append( + crate::Expression::Compose { + ty: root_type_lookup.handle, + components, + }, + span, + ); + + self.lookup_expression.insert( + id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::CompositeExtract => { + inst.expect_at_least(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + log::trace!("\t\t\tlooking up expr {:?}", base_id); + let mut lexp = self.lookup_expression.lookup(base_id)?.clone(); + lexp.handle = get_expr_handle!(base_id, &lexp); + for _ in 4..inst.wc { + let index = self.next()?; + log::trace!("\t\t\tlooking up type {:?}", lexp.type_id); + let type_lookup = self.lookup_type.lookup(lexp.type_id)?; + let type_id = match ctx.type_arena[type_lookup.handle].inner { + crate::TypeInner::Struct { .. } => { + self.lookup_member + .get(&(type_lookup.handle, index)) + .ok_or(Error::InvalidAccessType(lexp.type_id))? + .type_id + } + crate::TypeInner::Array { .. } + | crate::TypeInner::Vector { .. } + | crate::TypeInner::Matrix { .. } => type_lookup + .base_id + .ok_or(Error::InvalidAccessType(lexp.type_id))?, + ref other => { + log::warn!("composite type {:?}", other); + return Err(Error::UnsupportedType(type_lookup.handle)); + } + }; + lexp = LookupExpression { + handle: ctx.expressions.append( + crate::Expression::AccessIndex { + base: lexp.handle, + index, + }, + span, + ), + type_id, + block_id, + }; + } + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: lexp.handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::CompositeInsert => { + inst.expect_at_least(5)?; + + let result_type_id = self.next()?; + let id = self.next()?; + let object_id = self.next()?; + let composite_id = self.next()?; + let mut selections = Vec::with_capacity(inst.wc as usize - 5); + for _ in 5..inst.wc { + selections.push(self.next()?); + } + + let object_lexp = self.lookup_expression.lookup(object_id)?.clone(); + let object_handle = get_expr_handle!(object_id, &object_lexp); + let root_lexp = self.lookup_expression.lookup(composite_id)?.clone(); + let root_handle = get_expr_handle!(composite_id, &root_lexp); + let handle = self.insert_composite( + root_handle, + result_type_id, + object_handle, + &selections, + ctx.type_arena, + ctx.expressions, + span, + )?; + + self.lookup_expression.insert( + id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::CompositeConstruct => { + inst.expect_at_least(3)?; + + let result_type_id = self.next()?; + let id = self.next()?; + let mut components = Vec::with_capacity(inst.wc as usize - 2); + for _ in 3..inst.wc { + let comp_id = self.next()?; + log::trace!("\t\t\tlooking up expr {:?}", comp_id); + let lexp = self.lookup_expression.lookup(comp_id)?; + let handle = get_expr_handle!(comp_id, lexp); + components.push(handle); + } + let ty = self.lookup_type.lookup(result_type_id)?.handle; + let first = components[0]; + let expr = match ctx.type_arena[ty].inner { + // this is an optimization to detect the splat + crate::TypeInner::Vector { size, .. } + if components.len() == size as usize + && components[1..].iter().all(|&c| c == first) => + { + crate::Expression::Splat { size, value: first } + } + _ => crate::Expression::Compose { ty, components }, + }; + self.lookup_expression.insert( + id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::Load => { + inst.expect_at_least(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let pointer_id = self.next()?; + if inst.wc != 4 { + inst.expect(5)?; + let _memory_access = self.next()?; + } + + let base_lexp = self.lookup_expression.lookup(pointer_id)?; + let base_handle = get_expr_handle!(pointer_id, base_lexp); + let type_lookup = self.lookup_type.lookup(base_lexp.type_id)?; + let handle = match ctx.type_arena[type_lookup.handle].inner { + crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } => { + base_handle + } + _ => match self.lookup_load_override.get(&pointer_id) { + Some(&LookupLoadOverride::Loaded(handle)) => handle, + //Note: we aren't handling `LookupLoadOverride::Pending` properly here + _ => ctx.expressions.append( + crate::Expression::Load { + pointer: base_handle, + }, + span, + ), + }, + }; + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + Op::Store => { + inst.expect_at_least(3)?; + + let pointer_id = self.next()?; + let value_id = self.next()?; + if inst.wc != 3 { + inst.expect(4)?; + let _memory_access = self.next()?; + } + let base_expr = self.lookup_expression.lookup(pointer_id)?; + let base_handle = get_expr_handle!(pointer_id, base_expr); + let value_expr = self.lookup_expression.lookup(value_id)?; + let value_handle = get_expr_handle!(value_id, value_expr); + + block.extend(emitter.finish(ctx.expressions)); + block.push( + crate::Statement::Store { + pointer: base_handle, + value: value_handle, + }, + span, + ); + emitter.start(ctx.expressions); + } + // Arithmetic Instructions +, -, *, /, % + Op::SNegate | Op::FNegate => { + inst.expect(4)?; + self.parse_expr_unary_op_sign_adjusted( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + crate::UnaryOperator::Negate, + )?; + } + Op::IAdd + | Op::ISub + | Op::IMul + | Op::BitwiseOr + | Op::BitwiseXor + | Op::BitwiseAnd + | Op::SDiv + | Op::SRem => { + inst.expect(5)?; + let operator = map_binary_operator(inst.op)?; + self.parse_expr_binary_op_sign_adjusted( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + operator, + SignAnchor::Result, + )?; + } + Op::IEqual | Op::INotEqual => { + inst.expect(5)?; + let operator = map_binary_operator(inst.op)?; + self.parse_expr_binary_op_sign_adjusted( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + operator, + SignAnchor::Operand, + )?; + } + Op::FAdd => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Add, BINARY)?; + } + Op::FSub => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Subtract, BINARY)?; + } + Op::FMul => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Multiply, BINARY)?; + } + Op::UDiv | Op::FDiv => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Divide, BINARY)?; + } + Op::UMod | Op::FRem => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Modulo, BINARY)?; + } + Op::SMod => { + inst.expect(5)?; + + // x - y * int(floor(float(x) / float(y))) + + let start = self.data_offset; + let result_type_id = self.next()?; + let result_id = self.next()?; + let p1_id = self.next()?; + let p2_id = self.next()?; + let span = self.span_from_with_op(start); + + let p1_lexp = self.lookup_expression.lookup(p1_id)?; + let left = self.get_expr_handle( + p1_id, + p1_lexp, + ctx, + &mut emitter, + &mut block, + body_idx, + ); + let p2_lexp = self.lookup_expression.lookup(p2_id)?; + let right = self.get_expr_handle( + p2_id, + p2_lexp, + ctx, + &mut emitter, + &mut block, + body_idx, + ); + + let result_ty = self.lookup_type.lookup(result_type_id)?; + let inner = &ctx.type_arena[result_ty.handle].inner; + let kind = inner.scalar_kind().unwrap(); + let size = inner.size(ctx.gctx()) as u8; + + let left_cast = ctx.expressions.append( + crate::Expression::As { + expr: left, + kind: crate::ScalarKind::Float, + convert: Some(size), + }, + span, + ); + let right_cast = ctx.expressions.append( + crate::Expression::As { + expr: right, + kind: crate::ScalarKind::Float, + convert: Some(size), + }, + span, + ); + let div = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Divide, + left: left_cast, + right: right_cast, + }, + span, + ); + let floor = ctx.expressions.append( + crate::Expression::Math { + fun: crate::MathFunction::Floor, + arg: div, + arg1: None, + arg2: None, + arg3: None, + }, + span, + ); + let cast = ctx.expressions.append( + crate::Expression::As { + expr: floor, + kind, + convert: Some(size), + }, + span, + ); + let mult = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Multiply, + left: cast, + right, + }, + span, + ); + let sub = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Subtract, + left, + right: mult, + }, + span, + ); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: sub, + type_id: result_type_id, + block_id, + }, + ); + } + Op::FMod => { + inst.expect(5)?; + + // x - y * floor(x / y) + + let start = self.data_offset; + let span = self.span_from_with_op(start); + + let result_type_id = self.next()?; + let result_id = self.next()?; + let p1_id = self.next()?; + let p2_id = self.next()?; + + let p1_lexp = self.lookup_expression.lookup(p1_id)?; + let left = self.get_expr_handle( + p1_id, + p1_lexp, + ctx, + &mut emitter, + &mut block, + body_idx, + ); + let p2_lexp = self.lookup_expression.lookup(p2_id)?; + let right = self.get_expr_handle( + p2_id, + p2_lexp, + ctx, + &mut emitter, + &mut block, + body_idx, + ); + + let div = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Divide, + left, + right, + }, + span, + ); + let floor = ctx.expressions.append( + crate::Expression::Math { + fun: crate::MathFunction::Floor, + arg: div, + arg1: None, + arg2: None, + arg3: None, + }, + span, + ); + let mult = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Multiply, + left: floor, + right, + }, + span, + ); + let sub = ctx.expressions.append( + crate::Expression::Binary { + op: crate::BinaryOperator::Subtract, + left, + right: mult, + }, + span, + ); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: sub, + type_id: result_type_id, + block_id, + }, + ); + } + Op::VectorTimesScalar + | Op::VectorTimesMatrix + | Op::MatrixTimesScalar + | Op::MatrixTimesVector + | Op::MatrixTimesMatrix => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::Multiply, BINARY)?; + } + Op::Transpose => { + inst.expect(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let matrix_id = self.next()?; + let matrix_lexp = self.lookup_expression.lookup(matrix_id)?; + let matrix_handle = get_expr_handle!(matrix_id, matrix_lexp); + let expr = crate::Expression::Math { + fun: crate::MathFunction::Transpose, + arg: matrix_handle, + arg1: None, + arg2: None, + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::Dot => { + inst.expect(5)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let left_id = self.next()?; + let right_id = self.next()?; + let left_lexp = self.lookup_expression.lookup(left_id)?; + let left_handle = get_expr_handle!(left_id, left_lexp); + let right_lexp = self.lookup_expression.lookup(right_id)?; + let right_handle = get_expr_handle!(right_id, right_lexp); + let expr = crate::Expression::Math { + fun: crate::MathFunction::Dot, + arg: left_handle, + arg1: Some(right_handle), + arg2: None, + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::BitFieldInsert => { + inst.expect(7)?; + + let start = self.data_offset; + let span = self.span_from_with_op(start); + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + let insert_id = self.next()?; + let offset_id = self.next()?; + let count_id = self.next()?; + let base_lexp = self.lookup_expression.lookup(base_id)?; + let base_handle = get_expr_handle!(base_id, base_lexp); + let insert_lexp = self.lookup_expression.lookup(insert_id)?; + let insert_handle = get_expr_handle!(insert_id, insert_lexp); + let offset_lexp = self.lookup_expression.lookup(offset_id)?; + let offset_handle = get_expr_handle!(offset_id, offset_lexp); + let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; + let count_lexp = self.lookup_expression.lookup(count_id)?; + let count_handle = get_expr_handle!(count_id, count_lexp); + let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?; + + let offset_kind = ctx.type_arena[offset_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + let count_kind = ctx.type_arena[count_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + + let offset_cast_handle = if offset_kind != crate::ScalarKind::Uint { + ctx.expressions.append( + crate::Expression::As { + expr: offset_handle, + kind: crate::ScalarKind::Uint, + convert: None, + }, + span, + ) + } else { + offset_handle + }; + + let count_cast_handle = if count_kind != crate::ScalarKind::Uint { + ctx.expressions.append( + crate::Expression::As { + expr: count_handle, + kind: crate::ScalarKind::Uint, + convert: None, + }, + span, + ) + } else { + count_handle + }; + + let expr = crate::Expression::Math { + fun: crate::MathFunction::InsertBits, + arg: base_handle, + arg1: Some(insert_handle), + arg2: Some(offset_cast_handle), + arg3: Some(count_cast_handle), + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::BitFieldSExtract | Op::BitFieldUExtract => { + inst.expect(6)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + let offset_id = self.next()?; + let count_id = self.next()?; + let base_lexp = self.lookup_expression.lookup(base_id)?; + let base_handle = get_expr_handle!(base_id, base_lexp); + let offset_lexp = self.lookup_expression.lookup(offset_id)?; + let offset_handle = get_expr_handle!(offset_id, offset_lexp); + let offset_lookup_ty = self.lookup_type.lookup(offset_lexp.type_id)?; + let count_lexp = self.lookup_expression.lookup(count_id)?; + let count_handle = get_expr_handle!(count_id, count_lexp); + let count_lookup_ty = self.lookup_type.lookup(count_lexp.type_id)?; + + let offset_kind = ctx.type_arena[offset_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + let count_kind = ctx.type_arena[count_lookup_ty.handle] + .inner + .scalar_kind() + .unwrap(); + + let offset_cast_handle = if offset_kind != crate::ScalarKind::Uint { + ctx.expressions.append( + crate::Expression::As { + expr: offset_handle, + kind: crate::ScalarKind::Uint, + convert: None, + }, + span, + ) + } else { + offset_handle + }; + + let count_cast_handle = if count_kind != crate::ScalarKind::Uint { + ctx.expressions.append( + crate::Expression::As { + expr: count_handle, + kind: crate::ScalarKind::Uint, + convert: None, + }, + span, + ) + } else { + count_handle + }; + + let expr = crate::Expression::Math { + fun: crate::MathFunction::ExtractBits, + arg: base_handle, + arg1: Some(offset_cast_handle), + arg2: Some(count_cast_handle), + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::BitReverse | Op::BitCount => { + inst.expect(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + let base_lexp = self.lookup_expression.lookup(base_id)?; + let base_handle = get_expr_handle!(base_id, base_lexp); + let expr = crate::Expression::Math { + fun: match inst.op { + Op::BitReverse => crate::MathFunction::ReverseBits, + Op::BitCount => crate::MathFunction::CountOneBits, + _ => unreachable!(), + }, + arg: base_handle, + arg1: None, + arg2: None, + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::OuterProduct => { + inst.expect(5)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let left_id = self.next()?; + let right_id = self.next()?; + let left_lexp = self.lookup_expression.lookup(left_id)?; + let left_handle = get_expr_handle!(left_id, left_lexp); + let right_lexp = self.lookup_expression.lookup(right_id)?; + let right_handle = get_expr_handle!(right_id, right_lexp); + let expr = crate::Expression::Math { + fun: crate::MathFunction::Outer, + arg: left_handle, + arg1: Some(right_handle), + arg2: None, + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + // Bitwise instructions + Op::Not => { + inst.expect(4)?; + self.parse_expr_unary_op_sign_adjusted( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + crate::UnaryOperator::BitwiseNot, + )?; + } + Op::ShiftRightLogical => { + inst.expect(5)?; + //TODO: convert input and result to usigned + parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?; + } + Op::ShiftRightArithmetic => { + inst.expect(5)?; + //TODO: convert input and result to signed + parse_expr_op!(crate::BinaryOperator::ShiftRight, SHIFT)?; + } + Op::ShiftLeftLogical => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::ShiftLeft, SHIFT)?; + } + // Sampling + Op::Image => { + inst.expect(4)?; + self.parse_image_uncouple(block_id)?; + } + Op::SampledImage => { + inst.expect(5)?; + self.parse_image_couple()?; + } + Op::ImageWrite => { + let extra = inst.expect_at_least(4)?; + let stmt = + self.parse_image_write(extra, ctx, &mut emitter, &mut block, body_idx)?; + block.extend(emitter.finish(ctx.expressions)); + block.push(stmt, span); + emitter.start(ctx.expressions); + } + Op::ImageFetch | Op::ImageRead => { + let extra = inst.expect_at_least(5)?; + self.parse_image_load( + extra, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageSampleImplicitLod | Op::ImageSampleExplicitLod => { + let extra = inst.expect_at_least(5)?; + let options = image::SamplingOptions { + compare: false, + project: false, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageSampleProjImplicitLod | Op::ImageSampleProjExplicitLod => { + let extra = inst.expect_at_least(5)?; + let options = image::SamplingOptions { + compare: false, + project: true, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageSampleDrefImplicitLod | Op::ImageSampleDrefExplicitLod => { + let extra = inst.expect_at_least(6)?; + let options = image::SamplingOptions { + compare: true, + project: false, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageSampleProjDrefImplicitLod | Op::ImageSampleProjDrefExplicitLod => { + let extra = inst.expect_at_least(6)?; + let options = image::SamplingOptions { + compare: true, + project: true, + }; + self.parse_image_sample( + extra, + options, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageQuerySize => { + inst.expect(4)?; + self.parse_image_query_size( + false, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageQuerySizeLod => { + inst.expect(5)?; + self.parse_image_query_size( + true, + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + )?; + } + Op::ImageQueryLevels => { + inst.expect(4)?; + self.parse_image_query_other(crate::ImageQuery::NumLevels, ctx, block_id)?; + } + Op::ImageQuerySamples => { + inst.expect(4)?; + self.parse_image_query_other(crate::ImageQuery::NumSamples, ctx, block_id)?; + } + // other ops + Op::Select => { + inst.expect(6)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let condition = self.next()?; + let o1_id = self.next()?; + let o2_id = self.next()?; + + let cond_lexp = self.lookup_expression.lookup(condition)?; + let cond_handle = get_expr_handle!(condition, cond_lexp); + let o1_lexp = self.lookup_expression.lookup(o1_id)?; + let o1_handle = get_expr_handle!(o1_id, o1_lexp); + let o2_lexp = self.lookup_expression.lookup(o2_id)?; + let o2_handle = get_expr_handle!(o2_id, o2_lexp); + + let expr = crate::Expression::Select { + condition: cond_handle, + accept: o1_handle, + reject: o2_handle, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::VectorShuffle => { + inst.expect_at_least(5)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let v1_id = self.next()?; + let v2_id = self.next()?; + + let v1_lexp = self.lookup_expression.lookup(v1_id)?; + let v1_lty = self.lookup_type.lookup(v1_lexp.type_id)?; + let v1_handle = get_expr_handle!(v1_id, v1_lexp); + let n1 = match ctx.type_arena[v1_lty.handle].inner { + crate::TypeInner::Vector { size, .. } => size as u32, + _ => return Err(Error::InvalidInnerType(v1_lexp.type_id)), + }; + let v2_lexp = self.lookup_expression.lookup(v2_id)?; + let v2_lty = self.lookup_type.lookup(v2_lexp.type_id)?; + let v2_handle = get_expr_handle!(v2_id, v2_lexp); + let n2 = match ctx.type_arena[v2_lty.handle].inner { + crate::TypeInner::Vector { size, .. } => size as u32, + _ => return Err(Error::InvalidInnerType(v2_lexp.type_id)), + }; + + self.temp_bytes.clear(); + let mut max_component = 0; + for _ in 5..inst.wc as usize { + let mut index = self.next()?; + if index == u32::MAX { + // treat Undefined as X + index = 0; + } + max_component = max_component.max(index); + self.temp_bytes.push(index as u8); + } + + // Check for swizzle first. + let expr = if max_component < n1 { + use crate::SwizzleComponent as Sc; + let size = match self.temp_bytes.len() { + 2 => crate::VectorSize::Bi, + 3 => crate::VectorSize::Tri, + _ => crate::VectorSize::Quad, + }; + let mut pattern = [Sc::X; 4]; + for (pat, index) in pattern.iter_mut().zip(self.temp_bytes.drain(..)) { + *pat = match index { + 0 => Sc::X, + 1 => Sc::Y, + 2 => Sc::Z, + _ => Sc::W, + }; + } + crate::Expression::Swizzle { + size, + vector: v1_handle, + pattern, + } + } else { + // Fall back to access + compose + let mut components = Vec::with_capacity(self.temp_bytes.len()); + for index in self.temp_bytes.drain(..).map(|i| i as u32) { + let expr = if index < n1 { + crate::Expression::AccessIndex { + base: v1_handle, + index, + } + } else if index < n1 + n2 { + crate::Expression::AccessIndex { + base: v2_handle, + index: index - n1, + } + } else { + return Err(Error::InvalidAccessIndex(index)); + }; + components.push(ctx.expressions.append(expr, span)); + } + crate::Expression::Compose { + ty: self.lookup_type.lookup(result_type_id)?.handle, + components, + } + }; + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::Bitcast + | Op::ConvertSToF + | Op::ConvertUToF + | Op::ConvertFToU + | Op::ConvertFToS + | Op::FConvert + | Op::UConvert + | Op::SConvert => { + inst.expect(4)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let value_id = self.next()?; + + let value_lexp = self.lookup_expression.lookup(value_id)?; + let ty_lookup = self.lookup_type.lookup(result_type_id)?; + let scalar = match ctx.type_arena[ty_lookup.handle].inner { + crate::TypeInner::Scalar(scalar) + | crate::TypeInner::Vector { scalar, .. } + | crate::TypeInner::Matrix { scalar, .. } => scalar, + _ => return Err(Error::InvalidAsType(ty_lookup.handle)), + }; + + let expr = crate::Expression::As { + expr: get_expr_handle!(value_id, value_lexp), + kind: scalar.kind, + convert: if scalar.kind == crate::ScalarKind::Bool { + Some(crate::BOOL_WIDTH) + } else if inst.op == Op::Bitcast { + None + } else { + Some(scalar.width) + }, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::FunctionCall => { + inst.expect_at_least(4)?; + block.extend(emitter.finish(ctx.expressions)); + + let result_type_id = self.next()?; + let result_id = self.next()?; + let func_id = self.next()?; + + let mut arguments = Vec::with_capacity(inst.wc as usize - 4); + for _ in 0..arguments.capacity() { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + arguments.push(get_expr_handle!(arg_id, lexp)); + } + + // We just need an unique handle here, nothing more. + let function = self.add_call(ctx.function_id, func_id); + + let result = if self.lookup_void_type == Some(result_type_id) { + None + } else { + let expr_handle = ctx + .expressions + .append(crate::Expression::CallResult(function), span); + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: expr_handle, + type_id: result_type_id, + block_id, + }, + ); + Some(expr_handle) + }; + block.push( + crate::Statement::Call { + function, + arguments, + result, + }, + span, + ); + emitter.start(ctx.expressions); + } + Op::ExtInst => { + use crate::MathFunction as Mf; + use spirv::GLOp as Glo; + + let base_wc = 5; + inst.expect_at_least(base_wc)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let set_id = self.next()?; + if Some(set_id) != self.ext_glsl_id { + return Err(Error::UnsupportedExtInstSet(set_id)); + } + let inst_id = self.next()?; + let gl_op = Glo::from_u32(inst_id).ok_or(Error::UnsupportedExtInst(inst_id))?; + + let fun = match gl_op { + Glo::Round => Mf::Round, + Glo::RoundEven => Mf::Round, + Glo::Trunc => Mf::Trunc, + Glo::FAbs | Glo::SAbs => Mf::Abs, + Glo::FSign | Glo::SSign => Mf::Sign, + Glo::Floor => Mf::Floor, + Glo::Ceil => Mf::Ceil, + Glo::Fract => Mf::Fract, + Glo::Sin => Mf::Sin, + Glo::Cos => Mf::Cos, + Glo::Tan => Mf::Tan, + Glo::Asin => Mf::Asin, + Glo::Acos => Mf::Acos, + Glo::Atan => Mf::Atan, + Glo::Sinh => Mf::Sinh, + Glo::Cosh => Mf::Cosh, + Glo::Tanh => Mf::Tanh, + Glo::Atan2 => Mf::Atan2, + Glo::Asinh => Mf::Asinh, + Glo::Acosh => Mf::Acosh, + Glo::Atanh => Mf::Atanh, + Glo::Radians => Mf::Radians, + Glo::Degrees => Mf::Degrees, + Glo::Pow => Mf::Pow, + Glo::Exp => Mf::Exp, + Glo::Log => Mf::Log, + Glo::Exp2 => Mf::Exp2, + Glo::Log2 => Mf::Log2, + Glo::Sqrt => Mf::Sqrt, + Glo::InverseSqrt => Mf::InverseSqrt, + Glo::MatrixInverse => Mf::Inverse, + Glo::Determinant => Mf::Determinant, + Glo::ModfStruct => Mf::Modf, + Glo::FMin | Glo::UMin | Glo::SMin | Glo::NMin => Mf::Min, + Glo::FMax | Glo::UMax | Glo::SMax | Glo::NMax => Mf::Max, + Glo::FClamp | Glo::UClamp | Glo::SClamp | Glo::NClamp => Mf::Clamp, + Glo::FMix => Mf::Mix, + Glo::Step => Mf::Step, + Glo::SmoothStep => Mf::SmoothStep, + Glo::Fma => Mf::Fma, + Glo::FrexpStruct => Mf::Frexp, + Glo::Ldexp => Mf::Ldexp, + Glo::Length => Mf::Length, + Glo::Distance => Mf::Distance, + Glo::Cross => Mf::Cross, + Glo::Normalize => Mf::Normalize, + Glo::FaceForward => Mf::FaceForward, + Glo::Reflect => Mf::Reflect, + Glo::Refract => Mf::Refract, + Glo::PackUnorm4x8 => Mf::Pack4x8unorm, + Glo::PackSnorm4x8 => Mf::Pack4x8snorm, + Glo::PackHalf2x16 => Mf::Pack2x16float, + Glo::PackUnorm2x16 => Mf::Pack2x16unorm, + Glo::PackSnorm2x16 => Mf::Pack2x16snorm, + Glo::UnpackUnorm4x8 => Mf::Unpack4x8unorm, + Glo::UnpackSnorm4x8 => Mf::Unpack4x8snorm, + Glo::UnpackHalf2x16 => Mf::Unpack2x16float, + Glo::UnpackUnorm2x16 => Mf::Unpack2x16unorm, + Glo::UnpackSnorm2x16 => Mf::Unpack2x16snorm, + Glo::FindILsb => Mf::FindLsb, + Glo::FindUMsb | Glo::FindSMsb => Mf::FindMsb, + // TODO: https://github.com/gfx-rs/naga/issues/2526 + Glo::Modf | Glo::Frexp => return Err(Error::UnsupportedExtInst(inst_id)), + Glo::IMix + | Glo::PackDouble2x32 + | Glo::UnpackDouble2x32 + | Glo::InterpolateAtCentroid + | Glo::InterpolateAtSample + | Glo::InterpolateAtOffset => { + return Err(Error::UnsupportedExtInst(inst_id)) + } + }; + + let arg_count = fun.argument_count(); + inst.expect(base_wc + arg_count as u16)?; + let arg = { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + get_expr_handle!(arg_id, lexp) + }; + let arg1 = if arg_count > 1 { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + Some(get_expr_handle!(arg_id, lexp)) + } else { + None + }; + let arg2 = if arg_count > 2 { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + Some(get_expr_handle!(arg_id, lexp)) + } else { + None + }; + let arg3 = if arg_count > 3 { + let arg_id = self.next()?; + let lexp = self.lookup_expression.lookup(arg_id)?; + Some(get_expr_handle!(arg_id, lexp)) + } else { + None + }; + + let expr = crate::Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + // Relational and Logical Instructions + Op::LogicalNot => { + inst.expect(4)?; + parse_expr_op!(crate::UnaryOperator::LogicalNot, UNARY)?; + } + Op::LogicalOr => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::LogicalOr, BINARY)?; + } + Op::LogicalAnd => { + inst.expect(5)?; + parse_expr_op!(crate::BinaryOperator::LogicalAnd, BINARY)?; + } + Op::SGreaterThan | Op::SGreaterThanEqual | Op::SLessThan | Op::SLessThanEqual => { + inst.expect(5)?; + self.parse_expr_int_comparison( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + map_binary_operator(inst.op)?, + crate::ScalarKind::Sint, + )?; + } + Op::UGreaterThan | Op::UGreaterThanEqual | Op::ULessThan | Op::ULessThanEqual => { + inst.expect(5)?; + self.parse_expr_int_comparison( + ctx, + &mut emitter, + &mut block, + block_id, + body_idx, + map_binary_operator(inst.op)?, + crate::ScalarKind::Uint, + )?; + } + Op::FOrdEqual + | Op::FUnordEqual + | Op::FOrdNotEqual + | Op::FUnordNotEqual + | Op::FOrdLessThan + | Op::FUnordLessThan + | Op::FOrdGreaterThan + | Op::FUnordGreaterThan + | Op::FOrdLessThanEqual + | Op::FUnordLessThanEqual + | Op::FOrdGreaterThanEqual + | Op::FUnordGreaterThanEqual + | Op::LogicalEqual + | Op::LogicalNotEqual => { + inst.expect(5)?; + let operator = map_binary_operator(inst.op)?; + parse_expr_op!(operator, BINARY)?; + } + Op::Any | Op::All | Op::IsNan | Op::IsInf | Op::IsFinite | Op::IsNormal => { + inst.expect(4)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let arg_id = self.next()?; + + let arg_lexp = self.lookup_expression.lookup(arg_id)?; + let arg_handle = get_expr_handle!(arg_id, arg_lexp); + + let expr = crate::Expression::Relational { + fun: map_relational_fun(inst.op)?, + argument: arg_handle, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } + Op::Kill => { + inst.expect(1)?; + break Some(crate::Statement::Kill); + } + Op::Unreachable => { + inst.expect(1)?; + break None; + } + Op::Return => { + inst.expect(1)?; + break Some(crate::Statement::Return { value: None }); + } + Op::ReturnValue => { + inst.expect(2)?; + let value_id = self.next()?; + let value_lexp = self.lookup_expression.lookup(value_id)?; + let value_handle = get_expr_handle!(value_id, value_lexp); + break Some(crate::Statement::Return { + value: Some(value_handle), + }); + } + Op::Branch => { + inst.expect(2)?; + let target_id = self.next()?; + + // If this is a branch to a merge or continue block, then + // that ends the current body. + // + // Why can we count on finding an entry here when it's + // needed? SPIR-V requires dominators to appear before + // blocks they dominate, so we will have visited a + // structured control construct's header block before + // anything that could exit it. + if let Some(info) = ctx.mergers.get(&target_id) { + block.extend(emitter.finish(ctx.expressions)); + ctx.blocks.insert(block_id, block); + let body = &mut ctx.bodies[body_idx]; + body.data.push(BodyFragment::BlockId(block_id)); + + merger(body, info); + + return Ok(()); + } + + // If `target_id` has no entry in `ctx.body_for_label`, then + // this must be the only branch to it: + // + // - We've already established that it's not anybody's merge + // block. + // + // - It can't be a switch case. Only switch header blocks + // and other switch cases can branch to a switch case. + // Switch header blocks must dominate all their cases, so + // they must appear in the file before them, and when we + // see `Op::Switch` we populate `ctx.body_for_label` for + // every switch case. + // + // Thus, `target_id` must be a simple extension of the + // current block, which we dominate, so we know we'll + // encounter it later in the file. + ctx.body_for_label.entry(target_id).or_insert(body_idx); + + break None; + } + Op::BranchConditional => { + inst.expect_at_least(4)?; + + let condition = { + let condition_id = self.next()?; + let lexp = self.lookup_expression.lookup(condition_id)?; + get_expr_handle!(condition_id, lexp) + }; + + // HACK(eddyb) Naga doesn't seem to have this helper, + // so it's declared on the fly here for convenience. + #[derive(Copy, Clone)] + struct BranchTarget { + label_id: spirv::Word, + merge_info: Option, + } + let branch_target = |label_id| BranchTarget { + label_id, + merge_info: ctx.mergers.get(&label_id).copied(), + }; + + let true_target = branch_target(self.next()?); + let false_target = branch_target(self.next()?); + + // Consume branch weights + for _ in 4..inst.wc { + let _ = self.next()?; + } + + // Handle `OpBranchConditional`s used at the end of a loop + // body's "continuing" section as a "conditional backedge", + // i.e. a `do`-`while` condition, or `break if` in WGSL. + + // HACK(eddyb) this has to go to the parent *twice*, because + // `OpLoopMerge` left the "continuing" section nested in the + // loop body in terms of `parent`, but not `BodyFragment`. + let parent_body_idx = ctx.bodies[body_idx].parent; + let parent_parent_body_idx = ctx.bodies[parent_body_idx].parent; + match ctx.bodies[parent_parent_body_idx].data[..] { + // The `OpLoopMerge`'s `continuing` block and the loop's + // backedge block may not be the same, but they'll both + // belong to the same body. + [.., BodyFragment::Loop { + body: loop_body_idx, + continuing: loop_continuing_idx, + break_if: ref mut break_if_slot @ None, + }] if body_idx == loop_continuing_idx => { + // Try both orderings of break-vs-backedge, because + // SPIR-V is symmetrical here, unlike WGSL `break if`. + let break_if_cond = [true, false].into_iter().find_map(|true_breaks| { + let (break_candidate, backedge_candidate) = if true_breaks { + (true_target, false_target) + } else { + (false_target, true_target) + }; + + if break_candidate.merge_info + != Some(MergeBlockInformation::LoopMerge) + { + return None; + } + + // HACK(eddyb) since Naga doesn't explicitly track + // backedges, this is checking for the outcome of + // `OpLoopMerge` below (even if it looks weird). + let backedge_candidate_is_backedge = + backedge_candidate.merge_info.is_none() + && ctx.body_for_label.get(&backedge_candidate.label_id) + == Some(&loop_body_idx); + if !backedge_candidate_is_backedge { + return None; + } + + Some(if true_breaks { + condition + } else { + ctx.expressions.append( + crate::Expression::Unary { + op: crate::UnaryOperator::LogicalNot, + expr: condition, + }, + span, + ) + }) + }); + + if let Some(break_if_cond) = break_if_cond { + *break_if_slot = Some(break_if_cond); + + // This `OpBranchConditional` ends the "continuing" + // section of the loop body as normal, with the + // `break if` condition having been stashed above. + break None; + } + } + _ => {} + } + + block.extend(emitter.finish(ctx.expressions)); + ctx.blocks.insert(block_id, block); + let body = &mut ctx.bodies[body_idx]; + body.data.push(BodyFragment::BlockId(block_id)); + + let same_target = true_target.label_id == false_target.label_id; + + // Start a body block for the `accept` branch. + let accept = ctx.bodies.len(); + let mut accept_block = Body::with_parent(body_idx); + + // If the `OpBranchConditional` target is somebody else's + // merge or continue block, then put a `Break` or `Continue` + // statement in this new body block. + if let Some(info) = true_target.merge_info { + merger( + match same_target { + true => &mut ctx.bodies[body_idx], + false => &mut accept_block, + }, + &info, + ) + } else { + // Note the body index for the block we're branching to. + let prev = ctx.body_for_label.insert( + true_target.label_id, + match same_target { + true => body_idx, + false => accept, + }, + ); + debug_assert!(prev.is_none()); + } + + if same_target { + return Ok(()); + } + + ctx.bodies.push(accept_block); + + // Handle the `reject` branch just like the `accept` block. + let reject = ctx.bodies.len(); + let mut reject_block = Body::with_parent(body_idx); + + if let Some(info) = false_target.merge_info { + merger(&mut reject_block, &info) + } else { + let prev = ctx.body_for_label.insert(false_target.label_id, reject); + debug_assert!(prev.is_none()); + } + + ctx.bodies.push(reject_block); + + let body = &mut ctx.bodies[body_idx]; + body.data.push(BodyFragment::If { + condition, + accept, + reject, + }); + + return Ok(()); + } + Op::Switch => { + inst.expect_at_least(3)?; + let selector = self.next()?; + let default_id = self.next()?; + + // If the previous instruction was a `OpSelectionMerge` then we must + // promote the `MergeBlockInformation` to a `SwitchMerge` + if let Some(merge) = selection_merge_block { + ctx.mergers + .insert(merge, MergeBlockInformation::SwitchMerge); + } + + let default = ctx.bodies.len(); + ctx.bodies.push(Body::with_parent(body_idx)); + ctx.body_for_label.entry(default_id).or_insert(default); + + let selector_lexp = &self.lookup_expression[&selector]; + let selector_lty = self.lookup_type.lookup(selector_lexp.type_id)?; + let selector_handle = get_expr_handle!(selector, selector_lexp); + let selector = match ctx.type_arena[selector_lty.handle].inner { + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + width: _, + }) => { + // IR expects a signed integer, so do a bitcast + ctx.expressions.append( + crate::Expression::As { + kind: crate::ScalarKind::Sint, + expr: selector_handle, + convert: None, + }, + span, + ) + } + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint, + width: _, + }) => selector_handle, + ref other => unimplemented!("Unexpected selector {:?}", other), + }; + + // Clear past switch cases to prevent them from entering this one + self.switch_cases.clear(); + + for _ in 0..(inst.wc - 3) / 2 { + let literal = self.next()?; + let target = self.next()?; + + let case_body_idx = ctx.bodies.len(); + + // Check if any previous case already used this target block id, if so + // group them together to reorder them later so that no weird + // falltrough cases happen. + if let Some(&mut (_, ref mut literals)) = self.switch_cases.get_mut(&target) + { + literals.push(literal as i32); + continue; + } + + let mut body = Body::with_parent(body_idx); + + if let Some(info) = ctx.mergers.get(&target) { + merger(&mut body, info); + } + + ctx.bodies.push(body); + ctx.body_for_label.entry(target).or_insert(case_body_idx); + + // Register this target block id as already having been processed and + // the respective body index assigned and the first case value + self.switch_cases + .insert(target, (case_body_idx, vec![literal as i32])); + } + + // Loop trough the collected target blocks creating a new case for each + // literal pointing to it, only one case will have the true body and all the + // others will be empty falltrough so that they all execute the same body + // without duplicating code. + // + // Since `switch_cases` is an indexmap the order of insertion is preserved + // this is needed because spir-v defines falltrough order in the switch + // instruction. + let mut cases = Vec::with_capacity((inst.wc as usize - 3) / 2); + for &(case_body_idx, ref literals) in self.switch_cases.values() { + let value = literals[0]; + + for &literal in literals.iter().skip(1) { + let empty_body_idx = ctx.bodies.len(); + let body = Body::with_parent(body_idx); + + ctx.bodies.push(body); + + cases.push((literal, empty_body_idx)); + } + + cases.push((value, case_body_idx)); + } + + block.extend(emitter.finish(ctx.expressions)); + + let body = &mut ctx.bodies[body_idx]; + ctx.blocks.insert(block_id, block); + // Make sure the vector has space for at least two more allocations + body.data.reserve(2); + body.data.push(BodyFragment::BlockId(block_id)); + body.data.push(BodyFragment::Switch { + selector, + cases, + default, + }); + + return Ok(()); + } + Op::SelectionMerge => { + inst.expect(3)?; + let merge_block_id = self.next()?; + // TODO: Selection Control Mask + let _selection_control = self.next()?; + + // Indicate that the merge block is a continuation of the + // current `Body`. + ctx.body_for_label.entry(merge_block_id).or_insert(body_idx); + + // Let subsequent branches to the merge block know that + // they've reached the end of the selection construct. + ctx.mergers + .insert(merge_block_id, MergeBlockInformation::SelectionMerge); + + selection_merge_block = Some(merge_block_id); + } + Op::LoopMerge => { + inst.expect_at_least(4)?; + let merge_block_id = self.next()?; + let continuing = self.next()?; + + // TODO: Loop Control Parameters + for _ in 0..inst.wc - 3 { + self.next()?; + } + + // Indicate that the merge block is a continuation of the + // current `Body`. + ctx.body_for_label.entry(merge_block_id).or_insert(body_idx); + // Let subsequent branches to the merge block know that + // they're `Break` statements. + ctx.mergers + .insert(merge_block_id, MergeBlockInformation::LoopMerge); + + let loop_body_idx = ctx.bodies.len(); + ctx.bodies.push(Body::with_parent(body_idx)); + + let continue_idx = ctx.bodies.len(); + // The continue block inherits the scope of the loop body + ctx.bodies.push(Body::with_parent(loop_body_idx)); + ctx.body_for_label.entry(continuing).or_insert(continue_idx); + // Let subsequent branches to the continue block know that + // they're `Continue` statements. + ctx.mergers + .insert(continuing, MergeBlockInformation::LoopContinue); + + // The loop header always belongs to the loop body + ctx.body_for_label.insert(block_id, loop_body_idx); + + let parent_body = &mut ctx.bodies[body_idx]; + parent_body.data.push(BodyFragment::Loop { + body: loop_body_idx, + continuing: continue_idx, + break_if: None, + }); + body_idx = loop_body_idx; + } + Op::DPdxCoarse => { + parse_expr_op!( + crate::DerivativeAxis::X, + crate::DerivativeControl::Coarse, + DERIVATIVE + )?; + } + Op::DPdyCoarse => { + parse_expr_op!( + crate::DerivativeAxis::Y, + crate::DerivativeControl::Coarse, + DERIVATIVE + )?; + } + Op::FwidthCoarse => { + parse_expr_op!( + crate::DerivativeAxis::Width, + crate::DerivativeControl::Coarse, + DERIVATIVE + )?; + } + Op::DPdxFine => { + parse_expr_op!( + crate::DerivativeAxis::X, + crate::DerivativeControl::Fine, + DERIVATIVE + )?; + } + Op::DPdyFine => { + parse_expr_op!( + crate::DerivativeAxis::Y, + crate::DerivativeControl::Fine, + DERIVATIVE + )?; + } + Op::FwidthFine => { + parse_expr_op!( + crate::DerivativeAxis::Width, + crate::DerivativeControl::Fine, + DERIVATIVE + )?; + } + Op::DPdx => { + parse_expr_op!( + crate::DerivativeAxis::X, + crate::DerivativeControl::None, + DERIVATIVE + )?; + } + Op::DPdy => { + parse_expr_op!( + crate::DerivativeAxis::Y, + crate::DerivativeControl::None, + DERIVATIVE + )?; + } + Op::Fwidth => { + parse_expr_op!( + crate::DerivativeAxis::Width, + crate::DerivativeControl::None, + DERIVATIVE + )?; + } + Op::ArrayLength => { + inst.expect(5)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let structure_id = self.next()?; + let member_index = self.next()?; + + // We're assuming that the validation pass, if it's run, will catch if the + // wrong types or parameters are supplied here. + + let structure_ptr = self.lookup_expression.lookup(structure_id)?; + let structure_handle = get_expr_handle!(structure_id, structure_ptr); + + let member_ptr = ctx.expressions.append( + crate::Expression::AccessIndex { + base: structure_handle, + index: member_index, + }, + span, + ); + + let length = ctx + .expressions + .append(crate::Expression::ArrayLength(member_ptr), span); + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: length, + type_id: result_type_id, + block_id, + }, + ); + } + Op::CopyMemory => { + inst.expect_at_least(3)?; + let target_id = self.next()?; + let source_id = self.next()?; + let _memory_access = if inst.wc != 3 { + inst.expect(4)?; + spirv::MemoryAccess::from_bits(self.next()?) + .ok_or(Error::InvalidParameter(Op::CopyMemory))? + } else { + spirv::MemoryAccess::NONE + }; + + // TODO: check if the source and target types are the same? + let target = self.lookup_expression.lookup(target_id)?; + let target_handle = get_expr_handle!(target_id, target); + let source = self.lookup_expression.lookup(source_id)?; + let source_handle = get_expr_handle!(source_id, source); + + // This operation is practically the same as loading and then storing, I think. + let value_expr = ctx.expressions.append( + crate::Expression::Load { + pointer: source_handle, + }, + span, + ); + + block.extend(emitter.finish(ctx.expressions)); + block.push( + crate::Statement::Store { + pointer: target_handle, + value: value_expr, + }, + span, + ); + + emitter.start(ctx.expressions); + } + Op::ControlBarrier => { + inst.expect(4)?; + let exec_scope_id = self.next()?; + let _mem_scope_raw = self.next()?; + let semantics_id = self.next()?; + let exec_scope_const = self.lookup_constant.lookup(exec_scope_id)?; + let semantics_const = self.lookup_constant.lookup(semantics_id)?; + + let exec_scope = resolve_constant(ctx.gctx(), exec_scope_const.handle) + .ok_or(Error::InvalidBarrierScope(exec_scope_id))?; + let semantics = resolve_constant(ctx.gctx(), semantics_const.handle) + .ok_or(Error::InvalidBarrierMemorySemantics(semantics_id))?; + + if exec_scope == spirv::Scope::Workgroup as u32 { + let mut flags = crate::Barrier::empty(); + flags.set( + crate::Barrier::STORAGE, + semantics & spirv::MemorySemantics::UNIFORM_MEMORY.bits() != 0, + ); + flags.set( + crate::Barrier::WORK_GROUP, + semantics + & (spirv::MemorySemantics::SUBGROUP_MEMORY + | spirv::MemorySemantics::WORKGROUP_MEMORY) + .bits() + != 0, + ); + block.push(crate::Statement::Barrier(flags), span); + } else { + log::warn!("Unsupported barrier execution scope: {}", exec_scope); + } + } + Op::CopyObject => { + inst.expect(4)?; + let result_type_id = self.next()?; + let result_id = self.next()?; + let operand_id = self.next()?; + + let lookup = self.lookup_expression.lookup(operand_id)?; + let handle = get_expr_handle!(operand_id, lookup); + + self.lookup_expression.insert( + result_id, + LookupExpression { + handle, + type_id: result_type_id, + block_id, + }, + ); + } + _ => return Err(Error::UnsupportedInstruction(self.state, inst.op)), + } + }; + + block.extend(emitter.finish(ctx.expressions)); + if let Some(stmt) = terminator { + block.push(stmt, crate::Span::default()); + } + + // Save this block fragment in `block_ctx.blocks`, and mark it to be + // incorporated into the current body at `Statement` assembly time. + ctx.blocks.insert(block_id, block); + let body = &mut ctx.bodies[body_idx]; + body.data.push(BodyFragment::BlockId(block_id)); + Ok(()) + } + + fn make_expression_storage( + &mut self, + globals: &Arena, + constants: &Arena, + ) -> Arena { + let mut expressions = Arena::new(); + #[allow(clippy::panic)] + { + assert!(self.lookup_expression.is_empty()); + } + // register global variables + for (&id, var) in self.lookup_variable.iter() { + let span = globals.get_span(var.handle); + let handle = expressions.append(crate::Expression::GlobalVariable(var.handle), span); + self.lookup_expression.insert( + id, + LookupExpression { + type_id: var.type_id, + handle, + // Setting this to an invalid id will cause get_expr_handle + // to default to the main body making sure no load/stores + // are added. + block_id: 0, + }, + ); + } + // register constants + for (&id, con) in self.lookup_constant.iter() { + let span = constants.get_span(con.handle); + let handle = expressions.append(crate::Expression::Constant(con.handle), span); + self.lookup_expression.insert( + id, + LookupExpression { + type_id: con.type_id, + handle, + // Setting this to an invalid id will cause get_expr_handle + // to default to the main body making sure no load/stores + // are added. + block_id: 0, + }, + ); + } + // done + expressions + } + + fn switch(&mut self, state: ModuleState, op: spirv::Op) -> Result<(), Error> { + if state < self.state { + Err(Error::UnsupportedInstruction(self.state, op)) + } else { + self.state = state; + Ok(()) + } + } + + /// Walk the statement tree and patch it in the following cases: + /// 1. Function call targets are replaced by `deferred_function_calls` map + fn patch_statements( + &mut self, + statements: &mut crate::Block, + expressions: &mut Arena, + fun_parameter_sampling: &mut [image::SamplingFlags], + ) -> Result<(), Error> { + use crate::Statement as S; + let mut i = 0usize; + while i < statements.len() { + match statements[i] { + S::Emit(_) => {} + S::Block(ref mut block) => { + self.patch_statements(block, expressions, fun_parameter_sampling)?; + } + S::If { + condition: _, + ref mut accept, + ref mut reject, + } => { + self.patch_statements(reject, expressions, fun_parameter_sampling)?; + self.patch_statements(accept, expressions, fun_parameter_sampling)?; + } + S::Switch { + selector: _, + ref mut cases, + } => { + for case in cases.iter_mut() { + self.patch_statements(&mut case.body, expressions, fun_parameter_sampling)?; + } + } + S::Loop { + ref mut body, + ref mut continuing, + break_if: _, + } => { + self.patch_statements(body, expressions, fun_parameter_sampling)?; + self.patch_statements(continuing, expressions, fun_parameter_sampling)?; + } + S::Break + | S::Continue + | S::Return { .. } + | S::Kill + | S::Barrier(_) + | S::Store { .. } + | S::ImageStore { .. } + | S::Atomic { .. } + | S::RayQuery { .. } => {} + S::Call { + function: ref mut callee, + ref arguments, + .. + } => { + let fun_id = self.deferred_function_calls[callee.index()]; + let fun_lookup = self.lookup_function.lookup(fun_id)?; + *callee = fun_lookup.handle; + + // Patch sampling flags + for (arg_index, arg) in arguments.iter().enumerate() { + let flags = match fun_lookup.parameters_sampling.get(arg_index) { + Some(&flags) if !flags.is_empty() => flags, + _ => continue, + }; + + match expressions[*arg] { + crate::Expression::GlobalVariable(handle) => { + if let Some(sampling) = self.handle_sampling.get_mut(&handle) { + *sampling |= flags + } + } + crate::Expression::FunctionArgument(i) => { + fun_parameter_sampling[i as usize] |= flags; + } + ref other => return Err(Error::InvalidGlobalVar(other.clone())), + } + } + } + S::WorkGroupUniformLoad { .. } => unreachable!(), + } + i += 1; + } + Ok(()) + } + + fn patch_function( + &mut self, + handle: Option>, + fun: &mut crate::Function, + ) -> Result<(), Error> { + // Note: this search is a bit unfortunate + let (fun_id, mut parameters_sampling) = match handle { + Some(h) => { + let (&fun_id, lookup) = self + .lookup_function + .iter_mut() + .find(|&(_, ref lookup)| lookup.handle == h) + .unwrap(); + (fun_id, mem::take(&mut lookup.parameters_sampling)) + } + None => (0, Vec::new()), + }; + + for (_, expr) in fun.expressions.iter_mut() { + if let crate::Expression::CallResult(ref mut function) = *expr { + let fun_id = self.deferred_function_calls[function.index()]; + *function = self.lookup_function.lookup(fun_id)?.handle; + } + } + + self.patch_statements( + &mut fun.body, + &mut fun.expressions, + &mut parameters_sampling, + )?; + + if let Some(lookup) = self.lookup_function.get_mut(&fun_id) { + lookup.parameters_sampling = parameters_sampling; + } + Ok(()) + } + + pub fn parse(mut self) -> Result { + let mut module = { + if self.next()? != spirv::MAGIC_NUMBER { + return Err(Error::InvalidHeader); + } + let version_raw = self.next()?; + let generator = self.next()?; + let _bound = self.next()?; + let _schema = self.next()?; + log::info!("Generated by {} version {:x}", generator, version_raw); + crate::Module::default() + }; + + self.layouter.clear(); + self.dummy_functions = Arena::new(); + self.lookup_function.clear(); + self.function_call_graph.clear(); + + loop { + use spirv::Op; + + let inst = match self.next_inst() { + Ok(inst) => inst, + Err(Error::IncompleteData) => break, + Err(other) => return Err(other), + }; + log::debug!("\t{:?} [{}]", inst.op, inst.wc); + + match inst.op { + Op::Capability => self.parse_capability(inst), + Op::Extension => self.parse_extension(inst), + Op::ExtInstImport => self.parse_ext_inst_import(inst), + Op::MemoryModel => self.parse_memory_model(inst), + Op::EntryPoint => self.parse_entry_point(inst), + Op::ExecutionMode => self.parse_execution_mode(inst), + Op::String => self.parse_string(inst), + Op::Source => self.parse_source(inst), + Op::SourceExtension => self.parse_source_extension(inst), + Op::Name => self.parse_name(inst), + Op::MemberName => self.parse_member_name(inst), + Op::ModuleProcessed => self.parse_module_processed(inst), + Op::Decorate => self.parse_decorate(inst), + Op::MemberDecorate => self.parse_member_decorate(inst), + Op::TypeVoid => self.parse_type_void(inst), + Op::TypeBool => self.parse_type_bool(inst, &mut module), + Op::TypeInt => self.parse_type_int(inst, &mut module), + Op::TypeFloat => self.parse_type_float(inst, &mut module), + Op::TypeVector => self.parse_type_vector(inst, &mut module), + Op::TypeMatrix => self.parse_type_matrix(inst, &mut module), + Op::TypeFunction => self.parse_type_function(inst), + Op::TypePointer => self.parse_type_pointer(inst, &mut module), + Op::TypeArray => self.parse_type_array(inst, &mut module), + Op::TypeRuntimeArray => self.parse_type_runtime_array(inst, &mut module), + Op::TypeStruct => self.parse_type_struct(inst, &mut module), + Op::TypeImage => self.parse_type_image(inst, &mut module), + Op::TypeSampledImage => self.parse_type_sampled_image(inst), + Op::TypeSampler => self.parse_type_sampler(inst, &mut module), + Op::Constant | Op::SpecConstant => self.parse_constant(inst, &mut module), + Op::ConstantComposite => self.parse_composite_constant(inst, &mut module), + Op::ConstantNull | Op::Undef => self.parse_null_constant(inst, &mut module), + Op::ConstantTrue => self.parse_bool_constant(inst, true, &mut module), + Op::ConstantFalse => self.parse_bool_constant(inst, false, &mut module), + Op::Variable => self.parse_global_variable(inst, &mut module), + Op::Function => { + self.switch(ModuleState::Function, inst.op)?; + inst.expect(5)?; + self.parse_function(&mut module) + } + _ => Err(Error::UnsupportedInstruction(self.state, inst.op)), //TODO + }?; + } + + log::info!("Patching..."); + { + let mut nodes = petgraph::algo::toposort(&self.function_call_graph, None) + .map_err(|cycle| Error::FunctionCallCycle(cycle.node_id()))?; + nodes.reverse(); // we need dominated first + let mut functions = mem::take(&mut module.functions); + for fun_id in nodes { + if fun_id > !(functions.len() as u32) { + // skip all the fake IDs registered for the entry points + continue; + } + let lookup = self.lookup_function.get_mut(&fun_id).unwrap(); + // take out the function from the old array + let fun = mem::take(&mut functions[lookup.handle]); + // add it to the newly formed arena, and adjust the lookup + lookup.handle = module + .functions + .append(fun, functions.get_span(lookup.handle)); + } + } + // patch all the functions + for (handle, fun) in module.functions.iter_mut() { + self.patch_function(Some(handle), fun)?; + } + for ep in module.entry_points.iter_mut() { + self.patch_function(None, &mut ep.function)?; + } + + // Check all the images and samplers to have consistent comparison property. + for (handle, flags) in self.handle_sampling.drain() { + if !image::patch_comparison_type( + flags, + module.global_variables.get_mut(handle), + &mut module.types, + ) { + return Err(Error::InconsistentComparisonSampling(handle)); + } + } + + if !self.future_decor.is_empty() { + log::warn!("Unused item decorations: {:?}", self.future_decor); + self.future_decor.clear(); + } + if !self.future_member_decor.is_empty() { + log::warn!("Unused member decorations: {:?}", self.future_member_decor); + self.future_member_decor.clear(); + } + + Ok(module) + } + + fn parse_capability(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Capability, inst.op)?; + inst.expect(2)?; + let capability = self.next()?; + let cap = + spirv::Capability::from_u32(capability).ok_or(Error::UnknownCapability(capability))?; + if !SUPPORTED_CAPABILITIES.contains(&cap) { + if self.options.strict_capabilities { + return Err(Error::UnsupportedCapability(cap)); + } else { + log::warn!("Unknown capability {:?}", cap); + } + } + Ok(()) + } + + fn parse_extension(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Extension, inst.op)?; + inst.expect_at_least(2)?; + let (name, left) = self.next_string(inst.wc - 1)?; + if left != 0 { + return Err(Error::InvalidOperand); + } + if !SUPPORTED_EXTENSIONS.contains(&name.as_str()) { + return Err(Error::UnsupportedExtension(name)); + } + Ok(()) + } + + fn parse_ext_inst_import(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Extension, inst.op)?; + inst.expect_at_least(3)?; + let result_id = self.next()?; + let (name, left) = self.next_string(inst.wc - 2)?; + if left != 0 { + return Err(Error::InvalidOperand); + } + if !SUPPORTED_EXT_SETS.contains(&name.as_str()) { + return Err(Error::UnsupportedExtSet(name)); + } + self.ext_glsl_id = Some(result_id); + Ok(()) + } + + fn parse_memory_model(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::MemoryModel, inst.op)?; + inst.expect(3)?; + let _addressing_model = self.next()?; + let _memory_model = self.next()?; + Ok(()) + } + + fn parse_entry_point(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::EntryPoint, inst.op)?; + inst.expect_at_least(4)?; + let exec_model = self.next()?; + let exec_model = spirv::ExecutionModel::from_u32(exec_model) + .ok_or(Error::UnsupportedExecutionModel(exec_model))?; + let function_id = self.next()?; + let (name, left) = self.next_string(inst.wc - 3)?; + let ep = EntryPoint { + stage: match exec_model { + spirv::ExecutionModel::Vertex => crate::ShaderStage::Vertex, + spirv::ExecutionModel::Fragment => crate::ShaderStage::Fragment, + spirv::ExecutionModel::GLCompute => crate::ShaderStage::Compute, + _ => return Err(Error::UnsupportedExecutionModel(exec_model as u32)), + }, + name, + early_depth_test: None, + workgroup_size: [0; 3], + variable_ids: self.data.by_ref().take(left as usize).collect(), + }; + self.lookup_entry_point.insert(function_id, ep); + Ok(()) + } + + fn parse_execution_mode(&mut self, inst: Instruction) -> Result<(), Error> { + use spirv::ExecutionMode; + + self.switch(ModuleState::ExecutionMode, inst.op)?; + inst.expect_at_least(3)?; + + let ep_id = self.next()?; + let mode_id = self.next()?; + let args: Vec = self.data.by_ref().take(inst.wc as usize - 3).collect(); + + let ep = self + .lookup_entry_point + .get_mut(&ep_id) + .ok_or(Error::InvalidId(ep_id))?; + let mode = spirv::ExecutionMode::from_u32(mode_id) + .ok_or(Error::UnsupportedExecutionMode(mode_id))?; + + match mode { + ExecutionMode::EarlyFragmentTests => { + if ep.early_depth_test.is_none() { + ep.early_depth_test = Some(crate::EarlyDepthTest { conservative: None }); + } + } + ExecutionMode::DepthUnchanged => { + ep.early_depth_test = Some(crate::EarlyDepthTest { + conservative: Some(crate::ConservativeDepth::Unchanged), + }); + } + ExecutionMode::DepthGreater => { + ep.early_depth_test = Some(crate::EarlyDepthTest { + conservative: Some(crate::ConservativeDepth::GreaterEqual), + }); + } + ExecutionMode::DepthLess => { + ep.early_depth_test = Some(crate::EarlyDepthTest { + conservative: Some(crate::ConservativeDepth::LessEqual), + }); + } + ExecutionMode::DepthReplacing => { + // Ignored because it can be deduced from the IR. + } + ExecutionMode::OriginUpperLeft => { + // Ignored because the other option (OriginLowerLeft) is not valid in Vulkan mode. + } + ExecutionMode::LocalSize => { + ep.workgroup_size = [args[0], args[1], args[2]]; + } + _ => { + return Err(Error::UnsupportedExecutionMode(mode_id)); + } + } + + Ok(()) + } + + fn parse_string(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Source, inst.op)?; + inst.expect_at_least(3)?; + let _id = self.next()?; + let (_name, _) = self.next_string(inst.wc - 2)?; + Ok(()) + } + + fn parse_source(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Source, inst.op)?; + for _ in 1..inst.wc { + let _ = self.next()?; + } + Ok(()) + } + + fn parse_source_extension(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Source, inst.op)?; + inst.expect_at_least(2)?; + let (_name, _) = self.next_string(inst.wc - 1)?; + Ok(()) + } + + fn parse_name(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Name, inst.op)?; + inst.expect_at_least(3)?; + let id = self.next()?; + let (name, left) = self.next_string(inst.wc - 2)?; + if left != 0 { + return Err(Error::InvalidOperand); + } + self.future_decor.entry(id).or_default().name = Some(name); + Ok(()) + } + + fn parse_member_name(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Name, inst.op)?; + inst.expect_at_least(4)?; + let id = self.next()?; + let member = self.next()?; + let (name, left) = self.next_string(inst.wc - 3)?; + if left != 0 { + return Err(Error::InvalidOperand); + } + + self.future_member_decor + .entry((id, member)) + .or_default() + .name = Some(name); + Ok(()) + } + + fn parse_module_processed(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Name, inst.op)?; + inst.expect_at_least(2)?; + let (_info, left) = self.next_string(inst.wc - 1)?; + //Note: string is ignored + if left != 0 { + return Err(Error::InvalidOperand); + } + Ok(()) + } + + fn parse_decorate(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Annotation, inst.op)?; + inst.expect_at_least(3)?; + let id = self.next()?; + let mut dec = self.future_decor.remove(&id).unwrap_or_default(); + self.next_decoration(inst, 2, &mut dec)?; + self.future_decor.insert(id, dec); + Ok(()) + } + + fn parse_member_decorate(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Annotation, inst.op)?; + inst.expect_at_least(4)?; + let id = self.next()?; + let member = self.next()?; + + let mut dec = self + .future_member_decor + .remove(&(id, member)) + .unwrap_or_default(); + self.next_decoration(inst, 3, &mut dec)?; + self.future_member_decor.insert((id, member), dec); + Ok(()) + } + + fn parse_type_void(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Type, inst.op)?; + inst.expect(2)?; + let id = self.next()?; + self.lookup_void_type = Some(id); + Ok(()) + } + + fn parse_type_bool( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(2)?; + let id = self.next()?; + let inner = crate::TypeInner::Scalar(crate::Scalar::BOOL); + self.lookup_type.insert( + id, + LookupType { + handle: module.types.insert( + crate::Type { + name: self.future_decor.remove(&id).and_then(|dec| dec.name), + inner, + }, + self.span_from_with_op(start), + ), + base_id: None, + }, + ); + Ok(()) + } + + fn parse_type_int( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(4)?; + let id = self.next()?; + let width = self.next()?; + let sign = self.next()?; + let inner = crate::TypeInner::Scalar(crate::Scalar { + kind: match sign { + 0 => crate::ScalarKind::Uint, + 1 => crate::ScalarKind::Sint, + _ => return Err(Error::InvalidSign(sign)), + }, + width: map_width(width)?, + }); + self.lookup_type.insert( + id, + LookupType { + handle: module.types.insert( + crate::Type { + name: self.future_decor.remove(&id).and_then(|dec| dec.name), + inner, + }, + self.span_from_with_op(start), + ), + base_id: None, + }, + ); + Ok(()) + } + + fn parse_type_float( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(3)?; + let id = self.next()?; + let width = self.next()?; + let inner = crate::TypeInner::Scalar(crate::Scalar::float(map_width(width)?)); + self.lookup_type.insert( + id, + LookupType { + handle: module.types.insert( + crate::Type { + name: self.future_decor.remove(&id).and_then(|dec| dec.name), + inner, + }, + self.span_from_with_op(start), + ), + base_id: None, + }, + ); + Ok(()) + } + + fn parse_type_vector( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(4)?; + let id = self.next()?; + let type_id = self.next()?; + let type_lookup = self.lookup_type.lookup(type_id)?; + let scalar = match module.types[type_lookup.handle].inner { + crate::TypeInner::Scalar(scalar) => scalar, + _ => return Err(Error::InvalidInnerType(type_id)), + }; + let component_count = self.next()?; + let inner = crate::TypeInner::Vector { + size: map_vector_size(component_count)?, + scalar, + }; + self.lookup_type.insert( + id, + LookupType { + handle: module.types.insert( + crate::Type { + name: self.future_decor.remove(&id).and_then(|dec| dec.name), + inner, + }, + self.span_from_with_op(start), + ), + base_id: Some(type_id), + }, + ); + Ok(()) + } + + fn parse_type_matrix( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(4)?; + let id = self.next()?; + let vector_type_id = self.next()?; + let num_columns = self.next()?; + let decor = self.future_decor.remove(&id); + + let vector_type_lookup = self.lookup_type.lookup(vector_type_id)?; + let inner = match module.types[vector_type_lookup.handle].inner { + crate::TypeInner::Vector { size, scalar } => crate::TypeInner::Matrix { + columns: map_vector_size(num_columns)?, + rows: size, + scalar, + }, + _ => return Err(Error::InvalidInnerType(vector_type_id)), + }; + + self.lookup_type.insert( + id, + LookupType { + handle: module.types.insert( + crate::Type { + name: decor.and_then(|dec| dec.name), + inner, + }, + self.span_from_with_op(start), + ), + base_id: Some(vector_type_id), + }, + ); + Ok(()) + } + + fn parse_type_function(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Type, inst.op)?; + inst.expect_at_least(3)?; + let id = self.next()?; + let return_type_id = self.next()?; + let parameter_type_ids = self.data.by_ref().take(inst.wc as usize - 3).collect(); + self.lookup_function_type.insert( + id, + LookupFunctionType { + parameter_type_ids, + return_type_id, + }, + ); + Ok(()) + } + + fn parse_type_pointer( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(4)?; + let id = self.next()?; + let storage_class = self.next()?; + let type_id = self.next()?; + + let decor = self.future_decor.remove(&id); + let base_lookup_ty = self.lookup_type.lookup(type_id)?; + let base_inner = &module.types[base_lookup_ty.handle].inner; + + let space = if let Some(space) = base_inner.pointer_space() { + space + } else if self + .lookup_storage_buffer_types + .contains_key(&base_lookup_ty.handle) + { + crate::AddressSpace::Storage { + access: crate::StorageAccess::default(), + } + } else { + match map_storage_class(storage_class)? { + ExtendedClass::Global(space) => space, + ExtendedClass::Input | ExtendedClass::Output => crate::AddressSpace::Private, + } + }; + + // We don't support pointers to runtime-sized arrays in the `Uniform` + // storage class with the `BufferBlock` decoration. Runtime-sized arrays + // should be in the StorageBuffer class. + if let crate::TypeInner::Array { + size: crate::ArraySize::Dynamic, + .. + } = *base_inner + { + match space { + crate::AddressSpace::Storage { .. } => {} + _ => { + return Err(Error::UnsupportedRuntimeArrayStorageClass); + } + } + } + + // Don't bother with pointer stuff for `Handle` types. + let lookup_ty = if space == crate::AddressSpace::Handle { + base_lookup_ty.clone() + } else { + LookupType { + handle: module.types.insert( + crate::Type { + name: decor.and_then(|dec| dec.name), + inner: crate::TypeInner::Pointer { + base: base_lookup_ty.handle, + space, + }, + }, + self.span_from_with_op(start), + ), + base_id: Some(type_id), + } + }; + self.lookup_type.insert(id, lookup_ty); + Ok(()) + } + + fn parse_type_array( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(4)?; + let id = self.next()?; + let type_id = self.next()?; + let length_id = self.next()?; + let length_const = self.lookup_constant.lookup(length_id)?; + + let size = resolve_constant(module.to_ctx(), length_const.handle) + .and_then(NonZeroU32::new) + .ok_or(Error::InvalidArraySize(length_const.handle))?; + + let decor = self.future_decor.remove(&id).unwrap_or_default(); + let base = self.lookup_type.lookup(type_id)?.handle; + + self.layouter.update(module.to_ctx()).unwrap(); + + // HACK if the underlying type is an image or a sampler, let's assume + // that we're dealing with a binding-array + // + // Note that it's not a strictly correct assumption, but rather a trade + // off caused by an impedance mismatch between SPIR-V's and Naga's type + // systems - Naga distinguishes between arrays and binding-arrays via + // types (i.e. both kinds of arrays are just different types), while + // SPIR-V distinguishes between them through usage - e.g. given: + // + // ``` + // %image = OpTypeImage %float 2D 2 0 0 2 Rgba16f + // %uint_256 = OpConstant %uint 256 + // %image_array = OpTypeArray %image %uint_256 + // ``` + // + // ``` + // %image = OpTypeImage %float 2D 2 0 0 2 Rgba16f + // %uint_256 = OpConstant %uint 256 + // %image_array = OpTypeArray %image %uint_256 + // %image_array_ptr = OpTypePointer UniformConstant %image_array + // ``` + // + // ... in the first case, `%image_array` should technically correspond + // to `TypeInner::Array`, while in the second case it should say + // `TypeInner::BindingArray` (kinda, depending on whether `%image_array` + // is ever used as a freestanding type or rather always through the + // pointer-indirection). + // + // Anyway, at the moment we don't support other kinds of image / sampler + // arrays than those binding-based, so this assumption is pretty safe + // for now. + let inner = if let crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } = + module.types[base].inner + { + crate::TypeInner::BindingArray { + base, + size: crate::ArraySize::Constant(size), + } + } else { + crate::TypeInner::Array { + base, + size: crate::ArraySize::Constant(size), + stride: match decor.array_stride { + Some(stride) => stride.get(), + None => self.layouter[base].to_stride(), + }, + } + }; + + self.lookup_type.insert( + id, + LookupType { + handle: module.types.insert( + crate::Type { + name: decor.name, + inner, + }, + self.span_from_with_op(start), + ), + base_id: Some(type_id), + }, + ); + Ok(()) + } + + fn parse_type_runtime_array( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(3)?; + let id = self.next()?; + let type_id = self.next()?; + + let decor = self.future_decor.remove(&id).unwrap_or_default(); + let base = self.lookup_type.lookup(type_id)?.handle; + + self.layouter.update(module.to_ctx()).unwrap(); + + // HACK same case as in `parse_type_array()` + let inner = if let crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } = + module.types[base].inner + { + crate::TypeInner::BindingArray { + base: self.lookup_type.lookup(type_id)?.handle, + size: crate::ArraySize::Dynamic, + } + } else { + crate::TypeInner::Array { + base: self.lookup_type.lookup(type_id)?.handle, + size: crate::ArraySize::Dynamic, + stride: match decor.array_stride { + Some(stride) => stride.get(), + None => self.layouter[base].to_stride(), + }, + } + }; + + self.lookup_type.insert( + id, + LookupType { + handle: module.types.insert( + crate::Type { + name: decor.name, + inner, + }, + self.span_from_with_op(start), + ), + base_id: Some(type_id), + }, + ); + Ok(()) + } + + fn parse_type_struct( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect_at_least(2)?; + let id = self.next()?; + let parent_decor = self.future_decor.remove(&id); + let is_storage_buffer = parent_decor + .as_ref() + .map_or(false, |decor| decor.storage_buffer); + + self.layouter.update(module.to_ctx()).unwrap(); + + let mut members = Vec::::with_capacity(inst.wc as usize - 2); + let mut member_lookups = Vec::with_capacity(members.capacity()); + let mut storage_access = crate::StorageAccess::empty(); + let mut span = 0; + let mut alignment = Alignment::ONE; + for i in 0..u32::from(inst.wc) - 2 { + let type_id = self.next()?; + let ty = self.lookup_type.lookup(type_id)?.handle; + let decor = self + .future_member_decor + .remove(&(id, i)) + .unwrap_or_default(); + + storage_access |= decor.flags.to_storage_access(); + + member_lookups.push(LookupMember { + type_id, + row_major: decor.matrix_major == Some(Majority::Row), + }); + + let member_alignment = self.layouter[ty].alignment; + span = member_alignment.round_up(span); + alignment = member_alignment.max(alignment); + + let binding = decor.io_binding().ok(); + if let Some(offset) = decor.offset { + span = offset; + } + let offset = span; + + span += self.layouter[ty].size; + + let inner = &module.types[ty].inner; + if let crate::TypeInner::Matrix { + columns, + rows, + scalar, + } = *inner + { + if let Some(stride) = decor.matrix_stride { + let expected_stride = Alignment::from(rows) * scalar.width as u32; + if stride.get() != expected_stride { + return Err(Error::UnsupportedMatrixStride { + stride: stride.get(), + columns: columns as u8, + rows: rows as u8, + width: scalar.width, + }); + } + } + } + + members.push(crate::StructMember { + name: decor.name, + ty, + binding, + offset, + }); + } + + span = alignment.round_up(span); + + let inner = crate::TypeInner::Struct { span, members }; + + let ty_handle = module.types.insert( + crate::Type { + name: parent_decor.and_then(|dec| dec.name), + inner, + }, + self.span_from_with_op(start), + ); + + if is_storage_buffer { + self.lookup_storage_buffer_types + .insert(ty_handle, storage_access); + } + for (i, member_lookup) in member_lookups.into_iter().enumerate() { + self.lookup_member + .insert((ty_handle, i as u32), member_lookup); + } + self.lookup_type.insert( + id, + LookupType { + handle: ty_handle, + base_id: None, + }, + ); + Ok(()) + } + + fn parse_type_image( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(9)?; + + let id = self.next()?; + let sample_type_id = self.next()?; + let dim = self.next()?; + let is_depth = self.next()?; + let is_array = self.next()? != 0; + let is_msaa = self.next()? != 0; + let _is_sampled = self.next()?; + let format = self.next()?; + + let dim = map_image_dim(dim)?; + let decor = self.future_decor.remove(&id).unwrap_or_default(); + + // ensure there is a type for texture coordinate without extra components + module.types.insert( + crate::Type { + name: None, + inner: { + let scalar = crate::Scalar::F32; + match dim.required_coordinate_size() { + None => crate::TypeInner::Scalar(scalar), + Some(size) => crate::TypeInner::Vector { size, scalar }, + } + }, + }, + Default::default(), + ); + + let base_handle = self.lookup_type.lookup(sample_type_id)?.handle; + let kind = module.types[base_handle] + .inner + .scalar_kind() + .ok_or(Error::InvalidImageBaseType(base_handle))?; + + let inner = crate::TypeInner::Image { + class: if is_depth == 1 { + crate::ImageClass::Depth { multi: is_msaa } + } else if format != 0 { + crate::ImageClass::Storage { + format: map_image_format(format)?, + access: crate::StorageAccess::default(), + } + } else { + crate::ImageClass::Sampled { + kind, + multi: is_msaa, + } + }, + dim, + arrayed: is_array, + }; + + let handle = module.types.insert( + crate::Type { + name: decor.name, + inner, + }, + self.span_from_with_op(start), + ); + + self.lookup_type.insert( + id, + LookupType { + handle, + base_id: Some(sample_type_id), + }, + ); + Ok(()) + } + + fn parse_type_sampled_image(&mut self, inst: Instruction) -> Result<(), Error> { + self.switch(ModuleState::Type, inst.op)?; + inst.expect(3)?; + let id = self.next()?; + let image_id = self.next()?; + self.lookup_type.insert( + id, + LookupType { + handle: self.lookup_type.lookup(image_id)?.handle, + base_id: Some(image_id), + }, + ); + Ok(()) + } + + fn parse_type_sampler( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(2)?; + let id = self.next()?; + let decor = self.future_decor.remove(&id).unwrap_or_default(); + let handle = module.types.insert( + crate::Type { + name: decor.name, + inner: crate::TypeInner::Sampler { comparison: false }, + }, + self.span_from_with_op(start), + ); + self.lookup_type.insert( + id, + LookupType { + handle, + base_id: None, + }, + ); + Ok(()) + } + + fn parse_constant( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect_at_least(4)?; + let type_id = self.next()?; + let id = self.next()?; + let type_lookup = self.lookup_type.lookup(type_id)?; + let ty = type_lookup.handle; + + let literal = match module.types[ty].inner { + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Uint, + width, + }) => { + let low = self.next()?; + match width { + 4 => crate::Literal::U32(low), + _ => return Err(Error::InvalidTypeWidth(width as u32)), + } + } + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint, + width, + }) => { + let low = self.next()?; + match width { + 4 => crate::Literal::I32(low as i32), + 8 => { + inst.expect(5)?; + let high = self.next()?; + crate::Literal::I64((u64::from(high) << 32 | u64::from(low)) as i64) + } + _ => return Err(Error::InvalidTypeWidth(width as u32)), + } + } + crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Float, + width, + }) => { + let low = self.next()?; + match width { + 4 => crate::Literal::F32(f32::from_bits(low)), + 8 => { + inst.expect(5)?; + let high = self.next()?; + crate::Literal::F64(f64::from_bits( + (u64::from(high) << 32) | u64::from(low), + )) + } + _ => return Err(Error::InvalidTypeWidth(width as u32)), + } + } + _ => return Err(Error::UnsupportedType(type_lookup.handle)), + }; + + let decor = self.future_decor.remove(&id).unwrap_or_default(); + + let span = self.span_from_with_op(start); + + let init = module + .const_expressions + .append(crate::Expression::Literal(literal), span); + self.lookup_constant.insert( + id, + LookupConstant { + handle: module.constants.append( + crate::Constant { + r#override: decor.specialization(), + name: decor.name, + ty, + init, + }, + span, + ), + type_id, + }, + ); + Ok(()) + } + + fn parse_composite_constant( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect_at_least(3)?; + let type_id = self.next()?; + let id = self.next()?; + + let type_lookup = self.lookup_type.lookup(type_id)?; + let ty = type_lookup.handle; + + let mut components = Vec::with_capacity(inst.wc as usize - 3); + for _ in 0..components.capacity() { + let start = self.data_offset; + let component_id = self.next()?; + let span = self.span_from_with_op(start); + let constant = self.lookup_constant.lookup(component_id)?; + let expr = module + .const_expressions + .append(crate::Expression::Constant(constant.handle), span); + components.push(expr); + } + + let decor = self.future_decor.remove(&id).unwrap_or_default(); + + let span = self.span_from_with_op(start); + + let init = module + .const_expressions + .append(crate::Expression::Compose { ty, components }, span); + self.lookup_constant.insert( + id, + LookupConstant { + handle: module.constants.append( + crate::Constant { + r#override: decor.specialization(), + name: decor.name, + ty, + init, + }, + span, + ), + type_id, + }, + ); + Ok(()) + } + + fn parse_null_constant( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(3)?; + let type_id = self.next()?; + let id = self.next()?; + let span = self.span_from_with_op(start); + + let type_lookup = self.lookup_type.lookup(type_id)?; + let ty = type_lookup.handle; + + let decor = self.future_decor.remove(&id).unwrap_or_default(); + + let init = module + .const_expressions + .append(crate::Expression::ZeroValue(ty), span); + let handle = module.constants.append( + crate::Constant { + r#override: decor.specialization(), + name: decor.name, + ty, + init, + }, + span, + ); + self.lookup_constant + .insert(id, LookupConstant { handle, type_id }); + Ok(()) + } + + fn parse_bool_constant( + &mut self, + inst: Instruction, + value: bool, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect(3)?; + let type_id = self.next()?; + let id = self.next()?; + let span = self.span_from_with_op(start); + + let type_lookup = self.lookup_type.lookup(type_id)?; + let ty = type_lookup.handle; + + let decor = self.future_decor.remove(&id).unwrap_or_default(); + + let init = module.const_expressions.append( + crate::Expression::Literal(crate::Literal::Bool(value)), + span, + ); + self.lookup_constant.insert( + id, + LookupConstant { + handle: module.constants.append( + crate::Constant { + r#override: decor.specialization(), + name: decor.name, + ty, + init, + }, + span, + ), + type_id, + }, + ); + Ok(()) + } + + fn parse_global_variable( + &mut self, + inst: Instruction, + module: &mut crate::Module, + ) -> Result<(), Error> { + let start = self.data_offset; + self.switch(ModuleState::Type, inst.op)?; + inst.expect_at_least(4)?; + let type_id = self.next()?; + let id = self.next()?; + let storage_class = self.next()?; + let init = if inst.wc > 4 { + inst.expect(5)?; + let start = self.data_offset; + let init_id = self.next()?; + let span = self.span_from_with_op(start); + let lconst = self.lookup_constant.lookup(init_id)?; + let expr = module + .const_expressions + .append(crate::Expression::Constant(lconst.handle), span); + Some(expr) + } else { + None + }; + let span = self.span_from_with_op(start); + let mut dec = self.future_decor.remove(&id).unwrap_or_default(); + + let original_ty = self.lookup_type.lookup(type_id)?.handle; + let mut ty = original_ty; + + if let crate::TypeInner::Pointer { base, space: _ } = module.types[original_ty].inner { + ty = base; + } + + if let crate::TypeInner::BindingArray { .. } = module.types[original_ty].inner { + // Inside `parse_type_array()` we guess that an array of images or + // samplers must be a binding array, and here we validate that guess + if dec.desc_set.is_none() || dec.desc_index.is_none() { + return Err(Error::NonBindingArrayOfImageOrSamplers); + } + } + + if let crate::TypeInner::Image { + dim, + arrayed, + class: crate::ImageClass::Storage { format, access: _ }, + } = module.types[ty].inner + { + // Storage image types in IR have to contain the access, but not in the SPIR-V. + // The same image type in SPIR-V can be used (and has to be used) for multiple images. + // So we copy the type out and apply the variable access decorations. + let access = dec.flags.to_storage_access(); + + ty = module.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Image { + dim, + arrayed, + class: crate::ImageClass::Storage { format, access }, + }, + }, + Default::default(), + ); + } + + let ext_class = match self.lookup_storage_buffer_types.get(&ty) { + Some(&access) => ExtendedClass::Global(crate::AddressSpace::Storage { access }), + None => map_storage_class(storage_class)?, + }; + + // Fix empty name for gl_PerVertex struct generated by glslang + if let crate::TypeInner::Pointer { .. } = module.types[original_ty].inner { + if ext_class == ExtendedClass::Input || ext_class == ExtendedClass::Output { + if let Some(ref dec_name) = dec.name { + if dec_name.is_empty() { + dec.name = Some("perVertexStruct".to_string()) + } + } + } + } + + let (inner, var) = match ext_class { + ExtendedClass::Global(mut space) => { + if let crate::AddressSpace::Storage { ref mut access } = space { + *access &= dec.flags.to_storage_access(); + } + let var = crate::GlobalVariable { + binding: dec.resource_binding(), + name: dec.name, + space, + ty, + init, + }; + (Variable::Global, var) + } + ExtendedClass::Input => { + let binding = dec.io_binding()?; + let mut unsigned_ty = ty; + if let crate::Binding::BuiltIn(built_in) = binding { + let needs_inner_uint = match built_in { + crate::BuiltIn::BaseInstance + | crate::BuiltIn::BaseVertex + | crate::BuiltIn::InstanceIndex + | crate::BuiltIn::SampleIndex + | crate::BuiltIn::VertexIndex + | crate::BuiltIn::PrimitiveIndex + | crate::BuiltIn::LocalInvocationIndex => { + Some(crate::TypeInner::Scalar(crate::Scalar::U32)) + } + crate::BuiltIn::GlobalInvocationId + | crate::BuiltIn::LocalInvocationId + | crate::BuiltIn::WorkGroupId + | crate::BuiltIn::WorkGroupSize => Some(crate::TypeInner::Vector { + size: crate::VectorSize::Tri, + scalar: crate::Scalar::U32, + }), + _ => None, + }; + if let (Some(inner), Some(crate::ScalarKind::Sint)) = + (needs_inner_uint, module.types[ty].inner.scalar_kind()) + { + unsigned_ty = module + .types + .insert(crate::Type { name: None, inner }, Default::default()); + } + } + + let var = crate::GlobalVariable { + name: dec.name.clone(), + space: crate::AddressSpace::Private, + binding: None, + ty, + init: None, + }; + + let inner = Variable::Input(crate::FunctionArgument { + name: dec.name, + ty: unsigned_ty, + binding: Some(binding), + }); + (inner, var) + } + ExtendedClass::Output => { + // For output interface blocks, this would be a structure. + let binding = dec.io_binding().ok(); + let init = match binding { + Some(crate::Binding::BuiltIn(built_in)) => { + match null::generate_default_built_in( + Some(built_in), + ty, + &mut module.const_expressions, + span, + ) { + Ok(handle) => Some(handle), + Err(e) => { + log::warn!("Failed to initialize output built-in: {}", e); + None + } + } + } + Some(crate::Binding::Location { .. }) => None, + None => match module.types[ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + let mut components = Vec::with_capacity(members.len()); + for member in members.iter() { + let built_in = match member.binding { + Some(crate::Binding::BuiltIn(built_in)) => Some(built_in), + _ => None, + }; + let handle = null::generate_default_built_in( + built_in, + member.ty, + &mut module.const_expressions, + span, + )?; + components.push(handle); + } + Some( + module + .const_expressions + .append(crate::Expression::Compose { ty, components }, span), + ) + } + _ => None, + }, + }; + + let var = crate::GlobalVariable { + name: dec.name, + space: crate::AddressSpace::Private, + binding: None, + ty, + init, + }; + let inner = Variable::Output(crate::FunctionResult { ty, binding }); + (inner, var) + } + }; + + let handle = module.global_variables.append(var, span); + + if module.types[ty].inner.can_comparison_sample(module) { + log::debug!("\t\ttracking {:?} for sampling properties", handle); + + self.handle_sampling + .insert(handle, image::SamplingFlags::empty()); + } + + self.lookup_variable.insert( + id, + LookupVariable { + inner, + handle, + type_id, + }, + ); + Ok(()) + } +} + +fn make_index_literal( + ctx: &mut BlockContext, + index: u32, + block: &mut crate::Block, + emitter: &mut crate::proc::Emitter, + index_type: Handle, + index_type_id: spirv::Word, + span: crate::Span, +) -> Result, Error> { + block.extend(emitter.finish(ctx.expressions)); + + let literal = match ctx.type_arena[index_type].inner.scalar_kind() { + Some(crate::ScalarKind::Uint) => crate::Literal::U32(index), + Some(crate::ScalarKind::Sint) => crate::Literal::I32(index as i32), + _ => return Err(Error::InvalidIndexType(index_type_id)), + }; + let expr = ctx + .expressions + .append(crate::Expression::Literal(literal), span); + + emitter.start(ctx.expressions); + Ok(expr) +} + +fn resolve_constant( + gctx: crate::proc::GlobalCtx, + constant: Handle, +) -> Option { + match gctx.const_expressions[gctx.constants[constant].init] { + crate::Expression::Literal(crate::Literal::U32(id)) => Some(id), + crate::Expression::Literal(crate::Literal::I32(id)) => Some(id as u32), + _ => None, + } +} + +pub fn parse_u8_slice(data: &[u8], options: &Options) -> Result { + if data.len() % 4 != 0 { + return Err(Error::IncompleteData); + } + + let words = data + .chunks(4) + .map(|c| u32::from_le_bytes(c.try_into().unwrap())); + Frontend::new(words, options).parse() +} + +#[cfg(test)] +mod test { + #[test] + fn parse() { + let bin = vec![ + // Magic number. Version number: 1.0. + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, + // Generator number: 0. Bound: 0. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved word: 0. + 0x00, 0x00, 0x00, 0x00, // OpMemoryModel. Logical. + 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // GLSL450. + 0x01, 0x00, 0x00, 0x00, + ]; + let _ = super::parse_u8_slice(&bin, &Default::default()).unwrap(); + } +} + +/// Helper function to check if `child` is in the scope of `parent` +fn is_parent(mut child: usize, parent: usize, block_ctx: &BlockContext) -> bool { + loop { + if child == parent { + // The child is in the scope parent + break true; + } else if child == 0 { + // Searched finished at the root the child isn't in the parent's body + break false; + } + + child = block_ctx.bodies[child].parent; + } +} diff --git a/naga/src/front/spv/null.rs b/naga/src/front/spv/null.rs new file mode 100644 index 0000000000..42cccca80a --- /dev/null +++ b/naga/src/front/spv/null.rs @@ -0,0 +1,31 @@ +use super::Error; +use crate::arena::{Arena, Handle}; + +/// Create a default value for an output built-in. +pub fn generate_default_built_in( + built_in: Option, + ty: Handle, + const_expressions: &mut Arena, + span: crate::Span, +) -> Result, Error> { + let expr = match built_in { + Some(crate::BuiltIn::Position { .. }) => { + let zero = const_expressions + .append(crate::Expression::Literal(crate::Literal::F32(0.0)), span); + let one = const_expressions + .append(crate::Expression::Literal(crate::Literal::F32(1.0)), span); + crate::Expression::Compose { + ty, + components: vec![zero, zero, zero, one], + } + } + Some(crate::BuiltIn::PointSize) => crate::Expression::Literal(crate::Literal::F32(1.0)), + Some(crate::BuiltIn::FragDepth) => crate::Expression::Literal(crate::Literal::F32(0.0)), + Some(crate::BuiltIn::SampleMask) => { + crate::Expression::Literal(crate::Literal::U32(u32::MAX)) + } + // Note: `crate::BuiltIn::ClipDistance` is intentionally left for the default path + _ => crate::Expression::ZeroValue(ty), + }; + Ok(const_expressions.append(expr, span)) +} diff --git a/naga/src/front/type_gen.rs b/naga/src/front/type_gen.rs new file mode 100644 index 0000000000..34730c1db5 --- /dev/null +++ b/naga/src/front/type_gen.rs @@ -0,0 +1,437 @@ +/*! +Type generators. +*/ + +use crate::{arena::Handle, span::Span}; + +impl crate::Module { + /// Populate this module's [`SpecialTypes::ray_desc`] type. + /// + /// [`SpecialTypes::ray_desc`] is the type of the [`descriptor`] operand of + /// an [`Initialize`] [`RayQuery`] statement. In WGSL, it is a struct type + /// referred to as `RayDesc`. + /// + /// Backends consume values of this type to drive platform APIs, so if you + /// change any its fields, you must update the backends to match. Look for + /// backend code dealing with [`RayQueryFunction::Initialize`]. + /// + /// [`SpecialTypes::ray_desc`]: crate::SpecialTypes::ray_desc + /// [`descriptor`]: crate::RayQueryFunction::Initialize::descriptor + /// [`Initialize`]: crate::RayQueryFunction::Initialize + /// [`RayQuery`]: crate::Statement::RayQuery + /// [`RayQueryFunction::Initialize`]: crate::RayQueryFunction::Initialize + pub fn generate_ray_desc_type(&mut self) -> Handle { + if let Some(handle) = self.special_types.ray_desc { + return handle; + } + + let ty_flag = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar::U32), + }, + Span::UNDEFINED, + ); + let ty_scalar = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar::F32), + }, + Span::UNDEFINED, + ); + let ty_vector = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size: crate::VectorSize::Tri, + scalar: crate::Scalar::F32, + }, + }, + Span::UNDEFINED, + ); + + let handle = self.types.insert( + crate::Type { + name: Some("RayDesc".to_string()), + inner: crate::TypeInner::Struct { + members: vec![ + crate::StructMember { + name: Some("flags".to_string()), + ty: ty_flag, + binding: None, + offset: 0, + }, + crate::StructMember { + name: Some("cull_mask".to_string()), + ty: ty_flag, + binding: None, + offset: 4, + }, + crate::StructMember { + name: Some("tmin".to_string()), + ty: ty_scalar, + binding: None, + offset: 8, + }, + crate::StructMember { + name: Some("tmax".to_string()), + ty: ty_scalar, + binding: None, + offset: 12, + }, + crate::StructMember { + name: Some("origin".to_string()), + ty: ty_vector, + binding: None, + offset: 16, + }, + crate::StructMember { + name: Some("dir".to_string()), + ty: ty_vector, + binding: None, + offset: 32, + }, + ], + span: 48, + }, + }, + Span::UNDEFINED, + ); + + self.special_types.ray_desc = Some(handle); + handle + } + + /// Populate this module's [`SpecialTypes::ray_intersection`] type. + /// + /// [`SpecialTypes::ray_intersection`] is the type of a + /// `RayQueryGetIntersection` expression. In WGSL, it is a struct type + /// referred to as `RayIntersection`. + /// + /// Backends construct values of this type based on platform APIs, so if you + /// change any its fields, you must update the backends to match. Look for + /// the backend's handling for [`Expression::RayQueryGetIntersection`]. + /// + /// [`SpecialTypes::ray_intersection`]: crate::SpecialTypes::ray_intersection + /// [`Expression::RayQueryGetIntersection`]: crate::Expression::RayQueryGetIntersection + pub fn generate_ray_intersection_type(&mut self) -> Handle { + if let Some(handle) = self.special_types.ray_intersection { + return handle; + } + + let ty_flag = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar::U32), + }, + Span::UNDEFINED, + ); + let ty_scalar = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar::F32), + }, + Span::UNDEFINED, + ); + let ty_barycentrics = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size: crate::VectorSize::Bi, + scalar: crate::Scalar::F32, + }, + }, + Span::UNDEFINED, + ); + let ty_bool = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar::BOOL), + }, + Span::UNDEFINED, + ); + let ty_transform = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Matrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Tri, + scalar: crate::Scalar::F32, + }, + }, + Span::UNDEFINED, + ); + + let handle = self.types.insert( + crate::Type { + name: Some("RayIntersection".to_string()), + inner: crate::TypeInner::Struct { + members: vec![ + crate::StructMember { + name: Some("kind".to_string()), + ty: ty_flag, + binding: None, + offset: 0, + }, + crate::StructMember { + name: Some("t".to_string()), + ty: ty_scalar, + binding: None, + offset: 4, + }, + crate::StructMember { + name: Some("instance_custom_index".to_string()), + ty: ty_flag, + binding: None, + offset: 8, + }, + crate::StructMember { + name: Some("instance_id".to_string()), + ty: ty_flag, + binding: None, + offset: 12, + }, + crate::StructMember { + name: Some("sbt_record_offset".to_string()), + ty: ty_flag, + binding: None, + offset: 16, + }, + crate::StructMember { + name: Some("geometry_index".to_string()), + ty: ty_flag, + binding: None, + offset: 20, + }, + crate::StructMember { + name: Some("primitive_index".to_string()), + ty: ty_flag, + binding: None, + offset: 24, + }, + crate::StructMember { + name: Some("barycentrics".to_string()), + ty: ty_barycentrics, + binding: None, + offset: 28, + }, + crate::StructMember { + name: Some("front_face".to_string()), + ty: ty_bool, + binding: None, + offset: 36, + }, + crate::StructMember { + name: Some("object_to_world".to_string()), + ty: ty_transform, + binding: None, + offset: 48, + }, + crate::StructMember { + name: Some("world_to_object".to_string()), + ty: ty_transform, + binding: None, + offset: 112, + }, + ], + span: 176, + }, + }, + Span::UNDEFINED, + ); + + self.special_types.ray_intersection = Some(handle); + handle + } + + /// Populate this module's [`SpecialTypes::predeclared_types`] type and return the handle. + /// + /// [`SpecialTypes::predeclared_types`]: crate::SpecialTypes::predeclared_types + pub fn generate_predeclared_type( + &mut self, + special_type: crate::PredeclaredType, + ) -> Handle { + use std::fmt::Write; + + if let Some(value) = self.special_types.predeclared_types.get(&special_type) { + return *value; + } + + let ty = match special_type { + crate::PredeclaredType::AtomicCompareExchangeWeakResult(scalar) => { + let bool_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar::BOOL), + }, + Span::UNDEFINED, + ); + let scalar_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(scalar), + }, + Span::UNDEFINED, + ); + + crate::Type { + name: Some(format!( + "__atomic_compare_exchange_result<{:?},{}>", + scalar.kind, scalar.width, + )), + inner: crate::TypeInner::Struct { + members: vec![ + crate::StructMember { + name: Some("old_value".to_string()), + ty: scalar_ty, + binding: None, + offset: 0, + }, + crate::StructMember { + name: Some("exchanged".to_string()), + ty: bool_ty, + binding: None, + offset: 4, + }, + ], + span: 8, + }, + } + } + crate::PredeclaredType::ModfResult { size, width } => { + let float_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar::float(width)), + }, + Span::UNDEFINED, + ); + + let (member_ty, second_offset) = if let Some(size) = size { + let vec_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size, + scalar: crate::Scalar::float(width), + }, + }, + Span::UNDEFINED, + ); + (vec_ty, size as u32 * width as u32) + } else { + (float_ty, width as u32) + }; + + let mut type_name = "__modf_result_".to_string(); + if let Some(size) = size { + let _ = write!(type_name, "vec{}_", size as u8); + } + let _ = write!(type_name, "f{}", width * 8); + + crate::Type { + name: Some(type_name), + inner: crate::TypeInner::Struct { + members: vec![ + crate::StructMember { + name: Some("fract".to_string()), + ty: member_ty, + binding: None, + offset: 0, + }, + crate::StructMember { + name: Some("whole".to_string()), + ty: member_ty, + binding: None, + offset: second_offset, + }, + ], + span: second_offset * 2, + }, + } + } + crate::PredeclaredType::FrexpResult { size, width } => { + let float_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar::float(width)), + }, + Span::UNDEFINED, + ); + + let int_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint, + width, + }), + }, + Span::UNDEFINED, + ); + + let (fract_member_ty, exp_member_ty, second_offset) = if let Some(size) = size { + let vec_float_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size, + scalar: crate::Scalar::float(width), + }, + }, + Span::UNDEFINED, + ); + let vec_int_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size, + scalar: crate::Scalar { + kind: crate::ScalarKind::Sint, + width, + }, + }, + }, + Span::UNDEFINED, + ); + (vec_float_ty, vec_int_ty, size as u32 * width as u32) + } else { + (float_ty, int_ty, width as u32) + }; + + let mut type_name = "__frexp_result_".to_string(); + if let Some(size) = size { + let _ = write!(type_name, "vec{}_", size as u8); + } + let _ = write!(type_name, "f{}", width * 8); + + crate::Type { + name: Some(type_name), + inner: crate::TypeInner::Struct { + members: vec![ + crate::StructMember { + name: Some("fract".to_string()), + ty: fract_member_ty, + binding: None, + offset: 0, + }, + crate::StructMember { + name: Some("exp".to_string()), + ty: exp_member_ty, + binding: None, + offset: second_offset, + }, + ], + span: second_offset * 2, + }, + } + } + }; + + let handle = self.types.insert(ty, Span::UNDEFINED); + self.special_types + .predeclared_types + .insert(special_type, handle); + handle + } +} diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs new file mode 100644 index 0000000000..5b3657f1f1 --- /dev/null +++ b/naga/src/front/wgsl/error.rs @@ -0,0 +1,775 @@ +use crate::front::wgsl::parse::lexer::Token; +use crate::front::wgsl::Scalar; +use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError}; +use crate::{SourceLocation, Span}; +use codespan_reporting::diagnostic::{Diagnostic, Label}; +use codespan_reporting::files::SimpleFile; +use codespan_reporting::term; +use std::borrow::Cow; +use std::ops::Range; +use termcolor::{ColorChoice, NoColor, StandardStream}; +use thiserror::Error; + +#[derive(Clone, Debug)] +pub struct ParseError { + message: String, + labels: Vec<(Span, Cow<'static, str>)>, + notes: Vec, +} + +impl ParseError { + pub fn labels(&self) -> impl ExactSizeIterator + '_ { + self.labels + .iter() + .map(|&(span, ref msg)| (span, msg.as_ref())) + } + + pub fn message(&self) -> &str { + &self.message + } + + fn diagnostic(&self) -> Diagnostic<()> { + let diagnostic = Diagnostic::error() + .with_message(self.message.to_string()) + .with_labels( + self.labels + .iter() + .filter_map(|label| label.0.to_range().map(|range| (label, range))) + .map(|(label, range)| { + Label::primary((), range).with_message(label.1.to_string()) + }) + .collect(), + ) + .with_notes( + self.notes + .iter() + .map(|note| format!("note: {note}")) + .collect(), + ); + diagnostic + } + + /// Emits a summary of the error to standard error stream. + pub fn emit_to_stderr(&self, source: &str) { + self.emit_to_stderr_with_path(source, "wgsl") + } + + /// Emits a summary of the error to standard error stream. + pub fn emit_to_stderr_with_path

(&self, source: &str, path: P) + where + P: AsRef, + { + let path = path.as_ref().display().to_string(); + let files = SimpleFile::new(path, source); + let config = codespan_reporting::term::Config::default(); + let writer = StandardStream::stderr(ColorChoice::Auto); + term::emit(&mut writer.lock(), &config, &files, &self.diagnostic()) + .expect("cannot write error"); + } + + /// Emits a summary of the error to a string. + pub fn emit_to_string(&self, source: &str) -> String { + self.emit_to_string_with_path(source, "wgsl") + } + + /// Emits a summary of the error to a string. + pub fn emit_to_string_with_path

(&self, source: &str, path: P) -> String + where + P: AsRef, + { + let path = path.as_ref().display().to_string(); + let files = SimpleFile::new(path, source); + let config = codespan_reporting::term::Config::default(); + let mut writer = NoColor::new(Vec::new()); + term::emit(&mut writer, &config, &files, &self.diagnostic()).expect("cannot write error"); + String::from_utf8(writer.into_inner()).unwrap() + } + + /// Returns a [`SourceLocation`] for the first label in the error message. + pub fn location(&self, source: &str) -> Option { + self.labels.get(0).map(|label| label.0.location(source)) + } +} + +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for ParseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ExpectedToken<'a> { + Token(Token<'a>), + Identifier, + /// Expected: constant, parenthesized expression, identifier + PrimaryExpression, + /// Expected: assignment, increment/decrement expression + Assignment, + /// Expected: 'case', 'default', '}' + SwitchItem, + /// Expected: ',', ')' + WorkgroupSizeSeparator, + /// Expected: 'struct', 'let', 'var', 'type', ';', 'fn', eof + GlobalItem, + /// Expected a type. + Type, + /// Access of `var`, `let`, `const`. + Variable, + /// Access of a function + Function, +} + +#[derive(Clone, Copy, Debug, Error, PartialEq)] +pub enum NumberError { + #[error("invalid numeric literal format")] + Invalid, + #[error("numeric literal not representable by target type")] + NotRepresentable, + #[error("unimplemented f16 type")] + UnimplementedF16, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum InvalidAssignmentType { + Other, + Swizzle, + ImmutableBinding(Span), +} + +#[derive(Clone, Debug)] +pub enum Error<'a> { + Unexpected(Span, ExpectedToken<'a>), + UnexpectedComponents(Span), + UnexpectedOperationInConstContext(Span), + BadNumber(Span, NumberError), + BadMatrixScalarKind(Span, Scalar), + BadAccessor(Span), + BadTexture(Span), + BadTypeCast { + span: Span, + from_type: String, + to_type: String, + }, + BadTextureSampleType { + span: Span, + scalar: Scalar, + }, + BadIncrDecrReferenceType(Span), + InvalidResolve(ResolveError), + InvalidForInitializer(Span), + /// A break if appeared outside of a continuing block + InvalidBreakIf(Span), + InvalidGatherComponent(Span), + InvalidConstructorComponentType(Span, i32), + InvalidIdentifierUnderscore(Span), + ReservedIdentifierPrefix(Span), + UnknownAddressSpace(Span), + RepeatedAttribute(Span), + UnknownAttribute(Span), + UnknownBuiltin(Span), + UnknownAccess(Span), + UnknownIdent(Span, &'a str), + UnknownScalarType(Span), + UnknownType(Span), + UnknownStorageFormat(Span), + UnknownConservativeDepth(Span), + SizeAttributeTooLow(Span, u32), + AlignAttributeTooLow(Span, Alignment), + NonPowerOfTwoAlignAttribute(Span), + InconsistentBinding(Span), + TypeNotConstructible(Span), + TypeNotInferrable(Span), + InitializationTypeMismatch { + name: Span, + expected: String, + got: String, + }, + MissingType(Span), + MissingAttribute(&'static str, Span), + InvalidAtomicPointer(Span), + InvalidAtomicOperandType(Span), + InvalidRayQueryPointer(Span), + Pointer(&'static str, Span), + NotPointer(Span), + NotReference(&'static str, Span), + InvalidAssignment { + span: Span, + ty: InvalidAssignmentType, + }, + ReservedKeyword(Span), + /// Redefinition of an identifier (used for both module-scope and local redefinitions). + Redefinition { + /// Span of the identifier in the previous definition. + previous: Span, + + /// Span of the identifier in the new definition. + current: Span, + }, + /// A declaration refers to itself directly. + RecursiveDeclaration { + /// The location of the name of the declaration. + ident: Span, + + /// The point at which it is used. + usage: Span, + }, + /// A declaration refers to itself indirectly, through one or more other + /// definitions. + CyclicDeclaration { + /// The location of the name of some declaration in the cycle. + ident: Span, + + /// The edges of the cycle of references. + /// + /// Each `(decl, reference)` pair indicates that the declaration whose + /// name is `decl` has an identifier at `reference` whose definition is + /// the next declaration in the cycle. The last pair's `reference` is + /// the same identifier as `ident`, above. + path: Vec<(Span, Span)>, + }, + InvalidSwitchValue { + uint: bool, + span: Span, + }, + CalledEntryPoint(Span), + WrongArgumentCount { + span: Span, + expected: Range, + found: u32, + }, + FunctionReturnsVoid(Span), + InvalidWorkGroupUniformLoad(Span), + Internal(&'static str), + ExpectedConstExprConcreteIntegerScalar(Span), + ExpectedNonNegative(Span), + ExpectedPositiveArrayLength(Span), + MissingWorkgroupSize(Span), + ConstantEvaluatorError(ConstantEvaluatorError, Span), + AutoConversion { + dest_span: Span, + dest_type: String, + source_span: Span, + source_type: String, + }, + AutoConversionLeafScalar { + dest_span: Span, + dest_scalar: String, + source_span: Span, + source_type: String, + }, + ConcretizationFailed { + expr_span: Span, + expr_type: String, + scalar: String, + inner: ConstantEvaluatorError, + }, +} + +impl<'a> Error<'a> { + pub(crate) fn as_parse_error(&self, source: &'a str) -> ParseError { + match *self { + Error::Unexpected(unexpected_span, expected) => { + let expected_str = match expected { + ExpectedToken::Token(token) => { + match token { + Token::Separator(c) => format!("'{c}'"), + Token::Paren(c) => format!("'{c}'"), + Token::Attribute => "@".to_string(), + Token::Number(_) => "number".to_string(), + Token::Word(s) => s.to_string(), + Token::Operation(c) => format!("operation ('{c}')"), + Token::LogicalOperation(c) => format!("logical operation ('{c}')"), + Token::ShiftOperation(c) => format!("bitshift ('{c}{c}')"), + Token::AssignmentOperation(c) if c=='<' || c=='>' => format!("bitshift ('{c}{c}=')"), + Token::AssignmentOperation(c) => format!("operation ('{c}=')"), + Token::IncrementOperation => "increment operation".to_string(), + Token::DecrementOperation => "decrement operation".to_string(), + Token::Arrow => "->".to_string(), + Token::Unknown(c) => format!("unknown ('{c}')"), + Token::Trivia => "trivia".to_string(), + Token::End => "end".to_string(), + } + } + ExpectedToken::Identifier => "identifier".to_string(), + ExpectedToken::PrimaryExpression => "expression".to_string(), + ExpectedToken::Assignment => "assignment or increment/decrement".to_string(), + ExpectedToken::SwitchItem => "switch item ('case' or 'default') or a closing curly bracket to signify the end of the switch statement ('}')".to_string(), + ExpectedToken::WorkgroupSizeSeparator => "workgroup size separator (',') or a closing parenthesis".to_string(), + ExpectedToken::GlobalItem => "global item ('struct', 'const', 'var', 'alias', ';', 'fn') or the end of the file".to_string(), + ExpectedToken::Type => "type".to_string(), + ExpectedToken::Variable => "variable access".to_string(), + ExpectedToken::Function => "function name".to_string(), + }; + ParseError { + message: format!( + "expected {}, found '{}'", + expected_str, &source[unexpected_span], + ), + labels: vec![(unexpected_span, format!("expected {expected_str}").into())], + notes: vec![], + } + } + Error::UnexpectedComponents(bad_span) => ParseError { + message: "unexpected components".to_string(), + labels: vec![(bad_span, "unexpected components".into())], + notes: vec![], + }, + Error::UnexpectedOperationInConstContext(span) => ParseError { + message: "this operation is not supported in a const context".to_string(), + labels: vec![(span, "operation not supported here".into())], + notes: vec![], + }, + Error::BadNumber(bad_span, ref err) => ParseError { + message: format!("{}: `{}`", err, &source[bad_span],), + labels: vec![(bad_span, err.to_string().into())], + notes: vec![], + }, + Error::BadMatrixScalarKind(span, scalar) => ParseError { + message: format!( + "matrix scalar type must be floating-point, but found `{}`", + scalar.to_wgsl() + ), + labels: vec![(span, "must be floating-point (e.g. `f32`)".into())], + notes: vec![], + }, + Error::BadAccessor(accessor_span) => ParseError { + message: format!("invalid field accessor `{}`", &source[accessor_span],), + labels: vec![(accessor_span, "invalid accessor".into())], + notes: vec![], + }, + Error::UnknownIdent(ident_span, ident) => ParseError { + message: format!("no definition in scope for identifier: '{ident}'"), + labels: vec![(ident_span, "unknown identifier".into())], + notes: vec![], + }, + Error::UnknownScalarType(bad_span) => ParseError { + message: format!("unknown scalar type: '{}'", &source[bad_span]), + labels: vec![(bad_span, "unknown scalar type".into())], + notes: vec!["Valid scalar types are f32, f64, i32, u32, bool".into()], + }, + Error::BadTextureSampleType { span, scalar } => ParseError { + message: format!( + "texture sample type must be one of f32, i32 or u32, but found {}", + scalar.to_wgsl() + ), + labels: vec![(span, "must be one of f32, i32 or u32".into())], + notes: vec![], + }, + Error::BadIncrDecrReferenceType(span) => ParseError { + message: + "increment/decrement operation requires reference type to be one of i32 or u32" + .to_string(), + labels: vec![(span, "must be a reference type of i32 or u32".into())], + notes: vec![], + }, + Error::BadTexture(bad_span) => ParseError { + message: format!( + "expected an image, but found '{}' which is not an image", + &source[bad_span] + ), + labels: vec![(bad_span, "not an image".into())], + notes: vec![], + }, + Error::BadTypeCast { + span, + ref from_type, + ref to_type, + } => { + let msg = format!("cannot cast a {from_type} to a {to_type}"); + ParseError { + message: msg.clone(), + labels: vec![(span, msg.into())], + notes: vec![], + } + } + Error::InvalidResolve(ref resolve_error) => ParseError { + message: resolve_error.to_string(), + labels: vec![], + notes: vec![], + }, + Error::InvalidForInitializer(bad_span) => ParseError { + message: format!( + "for(;;) initializer is not an assignment or a function call: '{}'", + &source[bad_span] + ), + labels: vec![(bad_span, "not an assignment or function call".into())], + notes: vec![], + }, + Error::InvalidBreakIf(bad_span) => ParseError { + message: "A break if is only allowed in a continuing block".to_string(), + labels: vec![(bad_span, "not in a continuing block".into())], + notes: vec![], + }, + Error::InvalidGatherComponent(bad_span) => ParseError { + message: format!( + "textureGather component '{}' doesn't exist, must be 0, 1, 2, or 3", + &source[bad_span] + ), + labels: vec![(bad_span, "invalid component".into())], + notes: vec![], + }, + Error::InvalidConstructorComponentType(bad_span, component) => ParseError { + message: format!("invalid type for constructor component at index [{component}]"), + labels: vec![(bad_span, "invalid component type".into())], + notes: vec![], + }, + Error::InvalidIdentifierUnderscore(bad_span) => ParseError { + message: "Identifier can't be '_'".to_string(), + labels: vec![(bad_span, "invalid identifier".into())], + notes: vec![ + "Use phony assignment instead ('_ =' notice the absence of 'let' or 'var')" + .to_string(), + ], + }, + Error::ReservedIdentifierPrefix(bad_span) => ParseError { + message: format!( + "Identifier starts with a reserved prefix: '{}'", + &source[bad_span] + ), + labels: vec![(bad_span, "invalid identifier".into())], + notes: vec![], + }, + Error::UnknownAddressSpace(bad_span) => ParseError { + message: format!("unknown address space: '{}'", &source[bad_span]), + labels: vec![(bad_span, "unknown address space".into())], + notes: vec![], + }, + Error::RepeatedAttribute(bad_span) => ParseError { + message: format!("repeated attribute: '{}'", &source[bad_span]), + labels: vec![(bad_span, "repeated attribute".into())], + notes: vec![], + }, + Error::UnknownAttribute(bad_span) => ParseError { + message: format!("unknown attribute: '{}'", &source[bad_span]), + labels: vec![(bad_span, "unknown attribute".into())], + notes: vec![], + }, + Error::UnknownBuiltin(bad_span) => ParseError { + message: format!("unknown builtin: '{}'", &source[bad_span]), + labels: vec![(bad_span, "unknown builtin".into())], + notes: vec![], + }, + Error::UnknownAccess(bad_span) => ParseError { + message: format!("unknown access: '{}'", &source[bad_span]), + labels: vec![(bad_span, "unknown access".into())], + notes: vec![], + }, + Error::UnknownStorageFormat(bad_span) => ParseError { + message: format!("unknown storage format: '{}'", &source[bad_span]), + labels: vec![(bad_span, "unknown storage format".into())], + notes: vec![], + }, + Error::UnknownConservativeDepth(bad_span) => ParseError { + message: format!("unknown conservative depth: '{}'", &source[bad_span]), + labels: vec![(bad_span, "unknown conservative depth".into())], + notes: vec![], + }, + Error::UnknownType(bad_span) => ParseError { + message: format!("unknown type: '{}'", &source[bad_span]), + labels: vec![(bad_span, "unknown type".into())], + notes: vec![], + }, + Error::SizeAttributeTooLow(bad_span, min_size) => ParseError { + message: format!("struct member size must be at least {min_size}"), + labels: vec![(bad_span, format!("must be at least {min_size}").into())], + notes: vec![], + }, + Error::AlignAttributeTooLow(bad_span, min_align) => ParseError { + message: format!("struct member alignment must be at least {min_align}"), + labels: vec![(bad_span, format!("must be at least {min_align}").into())], + notes: vec![], + }, + Error::NonPowerOfTwoAlignAttribute(bad_span) => ParseError { + message: "struct member alignment must be a power of 2".to_string(), + labels: vec![(bad_span, "must be a power of 2".into())], + notes: vec![], + }, + Error::InconsistentBinding(span) => ParseError { + message: "input/output binding is not consistent".to_string(), + labels: vec![(span, "input/output binding is not consistent".into())], + notes: vec![], + }, + Error::TypeNotConstructible(span) => ParseError { + message: format!("type `{}` is not constructible", &source[span]), + labels: vec![(span, "type is not constructible".into())], + notes: vec![], + }, + Error::TypeNotInferrable(span) => ParseError { + message: "type can't be inferred".to_string(), + labels: vec![(span, "type can't be inferred".into())], + notes: vec![], + }, + Error::InitializationTypeMismatch { name, ref expected, ref got } => { + ParseError { + message: format!( + "the type of `{}` is expected to be `{}`, but got `{}`", + &source[name], expected, got, + ), + labels: vec![( + name, + format!("definition of `{}`", &source[name]).into(), + )], + notes: vec![], + } + } + Error::MissingType(name_span) => ParseError { + message: format!("variable `{}` needs a type", &source[name_span]), + labels: vec![( + name_span, + format!("definition of `{}`", &source[name_span]).into(), + )], + notes: vec![], + }, + Error::MissingAttribute(name, name_span) => ParseError { + message: format!( + "variable `{}` needs a '{}' attribute", + &source[name_span], name + ), + labels: vec![( + name_span, + format!("definition of `{}`", &source[name_span]).into(), + )], + notes: vec![], + }, + Error::InvalidAtomicPointer(span) => ParseError { + message: "atomic operation is done on a pointer to a non-atomic".to_string(), + labels: vec![(span, "atomic pointer is invalid".into())], + notes: vec![], + }, + Error::InvalidAtomicOperandType(span) => ParseError { + message: "atomic operand type is inconsistent with the operation".to_string(), + labels: vec![(span, "atomic operand type is invalid".into())], + notes: vec![], + }, + Error::InvalidRayQueryPointer(span) => ParseError { + message: "ray query operation is done on a pointer to a non-ray-query".to_string(), + labels: vec![(span, "ray query pointer is invalid".into())], + notes: vec![], + }, + Error::NotPointer(span) => ParseError { + message: "the operand of the `*` operator must be a pointer".to_string(), + labels: vec![(span, "expression is not a pointer".into())], + notes: vec![], + }, + Error::NotReference(what, span) => ParseError { + message: format!("{what} must be a reference"), + labels: vec![(span, "expression is not a reference".into())], + notes: vec![], + }, + Error::InvalidAssignment { span, ty } => { + let (extra_label, notes) = match ty { + InvalidAssignmentType::Swizzle => ( + None, + vec![ + "WGSL does not support assignments to swizzles".into(), + "consider assigning each component individually".into(), + ], + ), + InvalidAssignmentType::ImmutableBinding(binding_span) => ( + Some((binding_span, "this is an immutable binding".into())), + vec![format!( + "consider declaring '{}' with `var` instead of `let`", + &source[binding_span] + )], + ), + InvalidAssignmentType::Other => (None, vec![]), + }; + + ParseError { + message: "invalid left-hand side of assignment".into(), + labels: std::iter::once((span, "cannot assign to this expression".into())) + .chain(extra_label) + .collect(), + notes, + } + } + Error::Pointer(what, span) => ParseError { + message: format!("{what} must not be a pointer"), + labels: vec![(span, "expression is a pointer".into())], + notes: vec![], + }, + Error::ReservedKeyword(name_span) => ParseError { + message: format!("name `{}` is a reserved keyword", &source[name_span]), + labels: vec![( + name_span, + format!("definition of `{}`", &source[name_span]).into(), + )], + notes: vec![], + }, + Error::Redefinition { previous, current } => ParseError { + message: format!("redefinition of `{}`", &source[current]), + labels: vec![ + ( + current, + format!("redefinition of `{}`", &source[current]).into(), + ), + ( + previous, + format!("previous definition of `{}`", &source[previous]).into(), + ), + ], + notes: vec![], + }, + Error::RecursiveDeclaration { ident, usage } => ParseError { + message: format!("declaration of `{}` is recursive", &source[ident]), + labels: vec![(ident, "".into()), (usage, "uses itself here".into())], + notes: vec![], + }, + Error::CyclicDeclaration { ident, ref path } => ParseError { + message: format!("declaration of `{}` is cyclic", &source[ident]), + labels: path + .iter() + .enumerate() + .flat_map(|(i, &(ident, usage))| { + [ + (ident, "".into()), + ( + usage, + if i == path.len() - 1 { + "ending the cycle".into() + } else { + format!("uses `{}`", &source[ident]).into() + }, + ), + ] + }) + .collect(), + notes: vec![], + }, + Error::InvalidSwitchValue { uint, span } => ParseError { + message: "invalid switch value".to_string(), + labels: vec![( + span, + if uint { + "expected unsigned integer" + } else { + "expected signed integer" + } + .into(), + )], + notes: vec![if uint { + format!("suffix the integer with a `u`: '{}u'", &source[span]) + } else { + let span = span.to_range().unwrap(); + format!( + "remove the `u` suffix: '{}'", + &source[span.start..span.end - 1] + ) + }], + }, + Error::CalledEntryPoint(span) => ParseError { + message: "entry point cannot be called".to_string(), + labels: vec![(span, "entry point cannot be called".into())], + notes: vec![], + }, + Error::WrongArgumentCount { + span, + ref expected, + found, + } => ParseError { + message: format!( + "wrong number of arguments: expected {}, found {}", + if expected.len() < 2 { + format!("{}", expected.start) + } else { + format!("{}..{}", expected.start, expected.end) + }, + found + ), + labels: vec![(span, "wrong number of arguments".into())], + notes: vec![], + }, + Error::FunctionReturnsVoid(span) => ParseError { + message: "function does not return any value".to_string(), + labels: vec![(span, "".into())], + notes: vec![ + "perhaps you meant to call the function in a separate statement?".into(), + ], + }, + Error::InvalidWorkGroupUniformLoad(span) => ParseError { + message: "incorrect type passed to workgroupUniformLoad".into(), + labels: vec![(span, "".into())], + notes: vec!["passed type must be a workgroup pointer".into()], + }, + Error::Internal(message) => ParseError { + message: "internal WGSL front end error".to_string(), + labels: vec![], + notes: vec![message.into()], + }, + Error::ExpectedConstExprConcreteIntegerScalar(span) => ParseError { + message: "must be a const-expression that resolves to a concrete integer scalar (u32 or i32)".to_string(), + labels: vec![(span, "must resolve to u32 or i32".into())], + notes: vec![], + }, + Error::ExpectedNonNegative(span) => ParseError { + message: "must be non-negative (>= 0)".to_string(), + labels: vec![(span, "must be non-negative".into())], + notes: vec![], + }, + Error::ExpectedPositiveArrayLength(span) => ParseError { + message: "array element count must be positive (> 0)".to_string(), + labels: vec![(span, "must be positive".into())], + notes: vec![], + }, + Error::ConstantEvaluatorError(ref e, span) => ParseError { + message: e.to_string(), + labels: vec![(span, "see msg".into())], + notes: vec![], + }, + Error::MissingWorkgroupSize(span) => ParseError { + message: "workgroup size is missing on compute shader entry point".to_string(), + labels: vec![( + span, + "must be paired with a @workgroup_size attribute".into(), + )], + notes: vec![], + }, + Error::AutoConversion { dest_span, ref dest_type, source_span, ref source_type } => ParseError { + message: format!("automatic conversions cannot convert `{source_type}` to `{dest_type}`"), + labels: vec![ + ( + dest_span, + format!("a value of type {dest_type} is required here").into(), + ), + ( + source_span, + format!("this expression has type {source_type}").into(), + ) + ], + notes: vec![], + }, + Error::AutoConversionLeafScalar { dest_span, ref dest_scalar, source_span, ref source_type } => ParseError { + message: format!("automatic conversions cannot convert elements of `{source_type}` to `{dest_scalar}`"), + labels: vec![ + ( + dest_span, + format!("a value with elements of type {dest_scalar} is required here").into(), + ), + ( + source_span, + format!("this expression has type {source_type}").into(), + ) + ], + notes: vec![], + }, + Error::ConcretizationFailed { expr_span, ref expr_type, ref scalar, ref inner } => ParseError { + message: format!("failed to convert expression to a concrete type: {}", inner), + labels: vec![ + ( + expr_span, + format!("this expression has type {}", expr_type).into(), + ) + ], + notes: vec![ + format!("the expression should have been converted to have {} scalar type", scalar), + ] + }, + } + } +} diff --git a/naga/src/front/wgsl/index.rs b/naga/src/front/wgsl/index.rs new file mode 100644 index 0000000000..a5524fe8f1 --- /dev/null +++ b/naga/src/front/wgsl/index.rs @@ -0,0 +1,193 @@ +use super::Error; +use crate::front::wgsl::parse::ast; +use crate::{FastHashMap, Handle, Span}; + +/// A `GlobalDecl` list in which each definition occurs before all its uses. +pub struct Index<'a> { + dependency_order: Vec>>, +} + +impl<'a> Index<'a> { + /// Generate an `Index` for the given translation unit. + /// + /// Perform a topological sort on `tu`'s global declarations, placing + /// referents before the definitions that refer to them. + /// + /// Return an error if the graph of references between declarations contains + /// any cycles. + pub fn generate(tu: &ast::TranslationUnit<'a>) -> Result> { + // Produce a map from global definitions' names to their `Handle`s. + // While doing so, reject conflicting definitions. + let mut globals = FastHashMap::with_capacity_and_hasher(tu.decls.len(), Default::default()); + for (handle, decl) in tu.decls.iter() { + let ident = decl_ident(decl); + let name = ident.name; + if let Some(old) = globals.insert(name, handle) { + return Err(Error::Redefinition { + previous: decl_ident(&tu.decls[old]).span, + current: ident.span, + }); + } + } + + let len = tu.decls.len(); + let solver = DependencySolver { + globals: &globals, + module: tu, + visited: vec![false; len], + temp_visited: vec![false; len], + path: Vec::new(), + out: Vec::with_capacity(len), + }; + let dependency_order = solver.solve()?; + + Ok(Self { dependency_order }) + } + + /// Iterate over `GlobalDecl`s, visiting each definition before all its uses. + /// + /// Produce handles for all of the `GlobalDecl`s of the `TranslationUnit` + /// passed to `Index::generate`, ordered so that a given declaration is + /// produced before any other declaration that uses it. + pub fn visit_ordered(&self) -> impl Iterator>> + '_ { + self.dependency_order.iter().copied() + } +} + +/// An edge from a reference to its referent in the current depth-first +/// traversal. +/// +/// This is like `ast::Dependency`, except that we've determined which +/// `GlobalDecl` it refers to. +struct ResolvedDependency<'a> { + /// The referent of some identifier used in the current declaration. + decl: Handle>, + + /// Where that use occurs within the current declaration. + usage: Span, +} + +/// Local state for ordering a `TranslationUnit`'s module-scope declarations. +/// +/// Values of this type are used temporarily by `Index::generate` +/// to perform a depth-first sort on the declarations. +/// Technically, what we want is a topological sort, but a depth-first sort +/// has one key benefit - it's much more efficient in storing +/// the path of each node for error generation. +struct DependencySolver<'source, 'temp> { + /// A map from module-scope definitions' names to their handles. + globals: &'temp FastHashMap<&'source str, Handle>>, + + /// The translation unit whose declarations we're ordering. + module: &'temp ast::TranslationUnit<'source>, + + /// For each handle, whether we have pushed it onto `out` yet. + visited: Vec, + + /// For each handle, whether it is an predecessor in the current depth-first + /// traversal. This is used to detect cycles in the reference graph. + temp_visited: Vec, + + /// The current path in our depth-first traversal. Used for generating + /// error messages for non-trivial reference cycles. + path: Vec>, + + /// The list of declaration handles, with declarations before uses. + out: Vec>>, +} + +impl<'a> DependencySolver<'a, '_> { + /// Produce the sorted list of declaration handles, and check for cycles. + fn solve(mut self) -> Result>>, Error<'a>> { + for (id, _) in self.module.decls.iter() { + if self.visited[id.index()] { + continue; + } + + self.dfs(id)?; + } + + Ok(self.out) + } + + /// Ensure that all declarations used by `id` have been added to the + /// ordering, and then append `id` itself. + fn dfs(&mut self, id: Handle>) -> Result<(), Error<'a>> { + let decl = &self.module.decls[id]; + let id_usize = id.index(); + + self.temp_visited[id_usize] = true; + for dep in decl.dependencies.iter() { + if let Some(&dep_id) = self.globals.get(dep.ident) { + self.path.push(ResolvedDependency { + decl: dep_id, + usage: dep.usage, + }); + let dep_id_usize = dep_id.index(); + + if self.temp_visited[dep_id_usize] { + // Found a cycle. + return if dep_id == id { + // A declaration refers to itself directly. + Err(Error::RecursiveDeclaration { + ident: decl_ident(decl).span, + usage: dep.usage, + }) + } else { + // A declaration refers to itself indirectly, through + // one or more other definitions. Report the entire path + // of references. + let start_at = self + .path + .iter() + .rev() + .enumerate() + .find_map(|(i, dep)| (dep.decl == dep_id).then_some(i)) + .unwrap_or(0); + + Err(Error::CyclicDeclaration { + ident: decl_ident(&self.module.decls[dep_id]).span, + path: self.path[start_at..] + .iter() + .map(|curr_dep| { + let curr_id = curr_dep.decl; + let curr_decl = &self.module.decls[curr_id]; + + (decl_ident(curr_decl).span, curr_dep.usage) + }) + .collect(), + }) + }; + } else if !self.visited[dep_id_usize] { + self.dfs(dep_id)?; + } + + // Remove this edge from the current path. + self.path.pop(); + } + + // Ignore unresolved identifiers; they may be predeclared objects. + } + + // Remove this node from the current path. + self.temp_visited[id_usize] = false; + + // Now everything this declaration uses has been visited, and is already + // present in `out`. That means we we can append this one to the + // ordering, and mark it as visited. + self.out.push(id); + self.visited[id_usize] = true; + + Ok(()) + } +} + +const fn decl_ident<'a>(decl: &ast::GlobalDecl<'a>) -> ast::Ident<'a> { + match decl.kind { + ast::GlobalDeclKind::Fn(ref f) => f.name, + ast::GlobalDeclKind::Var(ref v) => v.name, + ast::GlobalDeclKind::Const(ref c) => c.name, + ast::GlobalDeclKind::Struct(ref s) => s.name, + ast::GlobalDeclKind::Type(ref t) => t.name, + } +} diff --git a/naga/src/front/wgsl/lower/construction.rs b/naga/src/front/wgsl/lower/construction.rs new file mode 100644 index 0000000000..e996e35227 --- /dev/null +++ b/naga/src/front/wgsl/lower/construction.rs @@ -0,0 +1,616 @@ +use std::num::NonZeroU32; + +use crate::front::wgsl::parse::ast; +use crate::{Handle, Span}; + +use crate::front::wgsl::error::Error; +use crate::front::wgsl::lower::{ExpressionContext, Lowerer}; + +/// A cooked form of `ast::ConstructorType` that uses Naga types whenever +/// possible. +enum Constructor { + /// A vector construction whose component type is inferred from the + /// argument: `vec3(1.0)`. + PartialVector { size: crate::VectorSize }, + + /// A matrix construction whose component type is inferred from the + /// argument: `mat2x2(1,2,3,4)`. + PartialMatrix { + columns: crate::VectorSize, + rows: crate::VectorSize, + }, + + /// An array whose component type and size are inferred from the arguments: + /// `array(3,4,5)`. + PartialArray, + + /// A known Naga type. + /// + /// When we match on this type, we need to see the `TypeInner` here, but at + /// the point that we build this value we'll still need mutable access to + /// the module later. To avoid borrowing from the module, the type parameter + /// `T` is `Handle` initially. Then we use `borrow_inner` to produce a + /// version holding a tuple `(Handle, &TypeInner)`. + Type(T), +} + +impl Constructor> { + /// Return an equivalent `Constructor` value that includes borrowed + /// `TypeInner` values alongside any type handles. + /// + /// The returned form is more convenient to match on, since the patterns + /// can actually see what the handle refers to. + fn borrow_inner( + self, + module: &crate::Module, + ) -> Constructor<(Handle, &crate::TypeInner)> { + match self { + Constructor::PartialVector { size } => Constructor::PartialVector { size }, + Constructor::PartialMatrix { columns, rows } => { + Constructor::PartialMatrix { columns, rows } + } + Constructor::PartialArray => Constructor::PartialArray, + Constructor::Type(handle) => Constructor::Type((handle, &module.types[handle].inner)), + } + } +} + +impl Constructor<(Handle, &crate::TypeInner)> { + fn to_error_string(&self, ctx: &ExpressionContext) -> String { + match *self { + Self::PartialVector { size } => { + format!("vec{}", size as u32,) + } + Self::PartialMatrix { columns, rows } => { + format!("mat{}x{}", columns as u32, rows as u32,) + } + Self::PartialArray => "array".to_string(), + Self::Type((handle, _inner)) => handle.to_wgsl(&ctx.module.to_ctx()), + } + } +} + +enum Components<'a> { + None, + One { + component: Handle, + span: Span, + ty_inner: &'a crate::TypeInner, + }, + Many { + components: Vec>, + spans: Vec, + }, +} + +impl Components<'_> { + fn into_components_vec(self) -> Vec> { + match self { + Self::None => vec![], + Self::One { component, .. } => vec![component], + Self::Many { components, .. } => components, + } + } +} + +impl<'source, 'temp> Lowerer<'source, 'temp> { + /// Generate Naga IR for a type constructor expression. + /// + /// The `constructor` value represents the head of the constructor + /// expression, which is at least a hint of which type is being built; if + /// it's one of the `Partial` variants, we need to consider the argument + /// types as well. + /// + /// This is used for [`Construct`] expressions, but also for [`Call`] + /// expressions, once we've determined that the "callable" (in WGSL spec + /// terms) is actually a type. + /// + /// [`Construct`]: ast::Expression::Construct + /// [`Call`]: ast::Expression::Call + pub fn construct( + &mut self, + span: Span, + constructor: &ast::ConstructorType<'source>, + ty_span: Span, + components: &[Handle>], + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + use crate::proc::TypeResolution as Tr; + + let constructor_h = self.constructor(constructor, ctx)?; + + let components = match *components { + [] => Components::None, + [component] => { + let span = ctx.ast_expressions.get_span(component); + let component = self.expression_for_abstract(component, ctx)?; + let ty_inner = super::resolve_inner!(ctx, component); + + Components::One { + component, + span, + ty_inner, + } + } + ref ast_components @ [_, _, ..] => { + let components = ast_components + .iter() + .map(|&expr| self.expression_for_abstract(expr, ctx)) + .collect::>()?; + let spans = ast_components + .iter() + .map(|&expr| ctx.ast_expressions.get_span(expr)) + .collect(); + + for &component in &components { + ctx.grow_types(component)?; + } + + Components::Many { components, spans } + } + }; + + // Even though we computed `constructor` above, wait until now to borrow + // a reference to the `TypeInner`, so that the component-handling code + // above can have mutable access to the type arena. + let constructor = constructor_h.borrow_inner(ctx.module); + + let expr; + match (components, constructor) { + // Empty constructor + (Components::None, dst_ty) => match dst_ty { + Constructor::Type((result_ty, _)) => { + return ctx.append_expression(crate::Expression::ZeroValue(result_ty), span) + } + Constructor::PartialVector { .. } + | Constructor::PartialMatrix { .. } + | Constructor::PartialArray => { + // We have no arguments from which to infer the result type, so + // partial constructors aren't acceptable here. + return Err(Error::TypeNotInferrable(ty_span)); + } + }, + + // Scalar constructor & conversion (scalar -> scalar) + ( + Components::One { + component, + ty_inner: &crate::TypeInner::Scalar { .. }, + .. + }, + Constructor::Type((_, &crate::TypeInner::Scalar(scalar))), + ) => { + expr = crate::Expression::As { + expr: component, + kind: scalar.kind, + convert: Some(scalar.width), + }; + } + + // Vector conversion (vector -> vector) + ( + Components::One { + component, + ty_inner: &crate::TypeInner::Vector { size: src_size, .. }, + .. + }, + Constructor::Type(( + _, + &crate::TypeInner::Vector { + size: dst_size, + scalar: dst_scalar, + }, + )), + ) if dst_size == src_size => { + expr = crate::Expression::As { + expr: component, + kind: dst_scalar.kind, + convert: Some(dst_scalar.width), + }; + } + + // Vector conversion (vector -> vector) - partial + ( + Components::One { + component, + ty_inner: &crate::TypeInner::Vector { size: src_size, .. }, + .. + }, + Constructor::PartialVector { size: dst_size }, + ) if dst_size == src_size => { + // This is a trivial conversion: the sizes match, and a Partial + // constructor doesn't specify a scalar type, so nothing can + // possibly happen. + return Ok(component); + } + + // Matrix conversion (matrix -> matrix) + ( + Components::One { + component, + ty_inner: + &crate::TypeInner::Matrix { + columns: src_columns, + rows: src_rows, + .. + }, + .. + }, + Constructor::Type(( + _, + &crate::TypeInner::Matrix { + columns: dst_columns, + rows: dst_rows, + scalar: dst_scalar, + }, + )), + ) if dst_columns == src_columns && dst_rows == src_rows => { + expr = crate::Expression::As { + expr: component, + kind: dst_scalar.kind, + convert: Some(dst_scalar.width), + }; + } + + // Matrix conversion (matrix -> matrix) - partial + ( + Components::One { + component, + ty_inner: + &crate::TypeInner::Matrix { + columns: src_columns, + rows: src_rows, + .. + }, + .. + }, + Constructor::PartialMatrix { + columns: dst_columns, + rows: dst_rows, + }, + ) if dst_columns == src_columns && dst_rows == src_rows => { + // This is a trivial conversion: the sizes match, and a Partial + // constructor doesn't specify a scalar type, so nothing can + // possibly happen. + return Ok(component); + } + + // Vector constructor (splat) - infer type + ( + Components::One { + component, + ty_inner: &crate::TypeInner::Scalar { .. }, + .. + }, + Constructor::PartialVector { size }, + ) => { + expr = crate::Expression::Splat { + size, + value: component, + }; + } + + // Vector constructor (splat) + ( + Components::One { + mut component, + ty_inner: &crate::TypeInner::Scalar(_), + .. + }, + Constructor::Type((_, &crate::TypeInner::Vector { size, scalar })), + ) => { + ctx.convert_slice_to_common_leaf_scalar( + std::slice::from_mut(&mut component), + scalar, + )?; + expr = crate::Expression::Splat { + size, + value: component, + }; + } + + // Vector constructor (by elements), partial + ( + Components::Many { + mut components, + spans, + }, + Constructor::PartialVector { size }, + ) => { + let consensus_scalar = + ctx.automatic_conversion_consensus(&components) + .map_err(|index| { + Error::InvalidConstructorComponentType(spans[index], index as i32) + })?; + ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?; + let inner = consensus_scalar.to_inner_vector(size); + let ty = ctx.ensure_type_exists(inner); + expr = crate::Expression::Compose { ty, components }; + } + + // Vector constructor (by elements), full type given + ( + Components::Many { mut components, .. }, + Constructor::Type((ty, &crate::TypeInner::Vector { scalar, .. })), + ) => { + ctx.try_automatic_conversions_for_vector(&mut components, scalar, ty_span)?; + expr = crate::Expression::Compose { ty, components }; + } + + // Matrix constructor (by elements), partial + ( + Components::Many { + mut components, + spans, + }, + Constructor::PartialMatrix { columns, rows }, + ) if components.len() == columns as usize * rows as usize => { + let consensus_scalar = + ctx.automatic_conversion_consensus(&components) + .map_err(|index| { + Error::InvalidConstructorComponentType(spans[index], index as i32) + })?; + // We actually only accept floating-point elements. + let consensus_scalar = consensus_scalar + .automatic_conversion_combine(crate::Scalar::ABSTRACT_FLOAT) + .unwrap_or(consensus_scalar); + ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?; + let vec_ty = ctx.ensure_type_exists(consensus_scalar.to_inner_vector(rows)); + + let components = components + .chunks(rows as usize) + .map(|vec_components| { + ctx.append_expression( + crate::Expression::Compose { + ty: vec_ty, + components: Vec::from(vec_components), + }, + Default::default(), + ) + }) + .collect::, _>>()?; + + let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix { + columns, + rows, + scalar: consensus_scalar, + }); + expr = crate::Expression::Compose { ty, components }; + } + + // Matrix constructor (by elements), type given + ( + Components::Many { mut components, .. }, + Constructor::Type(( + _, + &crate::TypeInner::Matrix { + columns, + rows, + scalar, + }, + )), + ) if components.len() == columns as usize * rows as usize => { + let element = Tr::Value(crate::TypeInner::Scalar(scalar)); + ctx.try_automatic_conversions_slice(&mut components, &element, ty_span)?; + let vec_ty = ctx.ensure_type_exists(scalar.to_inner_vector(rows)); + + let components = components + .chunks(rows as usize) + .map(|vec_components| { + ctx.append_expression( + crate::Expression::Compose { + ty: vec_ty, + components: Vec::from(vec_components), + }, + Default::default(), + ) + }) + .collect::, _>>()?; + + let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix { + columns, + rows, + scalar, + }); + expr = crate::Expression::Compose { ty, components }; + } + + // Matrix constructor (by columns), partial + ( + Components::Many { + mut components, + spans, + }, + Constructor::PartialMatrix { columns, rows }, + ) => { + let consensus_scalar = + ctx.automatic_conversion_consensus(&components) + .map_err(|index| { + Error::InvalidConstructorComponentType(spans[index], index as i32) + })?; + ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?; + let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix { + columns, + rows, + scalar: consensus_scalar, + }); + expr = crate::Expression::Compose { ty, components }; + } + + // Matrix constructor (by columns), type given + ( + Components::Many { mut components, .. }, + Constructor::Type(( + ty, + &crate::TypeInner::Matrix { + columns: _, + rows, + scalar, + }, + )), + ) => { + let component_ty = crate::TypeInner::Vector { size: rows, scalar }; + ctx.try_automatic_conversions_slice( + &mut components, + &Tr::Value(component_ty), + ty_span, + )?; + expr = crate::Expression::Compose { ty, components }; + } + + // Array constructor - infer type + (components, Constructor::PartialArray) => { + let mut components = components.into_components_vec(); + if let Ok(consensus_scalar) = ctx.automatic_conversion_consensus(&components) { + // Note that this will *not* necessarily convert all the + // components to the same type! The `automatic_conversion_consensus` + // method only considers the parameters' leaf scalar + // types; the parameters themselves could be any mix of + // vectors, matrices, and scalars. + // + // But *if* it is possible for this array construction + // expression to be well-typed at all, then all the + // parameters must have the same type constructors (vec, + // matrix, scalar) applied to their leaf scalars, so + // reconciling their scalars is always the right thing to + // do. And if this array construction is not well-typed, + // these conversions will not make it so, and we can let + // validation catch the error. + ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?; + } else { + // There's no consensus scalar. Emit the `Compose` + // expression anyway, and let validation catch the problem. + } + + let base = ctx.register_type(components[0])?; + + let inner = crate::TypeInner::Array { + base, + size: crate::ArraySize::Constant( + NonZeroU32::new(u32::try_from(components.len()).unwrap()).unwrap(), + ), + stride: { + self.layouter.update(ctx.module.to_ctx()).unwrap(); + self.layouter[base].to_stride() + }, + }; + let ty = ctx.ensure_type_exists(inner); + + expr = crate::Expression::Compose { ty, components }; + } + + // Array constructor, explicit type + (components, Constructor::Type((ty, &crate::TypeInner::Array { base, .. }))) => { + let mut components = components.into_components_vec(); + ctx.try_automatic_conversions_slice(&mut components, &Tr::Handle(base), ty_span)?; + expr = crate::Expression::Compose { ty, components }; + } + + // Struct constructor + ( + components, + Constructor::Type((ty, &crate::TypeInner::Struct { ref members, .. })), + ) => { + let mut components = components.into_components_vec(); + let struct_ty_span = ctx.module.types.get_span(ty); + + // Make a vector of the members' type handles in advance, to + // avoid borrowing `members` from `ctx` while we generate + // new code. + let members: Vec> = members.iter().map(|m| m.ty).collect(); + + for (component, &ty) in components.iter_mut().zip(&members) { + *component = + ctx.try_automatic_conversions(*component, &Tr::Handle(ty), struct_ty_span)?; + } + expr = crate::Expression::Compose { ty, components }; + } + + // ERRORS + + // Bad conversion (type cast) + (Components::One { span, ty_inner, .. }, constructor) => { + let from_type = ty_inner.to_wgsl(&ctx.module.to_ctx()); + return Err(Error::BadTypeCast { + span, + from_type, + to_type: constructor.to_error_string(ctx), + }); + } + + // Too many parameters for scalar constructor + ( + Components::Many { spans, .. }, + Constructor::Type((_, &crate::TypeInner::Scalar { .. })), + ) => { + let span = spans[1].until(spans.last().unwrap()); + return Err(Error::UnexpectedComponents(span)); + } + + // Other types can't be constructed + _ => return Err(Error::TypeNotConstructible(ty_span)), + } + + let expr = ctx.append_expression(expr, span)?; + Ok(expr) + } + + /// Build a [`Constructor`] for a WGSL construction expression. + /// + /// If `constructor` conveys enough information to determine which Naga [`Type`] + /// we're actually building (i.e., it's not a partial constructor), then + /// ensure the `Type` exists in [`ctx.module`], and return + /// [`Constructor::Type`]. + /// + /// Otherwise, return the [`Constructor`] partial variant corresponding to + /// `constructor`. + /// + /// [`Type`]: crate::Type + /// [`ctx.module`]: ExpressionContext::module + fn constructor<'out>( + &mut self, + constructor: &ast::ConstructorType<'source>, + ctx: &mut ExpressionContext<'source, '_, 'out>, + ) -> Result>, Error<'source>> { + let handle = match *constructor { + ast::ConstructorType::Scalar(scalar) => { + let ty = ctx.ensure_type_exists(scalar.to_inner_scalar()); + Constructor::Type(ty) + } + ast::ConstructorType::PartialVector { size } => Constructor::PartialVector { size }, + ast::ConstructorType::Vector { size, scalar } => { + let ty = ctx.ensure_type_exists(scalar.to_inner_vector(size)); + Constructor::Type(ty) + } + ast::ConstructorType::PartialMatrix { columns, rows } => { + Constructor::PartialMatrix { columns, rows } + } + ast::ConstructorType::Matrix { + rows, + columns, + width, + } => { + let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix { + columns, + rows, + scalar: crate::Scalar::float(width), + }); + Constructor::Type(ty) + } + ast::ConstructorType::PartialArray => Constructor::PartialArray, + ast::ConstructorType::Array { base, size } => { + let base = self.resolve_ast_type(base, &mut ctx.as_global())?; + let size = self.array_size(size, &mut ctx.as_global())?; + + self.layouter.update(ctx.module.to_ctx()).unwrap(); + let stride = self.layouter[base].to_stride(); + + let ty = ctx.ensure_type_exists(crate::TypeInner::Array { base, size, stride }); + Constructor::Type(ty) + } + ast::ConstructorType::Type(ty) => Constructor::Type(ty), + }; + + Ok(handle) + } +} diff --git a/naga/src/front/wgsl/lower/conversion.rs b/naga/src/front/wgsl/lower/conversion.rs new file mode 100644 index 0000000000..2a2690f096 --- /dev/null +++ b/naga/src/front/wgsl/lower/conversion.rs @@ -0,0 +1,503 @@ +//! WGSL's automatic conversions for abstract types. + +use crate::{Handle, Span}; + +impl<'source, 'temp, 'out> super::ExpressionContext<'source, 'temp, 'out> { + /// Try to use WGSL's automatic conversions to convert `expr` to `goal_ty`. + /// + /// If no conversions are necessary, return `expr` unchanged. + /// + /// If automatic conversions cannot convert `expr` to `goal_ty`, return an + /// [`AutoConversion`] error. + /// + /// Although the Load Rule is one of the automatic conversions, this + /// function assumes it has already been applied if appropriate, as + /// indicated by the fact that the Rust type of `expr` is not `Typed<_>`. + /// + /// [`AutoConversion`]: super::Error::AutoConversion + pub fn try_automatic_conversions( + &mut self, + expr: Handle, + goal_ty: &crate::proc::TypeResolution, + goal_span: Span, + ) -> Result, super::Error<'source>> { + let expr_span = self.get_expression_span(expr); + // Keep the TypeResolution so we can get type names for + // structs in error messages. + let expr_resolution = super::resolve!(self, expr); + let types = &self.module.types; + let expr_inner = expr_resolution.inner_with(types); + let goal_inner = goal_ty.inner_with(types); + + // If `expr` already has the requested type, we're done. + if expr_inner.equivalent(goal_inner, types) { + return Ok(expr); + } + + let (_expr_scalar, goal_scalar) = + match expr_inner.automatically_converts_to(goal_inner, types) { + Some(scalars) => scalars, + None => { + let gctx = &self.module.to_ctx(); + let source_type = expr_resolution.to_wgsl(gctx); + let dest_type = goal_ty.to_wgsl(gctx); + + return Err(super::Error::AutoConversion { + dest_span: goal_span, + dest_type, + source_span: expr_span, + source_type, + }); + } + }; + + self.convert_leaf_scalar(expr, expr_span, goal_scalar) + } + + /// Try to convert `expr`'s leaf scalar to `goal` using automatic conversions. + /// + /// If no conversions are necessary, return `expr` unchanged. + /// + /// If automatic conversions cannot convert `expr` to `goal_scalar`, return + /// an [`AutoConversionLeafScalar`] error. + /// + /// Although the Load Rule is one of the automatic conversions, this + /// function assumes it has already been applied if appropriate, as + /// indicated by the fact that the Rust type of `expr` is not `Typed<_>`. + /// + /// [`AutoConversionLeafScalar`]: super::Error::AutoConversionLeafScalar + pub fn try_automatic_conversion_for_leaf_scalar( + &mut self, + expr: Handle, + goal_scalar: crate::Scalar, + goal_span: Span, + ) -> Result, super::Error<'source>> { + let expr_span = self.get_expression_span(expr); + let expr_resolution = super::resolve!(self, expr); + let types = &self.module.types; + let expr_inner = expr_resolution.inner_with(types); + + let make_error = || { + let gctx = &self.module.to_ctx(); + let source_type = expr_resolution.to_wgsl(gctx); + super::Error::AutoConversionLeafScalar { + dest_span: goal_span, + dest_scalar: goal_scalar.to_wgsl(), + source_span: expr_span, + source_type, + } + }; + + let expr_scalar = match expr_inner.scalar() { + Some(scalar) => scalar, + None => return Err(make_error()), + }; + + if expr_scalar == goal_scalar { + return Ok(expr); + } + + if !expr_scalar.automatically_converts_to(goal_scalar) { + return Err(make_error()); + } + + assert!(expr_scalar.is_abstract()); + + self.convert_leaf_scalar(expr, expr_span, goal_scalar) + } + + fn convert_leaf_scalar( + &mut self, + expr: Handle, + expr_span: Span, + goal_scalar: crate::Scalar, + ) -> Result, super::Error<'source>> { + let expr_inner = super::resolve_inner!(self, expr); + if let crate::TypeInner::Array { .. } = *expr_inner { + self.as_const_evaluator() + .cast_array(expr, goal_scalar, expr_span) + .map_err(|err| super::Error::ConstantEvaluatorError(err, expr_span)) + } else { + let cast = crate::Expression::As { + expr, + kind: goal_scalar.kind, + convert: Some(goal_scalar.width), + }; + self.append_expression(cast, expr_span) + } + } + + /// Try to convert `exprs` to `goal_ty` using WGSL's automatic conversions. + pub fn try_automatic_conversions_slice( + &mut self, + exprs: &mut [Handle], + goal_ty: &crate::proc::TypeResolution, + goal_span: Span, + ) -> Result<(), super::Error<'source>> { + for expr in exprs.iter_mut() { + *expr = self.try_automatic_conversions(*expr, goal_ty, goal_span)?; + } + + Ok(()) + } + + /// Apply WGSL's automatic conversions to a vector constructor's arguments. + /// + /// When calling a vector constructor like `vec3(...)`, the parameters + /// can be a mix of scalars and vectors, with the latter being spread out to + /// contribute each of their components as a component of the new value. + /// When the element type is explicit, as with `` in the example above, + /// WGSL's automatic conversions should convert abstract scalar and vector + /// parameters to the constructor's required scalar type. + pub fn try_automatic_conversions_for_vector( + &mut self, + exprs: &mut [Handle], + goal_scalar: crate::Scalar, + goal_span: Span, + ) -> Result<(), super::Error<'source>> { + use crate::proc::TypeResolution as Tr; + use crate::TypeInner as Ti; + let goal_scalar_res = Tr::Value(Ti::Scalar(goal_scalar)); + + for (i, expr) in exprs.iter_mut().enumerate() { + // Keep the TypeResolution so we can get full type names + // in error messages. + let expr_resolution = super::resolve!(self, *expr); + let types = &self.module.types; + let expr_inner = expr_resolution.inner_with(types); + + match *expr_inner { + Ti::Scalar(_) => { + *expr = self.try_automatic_conversions(*expr, &goal_scalar_res, goal_span)?; + } + Ti::Vector { size, scalar: _ } => { + let goal_vector_res = Tr::Value(Ti::Vector { + size, + scalar: goal_scalar, + }); + *expr = self.try_automatic_conversions(*expr, &goal_vector_res, goal_span)?; + } + _ => { + let span = self.get_expression_span(*expr); + return Err(super::Error::InvalidConstructorComponentType( + span, i as i32, + )); + } + } + } + + Ok(()) + } + + /// Convert `expr` to the leaf scalar type `scalar`. + pub fn convert_to_leaf_scalar( + &mut self, + expr: &mut Handle, + goal: crate::Scalar, + ) -> Result<(), super::Error<'source>> { + let inner = super::resolve_inner!(self, *expr); + // Do nothing if `inner` doesn't even have leaf scalars; + // it's a type error that validation will catch. + if inner.scalar() != Some(goal) { + let cast = crate::Expression::As { + expr: *expr, + kind: goal.kind, + convert: Some(goal.width), + }; + let expr_span = self.get_expression_span(*expr); + *expr = self.append_expression(cast, expr_span)?; + } + + Ok(()) + } + + /// Convert all expressions in `exprs` to a common scalar type. + /// + /// Note that the caller is responsible for making sure these + /// conversions are actually justified. This function simply + /// generates `As` expressions, regardless of whether they are + /// permitted WGSL automatic conversions. Callers intending to + /// implement automatic conversions need to determine for + /// themselves whether the casts we we generate are justified, + /// perhaps by calling `TypeInner::automatically_converts_to` or + /// `Scalar::automatic_conversion_combine`. + pub fn convert_slice_to_common_leaf_scalar( + &mut self, + exprs: &mut [Handle], + goal: crate::Scalar, + ) -> Result<(), super::Error<'source>> { + for expr in exprs.iter_mut() { + self.convert_to_leaf_scalar(expr, goal)?; + } + + Ok(()) + } + + /// Return an expression for the concretized value of `expr`. + /// + /// If `expr` is already concrete, return it unchanged. + pub fn concretize( + &mut self, + mut expr: Handle, + ) -> Result, super::Error<'source>> { + let inner = super::resolve_inner!(self, expr); + if let Some(scalar) = inner.automatically_convertible_scalar(&self.module.types) { + let concretized = scalar.concretize(); + if concretized != scalar { + assert!(scalar.is_abstract()); + let expr_span = self.get_expression_span(expr); + expr = self + .as_const_evaluator() + .cast_array(expr, concretized, expr_span) + .map_err(|err| { + // A `TypeResolution` includes the type's full name, if + // it has one. Also, avoid holding the borrow of `inner` + // across the call to `cast_array`. + let expr_type = &self.typifier()[expr]; + super::Error::ConcretizationFailed { + expr_span, + expr_type: expr_type.to_wgsl(&self.module.to_ctx()), + scalar: concretized.to_wgsl(), + inner: err, + } + })?; + } + } + + Ok(expr) + } + + /// Find the consensus scalar of `components` under WGSL's automatic + /// conversions. + /// + /// If `components` can all be converted to any common scalar via + /// WGSL's automatic conversions, return the best such scalar. + /// + /// The `components` slice must not be empty. All elements' types must + /// have been resolved. + /// + /// If `components` are definitely not acceptable as arguments to such + /// constructors, return `Err(i)`, where `i` is the index in + /// `components` of some problematic argument. + /// + /// This function doesn't fully type-check the arguments - it only + /// considers their leaf scalar types. This means it may return `Ok` + /// even when the Naga validator will reject the resulting + /// construction expression later. + pub fn automatic_conversion_consensus<'handle, I>( + &self, + components: I, + ) -> Result + where + I: IntoIterator>, + I::IntoIter: Clone, // for debugging + { + let types = &self.module.types; + let mut inners = components + .into_iter() + .map(|&c| self.typifier()[c].inner_with(types)); + log::debug!( + "wgsl automatic_conversion_consensus: {:?}", + inners + .clone() + .map(|inner| inner.to_wgsl(&self.module.to_ctx())) + .collect::>() + ); + let mut best = inners.next().unwrap().scalar().ok_or(0_usize)?; + for (inner, i) in inners.zip(1..) { + let scalar = inner.scalar().ok_or(i)?; + match best.automatic_conversion_combine(scalar) { + Some(new_best) => { + best = new_best; + } + None => return Err(i), + } + } + + log::debug!(" consensus: {:?}", best.to_wgsl()); + Ok(best) + } +} + +impl crate::TypeInner { + /// Determine whether `self` automatically converts to `goal`. + /// + /// If WGSL's automatic conversions (excluding the Load Rule) will + /// convert `self` to `goal`, then return a pair `(from, to)`, + /// where `from` and `to` are the scalar types of the leaf values + /// of `self` and `goal`. + /// + /// This function assumes that `self` and `goal` are different + /// types. Callers should first check whether any conversion is + /// needed at all. + /// + /// If the automatic conversions cannot convert `self` to `goal`, + /// return `None`. + fn automatically_converts_to( + &self, + goal: &Self, + types: &crate::UniqueArena, + ) -> Option<(crate::Scalar, crate::Scalar)> { + use crate::ScalarKind as Sk; + use crate::TypeInner as Ti; + + // Automatic conversions only change the scalar type of a value's leaves + // (e.g., `vec4` to `vec4`), never the type + // constructors applied to those scalar types (e.g., never scalar to + // `vec4`, or `vec2` to `vec3`). So first we check that the type + // constructors match, extracting the leaf scalar types in the process. + let expr_scalar; + let goal_scalar; + match (self, goal) { + (&Ti::Scalar(expr), &Ti::Scalar(goal)) => { + expr_scalar = expr; + goal_scalar = goal; + } + ( + &Ti::Vector { + size: expr_size, + scalar: expr, + }, + &Ti::Vector { + size: goal_size, + scalar: goal, + }, + ) if expr_size == goal_size => { + expr_scalar = expr; + goal_scalar = goal; + } + ( + &Ti::Matrix { + rows: expr_rows, + columns: expr_columns, + scalar: expr, + }, + &Ti::Matrix { + rows: goal_rows, + columns: goal_columns, + scalar: goal, + }, + ) if expr_rows == goal_rows && expr_columns == goal_columns => { + expr_scalar = expr; + goal_scalar = goal; + } + ( + &Ti::Array { + base: expr_base, + size: expr_size, + stride: _, + }, + &Ti::Array { + base: goal_base, + size: goal_size, + stride: _, + }, + ) if expr_size == goal_size => { + return types[expr_base] + .inner + .automatically_converts_to(&types[goal_base].inner, types); + } + _ => return None, + } + + match (expr_scalar.kind, goal_scalar.kind) { + (Sk::AbstractFloat, Sk::Float) => {} + (Sk::AbstractInt, Sk::Sint | Sk::Uint | Sk::AbstractFloat | Sk::Float) => {} + _ => return None, + } + + log::trace!(" okay: expr {expr_scalar:?}, goal {goal_scalar:?}"); + Some((expr_scalar, goal_scalar)) + } + + fn automatically_convertible_scalar( + &self, + types: &crate::UniqueArena, + ) -> Option { + use crate::TypeInner as Ti; + match *self { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } | Ti::Matrix { scalar, .. } => { + Some(scalar) + } + Ti::Array { base, .. } => types[base].inner.automatically_convertible_scalar(types), + Ti::Atomic(_) + | Ti::Pointer { .. } + | Ti::ValuePointer { .. } + | Ti::Struct { .. } + | Ti::Image { .. } + | Ti::Sampler { .. } + | Ti::AccelerationStructure + | Ti::RayQuery + | Ti::BindingArray { .. } => None, + } + } +} + +impl crate::Scalar { + /// Find the common type of `self` and `other` under WGSL's + /// automatic conversions. + /// + /// If there are any scalars to which WGSL's automatic conversions + /// will convert both `self` and `other`, return the best such + /// scalar. Otherwise, return `None`. + pub const fn automatic_conversion_combine(self, other: Self) -> Option { + use crate::ScalarKind as Sk; + + match (self.kind, other.kind) { + // When the kinds match... + (Sk::AbstractFloat, Sk::AbstractFloat) + | (Sk::AbstractInt, Sk::AbstractInt) + | (Sk::Sint, Sk::Sint) + | (Sk::Uint, Sk::Uint) + | (Sk::Float, Sk::Float) + | (Sk::Bool, Sk::Bool) => { + if self.width == other.width { + // ... either no conversion is necessary ... + Some(self) + } else { + // ... or no conversion is possible. + // We never convert concrete to concrete, and + // abstract types should have only one size. + None + } + } + + // AbstractInt converts to AbstractFloat. + (Sk::AbstractFloat, Sk::AbstractInt) => Some(self), + (Sk::AbstractInt, Sk::AbstractFloat) => Some(other), + + // AbstractFloat converts to Float. + (Sk::AbstractFloat, Sk::Float) => Some(other), + (Sk::Float, Sk::AbstractFloat) => Some(self), + + // AbstractInt converts to concrete integer or float. + (Sk::AbstractInt, Sk::Uint | Sk::Sint | Sk::Float) => Some(other), + (Sk::Uint | Sk::Sint | Sk::Float, Sk::AbstractInt) => Some(self), + + // AbstractFloat can't be reconciled with concrete integer types. + (Sk::AbstractFloat, Sk::Uint | Sk::Sint) | (Sk::Uint | Sk::Sint, Sk::AbstractFloat) => { + None + } + + // Nothing can be reconciled with `bool`. + (Sk::Bool, _) | (_, Sk::Bool) => None, + + // Different concrete types cannot be reconciled. + (Sk::Sint | Sk::Uint | Sk::Float, Sk::Sint | Sk::Uint | Sk::Float) => None, + } + } + + /// Return `true` if automatic conversions will covert `self` to `goal`. + pub fn automatically_converts_to(self, goal: Self) -> bool { + self.automatic_conversion_combine(goal) == Some(goal) + } + + const fn concretize(self) -> Self { + use crate::ScalarKind as Sk; + match self.kind { + Sk::Sint | Sk::Uint | Sk::Float | Sk::Bool => self, + Sk::AbstractInt => Self::I32, + Sk::AbstractFloat => Self::F32, + } + } +} diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs new file mode 100644 index 0000000000..ba9b49e135 --- /dev/null +++ b/naga/src/front/wgsl/lower/mod.rs @@ -0,0 +1,2760 @@ +use std::num::NonZeroU32; + +use crate::front::wgsl::error::{Error, ExpectedToken, InvalidAssignmentType}; +use crate::front::wgsl::index::Index; +use crate::front::wgsl::parse::number::Number; +use crate::front::wgsl::parse::{ast, conv}; +use crate::front::Typifier; +use crate::proc::{ + ensure_block_returns, Alignment, ConstantEvaluator, Emitter, Layouter, ResolveContext, +}; +use crate::{Arena, FastHashMap, FastIndexMap, Handle, Span}; + +mod construction; +mod conversion; + +/// Resolves the inner type of a given expression. +/// +/// Expects a &mut [`ExpressionContext`] and a [`Handle`]. +/// +/// Returns a &[`crate::TypeInner`]. +/// +/// Ideally, we would simply have a function that takes a `&mut ExpressionContext` +/// and returns a `&TypeResolution`. Unfortunately, this leads the borrow checker +/// to conclude that the mutable borrow lasts for as long as we are using the +/// `&TypeResolution`, so we can't use the `ExpressionContext` for anything else - +/// like, say, resolving another operand's type. Using a macro that expands to +/// two separate calls, only the first of which needs a `&mut`, +/// lets the borrow checker see that the mutable borrow is over. +macro_rules! resolve_inner { + ($ctx:ident, $expr:expr) => {{ + $ctx.grow_types($expr)?; + $ctx.typifier()[$expr].inner_with(&$ctx.module.types) + }}; +} +pub(super) use resolve_inner; + +/// Resolves the inner types of two given expressions. +/// +/// Expects a &mut [`ExpressionContext`] and two [`Handle`]s. +/// +/// Returns a tuple containing two &[`crate::TypeInner`]. +/// +/// See the documentation of [`resolve_inner!`] for why this macro is necessary. +macro_rules! resolve_inner_binary { + ($ctx:ident, $left:expr, $right:expr) => {{ + $ctx.grow_types($left)?; + $ctx.grow_types($right)?; + ( + $ctx.typifier()[$left].inner_with(&$ctx.module.types), + $ctx.typifier()[$right].inner_with(&$ctx.module.types), + ) + }}; +} + +/// Resolves the type of a given expression. +/// +/// Expects a &mut [`ExpressionContext`] and a [`Handle`]. +/// +/// Returns a &[`TypeResolution`]. +/// +/// See the documentation of [`resolve_inner!`] for why this macro is necessary. +/// +/// [`TypeResolution`]: crate::proc::TypeResolution +macro_rules! resolve { + ($ctx:ident, $expr:expr) => {{ + $ctx.grow_types($expr)?; + &$ctx.typifier()[$expr] + }}; +} +pub(super) use resolve; + +/// State for constructing a `crate::Module`. +pub struct GlobalContext<'source, 'temp, 'out> { + /// The `TranslationUnit`'s expressions arena. + ast_expressions: &'temp Arena>, + + /// The `TranslationUnit`'s types arena. + types: &'temp Arena>, + + // Naga IR values. + /// The map from the names of module-scope declarations to the Naga IR + /// `Handle`s we have built for them, owned by `Lowerer::lower`. + globals: &'temp mut FastHashMap<&'source str, LoweredGlobalDecl>, + + /// The module we're constructing. + module: &'out mut crate::Module, + + const_typifier: &'temp mut Typifier, +} + +impl<'source> GlobalContext<'source, '_, '_> { + fn as_const(&mut self) -> ExpressionContext<'source, '_, '_> { + ExpressionContext { + ast_expressions: self.ast_expressions, + globals: self.globals, + types: self.types, + module: self.module, + const_typifier: self.const_typifier, + expr_type: ExpressionContextType::Constant, + } + } + + fn ensure_type_exists( + &mut self, + name: Option, + inner: crate::TypeInner, + ) -> Handle { + self.module + .types + .insert(crate::Type { inner, name }, Span::UNDEFINED) + } +} + +/// State for lowering a statement within a function. +pub struct StatementContext<'source, 'temp, 'out> { + // WGSL AST values. + /// A reference to [`TranslationUnit::expressions`] for the translation unit + /// we're lowering. + /// + /// [`TranslationUnit::expressions`]: ast::TranslationUnit::expressions + ast_expressions: &'temp Arena>, + + /// A reference to [`TranslationUnit::types`] for the translation unit + /// we're lowering. + /// + /// [`TranslationUnit::types`]: ast::TranslationUnit::types + types: &'temp Arena>, + + // Naga IR values. + /// The map from the names of module-scope declarations to the Naga IR + /// `Handle`s we have built for them, owned by `Lowerer::lower`. + globals: &'temp mut FastHashMap<&'source str, LoweredGlobalDecl>, + + /// A map from each `ast::Local` handle to the Naga expression + /// we've built for it: + /// + /// - WGSL function arguments become Naga [`FunctionArgument`] expressions. + /// + /// - WGSL `var` declarations become Naga [`LocalVariable`] expressions. + /// + /// - WGSL `let` declararations become arbitrary Naga expressions. + /// + /// This always borrows the `local_table` local variable in + /// [`Lowerer::function`]. + /// + /// [`LocalVariable`]: crate::Expression::LocalVariable + /// [`FunctionArgument`]: crate::Expression::FunctionArgument + local_table: &'temp mut FastHashMap, Typed>>, + + const_typifier: &'temp mut Typifier, + typifier: &'temp mut Typifier, + function: &'out mut crate::Function, + /// Stores the names of expressions that are assigned in `let` statement + /// Also stores the spans of the names, for use in errors. + named_expressions: &'out mut FastIndexMap, (String, Span)>, + module: &'out mut crate::Module, + + /// Which `Expression`s in `self.naga_expressions` are const expressions, in + /// the WGSL sense. + /// + /// According to the WGSL spec, a const expression must not refer to any + /// `let` declarations, even if those declarations' initializers are + /// themselves const expressions. So this tracker is not simply concerned + /// with the form of the expressions; it is also tracking whether WGSL says + /// we should consider them to be const. See the use of `force_non_const` in + /// the code for lowering `let` bindings. + expression_constness: &'temp mut crate::proc::ExpressionConstnessTracker, +} + +impl<'a, 'temp> StatementContext<'a, 'temp, '_> { + fn as_expression<'t>( + &'t mut self, + block: &'t mut crate::Block, + emitter: &'t mut Emitter, + ) -> ExpressionContext<'a, 't, '_> + where + 'temp: 't, + { + ExpressionContext { + globals: self.globals, + types: self.types, + ast_expressions: self.ast_expressions, + const_typifier: self.const_typifier, + module: self.module, + expr_type: ExpressionContextType::Runtime(RuntimeExpressionContext { + local_table: self.local_table, + function: self.function, + block, + emitter, + typifier: self.typifier, + expression_constness: self.expression_constness, + }), + } + } + + fn as_global(&mut self) -> GlobalContext<'a, '_, '_> { + GlobalContext { + ast_expressions: self.ast_expressions, + globals: self.globals, + types: self.types, + module: self.module, + const_typifier: self.const_typifier, + } + } + + fn invalid_assignment_type(&self, expr: Handle) -> InvalidAssignmentType { + if let Some(&(_, span)) = self.named_expressions.get(&expr) { + InvalidAssignmentType::ImmutableBinding(span) + } else { + match self.function.expressions[expr] { + crate::Expression::Swizzle { .. } => InvalidAssignmentType::Swizzle, + crate::Expression::Access { base, .. } => self.invalid_assignment_type(base), + crate::Expression::AccessIndex { base, .. } => self.invalid_assignment_type(base), + _ => InvalidAssignmentType::Other, + } + } + } +} + +pub struct RuntimeExpressionContext<'temp, 'out> { + /// A map from [`ast::Local`] handles to the Naga expressions we've built for them. + /// + /// This is always [`StatementContext::local_table`] for the + /// enclosing statement; see that documentation for details. + local_table: &'temp FastHashMap, Typed>>, + + function: &'out mut crate::Function, + block: &'temp mut crate::Block, + emitter: &'temp mut Emitter, + typifier: &'temp mut Typifier, + + /// Which `Expression`s in `self.naga_expressions` are const expressions, in + /// the WGSL sense. + /// + /// See [`StatementContext::expression_constness`] for details. + expression_constness: &'temp mut crate::proc::ExpressionConstnessTracker, +} + +/// The type of Naga IR expression we are lowering an [`ast::Expression`] to. +pub enum ExpressionContextType<'temp, 'out> { + /// We are lowering to an arbitrary runtime expression, to be + /// included in a function's body. + /// + /// The given [`RuntimeExpressionContext`] holds information about local + /// variables, arguments, and other definitions available only to runtime + /// expressions, not constant or override expressions. + Runtime(RuntimeExpressionContext<'temp, 'out>), + + /// We are lowering to a constant expression, to be included in the module's + /// constant expression arena. + /// + /// Everything constant expressions are allowed to refer to is + /// available in the [`ExpressionContext`], so this variant + /// carries no further information. + Constant, +} + +/// State for lowering an [`ast::Expression`] to Naga IR. +/// +/// [`ExpressionContext`]s come in two kinds, distinguished by +/// the value of the [`expr_type`] field: +/// +/// - A [`Runtime`] context contributes [`naga::Expression`]s to a [`naga::Function`]'s +/// runtime expression arena. +/// +/// - A [`Constant`] context contributes [`naga::Expression`]s to a [`naga::Module`]'s +/// constant expression arena. +/// +/// [`ExpressionContext`]s are constructed in restricted ways: +/// +/// - To get a [`Runtime`] [`ExpressionContext`], call +/// [`StatementContext::as_expression`]. +/// +/// - To get a [`Constant`] [`ExpressionContext`], call +/// [`GlobalContext::as_const`]. +/// +/// - You can demote a [`Runtime`] context to a [`Constant`] context +/// by calling [`as_const`], but there's no way to go in the other +/// direction, producing a runtime context from a constant one. This +/// is because runtime expressions can refer to constant +/// expressions, via [`Expression::Constant`], but constant +/// expressions can't refer to a function's expressions. +/// +/// Not to be confused with `wgsl::parse::ExpressionContext`, which is +/// for parsing the `ast::Expression` in the first place. +/// +/// [`expr_type`]: ExpressionContext::expr_type +/// [`Runtime`]: ExpressionContextType::Runtime +/// [`naga::Expression`]: crate::Expression +/// [`naga::Function`]: crate::Function +/// [`Constant`]: ExpressionContextType::Constant +/// [`naga::Module`]: crate::Module +/// [`as_const`]: ExpressionContext::as_const +/// [`Expression::Constant`]: crate::Expression::Constant +pub struct ExpressionContext<'source, 'temp, 'out> { + // WGSL AST values. + ast_expressions: &'temp Arena>, + types: &'temp Arena>, + + // Naga IR values. + /// The map from the names of module-scope declarations to the Naga IR + /// `Handle`s we have built for them, owned by `Lowerer::lower`. + globals: &'temp mut FastHashMap<&'source str, LoweredGlobalDecl>, + + /// The IR [`Module`] we're constructing. + /// + /// [`Module`]: crate::Module + module: &'out mut crate::Module, + + /// Type judgments for [`module::const_expressions`]. + /// + /// [`module::const_expressions`]: crate::Module::const_expressions + const_typifier: &'temp mut Typifier, + + /// Whether we are lowering a constant expression or a general + /// runtime expression, and the data needed in each case. + expr_type: ExpressionContextType<'temp, 'out>, +} + +impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> { + fn as_const(&mut self) -> ExpressionContext<'source, '_, '_> { + ExpressionContext { + globals: self.globals, + types: self.types, + ast_expressions: self.ast_expressions, + const_typifier: self.const_typifier, + module: self.module, + expr_type: ExpressionContextType::Constant, + } + } + + fn as_global(&mut self) -> GlobalContext<'source, '_, '_> { + GlobalContext { + ast_expressions: self.ast_expressions, + globals: self.globals, + types: self.types, + module: self.module, + const_typifier: self.const_typifier, + } + } + + fn as_const_evaluator(&mut self) -> ConstantEvaluator { + match self.expr_type { + ExpressionContextType::Runtime(ref mut rctx) => ConstantEvaluator::for_wgsl_function( + self.module, + &mut rctx.function.expressions, + rctx.expression_constness, + rctx.emitter, + rctx.block, + ), + ExpressionContextType::Constant => ConstantEvaluator::for_wgsl_module(self.module), + } + } + + fn append_expression( + &mut self, + expr: crate::Expression, + span: Span, + ) -> Result, Error<'source>> { + let mut eval = self.as_const_evaluator(); + match eval.try_eval_and_append(&expr, span) { + Ok(expr) => Ok(expr), + + // `expr` is not a constant expression. This is fine as + // long as we're not building `Module::const_expressions`. + Err(err) => match self.expr_type { + ExpressionContextType::Runtime(ref mut rctx) => { + Ok(rctx.function.expressions.append(expr, span)) + } + ExpressionContextType::Constant => Err(Error::ConstantEvaluatorError(err, span)), + }, + } + } + + fn const_access(&self, handle: Handle) -> Option { + match self.expr_type { + ExpressionContextType::Runtime(ref ctx) => { + if !ctx.expression_constness.is_const(handle) { + return None; + } + + self.module + .to_ctx() + .eval_expr_to_u32_from(handle, &ctx.function.expressions) + .ok() + } + ExpressionContextType::Constant => self.module.to_ctx().eval_expr_to_u32(handle).ok(), + } + } + + fn get_expression_span(&self, handle: Handle) -> Span { + match self.expr_type { + ExpressionContextType::Runtime(ref ctx) => ctx.function.expressions.get_span(handle), + ExpressionContextType::Constant => self.module.const_expressions.get_span(handle), + } + } + + fn typifier(&self) -> &Typifier { + match self.expr_type { + ExpressionContextType::Runtime(ref ctx) => ctx.typifier, + ExpressionContextType::Constant => self.const_typifier, + } + } + + fn runtime_expression_ctx( + &mut self, + span: Span, + ) -> Result<&mut RuntimeExpressionContext<'temp, 'out>, Error<'source>> { + match self.expr_type { + ExpressionContextType::Runtime(ref mut ctx) => Ok(ctx), + ExpressionContextType::Constant => Err(Error::UnexpectedOperationInConstContext(span)), + } + } + + fn gather_component( + &mut self, + expr: Handle, + component_span: Span, + gather_span: Span, + ) -> Result> { + match self.expr_type { + ExpressionContextType::Runtime(ref rctx) => { + if !rctx.expression_constness.is_const(expr) { + return Err(Error::ExpectedConstExprConcreteIntegerScalar( + component_span, + )); + } + + let index = self + .module + .to_ctx() + .eval_expr_to_u32_from(expr, &rctx.function.expressions) + .map_err(|err| match err { + crate::proc::U32EvalError::NonConst => { + Error::ExpectedConstExprConcreteIntegerScalar(component_span) + } + crate::proc::U32EvalError::Negative => { + Error::ExpectedNonNegative(component_span) + } + })?; + crate::SwizzleComponent::XYZW + .get(index as usize) + .copied() + .ok_or(Error::InvalidGatherComponent(component_span)) + } + // This means a `gather` operation appeared in a constant expression. + // This error refers to the `gather` itself, not its "component" argument. + ExpressionContextType::Constant => { + Err(Error::UnexpectedOperationInConstContext(gather_span)) + } + } + } + + /// Determine the type of `handle`, and add it to the module's arena. + /// + /// If you just need a `TypeInner` for `handle`'s type, use the + /// [`resolve_inner!`] macro instead. This function + /// should only be used when the type of `handle` needs to appear + /// in the module's final `Arena`, for example, if you're + /// creating a [`LocalVariable`] whose type is inferred from its + /// initializer. + /// + /// [`LocalVariable`]: crate::LocalVariable + fn register_type( + &mut self, + handle: Handle, + ) -> Result, Error<'source>> { + self.grow_types(handle)?; + // This is equivalent to calling ExpressionContext::typifier(), + // except that this lets the borrow checker see that it's okay + // to also borrow self.module.types mutably below. + let typifier = match self.expr_type { + ExpressionContextType::Runtime(ref ctx) => ctx.typifier, + ExpressionContextType::Constant => &*self.const_typifier, + }; + Ok(typifier.register_type(handle, &mut self.module.types)) + } + + /// Resolve the types of all expressions up through `handle`. + /// + /// Ensure that [`self.typifier`] has a [`TypeResolution`] for + /// every expression in [`self.function.expressions`]. + /// + /// This does not add types to any arena. The [`Typifier`] + /// documentation explains the steps we take to avoid filling + /// arenas with intermediate types. + /// + /// This function takes `&mut self`, so it can't conveniently + /// return a shared reference to the resulting `TypeResolution`: + /// the shared reference would extend the mutable borrow, and you + /// wouldn't be able to use `self` for anything else. Instead, you + /// should use [`register_type`] or one of [`resolve!`], + /// [`resolve_inner!`] or [`resolve_inner_binary!`]. + /// + /// [`self.typifier`]: ExpressionContext::typifier + /// [`TypeResolution`]: crate::proc::TypeResolution + /// [`register_type`]: Self::register_type + /// [`Typifier`]: Typifier + fn grow_types( + &mut self, + handle: Handle, + ) -> Result<&mut Self, Error<'source>> { + let empty_arena = Arena::new(); + let resolve_ctx; + let typifier; + let expressions; + match self.expr_type { + ExpressionContextType::Runtime(ref mut ctx) => { + resolve_ctx = ResolveContext::with_locals( + self.module, + &ctx.function.local_variables, + &ctx.function.arguments, + ); + typifier = &mut *ctx.typifier; + expressions = &ctx.function.expressions; + } + ExpressionContextType::Constant => { + resolve_ctx = ResolveContext::with_locals(self.module, &empty_arena, &[]); + typifier = self.const_typifier; + expressions = &self.module.const_expressions; + } + }; + typifier + .grow(handle, expressions, &resolve_ctx) + .map_err(Error::InvalidResolve)?; + + Ok(self) + } + + fn image_data( + &mut self, + image: Handle, + span: Span, + ) -> Result<(crate::ImageClass, bool), Error<'source>> { + match *resolve_inner!(self, image) { + crate::TypeInner::Image { class, arrayed, .. } => Ok((class, arrayed)), + _ => Err(Error::BadTexture(span)), + } + } + + fn prepare_args<'b>( + &mut self, + args: &'b [Handle>], + min_args: u32, + span: Span, + ) -> ArgumentContext<'b, 'source> { + ArgumentContext { + args: args.iter(), + min_args, + args_used: 0, + total_args: args.len() as u32, + span, + } + } + + /// Insert splats, if needed by the non-'*' operations. + /// + /// See the "Binary arithmetic expressions with mixed scalar and vector operands" + /// table in the WebGPU Shading Language specification for relevant operators. + /// + /// Multiply is not handled here as backends are expected to handle vec*scalar + /// operations, so inserting splats into the IR increases size needlessly. + fn binary_op_splat( + &mut self, + op: crate::BinaryOperator, + left: &mut Handle, + right: &mut Handle, + ) -> Result<(), Error<'source>> { + if matches!( + op, + crate::BinaryOperator::Add + | crate::BinaryOperator::Subtract + | crate::BinaryOperator::Divide + | crate::BinaryOperator::Modulo + ) { + match resolve_inner_binary!(self, *left, *right) { + (&crate::TypeInner::Vector { size, .. }, &crate::TypeInner::Scalar { .. }) => { + *right = self.append_expression( + crate::Expression::Splat { + size, + value: *right, + }, + self.get_expression_span(*right), + )?; + } + (&crate::TypeInner::Scalar { .. }, &crate::TypeInner::Vector { size, .. }) => { + *left = self.append_expression( + crate::Expression::Splat { size, value: *left }, + self.get_expression_span(*left), + )?; + } + _ => {} + } + } + + Ok(()) + } + + /// Add a single expression to the expression table that is not covered by `self.emitter`. + /// + /// This is useful for `CallResult` and `AtomicResult` expressions, which should not be covered by + /// `Emit` statements. + fn interrupt_emitter( + &mut self, + expression: crate::Expression, + span: Span, + ) -> Result, Error<'source>> { + match self.expr_type { + ExpressionContextType::Runtime(ref mut rctx) => { + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + } + ExpressionContextType::Constant => {} + } + let result = self.append_expression(expression, span); + match self.expr_type { + ExpressionContextType::Runtime(ref mut rctx) => { + rctx.emitter.start(&rctx.function.expressions); + } + ExpressionContextType::Constant => {} + } + result + } + + /// Apply the WGSL Load Rule to `expr`. + /// + /// If `expr` is has type `ref`, perform a load to produce a value of type + /// `T`. Otherwise, return `expr` unchanged. + fn apply_load_rule( + &mut self, + expr: Typed>, + ) -> Result, Error<'source>> { + match expr { + Typed::Reference(pointer) => { + let load = crate::Expression::Load { pointer }; + let span = self.get_expression_span(pointer); + self.append_expression(load, span) + } + Typed::Plain(handle) => Ok(handle), + } + } + + fn ensure_type_exists(&mut self, inner: crate::TypeInner) -> Handle { + self.as_global().ensure_type_exists(None, inner) + } +} + +struct ArgumentContext<'ctx, 'source> { + args: std::slice::Iter<'ctx, Handle>>, + min_args: u32, + args_used: u32, + total_args: u32, + span: Span, +} + +impl<'source> ArgumentContext<'_, 'source> { + pub fn finish(self) -> Result<(), Error<'source>> { + if self.args.len() == 0 { + Ok(()) + } else { + Err(Error::WrongArgumentCount { + found: self.total_args, + expected: self.min_args..self.args_used + 1, + span: self.span, + }) + } + } + + pub fn next(&mut self) -> Result>, Error<'source>> { + match self.args.next().copied() { + Some(arg) => { + self.args_used += 1; + Ok(arg) + } + None => Err(Error::WrongArgumentCount { + found: self.total_args, + expected: self.min_args..self.args_used + 1, + span: self.span, + }), + } + } +} + +/// WGSL type annotations on expressions, types, values, etc. +/// +/// Naga and WGSL types are very close, but Naga lacks WGSL's `ref` types, which +/// we need to know to apply the Load Rule. This enum carries some WGSL or Naga +/// datum along with enough information to determine its corresponding WGSL +/// type. +/// +/// The `T` type parameter can be any expression-like thing: +/// +/// - `Typed>` can represent a full WGSL type. For example, +/// given some Naga `Pointer` type `ptr`, a WGSL reference type is a +/// `Typed::Reference(ptr)` whereas a WGSL pointer type is a +/// `Typed::Plain(ptr)`. +/// +/// - `Typed` or `Typed>` can +/// represent references similarly. +/// +/// Use the `map` and `try_map` methods to convert from one expression +/// representation to another. +/// +/// [`Expression`]: crate::Expression +#[derive(Debug, Copy, Clone)] +enum Typed { + /// A WGSL reference. + Reference(T), + + /// A WGSL plain type. + Plain(T), +} + +impl Typed { + fn map(self, mut f: impl FnMut(T) -> U) -> Typed { + match self { + Self::Reference(v) => Typed::Reference(f(v)), + Self::Plain(v) => Typed::Plain(f(v)), + } + } + + fn try_map(self, mut f: impl FnMut(T) -> Result) -> Result, E> { + Ok(match self { + Self::Reference(expr) => Typed::Reference(f(expr)?), + Self::Plain(expr) => Typed::Plain(f(expr)?), + }) + } +} + +/// A single vector component or swizzle. +/// +/// This represents the things that can appear after the `.` in a vector access +/// expression: either a single component name, or a series of them, +/// representing a swizzle. +enum Components { + Single(u32), + Swizzle { + size: crate::VectorSize, + pattern: [crate::SwizzleComponent; 4], + }, +} + +impl Components { + const fn letter_component(letter: char) -> Option { + use crate::SwizzleComponent as Sc; + match letter { + 'x' | 'r' => Some(Sc::X), + 'y' | 'g' => Some(Sc::Y), + 'z' | 'b' => Some(Sc::Z), + 'w' | 'a' => Some(Sc::W), + _ => None, + } + } + + fn single_component(name: &str, name_span: Span) -> Result { + let ch = name.chars().next().ok_or(Error::BadAccessor(name_span))?; + match Self::letter_component(ch) { + Some(sc) => Ok(sc as u32), + None => Err(Error::BadAccessor(name_span)), + } + } + + /// Construct a `Components` value from a 'member' name, like `"wzy"` or `"x"`. + /// + /// Use `name_span` for reporting errors in parsing the component string. + fn new(name: &str, name_span: Span) -> Result { + let size = match name.len() { + 1 => return Ok(Components::Single(Self::single_component(name, name_span)?)), + 2 => crate::VectorSize::Bi, + 3 => crate::VectorSize::Tri, + 4 => crate::VectorSize::Quad, + _ => return Err(Error::BadAccessor(name_span)), + }; + + let mut pattern = [crate::SwizzleComponent::X; 4]; + for (comp, ch) in pattern.iter_mut().zip(name.chars()) { + *comp = Self::letter_component(ch).ok_or(Error::BadAccessor(name_span))?; + } + + Ok(Components::Swizzle { size, pattern }) + } +} + +/// An `ast::GlobalDecl` for which we have built the Naga IR equivalent. +enum LoweredGlobalDecl { + Function(Handle), + Var(Handle), + Const(Handle), + Type(Handle), + EntryPoint, +} + +enum Texture { + Gather, + GatherCompare, + + Sample, + SampleBias, + SampleCompare, + SampleCompareLevel, + SampleGrad, + SampleLevel, + // SampleBaseClampToEdge, +} + +impl Texture { + pub fn map(word: &str) -> Option { + Some(match word { + "textureGather" => Self::Gather, + "textureGatherCompare" => Self::GatherCompare, + + "textureSample" => Self::Sample, + "textureSampleBias" => Self::SampleBias, + "textureSampleCompare" => Self::SampleCompare, + "textureSampleCompareLevel" => Self::SampleCompareLevel, + "textureSampleGrad" => Self::SampleGrad, + "textureSampleLevel" => Self::SampleLevel, + // "textureSampleBaseClampToEdge" => Some(Self::SampleBaseClampToEdge), + _ => return None, + }) + } + + pub const fn min_argument_count(&self) -> u32 { + match *self { + Self::Gather => 3, + Self::GatherCompare => 4, + + Self::Sample => 3, + Self::SampleBias => 5, + Self::SampleCompare => 5, + Self::SampleCompareLevel => 5, + Self::SampleGrad => 6, + Self::SampleLevel => 5, + // Self::SampleBaseClampToEdge => 3, + } + } +} + +pub struct Lowerer<'source, 'temp> { + index: &'temp Index<'source>, + layouter: Layouter, +} + +impl<'source, 'temp> Lowerer<'source, 'temp> { + pub fn new(index: &'temp Index<'source>) -> Self { + Self { + index, + layouter: Layouter::default(), + } + } + + pub fn lower( + &mut self, + tu: &'temp ast::TranslationUnit<'source>, + ) -> Result> { + let mut module = crate::Module::default(); + + let mut ctx = GlobalContext { + ast_expressions: &tu.expressions, + globals: &mut FastHashMap::default(), + types: &tu.types, + module: &mut module, + const_typifier: &mut Typifier::new(), + }; + + for decl_handle in self.index.visit_ordered() { + let span = tu.decls.get_span(decl_handle); + let decl = &tu.decls[decl_handle]; + + match decl.kind { + ast::GlobalDeclKind::Fn(ref f) => { + let lowered_decl = self.function(f, span, &mut ctx)?; + ctx.globals.insert(f.name.name, lowered_decl); + } + ast::GlobalDeclKind::Var(ref v) => { + let ty = self.resolve_ast_type(v.ty, &mut ctx)?; + + let init; + if let Some(init_ast) = v.init { + let mut ectx = ctx.as_const(); + let lowered = self.expression_for_abstract(init_ast, &mut ectx)?; + let ty_res = crate::proc::TypeResolution::Handle(ty); + let converted = ectx + .try_automatic_conversions(lowered, &ty_res, v.name.span) + .map_err(|error| match error { + Error::AutoConversion { + dest_span: _, + dest_type, + source_span: _, + source_type, + } => Error::InitializationTypeMismatch { + name: v.name.span, + expected: dest_type, + got: source_type, + }, + other => other, + })?; + init = Some(converted); + } else { + init = None; + } + + let binding = if let Some(ref binding) = v.binding { + Some(crate::ResourceBinding { + group: self.const_u32(binding.group, &mut ctx.as_const())?.0, + binding: self.const_u32(binding.binding, &mut ctx.as_const())?.0, + }) + } else { + None + }; + + let handle = ctx.module.global_variables.append( + crate::GlobalVariable { + name: Some(v.name.name.to_string()), + space: v.space, + binding, + ty, + init, + }, + span, + ); + + ctx.globals + .insert(v.name.name, LoweredGlobalDecl::Var(handle)); + } + ast::GlobalDeclKind::Const(ref c) => { + let mut ectx = ctx.as_const(); + let mut init = self.expression_for_abstract(c.init, &mut ectx)?; + + let ty; + if let Some(explicit_ty) = c.ty { + let explicit_ty = + self.resolve_ast_type(explicit_ty, &mut ectx.as_global())?; + let explicit_ty_res = crate::proc::TypeResolution::Handle(explicit_ty); + init = ectx + .try_automatic_conversions(init, &explicit_ty_res, c.name.span) + .map_err(|error| match error { + Error::AutoConversion { + dest_span: _, + dest_type, + source_span: _, + source_type, + } => Error::InitializationTypeMismatch { + name: c.name.span, + expected: dest_type, + got: source_type, + }, + other => other, + })?; + ty = explicit_ty; + } else { + init = ectx.concretize(init)?; + ty = ectx.register_type(init)?; + } + + let handle = ctx.module.constants.append( + crate::Constant { + name: Some(c.name.name.to_string()), + r#override: crate::Override::None, + ty, + init, + }, + span, + ); + + ctx.globals + .insert(c.name.name, LoweredGlobalDecl::Const(handle)); + } + ast::GlobalDeclKind::Struct(ref s) => { + let handle = self.r#struct(s, span, &mut ctx)?; + ctx.globals + .insert(s.name.name, LoweredGlobalDecl::Type(handle)); + } + ast::GlobalDeclKind::Type(ref alias) => { + let ty = self.resolve_named_ast_type( + alias.ty, + Some(alias.name.name.to_string()), + &mut ctx, + )?; + ctx.globals + .insert(alias.name.name, LoweredGlobalDecl::Type(ty)); + } + } + } + + // Constant evaluation may leave abstract-typed literals and + // compositions in expression arenas, so we need to compact the module + // to remove unused expressions and types. + crate::compact::compact(&mut module); + + Ok(module) + } + + fn function( + &mut self, + f: &ast::Function<'source>, + span: Span, + ctx: &mut GlobalContext<'source, '_, '_>, + ) -> Result> { + let mut local_table = FastHashMap::default(); + let mut expressions = Arena::new(); + let mut named_expressions = FastIndexMap::default(); + + let arguments = f + .arguments + .iter() + .enumerate() + .map(|(i, arg)| { + let ty = self.resolve_ast_type(arg.ty, ctx)?; + let expr = expressions + .append(crate::Expression::FunctionArgument(i as u32), arg.name.span); + local_table.insert(arg.handle, Typed::Plain(expr)); + named_expressions.insert(expr, (arg.name.name.to_string(), arg.name.span)); + + Ok(crate::FunctionArgument { + name: Some(arg.name.name.to_string()), + ty, + binding: self.binding(&arg.binding, ty, ctx)?, + }) + }) + .collect::, _>>()?; + + let result = f + .result + .as_ref() + .map(|res| { + let ty = self.resolve_ast_type(res.ty, ctx)?; + Ok(crate::FunctionResult { + ty, + binding: self.binding(&res.binding, ty, ctx)?, + }) + }) + .transpose()?; + + let mut function = crate::Function { + name: Some(f.name.name.to_string()), + arguments, + result, + local_variables: Arena::new(), + expressions, + named_expressions: crate::NamedExpressions::default(), + body: crate::Block::default(), + }; + + let mut typifier = Typifier::default(); + let mut stmt_ctx = StatementContext { + local_table: &mut local_table, + globals: ctx.globals, + ast_expressions: ctx.ast_expressions, + const_typifier: ctx.const_typifier, + typifier: &mut typifier, + function: &mut function, + named_expressions: &mut named_expressions, + types: ctx.types, + module: ctx.module, + expression_constness: &mut crate::proc::ExpressionConstnessTracker::new(), + }; + let mut body = self.block(&f.body, false, &mut stmt_ctx)?; + ensure_block_returns(&mut body); + + function.body = body; + function.named_expressions = named_expressions + .into_iter() + .map(|(key, (name, _))| (key, name)) + .collect(); + + if let Some(ref entry) = f.entry_point { + let workgroup_size = if let Some(workgroup_size) = entry.workgroup_size { + // TODO: replace with try_map once stabilized + let mut workgroup_size_out = [1; 3]; + for (i, size) in workgroup_size.into_iter().enumerate() { + if let Some(size_expr) = size { + workgroup_size_out[i] = self.const_u32(size_expr, &mut ctx.as_const())?.0; + } + } + workgroup_size_out + } else { + [0; 3] + }; + + ctx.module.entry_points.push(crate::EntryPoint { + name: f.name.name.to_string(), + stage: entry.stage, + early_depth_test: entry.early_depth_test, + workgroup_size, + function, + }); + Ok(LoweredGlobalDecl::EntryPoint) + } else { + let handle = ctx.module.functions.append(function, span); + Ok(LoweredGlobalDecl::Function(handle)) + } + } + + fn block( + &mut self, + b: &ast::Block<'source>, + is_inside_loop: bool, + ctx: &mut StatementContext<'source, '_, '_>, + ) -> Result> { + let mut block = crate::Block::default(); + + for stmt in b.stmts.iter() { + self.statement(stmt, &mut block, is_inside_loop, ctx)?; + } + + Ok(block) + } + + fn statement( + &mut self, + stmt: &ast::Statement<'source>, + block: &mut crate::Block, + is_inside_loop: bool, + ctx: &mut StatementContext<'source, '_, '_>, + ) -> Result<(), Error<'source>> { + let out = match stmt.kind { + ast::StatementKind::Block(ref block) => { + let block = self.block(block, is_inside_loop, ctx)?; + crate::Statement::Block(block) + } + ast::StatementKind::LocalDecl(ref decl) => match *decl { + ast::LocalDecl::Let(ref l) => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let value = + self.expression(l.init, &mut ctx.as_expression(block, &mut emitter))?; + + // The WGSL spec says that any expression that refers to a + // `let`-bound variable is not a const expression. This + // affects when errors must be reported, so we can't even + // treat suitable `let` bindings as constant as an + // optimization. + ctx.expression_constness.force_non_const(value); + + let explicit_ty = + l.ty.map(|ty| self.resolve_ast_type(ty, &mut ctx.as_global())) + .transpose()?; + + if let Some(ty) = explicit_ty { + let mut ctx = ctx.as_expression(block, &mut emitter); + let init_ty = ctx.register_type(value)?; + if !ctx.module.types[ty] + .inner + .equivalent(&ctx.module.types[init_ty].inner, &ctx.module.types) + { + let gctx = &ctx.module.to_ctx(); + return Err(Error::InitializationTypeMismatch { + name: l.name.span, + expected: ty.to_wgsl(gctx), + got: init_ty.to_wgsl(gctx), + }); + } + } + + block.extend(emitter.finish(&ctx.function.expressions)); + ctx.local_table.insert(l.handle, Typed::Plain(value)); + ctx.named_expressions + .insert(value, (l.name.name.to_string(), l.name.span)); + + return Ok(()); + } + ast::LocalDecl::Var(ref v) => { + let explicit_ty = + v.ty.map(|ast| self.resolve_ast_type(ast, &mut ctx.as_global())) + .transpose()?; + + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + let mut ectx = ctx.as_expression(block, &mut emitter); + + let ty; + let initializer; + match (v.init, explicit_ty) { + (Some(init), Some(explicit_ty)) => { + let init = self.expression_for_abstract(init, &mut ectx)?; + let ty_res = crate::proc::TypeResolution::Handle(explicit_ty); + let init = ectx + .try_automatic_conversions(init, &ty_res, v.name.span) + .map_err(|error| match error { + Error::AutoConversion { + dest_span: _, + dest_type, + source_span: _, + source_type, + } => Error::InitializationTypeMismatch { + name: v.name.span, + expected: dest_type, + got: source_type, + }, + other => other, + })?; + ty = explicit_ty; + initializer = Some(init); + } + (Some(init), None) => { + let concretized = self.expression(init, &mut ectx)?; + ty = ectx.register_type(concretized)?; + initializer = Some(concretized); + } + (None, Some(explicit_ty)) => { + ty = explicit_ty; + initializer = None; + } + (None, None) => return Err(Error::MissingType(v.name.span)), + } + + let (const_initializer, initializer) = { + match initializer { + Some(init) => { + // It's not correct to hoist the initializer up + // to the top of the function if: + // - the initialization is inside a loop, and should + // take place on every iteration, or + // - the initialization is not a constant + // expression, so its value depends on the + // state at the point of initialization. + if is_inside_loop || !ctx.expression_constness.is_const(init) { + (None, Some(init)) + } else { + (Some(init), None) + } + } + None => (None, None), + } + }; + + let var = ctx.function.local_variables.append( + crate::LocalVariable { + name: Some(v.name.name.to_string()), + ty, + init: const_initializer, + }, + stmt.span, + ); + + let handle = ctx.as_expression(block, &mut emitter).interrupt_emitter( + crate::Expression::LocalVariable(var), + Span::UNDEFINED, + )?; + block.extend(emitter.finish(&ctx.function.expressions)); + ctx.local_table.insert(v.handle, Typed::Reference(handle)); + + match initializer { + Some(initializer) => crate::Statement::Store { + pointer: handle, + value: initializer, + }, + None => return Ok(()), + } + } + }, + ast::StatementKind::If { + condition, + ref accept, + ref reject, + } => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let condition = + self.expression(condition, &mut ctx.as_expression(block, &mut emitter))?; + block.extend(emitter.finish(&ctx.function.expressions)); + + let accept = self.block(accept, is_inside_loop, ctx)?; + let reject = self.block(reject, is_inside_loop, ctx)?; + + crate::Statement::If { + condition, + accept, + reject, + } + } + ast::StatementKind::Switch { + selector, + ref cases, + } => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let mut ectx = ctx.as_expression(block, &mut emitter); + let selector = self.expression(selector, &mut ectx)?; + + let uint = + resolve_inner!(ectx, selector).scalar_kind() == Some(crate::ScalarKind::Uint); + block.extend(emitter.finish(&ctx.function.expressions)); + + let cases = cases + .iter() + .map(|case| { + Ok(crate::SwitchCase { + value: match case.value { + ast::SwitchValue::Expr(expr) => { + let span = ctx.ast_expressions.get_span(expr); + let expr = + self.expression(expr, &mut ctx.as_global().as_const())?; + match ctx.module.to_ctx().eval_expr_to_literal(expr) { + Some(crate::Literal::I32(value)) if !uint => { + crate::SwitchValue::I32(value) + } + Some(crate::Literal::U32(value)) if uint => { + crate::SwitchValue::U32(value) + } + _ => { + return Err(Error::InvalidSwitchValue { uint, span }); + } + } + } + ast::SwitchValue::Default => crate::SwitchValue::Default, + }, + body: self.block(&case.body, is_inside_loop, ctx)?, + fall_through: case.fall_through, + }) + }) + .collect::>()?; + + crate::Statement::Switch { selector, cases } + } + ast::StatementKind::Loop { + ref body, + ref continuing, + break_if, + } => { + let body = self.block(body, true, ctx)?; + let mut continuing = self.block(continuing, true, ctx)?; + + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + let break_if = break_if + .map(|expr| { + self.expression(expr, &mut ctx.as_expression(&mut continuing, &mut emitter)) + }) + .transpose()?; + continuing.extend(emitter.finish(&ctx.function.expressions)); + + crate::Statement::Loop { + body, + continuing, + break_if, + } + } + ast::StatementKind::Break => crate::Statement::Break, + ast::StatementKind::Continue => crate::Statement::Continue, + ast::StatementKind::Return { value } => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let value = value + .map(|expr| self.expression(expr, &mut ctx.as_expression(block, &mut emitter))) + .transpose()?; + block.extend(emitter.finish(&ctx.function.expressions)); + + crate::Statement::Return { value } + } + ast::StatementKind::Kill => crate::Statement::Kill, + ast::StatementKind::Call { + ref function, + ref arguments, + } => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let _ = self.call( + stmt.span, + function, + arguments, + &mut ctx.as_expression(block, &mut emitter), + )?; + block.extend(emitter.finish(&ctx.function.expressions)); + return Ok(()); + } + ast::StatementKind::Assign { + target: ast_target, + op, + value, + } => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let target = self.expression_for_reference( + ast_target, + &mut ctx.as_expression(block, &mut emitter), + )?; + let mut value = + self.expression(value, &mut ctx.as_expression(block, &mut emitter))?; + + let target_handle = match target { + Typed::Reference(handle) => handle, + Typed::Plain(handle) => { + let ty = ctx.invalid_assignment_type(handle); + return Err(Error::InvalidAssignment { + span: ctx.ast_expressions.get_span(ast_target), + ty, + }); + } + }; + + let value = match op { + Some(op) => { + let mut ctx = ctx.as_expression(block, &mut emitter); + let mut left = ctx.apply_load_rule(target)?; + ctx.binary_op_splat(op, &mut left, &mut value)?; + ctx.append_expression( + crate::Expression::Binary { + op, + left, + right: value, + }, + stmt.span, + )? + } + None => value, + }; + block.extend(emitter.finish(&ctx.function.expressions)); + + crate::Statement::Store { + pointer: target_handle, + value, + } + } + ast::StatementKind::Increment(value) | ast::StatementKind::Decrement(value) => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let op = match stmt.kind { + ast::StatementKind::Increment(_) => crate::BinaryOperator::Add, + ast::StatementKind::Decrement(_) => crate::BinaryOperator::Subtract, + _ => unreachable!(), + }; + + let value_span = ctx.ast_expressions.get_span(value); + let target = self + .expression_for_reference(value, &mut ctx.as_expression(block, &mut emitter))?; + let target_handle = match target { + Typed::Reference(handle) => handle, + Typed::Plain(_) => return Err(Error::BadIncrDecrReferenceType(value_span)), + }; + + let mut ectx = ctx.as_expression(block, &mut emitter); + let scalar = match *resolve_inner!(ectx, target_handle) { + crate::TypeInner::ValuePointer { + size: None, scalar, .. + } => scalar, + crate::TypeInner::Pointer { base, .. } => match ectx.module.types[base].inner { + crate::TypeInner::Scalar(scalar) => scalar, + _ => return Err(Error::BadIncrDecrReferenceType(value_span)), + }, + _ => return Err(Error::BadIncrDecrReferenceType(value_span)), + }; + let literal = match scalar.kind { + crate::ScalarKind::Sint | crate::ScalarKind::Uint => { + crate::Literal::one(scalar) + .ok_or(Error::BadIncrDecrReferenceType(value_span))? + } + _ => return Err(Error::BadIncrDecrReferenceType(value_span)), + }; + + let right = + ectx.interrupt_emitter(crate::Expression::Literal(literal), Span::UNDEFINED)?; + let rctx = ectx.runtime_expression_ctx(stmt.span)?; + let left = rctx.function.expressions.append( + crate::Expression::Load { + pointer: target_handle, + }, + value_span, + ); + let value = rctx + .function + .expressions + .append(crate::Expression::Binary { op, left, right }, stmt.span); + + block.extend(emitter.finish(&ctx.function.expressions)); + crate::Statement::Store { + pointer: target_handle, + value, + } + } + ast::StatementKind::Ignore(expr) => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let _ = self.expression(expr, &mut ctx.as_expression(block, &mut emitter))?; + block.extend(emitter.finish(&ctx.function.expressions)); + return Ok(()); + } + }; + + block.push(out, stmt.span); + + Ok(()) + } + + /// Lower `expr` and apply the Load Rule if possible. + /// + /// For the time being, this concretizes abstract values, to support + /// consumers that haven't been adapted to consume them yet. Consumers + /// prepared for abstract values can call [`expression_for_abstract`]. + /// + /// [`expression_for_abstract`]: Lowerer::expression_for_abstract + fn expression( + &mut self, + expr: Handle>, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + let expr = self.expression_for_abstract(expr, ctx)?; + ctx.concretize(expr) + } + + fn expression_for_abstract( + &mut self, + expr: Handle>, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + let expr = self.expression_for_reference(expr, ctx)?; + ctx.apply_load_rule(expr) + } + + fn expression_for_reference( + &mut self, + expr: Handle>, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result>, Error<'source>> { + let span = ctx.ast_expressions.get_span(expr); + let expr = &ctx.ast_expressions[expr]; + + let expr: Typed = match *expr { + ast::Expression::Literal(literal) => { + let literal = match literal { + ast::Literal::Number(Number::F32(f)) => crate::Literal::F32(f), + ast::Literal::Number(Number::I32(i)) => crate::Literal::I32(i), + ast::Literal::Number(Number::U32(u)) => crate::Literal::U32(u), + ast::Literal::Number(Number::F64(f)) => crate::Literal::F64(f), + ast::Literal::Number(Number::AbstractInt(i)) => crate::Literal::AbstractInt(i), + ast::Literal::Number(Number::AbstractFloat(f)) => { + crate::Literal::AbstractFloat(f) + } + ast::Literal::Bool(b) => crate::Literal::Bool(b), + }; + let handle = ctx.interrupt_emitter(crate::Expression::Literal(literal), span)?; + return Ok(Typed::Plain(handle)); + } + ast::Expression::Ident(ast::IdentExpr::Local(local)) => { + let rctx = ctx.runtime_expression_ctx(span)?; + return Ok(rctx.local_table[&local]); + } + ast::Expression::Ident(ast::IdentExpr::Unresolved(name)) => { + let global = ctx + .globals + .get(name) + .ok_or(Error::UnknownIdent(span, name))?; + let expr = match *global { + LoweredGlobalDecl::Var(handle) => { + let expr = crate::Expression::GlobalVariable(handle); + match ctx.module.global_variables[handle].space { + crate::AddressSpace::Handle => Typed::Plain(expr), + _ => Typed::Reference(expr), + } + } + LoweredGlobalDecl::Const(handle) => { + Typed::Plain(crate::Expression::Constant(handle)) + } + _ => { + return Err(Error::Unexpected(span, ExpectedToken::Variable)); + } + }; + + return expr.try_map(|handle| ctx.interrupt_emitter(handle, span)); + } + ast::Expression::Construct { + ref ty, + ty_span, + ref components, + } => { + let handle = self.construct(span, ty, ty_span, components, ctx)?; + return Ok(Typed::Plain(handle)); + } + ast::Expression::Unary { op, expr } => { + let expr = self.expression_for_abstract(expr, ctx)?; + Typed::Plain(crate::Expression::Unary { op, expr }) + } + ast::Expression::AddrOf(expr) => { + // The `&` operator simply converts a reference to a pointer. And since a + // reference is required, the Load Rule is not applied. + match self.expression_for_reference(expr, ctx)? { + Typed::Reference(handle) => { + // No code is generated. We just declare the reference a pointer now. + return Ok(Typed::Plain(handle)); + } + Typed::Plain(_) => { + return Err(Error::NotReference("the operand of the `&` operator", span)); + } + } + } + ast::Expression::Deref(expr) => { + // The pointer we dereference must be loaded. + let pointer = self.expression(expr, ctx)?; + + if resolve_inner!(ctx, pointer).pointer_space().is_none() { + return Err(Error::NotPointer(span)); + } + + // No code is generated. We just declare the pointer a reference now. + return Ok(Typed::Reference(pointer)); + } + ast::Expression::Binary { op, left, right } => { + self.binary(op, left, right, span, ctx)? + } + ast::Expression::Call { + ref function, + ref arguments, + } => { + let handle = self + .call(span, function, arguments, ctx)? + .ok_or(Error::FunctionReturnsVoid(function.span))?; + return Ok(Typed::Plain(handle)); + } + ast::Expression::Index { base, index } => { + let lowered_base = self.expression_for_reference(base, ctx)?; + let index = self.expression(index, ctx)?; + + if let Typed::Plain(handle) = lowered_base { + if resolve_inner!(ctx, handle).pointer_space().is_some() { + return Err(Error::Pointer( + "the value indexed by a `[]` subscripting expression", + ctx.ast_expressions.get_span(base), + )); + } + } + + lowered_base.map(|base| match ctx.const_access(index) { + Some(index) => crate::Expression::AccessIndex { base, index }, + None => crate::Expression::Access { base, index }, + }) + } + ast::Expression::Member { base, ref field } => { + let lowered_base = self.expression_for_reference(base, ctx)?; + + let temp_inner; + let composite_type: &crate::TypeInner = match lowered_base { + Typed::Reference(handle) => { + let inner = resolve_inner!(ctx, handle); + match *inner { + crate::TypeInner::Pointer { base, .. } => &ctx.module.types[base].inner, + crate::TypeInner::ValuePointer { + size: None, scalar, .. + } => { + temp_inner = crate::TypeInner::Scalar(scalar); + &temp_inner + } + crate::TypeInner::ValuePointer { + size: Some(size), + scalar, + .. + } => { + temp_inner = crate::TypeInner::Vector { size, scalar }; + &temp_inner + } + _ => unreachable!( + "In Typed::Reference(handle), handle must be a Naga pointer" + ), + } + } + + Typed::Plain(handle) => { + let inner = resolve_inner!(ctx, handle); + if let crate::TypeInner::Pointer { .. } + | crate::TypeInner::ValuePointer { .. } = *inner + { + return Err(Error::Pointer( + "the value accessed by a `.member` expression", + ctx.ast_expressions.get_span(base), + )); + } + inner + } + }; + + let access = match *composite_type { + crate::TypeInner::Struct { ref members, .. } => { + let index = members + .iter() + .position(|m| m.name.as_deref() == Some(field.name)) + .ok_or(Error::BadAccessor(field.span))? + as u32; + + lowered_base.map(|base| crate::Expression::AccessIndex { base, index }) + } + crate::TypeInner::Vector { .. } | crate::TypeInner::Matrix { .. } => { + match Components::new(field.name, field.span)? { + Components::Swizzle { size, pattern } => { + // Swizzles aren't allowed on matrices, but + // validation will catch that. + Typed::Plain(crate::Expression::Swizzle { + size, + vector: ctx.apply_load_rule(lowered_base)?, + pattern, + }) + } + Components::Single(index) => lowered_base + .map(|base| crate::Expression::AccessIndex { base, index }), + } + } + _ => return Err(Error::BadAccessor(field.span)), + }; + + access + } + ast::Expression::Bitcast { expr, to, ty_span } => { + let expr = self.expression(expr, ctx)?; + let to_resolved = self.resolve_ast_type(to, &mut ctx.as_global())?; + + let element_scalar = match ctx.module.types[to_resolved].inner { + crate::TypeInner::Scalar(scalar) => scalar, + crate::TypeInner::Vector { scalar, .. } => scalar, + _ => { + let ty = resolve!(ctx, expr); + let gctx = &ctx.module.to_ctx(); + return Err(Error::BadTypeCast { + from_type: ty.to_wgsl(gctx), + span: ty_span, + to_type: to_resolved.to_wgsl(gctx), + }); + } + }; + + Typed::Plain(crate::Expression::As { + expr, + kind: element_scalar.kind, + convert: None, + }) + } + }; + + expr.try_map(|handle| ctx.append_expression(handle, span)) + } + + fn binary( + &mut self, + op: crate::BinaryOperator, + left: Handle>, + right: Handle>, + span: Span, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + // Load both operands. + let mut left = self.expression_for_abstract(left, ctx)?; + let mut right = self.expression_for_abstract(right, ctx)?; + + // Convert `scalar op vector` to `vector op vector` by introducing + // `Splat` expressions. + ctx.binary_op_splat(op, &mut left, &mut right)?; + + // Apply automatic conversions. + match op { + // Shift operators require the right operand to be `u32` or + // `vecN`. We can let the validator sort out vector length + // issues, but the right operand must be, or convert to, a u32 leaf + // scalar. + crate::BinaryOperator::ShiftLeft | crate::BinaryOperator::ShiftRight => { + right = + ctx.try_automatic_conversion_for_leaf_scalar(right, crate::Scalar::U32, span)?; + } + + // All other operators follow the same pattern: reconcile the + // scalar leaf types. If there's no reconciliation possible, + // leave the expressions as they are: validation will report the + // problem. + _ => { + ctx.grow_types(left)?; + ctx.grow_types(right)?; + if let Ok(consensus_scalar) = + ctx.automatic_conversion_consensus([left, right].iter()) + { + ctx.convert_to_leaf_scalar(&mut left, consensus_scalar)?; + ctx.convert_to_leaf_scalar(&mut right, consensus_scalar)?; + } + } + } + + Ok(Typed::Plain(crate::Expression::Binary { op, left, right })) + } + + /// Generate Naga IR for call expressions and statements, and type + /// constructor expressions. + /// + /// The "function" being called is simply an `Ident` that we know refers to + /// some module-scope definition. + /// + /// - If it is the name of a type, then the expression is a type constructor + /// expression: either constructing a value from components, a conversion + /// expression, or a zero value expression. + /// + /// - If it is the name of a function, then we're generating a [`Call`] + /// statement. We may be in the midst of generating code for an + /// expression, in which case we must generate an `Emit` statement to + /// force evaluation of the IR expressions we've generated so far, add the + /// `Call` statement to the current block, and then resume generating + /// expressions. + /// + /// [`Call`]: crate::Statement::Call + fn call( + &mut self, + span: Span, + function: &ast::Ident<'source>, + arguments: &[Handle>], + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result>, Error<'source>> { + match ctx.globals.get(function.name) { + Some(&LoweredGlobalDecl::Type(ty)) => { + let handle = self.construct( + span, + &ast::ConstructorType::Type(ty), + function.span, + arguments, + ctx, + )?; + Ok(Some(handle)) + } + Some(&LoweredGlobalDecl::Const(_) | &LoweredGlobalDecl::Var(_)) => { + Err(Error::Unexpected(function.span, ExpectedToken::Function)) + } + Some(&LoweredGlobalDecl::EntryPoint) => Err(Error::CalledEntryPoint(function.span)), + Some(&LoweredGlobalDecl::Function(function)) => { + let arguments = arguments + .iter() + .map(|&arg| self.expression(arg, ctx)) + .collect::, _>>()?; + + let has_result = ctx.module.functions[function].result.is_some(); + let rctx = ctx.runtime_expression_ctx(span)?; + // we need to always do this before a fn call since all arguments need to be emitted before the fn call + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + let result = has_result.then(|| { + rctx.function + .expressions + .append(crate::Expression::CallResult(function), span) + }); + rctx.emitter.start(&rctx.function.expressions); + rctx.block.push( + crate::Statement::Call { + function, + arguments, + result, + }, + span, + ); + + Ok(result) + } + None => { + let span = function.span; + let expr = if let Some(fun) = conv::map_relational_fun(function.name) { + let mut args = ctx.prepare_args(arguments, 1, span); + let argument = self.expression(args.next()?, ctx)?; + args.finish()?; + + // Check for no-op all(bool) and any(bool): + let argument_unmodified = matches!( + fun, + crate::RelationalFunction::All | crate::RelationalFunction::Any + ) && { + matches!( + resolve_inner!(ctx, argument), + &crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Bool, + .. + }) + ) + }; + + if argument_unmodified { + return Ok(Some(argument)); + } else { + crate::Expression::Relational { fun, argument } + } + } else if let Some((axis, ctrl)) = conv::map_derivative(function.name) { + let mut args = ctx.prepare_args(arguments, 1, span); + let expr = self.expression(args.next()?, ctx)?; + args.finish()?; + + crate::Expression::Derivative { axis, ctrl, expr } + } else if let Some(fun) = conv::map_standard_fun(function.name) { + let expected = fun.argument_count() as _; + let mut args = ctx.prepare_args(arguments, expected, span); + + let arg = self.expression(args.next()?, ctx)?; + let arg1 = args + .next() + .map(|x| self.expression(x, ctx)) + .ok() + .transpose()?; + let arg2 = args + .next() + .map(|x| self.expression(x, ctx)) + .ok() + .transpose()?; + let arg3 = args + .next() + .map(|x| self.expression(x, ctx)) + .ok() + .transpose()?; + + args.finish()?; + + if fun == crate::MathFunction::Modf || fun == crate::MathFunction::Frexp { + if let Some((size, width)) = match *resolve_inner!(ctx, arg) { + crate::TypeInner::Scalar(crate::Scalar { width, .. }) => { + Some((None, width)) + } + crate::TypeInner::Vector { + size, + scalar: crate::Scalar { width, .. }, + .. + } => Some((Some(size), width)), + _ => None, + } { + ctx.module.generate_predeclared_type( + if fun == crate::MathFunction::Modf { + crate::PredeclaredType::ModfResult { size, width } + } else { + crate::PredeclaredType::FrexpResult { size, width } + }, + ); + } + } + + crate::Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + } + } else if let Some(fun) = Texture::map(function.name) { + self.texture_sample_helper(fun, arguments, span, ctx)? + } else { + match function.name { + "select" => { + let mut args = ctx.prepare_args(arguments, 3, span); + + let reject = self.expression(args.next()?, ctx)?; + let accept = self.expression(args.next()?, ctx)?; + let condition = self.expression(args.next()?, ctx)?; + + args.finish()?; + + crate::Expression::Select { + reject, + accept, + condition, + } + } + "arrayLength" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let expr = self.expression(args.next()?, ctx)?; + args.finish()?; + + crate::Expression::ArrayLength(expr) + } + "atomicLoad" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let pointer = self.atomic_pointer(args.next()?, ctx)?; + args.finish()?; + + crate::Expression::Load { pointer } + } + "atomicStore" => { + let mut args = ctx.prepare_args(arguments, 2, span); + let pointer = self.atomic_pointer(args.next()?, ctx)?; + let value = self.expression(args.next()?, ctx)?; + args.finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.emitter.start(&rctx.function.expressions); + rctx.block + .push(crate::Statement::Store { pointer, value }, span); + return Ok(None); + } + "atomicAdd" => { + return Ok(Some(self.atomic_helper( + span, + crate::AtomicFunction::Add, + arguments, + ctx, + )?)) + } + "atomicSub" => { + return Ok(Some(self.atomic_helper( + span, + crate::AtomicFunction::Subtract, + arguments, + ctx, + )?)) + } + "atomicAnd" => { + return Ok(Some(self.atomic_helper( + span, + crate::AtomicFunction::And, + arguments, + ctx, + )?)) + } + "atomicOr" => { + return Ok(Some(self.atomic_helper( + span, + crate::AtomicFunction::InclusiveOr, + arguments, + ctx, + )?)) + } + "atomicXor" => { + return Ok(Some(self.atomic_helper( + span, + crate::AtomicFunction::ExclusiveOr, + arguments, + ctx, + )?)) + } + "atomicMin" => { + return Ok(Some(self.atomic_helper( + span, + crate::AtomicFunction::Min, + arguments, + ctx, + )?)) + } + "atomicMax" => { + return Ok(Some(self.atomic_helper( + span, + crate::AtomicFunction::Max, + arguments, + ctx, + )?)) + } + "atomicExchange" => { + return Ok(Some(self.atomic_helper( + span, + crate::AtomicFunction::Exchange { compare: None }, + arguments, + ctx, + )?)) + } + "atomicCompareExchangeWeak" => { + let mut args = ctx.prepare_args(arguments, 3, span); + + let pointer = self.atomic_pointer(args.next()?, ctx)?; + + let compare = self.expression(args.next()?, ctx)?; + + let value = args.next()?; + let value_span = ctx.ast_expressions.get_span(value); + let value = self.expression(value, ctx)?; + + args.finish()?; + + let expression = match *resolve_inner!(ctx, value) { + crate::TypeInner::Scalar(scalar) => { + crate::Expression::AtomicResult { + ty: ctx.module.generate_predeclared_type( + crate::PredeclaredType::AtomicCompareExchangeWeakResult( + scalar, + ), + ), + comparison: true, + } + } + _ => return Err(Error::InvalidAtomicOperandType(value_span)), + }; + + let result = ctx.interrupt_emitter(expression, span)?; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block.push( + crate::Statement::Atomic { + pointer, + fun: crate::AtomicFunction::Exchange { + compare: Some(compare), + }, + value, + result, + }, + span, + ); + return Ok(Some(result)); + } + "storageBarrier" => { + ctx.prepare_args(arguments, 0, span).finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(crate::Statement::Barrier(crate::Barrier::STORAGE), span); + return Ok(None); + } + "workgroupBarrier" => { + ctx.prepare_args(arguments, 0, span).finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(crate::Statement::Barrier(crate::Barrier::WORK_GROUP), span); + return Ok(None); + } + "workgroupUniformLoad" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let expr = args.next()?; + args.finish()?; + + let pointer = self.expression(expr, ctx)?; + let result_ty = match *resolve_inner!(ctx, pointer) { + crate::TypeInner::Pointer { + base, + space: crate::AddressSpace::WorkGroup, + } => base, + ref other => { + log::error!("Type {other:?} passed to workgroupUniformLoad"); + let span = ctx.ast_expressions.get_span(expr); + return Err(Error::InvalidWorkGroupUniformLoad(span)); + } + }; + let result = ctx.interrupt_emitter( + crate::Expression::WorkGroupUniformLoadResult { ty: result_ty }, + span, + )?; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block.push( + crate::Statement::WorkGroupUniformLoad { pointer, result }, + span, + ); + + return Ok(Some(result)); + } + "textureStore" => { + let mut args = ctx.prepare_args(arguments, 3, span); + + let image = args.next()?; + let image_span = ctx.ast_expressions.get_span(image); + let image = self.expression(image, ctx)?; + + let coordinate = self.expression(args.next()?, ctx)?; + + let (_, arrayed) = ctx.image_data(image, image_span)?; + let array_index = arrayed + .then(|| { + args.min_args += 1; + self.expression(args.next()?, ctx) + }) + .transpose()?; + + let value = self.expression(args.next()?, ctx)?; + + args.finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.emitter.start(&rctx.function.expressions); + let stmt = crate::Statement::ImageStore { + image, + coordinate, + array_index, + value, + }; + rctx.block.push(stmt, span); + return Ok(None); + } + "textureLoad" => { + let mut args = ctx.prepare_args(arguments, 2, span); + + let image = args.next()?; + let image_span = ctx.ast_expressions.get_span(image); + let image = self.expression(image, ctx)?; + + let coordinate = self.expression(args.next()?, ctx)?; + + let (class, arrayed) = ctx.image_data(image, image_span)?; + let array_index = arrayed + .then(|| { + args.min_args += 1; + self.expression(args.next()?, ctx) + }) + .transpose()?; + + let level = class + .is_mipmapped() + .then(|| { + args.min_args += 1; + self.expression(args.next()?, ctx) + }) + .transpose()?; + + let sample = class + .is_multisampled() + .then(|| self.expression(args.next()?, ctx)) + .transpose()?; + + args.finish()?; + + crate::Expression::ImageLoad { + image, + coordinate, + array_index, + level, + sample, + } + } + "textureDimensions" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let image = self.expression(args.next()?, ctx)?; + let level = args + .next() + .map(|arg| self.expression(arg, ctx)) + .ok() + .transpose()?; + args.finish()?; + + crate::Expression::ImageQuery { + image, + query: crate::ImageQuery::Size { level }, + } + } + "textureNumLevels" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let image = self.expression(args.next()?, ctx)?; + args.finish()?; + + crate::Expression::ImageQuery { + image, + query: crate::ImageQuery::NumLevels, + } + } + "textureNumLayers" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let image = self.expression(args.next()?, ctx)?; + args.finish()?; + + crate::Expression::ImageQuery { + image, + query: crate::ImageQuery::NumLayers, + } + } + "textureNumSamples" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let image = self.expression(args.next()?, ctx)?; + args.finish()?; + + crate::Expression::ImageQuery { + image, + query: crate::ImageQuery::NumSamples, + } + } + "rayQueryInitialize" => { + let mut args = ctx.prepare_args(arguments, 3, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + let acceleration_structure = self.expression(args.next()?, ctx)?; + let descriptor = self.expression(args.next()?, ctx)?; + args.finish()?; + + let _ = ctx.module.generate_ray_desc_type(); + let fun = crate::RayQueryFunction::Initialize { + acceleration_structure, + descriptor, + }; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.emitter.start(&rctx.function.expressions); + rctx.block + .push(crate::Statement::RayQuery { query, fun }, span); + return Ok(None); + } + "rayQueryProceed" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + args.finish()?; + + let result = ctx.interrupt_emitter( + crate::Expression::RayQueryProceedResult, + span, + )?; + let fun = crate::RayQueryFunction::Proceed { result }; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(crate::Statement::RayQuery { query, fun }, span); + return Ok(Some(result)); + } + "rayQueryGetCommittedIntersection" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + args.finish()?; + + let _ = ctx.module.generate_ray_intersection_type(); + + crate::Expression::RayQueryGetIntersection { + query, + committed: true, + } + } + "RayDesc" => { + let ty = ctx.module.generate_ray_desc_type(); + let handle = self.construct( + span, + &ast::ConstructorType::Type(ty), + function.span, + arguments, + ctx, + )?; + return Ok(Some(handle)); + } + _ => return Err(Error::UnknownIdent(function.span, function.name)), + } + }; + + let expr = ctx.append_expression(expr, span)?; + Ok(Some(expr)) + } + } + } + + fn atomic_pointer( + &mut self, + expr: Handle>, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + let span = ctx.ast_expressions.get_span(expr); + let pointer = self.expression(expr, ctx)?; + + match *resolve_inner!(ctx, pointer) { + crate::TypeInner::Pointer { base, .. } => match ctx.module.types[base].inner { + crate::TypeInner::Atomic { .. } => Ok(pointer), + ref other => { + log::error!("Pointer type to {:?} passed to atomic op", other); + Err(Error::InvalidAtomicPointer(span)) + } + }, + ref other => { + log::error!("Type {:?} passed to atomic op", other); + Err(Error::InvalidAtomicPointer(span)) + } + } + } + + fn atomic_helper( + &mut self, + span: Span, + fun: crate::AtomicFunction, + args: &[Handle>], + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + let mut args = ctx.prepare_args(args, 2, span); + + let pointer = self.atomic_pointer(args.next()?, ctx)?; + + let value = args.next()?; + let value = self.expression(value, ctx)?; + let ty = ctx.register_type(value)?; + + args.finish()?; + + let result = ctx.interrupt_emitter( + crate::Expression::AtomicResult { + ty, + comparison: false, + }, + span, + )?; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block.push( + crate::Statement::Atomic { + pointer, + fun, + value, + result, + }, + span, + ); + Ok(result) + } + + fn texture_sample_helper( + &mut self, + fun: Texture, + args: &[Handle>], + span: Span, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result> { + let mut args = ctx.prepare_args(args, fun.min_argument_count(), span); + + fn get_image_and_span<'source>( + lowerer: &mut Lowerer<'source, '_>, + args: &mut ArgumentContext<'_, 'source>, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result<(Handle, Span), Error<'source>> { + let image = args.next()?; + let image_span = ctx.ast_expressions.get_span(image); + let image = lowerer.expression(image, ctx)?; + Ok((image, image_span)) + } + + let (image, image_span, gather) = match fun { + Texture::Gather => { + let image_or_component = args.next()?; + let image_or_component_span = ctx.ast_expressions.get_span(image_or_component); + // Gathers from depth textures don't take an initial `component` argument. + let lowered_image_or_component = self.expression(image_or_component, ctx)?; + + match *resolve_inner!(ctx, lowered_image_or_component) { + crate::TypeInner::Image { + class: crate::ImageClass::Depth { .. }, + .. + } => ( + lowered_image_or_component, + image_or_component_span, + Some(crate::SwizzleComponent::X), + ), + _ => { + let (image, image_span) = get_image_and_span(self, &mut args, ctx)?; + ( + image, + image_span, + Some(ctx.gather_component( + lowered_image_or_component, + image_or_component_span, + span, + )?), + ) + } + } + } + Texture::GatherCompare => { + let (image, image_span) = get_image_and_span(self, &mut args, ctx)?; + (image, image_span, Some(crate::SwizzleComponent::X)) + } + + _ => { + let (image, image_span) = get_image_and_span(self, &mut args, ctx)?; + (image, image_span, None) + } + }; + + let sampler = self.expression(args.next()?, ctx)?; + + let coordinate = self.expression(args.next()?, ctx)?; + + let (_, arrayed) = ctx.image_data(image, image_span)?; + let array_index = arrayed + .then(|| self.expression(args.next()?, ctx)) + .transpose()?; + + let (level, depth_ref) = match fun { + Texture::Gather => (crate::SampleLevel::Zero, None), + Texture::GatherCompare => { + let reference = self.expression(args.next()?, ctx)?; + (crate::SampleLevel::Zero, Some(reference)) + } + + Texture::Sample => (crate::SampleLevel::Auto, None), + Texture::SampleBias => { + let bias = self.expression(args.next()?, ctx)?; + (crate::SampleLevel::Bias(bias), None) + } + Texture::SampleCompare => { + let reference = self.expression(args.next()?, ctx)?; + (crate::SampleLevel::Auto, Some(reference)) + } + Texture::SampleCompareLevel => { + let reference = self.expression(args.next()?, ctx)?; + (crate::SampleLevel::Zero, Some(reference)) + } + Texture::SampleGrad => { + let x = self.expression(args.next()?, ctx)?; + let y = self.expression(args.next()?, ctx)?; + (crate::SampleLevel::Gradient { x, y }, None) + } + Texture::SampleLevel => { + let level = self.expression(args.next()?, ctx)?; + (crate::SampleLevel::Exact(level), None) + } + }; + + let offset = args + .next() + .map(|arg| self.expression(arg, &mut ctx.as_const())) + .ok() + .transpose()?; + + args.finish()?; + + Ok(crate::Expression::ImageSample { + image, + sampler, + gather, + coordinate, + array_index, + offset, + level, + depth_ref, + }) + } + + fn r#struct( + &mut self, + s: &ast::Struct<'source>, + span: Span, + ctx: &mut GlobalContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + let mut offset = 0; + let mut struct_alignment = Alignment::ONE; + let mut members = Vec::with_capacity(s.members.len()); + + for member in s.members.iter() { + let ty = self.resolve_ast_type(member.ty, ctx)?; + + self.layouter.update(ctx.module.to_ctx()).unwrap(); + + let member_min_size = self.layouter[ty].size; + let member_min_alignment = self.layouter[ty].alignment; + + let member_size = if let Some(size_expr) = member.size { + let (size, span) = self.const_u32(size_expr, &mut ctx.as_const())?; + if size < member_min_size { + return Err(Error::SizeAttributeTooLow(span, member_min_size)); + } else { + size + } + } else { + member_min_size + }; + + let member_alignment = if let Some(align_expr) = member.align { + let (align, span) = self.const_u32(align_expr, &mut ctx.as_const())?; + if let Some(alignment) = Alignment::new(align) { + if alignment < member_min_alignment { + return Err(Error::AlignAttributeTooLow(span, member_min_alignment)); + } else { + alignment + } + } else { + return Err(Error::NonPowerOfTwoAlignAttribute(span)); + } + } else { + member_min_alignment + }; + + let binding = self.binding(&member.binding, ty, ctx)?; + + offset = member_alignment.round_up(offset); + struct_alignment = struct_alignment.max(member_alignment); + + members.push(crate::StructMember { + name: Some(member.name.name.to_owned()), + ty, + binding, + offset, + }); + + offset += member_size; + } + + let size = struct_alignment.round_up(offset); + let inner = crate::TypeInner::Struct { + members, + span: size, + }; + + let handle = ctx.module.types.insert( + crate::Type { + name: Some(s.name.name.to_string()), + inner, + }, + span, + ); + Ok(handle) + } + + fn const_u32( + &mut self, + expr: Handle>, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result<(u32, Span), Error<'source>> { + let span = ctx.ast_expressions.get_span(expr); + let expr = self.expression(expr, ctx)?; + let value = ctx + .module + .to_ctx() + .eval_expr_to_u32(expr) + .map_err(|err| match err { + crate::proc::U32EvalError::NonConst => { + Error::ExpectedConstExprConcreteIntegerScalar(span) + } + crate::proc::U32EvalError::Negative => Error::ExpectedNonNegative(span), + })?; + Ok((value, span)) + } + + fn array_size( + &mut self, + size: ast::ArraySize<'source>, + ctx: &mut GlobalContext<'source, '_, '_>, + ) -> Result> { + Ok(match size { + ast::ArraySize::Constant(expr) => { + let span = ctx.ast_expressions.get_span(expr); + let const_expr = self.expression(expr, &mut ctx.as_const())?; + let len = + ctx.module + .to_ctx() + .eval_expr_to_u32(const_expr) + .map_err(|err| match err { + crate::proc::U32EvalError::NonConst => { + Error::ExpectedConstExprConcreteIntegerScalar(span) + } + crate::proc::U32EvalError::Negative => { + Error::ExpectedPositiveArrayLength(span) + } + })?; + let size = NonZeroU32::new(len).ok_or(Error::ExpectedPositiveArrayLength(span))?; + crate::ArraySize::Constant(size) + } + ast::ArraySize::Dynamic => crate::ArraySize::Dynamic, + }) + } + + /// Build the Naga equivalent of a named AST type. + /// + /// Return a Naga `Handle` representing the front-end type + /// `handle`, which should be named `name`, if given. + /// + /// If `handle` refers to a type cached in [`SpecialTypes`], + /// `name` may be ignored. + /// + /// [`SpecialTypes`]: crate::SpecialTypes + fn resolve_named_ast_type( + &mut self, + handle: Handle>, + name: Option, + ctx: &mut GlobalContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + let inner = match ctx.types[handle] { + ast::Type::Scalar(scalar) => scalar.to_inner_scalar(), + ast::Type::Vector { size, scalar } => scalar.to_inner_vector(size), + ast::Type::Matrix { + rows, + columns, + width, + } => crate::TypeInner::Matrix { + columns, + rows, + scalar: crate::Scalar::float(width), + }, + ast::Type::Atomic(scalar) => scalar.to_inner_atomic(), + ast::Type::Pointer { base, space } => { + let base = self.resolve_ast_type(base, ctx)?; + crate::TypeInner::Pointer { base, space } + } + ast::Type::Array { base, size } => { + let base = self.resolve_ast_type(base, ctx)?; + let size = self.array_size(size, ctx)?; + + self.layouter.update(ctx.module.to_ctx()).unwrap(); + let stride = self.layouter[base].to_stride(); + + crate::TypeInner::Array { base, size, stride } + } + ast::Type::Image { + dim, + arrayed, + class, + } => crate::TypeInner::Image { + dim, + arrayed, + class, + }, + ast::Type::Sampler { comparison } => crate::TypeInner::Sampler { comparison }, + ast::Type::AccelerationStructure => crate::TypeInner::AccelerationStructure, + ast::Type::RayQuery => crate::TypeInner::RayQuery, + ast::Type::BindingArray { base, size } => { + let base = self.resolve_ast_type(base, ctx)?; + let size = self.array_size(size, ctx)?; + crate::TypeInner::BindingArray { base, size } + } + ast::Type::RayDesc => { + return Ok(ctx.module.generate_ray_desc_type()); + } + ast::Type::RayIntersection => { + return Ok(ctx.module.generate_ray_intersection_type()); + } + ast::Type::User(ref ident) => { + return match ctx.globals.get(ident.name) { + Some(&LoweredGlobalDecl::Type(handle)) => Ok(handle), + Some(_) => Err(Error::Unexpected(ident.span, ExpectedToken::Type)), + None => Err(Error::UnknownType(ident.span)), + } + } + }; + + Ok(ctx.ensure_type_exists(name, inner)) + } + + /// Return a Naga `Handle` representing the front-end type `handle`. + fn resolve_ast_type( + &mut self, + handle: Handle>, + ctx: &mut GlobalContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + self.resolve_named_ast_type(handle, None, ctx) + } + + fn binding( + &mut self, + binding: &Option>, + ty: Handle, + ctx: &mut GlobalContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + Ok(match *binding { + Some(ast::Binding::BuiltIn(b)) => Some(crate::Binding::BuiltIn(b)), + Some(ast::Binding::Location { + location, + second_blend_source, + interpolation, + sampling, + }) => { + let mut binding = crate::Binding::Location { + location: self.const_u32(location, &mut ctx.as_const())?.0, + second_blend_source, + interpolation, + sampling, + }; + binding.apply_default_interpolation(&ctx.module.types[ty].inner); + Some(binding) + } + None => None, + }) + } + + fn ray_query_pointer( + &mut self, + expr: Handle>, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result, Error<'source>> { + let span = ctx.ast_expressions.get_span(expr); + let pointer = self.expression(expr, ctx)?; + + match *resolve_inner!(ctx, pointer) { + crate::TypeInner::Pointer { base, .. } => match ctx.module.types[base].inner { + crate::TypeInner::RayQuery => Ok(pointer), + ref other => { + log::error!("Pointer type to {:?} passed to ray query op", other); + Err(Error::InvalidRayQueryPointer(span)) + } + }, + ref other => { + log::error!("Type {:?} passed to ray query op", other); + Err(Error::InvalidRayQueryPointer(span)) + } + } + } +} diff --git a/naga/src/front/wgsl/mod.rs b/naga/src/front/wgsl/mod.rs new file mode 100644 index 0000000000..b6151fe1c0 --- /dev/null +++ b/naga/src/front/wgsl/mod.rs @@ -0,0 +1,49 @@ +/*! +Frontend for [WGSL][wgsl] (WebGPU Shading Language). + +[wgsl]: https://gpuweb.github.io/gpuweb/wgsl.html +*/ + +mod error; +mod index; +mod lower; +mod parse; +#[cfg(test)] +mod tests; +mod to_wgsl; + +use crate::front::wgsl::error::Error; +use crate::front::wgsl::parse::Parser; +use thiserror::Error; + +pub use crate::front::wgsl::error::ParseError; +use crate::front::wgsl::lower::Lowerer; +use crate::Scalar; + +pub struct Frontend { + parser: Parser, +} + +impl Frontend { + pub const fn new() -> Self { + Self { + parser: Parser::new(), + } + } + + pub fn parse(&mut self, source: &str) -> Result { + self.inner(source).map_err(|x| x.as_parse_error(source)) + } + + fn inner<'a>(&mut self, source: &'a str) -> Result> { + let tu = self.parser.parse(source)?; + let index = index::Index::generate(&tu)?; + let module = Lowerer::new(&index).lower(&tu)?; + + Ok(module) + } +} + +pub fn parse_str(source: &str) -> Result { + Frontend::new().parse(source) +} diff --git a/naga/src/front/wgsl/parse/ast.rs b/naga/src/front/wgsl/parse/ast.rs new file mode 100644 index 0000000000..dbaac523cb --- /dev/null +++ b/naga/src/front/wgsl/parse/ast.rs @@ -0,0 +1,491 @@ +use crate::front::wgsl::parse::number::Number; +use crate::front::wgsl::Scalar; +use crate::{Arena, FastIndexSet, Handle, Span}; +use std::hash::Hash; + +#[derive(Debug, Default)] +pub struct TranslationUnit<'a> { + pub decls: Arena>, + /// The common expressions arena for the entire translation unit. + /// + /// All functions, global initializers, array lengths, etc. store their + /// expressions here. We apportion these out to individual Naga + /// [`Function`]s' expression arenas at lowering time. Keeping them all in a + /// single arena simplifies handling of things like array lengths (which are + /// effectively global and thus don't clearly belong to any function) and + /// initializers (which can appear in both function-local and module-scope + /// contexts). + /// + /// [`Function`]: crate::Function + pub expressions: Arena>, + + /// Non-user-defined types, like `vec4` or `array`. + /// + /// These are referred to by `Handle>` values. + /// User-defined types are referred to by name until lowering. + pub types: Arena>, +} + +#[derive(Debug, Clone, Copy)] +pub struct Ident<'a> { + pub name: &'a str, + pub span: Span, +} + +#[derive(Debug)] +pub enum IdentExpr<'a> { + Unresolved(&'a str), + Local(Handle), +} + +/// A reference to a module-scope definition or predeclared object. +/// +/// Each [`GlobalDecl`] holds a set of these values, to be resolved to +/// specific definitions later. To support de-duplication, `Eq` and +/// `Hash` on a `Dependency` value consider only the name, not the +/// source location at which the reference occurs. +#[derive(Debug)] +pub struct Dependency<'a> { + /// The name referred to. + pub ident: &'a str, + + /// The location at which the reference to that name occurs. + pub usage: Span, +} + +impl Hash for Dependency<'_> { + fn hash(&self, state: &mut H) { + self.ident.hash(state); + } +} + +impl PartialEq for Dependency<'_> { + fn eq(&self, other: &Self) -> bool { + self.ident == other.ident + } +} + +impl Eq for Dependency<'_> {} + +/// A module-scope declaration. +#[derive(Debug)] +pub struct GlobalDecl<'a> { + pub kind: GlobalDeclKind<'a>, + + /// Names of all module-scope or predeclared objects this + /// declaration uses. + pub dependencies: FastIndexSet>, +} + +#[derive(Debug)] +pub enum GlobalDeclKind<'a> { + Fn(Function<'a>), + Var(GlobalVariable<'a>), + Const(Const<'a>), + Struct(Struct<'a>), + Type(TypeAlias<'a>), +} + +#[derive(Debug)] +pub struct FunctionArgument<'a> { + pub name: Ident<'a>, + pub ty: Handle>, + pub binding: Option>, + pub handle: Handle, +} + +#[derive(Debug)] +pub struct FunctionResult<'a> { + pub ty: Handle>, + pub binding: Option>, +} + +#[derive(Debug)] +pub struct EntryPoint<'a> { + pub stage: crate::ShaderStage, + pub early_depth_test: Option, + pub workgroup_size: Option<[Option>>; 3]>, +} + +#[cfg(doc)] +use crate::front::wgsl::lower::{RuntimeExpressionContext, StatementContext}; + +#[derive(Debug)] +pub struct Function<'a> { + pub entry_point: Option>, + pub name: Ident<'a>, + pub arguments: Vec>, + pub result: Option>, + + /// Local variable and function argument arena. + /// + /// Note that the `Local` here is actually a zero-sized type. The AST keeps + /// all the detailed information about locals - names, types, etc. - in + /// [`LocalDecl`] statements. For arguments, that information is kept in + /// [`arguments`]. This `Arena`'s only role is to assign a unique `Handle` + /// to each of them, and track their definitions' spans for use in + /// diagnostics. + /// + /// In the AST, when an [`Ident`] expression refers to a local variable or + /// argument, its [`IdentExpr`] holds the referent's `Handle` in this + /// arena. + /// + /// During lowering, [`LocalDecl`] statements add entries to a per-function + /// table that maps `Handle` values to their Naga representations, + /// accessed via [`StatementContext::local_table`] and + /// [`RuntimeExpressionContext::local_table`]. This table is then consulted when + /// lowering subsequent [`Ident`] expressions. + /// + /// [`LocalDecl`]: StatementKind::LocalDecl + /// [`arguments`]: Function::arguments + /// [`Ident`]: Expression::Ident + /// [`StatementContext::local_table`]: StatementContext::local_table + /// [`RuntimeExpressionContext::local_table`]: RuntimeExpressionContext::local_table + pub locals: Arena, + + pub body: Block<'a>, +} + +#[derive(Debug)] +pub enum Binding<'a> { + BuiltIn(crate::BuiltIn), + Location { + location: Handle>, + second_blend_source: bool, + interpolation: Option, + sampling: Option, + }, +} + +#[derive(Debug)] +pub struct ResourceBinding<'a> { + pub group: Handle>, + pub binding: Handle>, +} + +#[derive(Debug)] +pub struct GlobalVariable<'a> { + pub name: Ident<'a>, + pub space: crate::AddressSpace, + pub binding: Option>, + pub ty: Handle>, + pub init: Option>>, +} + +#[derive(Debug)] +pub struct StructMember<'a> { + pub name: Ident<'a>, + pub ty: Handle>, + pub binding: Option>, + pub align: Option>>, + pub size: Option>>, +} + +#[derive(Debug)] +pub struct Struct<'a> { + pub name: Ident<'a>, + pub members: Vec>, +} + +#[derive(Debug)] +pub struct TypeAlias<'a> { + pub name: Ident<'a>, + pub ty: Handle>, +} + +#[derive(Debug)] +pub struct Const<'a> { + pub name: Ident<'a>, + pub ty: Option>>, + pub init: Handle>, +} + +/// The size of an [`Array`] or [`BindingArray`]. +/// +/// [`Array`]: Type::Array +/// [`BindingArray`]: Type::BindingArray +#[derive(Debug, Copy, Clone)] +pub enum ArraySize<'a> { + /// The length as a constant expression. + Constant(Handle>), + Dynamic, +} + +#[derive(Debug)] +pub enum Type<'a> { + Scalar(Scalar), + Vector { + size: crate::VectorSize, + scalar: Scalar, + }, + Matrix { + columns: crate::VectorSize, + rows: crate::VectorSize, + width: crate::Bytes, + }, + Atomic(Scalar), + Pointer { + base: Handle>, + space: crate::AddressSpace, + }, + Array { + base: Handle>, + size: ArraySize<'a>, + }, + Image { + dim: crate::ImageDimension, + arrayed: bool, + class: crate::ImageClass, + }, + Sampler { + comparison: bool, + }, + AccelerationStructure, + RayQuery, + RayDesc, + RayIntersection, + BindingArray { + base: Handle>, + size: ArraySize<'a>, + }, + + /// A user-defined type, like a struct or a type alias. + User(Ident<'a>), +} + +#[derive(Debug, Default)] +pub struct Block<'a> { + pub stmts: Vec>, +} + +#[derive(Debug)] +pub struct Statement<'a> { + pub kind: StatementKind<'a>, + pub span: Span, +} + +#[derive(Debug)] +pub enum StatementKind<'a> { + LocalDecl(LocalDecl<'a>), + Block(Block<'a>), + If { + condition: Handle>, + accept: Block<'a>, + reject: Block<'a>, + }, + Switch { + selector: Handle>, + cases: Vec>, + }, + Loop { + body: Block<'a>, + continuing: Block<'a>, + break_if: Option>>, + }, + Break, + Continue, + Return { + value: Option>>, + }, + Kill, + Call { + function: Ident<'a>, + arguments: Vec>>, + }, + Assign { + target: Handle>, + op: Option, + value: Handle>, + }, + Increment(Handle>), + Decrement(Handle>), + Ignore(Handle>), +} + +#[derive(Debug)] +pub enum SwitchValue<'a> { + Expr(Handle>), + Default, +} + +#[derive(Debug)] +pub struct SwitchCase<'a> { + pub value: SwitchValue<'a>, + pub body: Block<'a>, + pub fall_through: bool, +} + +/// A type at the head of a [`Construct`] expression. +/// +/// WGSL has two types of [`type constructor expressions`]: +/// +/// - Those that fully specify the type being constructed, like +/// `vec3(x,y,z)`, which obviously constructs a `vec3`. +/// +/// - Those that leave the component type of the composite being constructed +/// implicit, to be inferred from the argument types, like `vec3(x,y,z)`, +/// which constructs a `vec3` where `T` is the type of `x`, `y`, and `z`. +/// +/// This enum represents the head type of both cases. The `PartialFoo` variants +/// represent the second case, where the component type is implicit. +/// +/// This does not cover structs or types referred to by type aliases. See the +/// documentation for [`Construct`] and [`Call`] expressions for details. +/// +/// [`Construct`]: Expression::Construct +/// [`type constructor expressions`]: https://gpuweb.github.io/gpuweb/wgsl/#type-constructor-expr +/// [`Call`]: Expression::Call +#[derive(Debug)] +pub enum ConstructorType<'a> { + /// A scalar type or conversion: `f32(1)`. + Scalar(Scalar), + + /// A vector construction whose component type is inferred from the + /// argument: `vec3(1.0)`. + PartialVector { size: crate::VectorSize }, + + /// A vector construction whose component type is written out: + /// `vec3(1.0)`. + Vector { + size: crate::VectorSize, + scalar: Scalar, + }, + + /// A matrix construction whose component type is inferred from the + /// argument: `mat2x2(1,2,3,4)`. + PartialMatrix { + columns: crate::VectorSize, + rows: crate::VectorSize, + }, + + /// A matrix construction whose component type is written out: + /// `mat2x2(1,2,3,4)`. + Matrix { + columns: crate::VectorSize, + rows: crate::VectorSize, + width: crate::Bytes, + }, + + /// An array whose component type and size are inferred from the arguments: + /// `array(3,4,5)`. + PartialArray, + + /// An array whose component type and size are written out: + /// `array(3,4,5)`. + Array { + base: Handle>, + size: ArraySize<'a>, + }, + + /// Constructing a value of a known Naga IR type. + /// + /// This variant is produced only during lowering, when we have Naga types + /// available, never during parsing. + Type(Handle), +} + +#[derive(Debug, Copy, Clone)] +pub enum Literal { + Bool(bool), + Number(Number), +} + +#[cfg(doc)] +use crate::front::wgsl::lower::Lowerer; + +#[derive(Debug)] +pub enum Expression<'a> { + Literal(Literal), + Ident(IdentExpr<'a>), + + /// A type constructor expression. + /// + /// This is only used for expressions like `KEYWORD(EXPR...)` and + /// `KEYWORD(EXPR...)`, where `KEYWORD` is a [type-defining keyword] like + /// `vec3`. These keywords cannot be shadowed by user definitions, so we can + /// tell that such an expression is a construction immediately. + /// + /// For ordinary identifiers, we can't tell whether an expression like + /// `IDENTIFIER(EXPR, ...)` is a construction expression or a function call + /// until we know `IDENTIFIER`'s definition, so we represent those as + /// [`Call`] expressions. + /// + /// [type-defining keyword]: https://gpuweb.github.io/gpuweb/wgsl/#type-defining-keywords + /// [`Call`]: Expression::Call + Construct { + ty: ConstructorType<'a>, + ty_span: Span, + components: Vec>>, + }, + Unary { + op: crate::UnaryOperator, + expr: Handle>, + }, + AddrOf(Handle>), + Deref(Handle>), + Binary { + op: crate::BinaryOperator, + left: Handle>, + right: Handle>, + }, + + /// A function call or type constructor expression. + /// + /// We can't tell whether an expression like `IDENTIFIER(EXPR, ...)` is a + /// construction expression or a function call until we know `IDENTIFIER`'s + /// definition, so we represent everything of that form as one of these + /// expressions until lowering. At that point, [`Lowerer::call`] has + /// everything's definition in hand, and can decide whether to emit a Naga + /// [`Constant`], [`As`], [`Splat`], or [`Compose`] expression. + /// + /// [`Lowerer::call`]: Lowerer::call + /// [`Constant`]: crate::Expression::Constant + /// [`As`]: crate::Expression::As + /// [`Splat`]: crate::Expression::Splat + /// [`Compose`]: crate::Expression::Compose + Call { + function: Ident<'a>, + arguments: Vec>>, + }, + Index { + base: Handle>, + index: Handle>, + }, + Member { + base: Handle>, + field: Ident<'a>, + }, + Bitcast { + expr: Handle>, + to: Handle>, + ty_span: Span, + }, +} + +#[derive(Debug)] +pub struct LocalVariable<'a> { + pub name: Ident<'a>, + pub ty: Option>>, + pub init: Option>>, + pub handle: Handle, +} + +#[derive(Debug)] +pub struct Let<'a> { + pub name: Ident<'a>, + pub ty: Option>>, + pub init: Handle>, + pub handle: Handle, +} + +#[derive(Debug)] +pub enum LocalDecl<'a> { + Var(LocalVariable<'a>), + Let(Let<'a>), +} + +#[derive(Debug)] +/// A placeholder for a local variable declaration. +/// +/// See [`Function::locals`] for more information. +pub struct Local; diff --git a/naga/src/front/wgsl/parse/conv.rs b/naga/src/front/wgsl/parse/conv.rs new file mode 100644 index 0000000000..08f1e39285 --- /dev/null +++ b/naga/src/front/wgsl/parse/conv.rs @@ -0,0 +1,254 @@ +use super::Error; +use crate::front::wgsl::Scalar; +use crate::Span; + +pub fn map_address_space(word: &str, span: Span) -> Result> { + match word { + "private" => Ok(crate::AddressSpace::Private), + "workgroup" => Ok(crate::AddressSpace::WorkGroup), + "uniform" => Ok(crate::AddressSpace::Uniform), + "storage" => Ok(crate::AddressSpace::Storage { + access: crate::StorageAccess::default(), + }), + "push_constant" => Ok(crate::AddressSpace::PushConstant), + "function" => Ok(crate::AddressSpace::Function), + _ => Err(Error::UnknownAddressSpace(span)), + } +} + +pub fn map_built_in(word: &str, span: Span) -> Result> { + Ok(match word { + "position" => crate::BuiltIn::Position { invariant: false }, + // vertex + "vertex_index" => crate::BuiltIn::VertexIndex, + "instance_index" => crate::BuiltIn::InstanceIndex, + "view_index" => crate::BuiltIn::ViewIndex, + // fragment + "front_facing" => crate::BuiltIn::FrontFacing, + "frag_depth" => crate::BuiltIn::FragDepth, + "primitive_index" => crate::BuiltIn::PrimitiveIndex, + "sample_index" => crate::BuiltIn::SampleIndex, + "sample_mask" => crate::BuiltIn::SampleMask, + // compute + "global_invocation_id" => crate::BuiltIn::GlobalInvocationId, + "local_invocation_id" => crate::BuiltIn::LocalInvocationId, + "local_invocation_index" => crate::BuiltIn::LocalInvocationIndex, + "workgroup_id" => crate::BuiltIn::WorkGroupId, + "num_workgroups" => crate::BuiltIn::NumWorkGroups, + _ => return Err(Error::UnknownBuiltin(span)), + }) +} + +pub fn map_interpolation(word: &str, span: Span) -> Result> { + match word { + "linear" => Ok(crate::Interpolation::Linear), + "flat" => Ok(crate::Interpolation::Flat), + "perspective" => Ok(crate::Interpolation::Perspective), + _ => Err(Error::UnknownAttribute(span)), + } +} + +pub fn map_sampling(word: &str, span: Span) -> Result> { + match word { + "center" => Ok(crate::Sampling::Center), + "centroid" => Ok(crate::Sampling::Centroid), + "sample" => Ok(crate::Sampling::Sample), + _ => Err(Error::UnknownAttribute(span)), + } +} + +pub fn map_storage_format(word: &str, span: Span) -> Result> { + use crate::StorageFormat as Sf; + Ok(match word { + "r8unorm" => Sf::R8Unorm, + "r8snorm" => Sf::R8Snorm, + "r8uint" => Sf::R8Uint, + "r8sint" => Sf::R8Sint, + "r16unorm" => Sf::R16Unorm, + "r16snorm" => Sf::R16Snorm, + "r16uint" => Sf::R16Uint, + "r16sint" => Sf::R16Sint, + "r16float" => Sf::R16Float, + "rg8unorm" => Sf::Rg8Unorm, + "rg8snorm" => Sf::Rg8Snorm, + "rg8uint" => Sf::Rg8Uint, + "rg8sint" => Sf::Rg8Sint, + "r32uint" => Sf::R32Uint, + "r32sint" => Sf::R32Sint, + "r32float" => Sf::R32Float, + "rg16unorm" => Sf::Rg16Unorm, + "rg16snorm" => Sf::Rg16Snorm, + "rg16uint" => Sf::Rg16Uint, + "rg16sint" => Sf::Rg16Sint, + "rg16float" => Sf::Rg16Float, + "rgba8unorm" => Sf::Rgba8Unorm, + "rgba8snorm" => Sf::Rgba8Snorm, + "rgba8uint" => Sf::Rgba8Uint, + "rgba8sint" => Sf::Rgba8Sint, + "rgb10a2uint" => Sf::Rgb10a2Uint, + "rgb10a2unorm" => Sf::Rgb10a2Unorm, + "rg11b10float" => Sf::Rg11b10Float, + "rg32uint" => Sf::Rg32Uint, + "rg32sint" => Sf::Rg32Sint, + "rg32float" => Sf::Rg32Float, + "rgba16unorm" => Sf::Rgba16Unorm, + "rgba16snorm" => Sf::Rgba16Snorm, + "rgba16uint" => Sf::Rgba16Uint, + "rgba16sint" => Sf::Rgba16Sint, + "rgba16float" => Sf::Rgba16Float, + "rgba32uint" => Sf::Rgba32Uint, + "rgba32sint" => Sf::Rgba32Sint, + "rgba32float" => Sf::Rgba32Float, + "bgra8unorm" => Sf::Bgra8Unorm, + _ => return Err(Error::UnknownStorageFormat(span)), + }) +} + +pub fn get_scalar_type(word: &str) -> Option { + use crate::ScalarKind as Sk; + match word { + // "f16" => Some(Scalar { kind: Sk::Float, width: 2 }), + "f32" => Some(Scalar { + kind: Sk::Float, + width: 4, + }), + "f64" => Some(Scalar { + kind: Sk::Float, + width: 8, + }), + "i32" => Some(Scalar { + kind: Sk::Sint, + width: 4, + }), + "u32" => Some(Scalar { + kind: Sk::Uint, + width: 4, + }), + "bool" => Some(Scalar { + kind: Sk::Bool, + width: crate::BOOL_WIDTH, + }), + _ => None, + } +} + +pub fn map_derivative(word: &str) -> Option<(crate::DerivativeAxis, crate::DerivativeControl)> { + use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; + match word { + "dpdxCoarse" => Some((Axis::X, Ctrl::Coarse)), + "dpdyCoarse" => Some((Axis::Y, Ctrl::Coarse)), + "fwidthCoarse" => Some((Axis::Width, Ctrl::Coarse)), + "dpdxFine" => Some((Axis::X, Ctrl::Fine)), + "dpdyFine" => Some((Axis::Y, Ctrl::Fine)), + "fwidthFine" => Some((Axis::Width, Ctrl::Fine)), + "dpdx" => Some((Axis::X, Ctrl::None)), + "dpdy" => Some((Axis::Y, Ctrl::None)), + "fwidth" => Some((Axis::Width, Ctrl::None)), + _ => None, + } +} + +pub fn map_relational_fun(word: &str) -> Option { + match word { + "any" => Some(crate::RelationalFunction::Any), + "all" => Some(crate::RelationalFunction::All), + _ => None, + } +} + +pub fn map_standard_fun(word: &str) -> Option { + use crate::MathFunction as Mf; + Some(match word { + // comparison + "abs" => Mf::Abs, + "min" => Mf::Min, + "max" => Mf::Max, + "clamp" => Mf::Clamp, + "saturate" => Mf::Saturate, + // trigonometry + "cos" => Mf::Cos, + "cosh" => Mf::Cosh, + "sin" => Mf::Sin, + "sinh" => Mf::Sinh, + "tan" => Mf::Tan, + "tanh" => Mf::Tanh, + "acos" => Mf::Acos, + "acosh" => Mf::Acosh, + "asin" => Mf::Asin, + "asinh" => Mf::Asinh, + "atan" => Mf::Atan, + "atanh" => Mf::Atanh, + "atan2" => Mf::Atan2, + "radians" => Mf::Radians, + "degrees" => Mf::Degrees, + // decomposition + "ceil" => Mf::Ceil, + "floor" => Mf::Floor, + "round" => Mf::Round, + "fract" => Mf::Fract, + "trunc" => Mf::Trunc, + "modf" => Mf::Modf, + "frexp" => Mf::Frexp, + "ldexp" => Mf::Ldexp, + // exponent + "exp" => Mf::Exp, + "exp2" => Mf::Exp2, + "log" => Mf::Log, + "log2" => Mf::Log2, + "pow" => Mf::Pow, + // geometry + "dot" => Mf::Dot, + "cross" => Mf::Cross, + "distance" => Mf::Distance, + "length" => Mf::Length, + "normalize" => Mf::Normalize, + "faceForward" => Mf::FaceForward, + "reflect" => Mf::Reflect, + "refract" => Mf::Refract, + // computational + "sign" => Mf::Sign, + "fma" => Mf::Fma, + "mix" => Mf::Mix, + "step" => Mf::Step, + "smoothstep" => Mf::SmoothStep, + "sqrt" => Mf::Sqrt, + "inverseSqrt" => Mf::InverseSqrt, + "transpose" => Mf::Transpose, + "determinant" => Mf::Determinant, + // bits + "countTrailingZeros" => Mf::CountTrailingZeros, + "countLeadingZeros" => Mf::CountLeadingZeros, + "countOneBits" => Mf::CountOneBits, + "reverseBits" => Mf::ReverseBits, + "extractBits" => Mf::ExtractBits, + "insertBits" => Mf::InsertBits, + "firstTrailingBit" => Mf::FindLsb, + "firstLeadingBit" => Mf::FindMsb, + // data packing + "pack4x8snorm" => Mf::Pack4x8snorm, + "pack4x8unorm" => Mf::Pack4x8unorm, + "pack2x16snorm" => Mf::Pack2x16snorm, + "pack2x16unorm" => Mf::Pack2x16unorm, + "pack2x16float" => Mf::Pack2x16float, + // data unpacking + "unpack4x8snorm" => Mf::Unpack4x8snorm, + "unpack4x8unorm" => Mf::Unpack4x8unorm, + "unpack2x16snorm" => Mf::Unpack2x16snorm, + "unpack2x16unorm" => Mf::Unpack2x16unorm, + "unpack2x16float" => Mf::Unpack2x16float, + _ => return None, + }) +} + +pub fn map_conservative_depth( + word: &str, + span: Span, +) -> Result> { + use crate::ConservativeDepth as Cd; + match word { + "greater_equal" => Ok(Cd::GreaterEqual), + "less_equal" => Ok(Cd::LessEqual), + "unchanged" => Ok(Cd::Unchanged), + _ => Err(Error::UnknownConservativeDepth(span)), + } +} diff --git a/naga/src/front/wgsl/parse/lexer.rs b/naga/src/front/wgsl/parse/lexer.rs new file mode 100644 index 0000000000..d03a448561 --- /dev/null +++ b/naga/src/front/wgsl/parse/lexer.rs @@ -0,0 +1,739 @@ +use super::{number::consume_number, Error, ExpectedToken}; +use crate::front::wgsl::error::NumberError; +use crate::front::wgsl::parse::{conv, Number}; +use crate::front::wgsl::Scalar; +use crate::Span; + +type TokenSpan<'a> = (Token<'a>, Span); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Token<'a> { + Separator(char), + Paren(char), + Attribute, + Number(Result), + Word(&'a str), + Operation(char), + LogicalOperation(char), + ShiftOperation(char), + AssignmentOperation(char), + IncrementOperation, + DecrementOperation, + Arrow, + Unknown(char), + Trivia, + End, +} + +fn consume_any(input: &str, what: impl Fn(char) -> bool) -> (&str, &str) { + let pos = input.find(|c| !what(c)).unwrap_or(input.len()); + input.split_at(pos) +} + +/// Return the token at the start of `input`. +/// +/// If `generic` is `false`, then the bit shift operators `>>` or `<<` +/// are valid lookahead tokens for the current parser state (see [§3.1 +/// Parsing] in the WGSL specification). In other words: +/// +/// - If `generic` is `true`, then we are expecting an angle bracket +/// around a generic type parameter, like the `<` and `>` in +/// `vec3`, so interpret `<` and `>` as `Token::Paren` tokens, +/// even if they're part of `<<` or `>>` sequences. +/// +/// - Otherwise, interpret `<<` and `>>` as shift operators: +/// `Token::LogicalOperation` tokens. +/// +/// [§3.1 Parsing]: https://gpuweb.github.io/gpuweb/wgsl/#parsing +fn consume_token(input: &str, generic: bool) -> (Token<'_>, &str) { + let mut chars = input.chars(); + let cur = match chars.next() { + Some(c) => c, + None => return (Token::End, ""), + }; + match cur { + ':' | ';' | ',' => (Token::Separator(cur), chars.as_str()), + '.' => { + let og_chars = chars.as_str(); + match chars.next() { + Some('0'..='9') => consume_number(input), + _ => (Token::Separator(cur), og_chars), + } + } + '@' => (Token::Attribute, chars.as_str()), + '(' | ')' | '{' | '}' | '[' | ']' => (Token::Paren(cur), chars.as_str()), + '<' | '>' => { + let og_chars = chars.as_str(); + match chars.next() { + Some('=') if !generic => (Token::LogicalOperation(cur), chars.as_str()), + Some(c) if c == cur && !generic => { + let og_chars = chars.as_str(); + match chars.next() { + Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), + _ => (Token::ShiftOperation(cur), og_chars), + } + } + _ => (Token::Paren(cur), og_chars), + } + } + '0'..='9' => consume_number(input), + '/' => { + let og_chars = chars.as_str(); + match chars.next() { + Some('/') => { + let _ = chars.position(is_comment_end); + (Token::Trivia, chars.as_str()) + } + Some('*') => { + let mut depth = 1; + let mut prev = None; + + for c in &mut chars { + match (prev, c) { + (Some('*'), '/') => { + prev = None; + depth -= 1; + if depth == 0 { + return (Token::Trivia, chars.as_str()); + } + } + (Some('/'), '*') => { + prev = None; + depth += 1; + } + _ => { + prev = Some(c); + } + } + } + + (Token::End, "") + } + Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), + _ => (Token::Operation(cur), og_chars), + } + } + '-' => { + let og_chars = chars.as_str(); + match chars.next() { + Some('>') => (Token::Arrow, chars.as_str()), + Some('-') => (Token::DecrementOperation, chars.as_str()), + Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), + _ => (Token::Operation(cur), og_chars), + } + } + '+' => { + let og_chars = chars.as_str(); + match chars.next() { + Some('+') => (Token::IncrementOperation, chars.as_str()), + Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), + _ => (Token::Operation(cur), og_chars), + } + } + '*' | '%' | '^' => { + let og_chars = chars.as_str(); + match chars.next() { + Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), + _ => (Token::Operation(cur), og_chars), + } + } + '~' => (Token::Operation(cur), chars.as_str()), + '=' | '!' => { + let og_chars = chars.as_str(); + match chars.next() { + Some('=') => (Token::LogicalOperation(cur), chars.as_str()), + _ => (Token::Operation(cur), og_chars), + } + } + '&' | '|' => { + let og_chars = chars.as_str(); + match chars.next() { + Some(c) if c == cur => (Token::LogicalOperation(cur), chars.as_str()), + Some('=') => (Token::AssignmentOperation(cur), chars.as_str()), + _ => (Token::Operation(cur), og_chars), + } + } + _ if is_blankspace(cur) => { + let (_, rest) = consume_any(input, is_blankspace); + (Token::Trivia, rest) + } + _ if is_word_start(cur) => { + let (word, rest) = consume_any(input, is_word_part); + (Token::Word(word), rest) + } + _ => (Token::Unknown(cur), chars.as_str()), + } +} + +/// Returns whether or not a char is a comment end +/// (Unicode Pattern_White_Space excluding U+0020, U+0009, U+200E and U+200F) +const fn is_comment_end(c: char) -> bool { + match c { + '\u{000a}'..='\u{000d}' | '\u{0085}' | '\u{2028}' | '\u{2029}' => true, + _ => false, + } +} + +/// Returns whether or not a char is a blankspace (Unicode Pattern_White_Space) +const fn is_blankspace(c: char) -> bool { + match c { + '\u{0020}' + | '\u{0009}'..='\u{000d}' + | '\u{0085}' + | '\u{200e}' + | '\u{200f}' + | '\u{2028}' + | '\u{2029}' => true, + _ => false, + } +} + +/// Returns whether or not a char is a word start (Unicode XID_Start + '_') +fn is_word_start(c: char) -> bool { + c == '_' || unicode_xid::UnicodeXID::is_xid_start(c) +} + +/// Returns whether or not a char is a word part (Unicode XID_Continue) +fn is_word_part(c: char) -> bool { + unicode_xid::UnicodeXID::is_xid_continue(c) +} + +#[derive(Clone)] +pub(in crate::front::wgsl) struct Lexer<'a> { + input: &'a str, + pub(in crate::front::wgsl) source: &'a str, + // The byte offset of the end of the last non-trivia token. + last_end_offset: usize, +} + +impl<'a> Lexer<'a> { + pub(in crate::front::wgsl) const fn new(input: &'a str) -> Self { + Lexer { + input, + source: input, + last_end_offset: 0, + } + } + + /// Calls the function with a lexer and returns the result of the function as well as the span for everything the function parsed + /// + /// # Examples + /// ```ignore + /// let lexer = Lexer::new("5"); + /// let (value, span) = lexer.capture_span(Lexer::next_uint_literal); + /// assert_eq!(value, 5); + /// ``` + #[inline] + pub fn capture_span( + &mut self, + inner: impl FnOnce(&mut Self) -> Result, + ) -> Result<(T, Span), E> { + let start = self.current_byte_offset(); + let res = inner(self)?; + let end = self.current_byte_offset(); + Ok((res, Span::from(start..end))) + } + + pub(in crate::front::wgsl) fn start_byte_offset(&mut self) -> usize { + loop { + // Eat all trivia because `next` doesn't eat trailing trivia. + let (token, rest) = consume_token(self.input, false); + if let Token::Trivia = token { + self.input = rest; + } else { + return self.current_byte_offset(); + } + } + } + + fn peek_token_and_rest(&mut self) -> (TokenSpan<'a>, &'a str) { + let mut cloned = self.clone(); + let token = cloned.next(); + let rest = cloned.input; + (token, rest) + } + + const fn current_byte_offset(&self) -> usize { + self.source.len() - self.input.len() + } + + pub(in crate::front::wgsl) fn span_from(&self, offset: usize) -> Span { + Span::from(offset..self.last_end_offset) + } + + /// Return the next non-whitespace token from `self`. + /// + /// Assume we are a parse state where bit shift operators may + /// occur, but not angle brackets. + #[must_use] + pub(in crate::front::wgsl) fn next(&mut self) -> TokenSpan<'a> { + self.next_impl(false) + } + + /// Return the next non-whitespace token from `self`. + /// + /// Assume we are in a parse state where angle brackets may occur, + /// but not bit shift operators. + #[must_use] + pub(in crate::front::wgsl) fn next_generic(&mut self) -> TokenSpan<'a> { + self.next_impl(true) + } + + /// Return the next non-whitespace token from `self`, with a span. + /// + /// See [`consume_token`] for the meaning of `generic`. + fn next_impl(&mut self, generic: bool) -> TokenSpan<'a> { + let mut start_byte_offset = self.current_byte_offset(); + loop { + let (token, rest) = consume_token(self.input, generic); + self.input = rest; + match token { + Token::Trivia => start_byte_offset = self.current_byte_offset(), + _ => { + self.last_end_offset = self.current_byte_offset(); + return (token, self.span_from(start_byte_offset)); + } + } + } + } + + #[must_use] + pub(in crate::front::wgsl) fn peek(&mut self) -> TokenSpan<'a> { + let (token, _) = self.peek_token_and_rest(); + token + } + + pub(in crate::front::wgsl) fn expect_span( + &mut self, + expected: Token<'a>, + ) -> Result> { + let next = self.next(); + if next.0 == expected { + Ok(next.1) + } else { + Err(Error::Unexpected(next.1, ExpectedToken::Token(expected))) + } + } + + pub(in crate::front::wgsl) fn expect(&mut self, expected: Token<'a>) -> Result<(), Error<'a>> { + self.expect_span(expected)?; + Ok(()) + } + + pub(in crate::front::wgsl) fn expect_generic_paren( + &mut self, + expected: char, + ) -> Result<(), Error<'a>> { + let next = self.next_generic(); + if next.0 == Token::Paren(expected) { + Ok(()) + } else { + Err(Error::Unexpected( + next.1, + ExpectedToken::Token(Token::Paren(expected)), + )) + } + } + + /// If the next token matches it is skipped and true is returned + pub(in crate::front::wgsl) fn skip(&mut self, what: Token<'_>) -> bool { + let (peeked_token, rest) = self.peek_token_and_rest(); + if peeked_token.0 == what { + self.input = rest; + true + } else { + false + } + } + + pub(in crate::front::wgsl) fn next_ident_with_span( + &mut self, + ) -> Result<(&'a str, Span), Error<'a>> { + match self.next() { + (Token::Word("_"), span) => Err(Error::InvalidIdentifierUnderscore(span)), + (Token::Word(word), span) if word.starts_with("__") => { + Err(Error::ReservedIdentifierPrefix(span)) + } + (Token::Word(word), span) => Ok((word, span)), + other => Err(Error::Unexpected(other.1, ExpectedToken::Identifier)), + } + } + + pub(in crate::front::wgsl) fn next_ident( + &mut self, + ) -> Result, Error<'a>> { + let ident = self + .next_ident_with_span() + .map(|(name, span)| super::ast::Ident { name, span })?; + + if crate::keywords::wgsl::RESERVED.contains(&ident.name) { + return Err(Error::ReservedKeyword(ident.span)); + } + + Ok(ident) + } + + /// Parses a generic scalar type, for example ``. + pub(in crate::front::wgsl) fn next_scalar_generic(&mut self) -> Result> { + self.expect_generic_paren('<')?; + let pair = match self.next() { + (Token::Word(word), span) => { + conv::get_scalar_type(word).ok_or(Error::UnknownScalarType(span)) + } + (_, span) => Err(Error::UnknownScalarType(span)), + }?; + self.expect_generic_paren('>')?; + Ok(pair) + } + + /// Parses a generic scalar type, for example ``. + /// + /// Returns the span covering the inner type, excluding the brackets. + pub(in crate::front::wgsl) fn next_scalar_generic_with_span( + &mut self, + ) -> Result<(Scalar, Span), Error<'a>> { + self.expect_generic_paren('<')?; + let pair = match self.next() { + (Token::Word(word), span) => conv::get_scalar_type(word) + .map(|scalar| (scalar, span)) + .ok_or(Error::UnknownScalarType(span)), + (_, span) => Err(Error::UnknownScalarType(span)), + }?; + self.expect_generic_paren('>')?; + Ok(pair) + } + + pub(in crate::front::wgsl) fn next_storage_access( + &mut self, + ) -> Result> { + let (ident, span) = self.next_ident_with_span()?; + match ident { + "read" => Ok(crate::StorageAccess::LOAD), + "write" => Ok(crate::StorageAccess::STORE), + "read_write" => Ok(crate::StorageAccess::LOAD | crate::StorageAccess::STORE), + _ => Err(Error::UnknownAccess(span)), + } + } + + pub(in crate::front::wgsl) fn next_format_generic( + &mut self, + ) -> Result<(crate::StorageFormat, crate::StorageAccess), Error<'a>> { + self.expect(Token::Paren('<'))?; + let (ident, ident_span) = self.next_ident_with_span()?; + let format = conv::map_storage_format(ident, ident_span)?; + self.expect(Token::Separator(','))?; + let access = self.next_storage_access()?; + self.expect(Token::Paren('>'))?; + Ok((format, access)) + } + + pub(in crate::front::wgsl) fn open_arguments(&mut self) -> Result<(), Error<'a>> { + self.expect(Token::Paren('(')) + } + + pub(in crate::front::wgsl) fn close_arguments(&mut self) -> Result<(), Error<'a>> { + let _ = self.skip(Token::Separator(',')); + self.expect(Token::Paren(')')) + } + + pub(in crate::front::wgsl) fn next_argument(&mut self) -> Result> { + let paren = Token::Paren(')'); + if self.skip(Token::Separator(',')) { + Ok(!self.skip(paren)) + } else { + self.expect(paren).map(|()| false) + } + } +} + +#[cfg(test)] +#[track_caller] +fn sub_test(source: &str, expected_tokens: &[Token]) { + let mut lex = Lexer::new(source); + for &token in expected_tokens { + assert_eq!(lex.next().0, token); + } + assert_eq!(lex.next().0, Token::End); +} + +#[test] +fn test_numbers() { + // WGSL spec examples // + + // decimal integer + sub_test( + "0x123 0X123u 1u 123 0 0i 0x3f", + &[ + Token::Number(Ok(Number::AbstractInt(291))), + Token::Number(Ok(Number::U32(291))), + Token::Number(Ok(Number::U32(1))), + Token::Number(Ok(Number::AbstractInt(123))), + Token::Number(Ok(Number::AbstractInt(0))), + Token::Number(Ok(Number::I32(0))), + Token::Number(Ok(Number::AbstractInt(63))), + ], + ); + // decimal floating point + sub_test( + "0.e+4f 01. .01 12.34 .0f 0h 1e-3 0xa.fp+2 0x1P+4f 0X.3 0x3p+2h 0X1.fp-4 0x3.2p+2h", + &[ + Token::Number(Ok(Number::F32(0.))), + Token::Number(Ok(Number::AbstractFloat(1.))), + Token::Number(Ok(Number::AbstractFloat(0.01))), + Token::Number(Ok(Number::AbstractFloat(12.34))), + Token::Number(Ok(Number::F32(0.))), + Token::Number(Err(NumberError::UnimplementedF16)), + Token::Number(Ok(Number::AbstractFloat(0.001))), + Token::Number(Ok(Number::AbstractFloat(43.75))), + Token::Number(Ok(Number::F32(16.))), + Token::Number(Ok(Number::AbstractFloat(0.1875))), + Token::Number(Err(NumberError::UnimplementedF16)), + Token::Number(Ok(Number::AbstractFloat(0.12109375))), + Token::Number(Err(NumberError::UnimplementedF16)), + ], + ); + + // MIN / MAX // + + // min / max decimal integer + sub_test( + "0i 2147483647i 2147483648i", + &[ + Token::Number(Ok(Number::I32(0))), + Token::Number(Ok(Number::I32(i32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + // min / max decimal unsigned integer + sub_test( + "0u 4294967295u 4294967296u", + &[ + Token::Number(Ok(Number::U32(u32::MIN))), + Token::Number(Ok(Number::U32(u32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + + // min / max hexadecimal signed integer + sub_test( + "0x0i 0x7FFFFFFFi 0x80000000i", + &[ + Token::Number(Ok(Number::I32(0))), + Token::Number(Ok(Number::I32(i32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + // min / max hexadecimal unsigned integer + sub_test( + "0x0u 0xFFFFFFFFu 0x100000000u", + &[ + Token::Number(Ok(Number::U32(u32::MIN))), + Token::Number(Ok(Number::U32(u32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + + // min/max decimal abstract int + sub_test( + "0 9223372036854775807 9223372036854775808", + &[ + Token::Number(Ok(Number::AbstractInt(0))), + Token::Number(Ok(Number::AbstractInt(i64::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + + // min/max hexadecimal abstract int + sub_test( + "0 0x7fffffffffffffff 0x8000000000000000", + &[ + Token::Number(Ok(Number::AbstractInt(0))), + Token::Number(Ok(Number::AbstractInt(i64::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + + /// ≈ 2^-126 * 2^−23 (= 2^−149) + const SMALLEST_POSITIVE_SUBNORMAL_F32: f32 = 1e-45; + /// ≈ 2^-126 * (1 − 2^−23) + const LARGEST_SUBNORMAL_F32: f32 = 1.1754942e-38; + /// ≈ 2^-126 + const SMALLEST_POSITIVE_NORMAL_F32: f32 = f32::MIN_POSITIVE; + /// ≈ 1 − 2^−24 + const LARGEST_F32_LESS_THAN_ONE: f32 = 0.99999994; + /// ≈ 1 + 2^−23 + const SMALLEST_F32_LARGER_THAN_ONE: f32 = 1.0000001; + /// ≈ 2^127 * (2 − 2^−23) + const LARGEST_NORMAL_F32: f32 = f32::MAX; + + // decimal floating point + sub_test( + "1e-45f 1.1754942e-38f 1.17549435e-38f 0.99999994f 1.0000001f 3.40282347e+38f", + &[ + Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_SUBNORMAL_F32))), + Token::Number(Ok(Number::F32(LARGEST_SUBNORMAL_F32))), + Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_NORMAL_F32))), + Token::Number(Ok(Number::F32(LARGEST_F32_LESS_THAN_ONE))), + Token::Number(Ok(Number::F32(SMALLEST_F32_LARGER_THAN_ONE))), + Token::Number(Ok(Number::F32(LARGEST_NORMAL_F32))), + ], + ); + sub_test( + "3.40282367e+38f", + &[ + Token::Number(Err(NumberError::NotRepresentable)), // ≈ 2^128 + ], + ); + + // hexadecimal floating point + sub_test( + "0x1p-149f 0x7FFFFFp-149f 0x1p-126f 0xFFFFFFp-24f 0x800001p-23f 0xFFFFFFp+104f", + &[ + Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_SUBNORMAL_F32))), + Token::Number(Ok(Number::F32(LARGEST_SUBNORMAL_F32))), + Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_NORMAL_F32))), + Token::Number(Ok(Number::F32(LARGEST_F32_LESS_THAN_ONE))), + Token::Number(Ok(Number::F32(SMALLEST_F32_LARGER_THAN_ONE))), + Token::Number(Ok(Number::F32(LARGEST_NORMAL_F32))), + ], + ); + sub_test( + "0x1p128f 0x1.000001p0f", + &[ + Token::Number(Err(NumberError::NotRepresentable)), // = 2^128 + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); +} + +#[test] +fn double_floats() { + sub_test( + "0x1.2p4lf 0x1p8lf 0.0625lf 625e-4lf 10lf 10l", + &[ + Token::Number(Ok(Number::F64(18.0))), + Token::Number(Ok(Number::F64(256.0))), + Token::Number(Ok(Number::F64(0.0625))), + Token::Number(Ok(Number::F64(0.0625))), + Token::Number(Ok(Number::F64(10.0))), + Token::Number(Ok(Number::AbstractInt(10))), + Token::Word("l"), + ], + ) +} + +#[test] +fn test_tokens() { + sub_test("id123_OK", &[Token::Word("id123_OK")]); + sub_test( + "92No", + &[ + Token::Number(Ok(Number::AbstractInt(92))), + Token::Word("No"), + ], + ); + sub_test( + "2u3o", + &[ + Token::Number(Ok(Number::U32(2))), + Token::Number(Ok(Number::AbstractInt(3))), + Token::Word("o"), + ], + ); + sub_test( + "2.4f44po", + &[ + Token::Number(Ok(Number::F32(2.4))), + Token::Number(Ok(Number::AbstractInt(44))), + Token::Word("po"), + ], + ); + sub_test( + "Δέλτα réflexion Кызыл 𐰓𐰏𐰇 朝焼け سلام 검정 שָׁלוֹם गुलाबी փիրուզ", + &[ + Token::Word("Δέλτα"), + Token::Word("réflexion"), + Token::Word("Кызыл"), + Token::Word("𐰓𐰏𐰇"), + Token::Word("朝焼け"), + Token::Word("سلام"), + Token::Word("검정"), + Token::Word("שָׁלוֹם"), + Token::Word("गुलाबी"), + Token::Word("փիրուզ"), + ], + ); + sub_test("æNoø", &[Token::Word("æNoø")]); + sub_test("No¾", &[Token::Word("No"), Token::Unknown('¾')]); + sub_test("No好", &[Token::Word("No好")]); + sub_test("_No", &[Token::Word("_No")]); + sub_test( + "*/*/***/*//=/*****//", + &[ + Token::Operation('*'), + Token::AssignmentOperation('/'), + Token::Operation('/'), + ], + ); + + // Type suffixes are only allowed on hex float literals + // if you provided an exponent. + sub_test( + "0x1.2f 0x1.2f 0x1.2h 0x1.2H 0x1.2lf", + &[ + // The 'f' suffixes are taken as a hex digit: + // the fractional part is 0x2f / 256. + Token::Number(Ok(Number::AbstractFloat(1.0 + 0x2f as f64 / 256.0))), + Token::Number(Ok(Number::AbstractFloat(1.0 + 0x2f as f64 / 256.0))), + Token::Number(Ok(Number::AbstractFloat(1.125))), + Token::Word("h"), + Token::Number(Ok(Number::AbstractFloat(1.125))), + Token::Word("H"), + Token::Number(Ok(Number::AbstractFloat(1.125))), + Token::Word("lf"), + ], + ) +} + +#[test] +fn test_variable_decl() { + sub_test( + "@group(0 ) var< uniform> texture: texture_multisampled_2d ;", + &[ + Token::Attribute, + Token::Word("group"), + Token::Paren('('), + Token::Number(Ok(Number::AbstractInt(0))), + Token::Paren(')'), + Token::Word("var"), + Token::Paren('<'), + Token::Word("uniform"), + Token::Paren('>'), + Token::Word("texture"), + Token::Separator(':'), + Token::Word("texture_multisampled_2d"), + Token::Paren('<'), + Token::Word("f32"), + Token::Paren('>'), + Token::Separator(';'), + ], + ); + sub_test( + "var buffer: array;", + &[ + Token::Word("var"), + Token::Paren('<'), + Token::Word("storage"), + Token::Separator(','), + Token::Word("read_write"), + Token::Paren('>'), + Token::Word("buffer"), + Token::Separator(':'), + Token::Word("array"), + Token::Paren('<'), + Token::Word("u32"), + Token::Paren('>'), + Token::Separator(';'), + ], + ); +} diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs new file mode 100644 index 0000000000..51fc2f013b --- /dev/null +++ b/naga/src/front/wgsl/parse/mod.rs @@ -0,0 +1,2350 @@ +use crate::front::wgsl::error::{Error, ExpectedToken}; +use crate::front::wgsl::parse::lexer::{Lexer, Token}; +use crate::front::wgsl::parse::number::Number; +use crate::front::wgsl::Scalar; +use crate::front::SymbolTable; +use crate::{Arena, FastIndexSet, Handle, ShaderStage, Span}; + +pub mod ast; +pub mod conv; +pub mod lexer; +pub mod number; + +/// State for constructing an AST expression. +/// +/// Not to be confused with [`lower::ExpressionContext`], which is for producing +/// Naga IR from the AST we produce here. +/// +/// [`lower::ExpressionContext`]: super::lower::ExpressionContext +struct ExpressionContext<'input, 'temp, 'out> { + /// The [`TranslationUnit::expressions`] arena to which we should contribute + /// expressions. + /// + /// [`TranslationUnit::expressions`]: ast::TranslationUnit::expressions + expressions: &'out mut Arena>, + + /// The [`TranslationUnit::types`] arena to which we should contribute new + /// types. + /// + /// [`TranslationUnit::types`]: ast::TranslationUnit::types + types: &'out mut Arena>, + + /// A map from identifiers in scope to the locals/arguments they represent. + /// + /// The handles refer to the [`Function::locals`] area; see that field's + /// documentation for details. + /// + /// [`Function::locals`]: ast::Function::locals + local_table: &'temp mut SymbolTable<&'input str, Handle>, + + /// The [`Function::locals`] arena for the function we're building. + /// + /// [`Function::locals`]: ast::Function::locals + locals: &'out mut Arena, + + /// Identifiers used by the current global declaration that have no local definition. + /// + /// This becomes the [`GlobalDecl`]'s [`dependencies`] set. + /// + /// Note that we don't know at parse time what kind of [`GlobalDecl`] the + /// name refers to. We can't look up names until we've seen the entire + /// translation unit. + /// + /// [`GlobalDecl`]: ast::GlobalDecl + /// [`dependencies`]: ast::GlobalDecl::dependencies + unresolved: &'out mut FastIndexSet>, +} + +impl<'a> ExpressionContext<'a, '_, '_> { + fn parse_binary_op( + &mut self, + lexer: &mut Lexer<'a>, + classifier: impl Fn(Token<'a>) -> Option, + mut parser: impl FnMut( + &mut Lexer<'a>, + &mut Self, + ) -> Result>, Error<'a>>, + ) -> Result>, Error<'a>> { + let start = lexer.start_byte_offset(); + let mut accumulator = parser(lexer, self)?; + while let Some(op) = classifier(lexer.peek().0) { + let _ = lexer.next(); + let left = accumulator; + let right = parser(lexer, self)?; + accumulator = self.expressions.append( + ast::Expression::Binary { op, left, right }, + lexer.span_from(start), + ); + } + Ok(accumulator) + } + + fn declare_local(&mut self, name: ast::Ident<'a>) -> Result, Error<'a>> { + let handle = self.locals.append(ast::Local, name.span); + if let Some(old) = self.local_table.add(name.name, handle) { + Err(Error::Redefinition { + previous: self.locals.get_span(old), + current: name.span, + }) + } else { + Ok(handle) + } + } +} + +/// Which grammar rule we are in the midst of parsing. +/// +/// This is used for error checking. `Parser` maintains a stack of +/// these and (occasionally) checks that it is being pushed and popped +/// as expected. +#[derive(Clone, Debug, PartialEq)] +enum Rule { + Attribute, + VariableDecl, + TypeDecl, + FunctionDecl, + Block, + Statement, + PrimaryExpr, + SingularExpr, + UnaryExpr, + GeneralExpr, +} + +struct ParsedAttribute { + value: Option, +} + +impl Default for ParsedAttribute { + fn default() -> Self { + Self { value: None } + } +} + +impl ParsedAttribute { + fn set(&mut self, value: T, name_span: Span) -> Result<(), Error<'static>> { + if self.value.is_some() { + return Err(Error::RepeatedAttribute(name_span)); + } + self.value = Some(value); + Ok(()) + } +} + +#[derive(Default)] +struct BindingParser<'a> { + location: ParsedAttribute>>, + second_blend_source: ParsedAttribute, + built_in: ParsedAttribute, + interpolation: ParsedAttribute, + sampling: ParsedAttribute, + invariant: ParsedAttribute, +} + +impl<'a> BindingParser<'a> { + fn parse( + &mut self, + parser: &mut Parser, + lexer: &mut Lexer<'a>, + name: &'a str, + name_span: Span, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result<(), Error<'a>> { + match name { + "location" => { + lexer.expect(Token::Paren('('))?; + self.location + .set(parser.general_expression(lexer, ctx)?, name_span)?; + lexer.expect(Token::Paren(')'))?; + } + "builtin" => { + lexer.expect(Token::Paren('('))?; + let (raw, span) = lexer.next_ident_with_span()?; + self.built_in + .set(conv::map_built_in(raw, span)?, name_span)?; + lexer.expect(Token::Paren(')'))?; + } + "interpolate" => { + lexer.expect(Token::Paren('('))?; + let (raw, span) = lexer.next_ident_with_span()?; + self.interpolation + .set(conv::map_interpolation(raw, span)?, name_span)?; + if lexer.skip(Token::Separator(',')) { + let (raw, span) = lexer.next_ident_with_span()?; + self.sampling + .set(conv::map_sampling(raw, span)?, name_span)?; + } + lexer.expect(Token::Paren(')'))?; + } + "second_blend_source" => { + self.second_blend_source.set(true, name_span)?; + } + "invariant" => { + self.invariant.set(true, name_span)?; + } + _ => return Err(Error::UnknownAttribute(name_span)), + } + Ok(()) + } + + fn finish(self, span: Span) -> Result>, Error<'a>> { + match ( + self.location.value, + self.built_in.value, + self.interpolation.value, + self.sampling.value, + self.invariant.value.unwrap_or_default(), + ) { + (None, None, None, None, false) => Ok(None), + (Some(location), None, interpolation, sampling, false) => { + // Before handing over the completed `Module`, we call + // `apply_default_interpolation` to ensure that the interpolation and + // sampling have been explicitly specified on all vertex shader output and fragment + // shader input user bindings, so leaving them potentially `None` here is fine. + Ok(Some(ast::Binding::Location { + location, + interpolation, + sampling, + second_blend_source: self.second_blend_source.value.unwrap_or(false), + })) + } + (None, Some(crate::BuiltIn::Position { .. }), None, None, invariant) => { + Ok(Some(ast::Binding::BuiltIn(crate::BuiltIn::Position { + invariant, + }))) + } + (None, Some(built_in), None, None, false) => Ok(Some(ast::Binding::BuiltIn(built_in))), + (_, _, _, _, _) => Err(Error::InconsistentBinding(span)), + } + } +} + +pub struct Parser { + rules: Vec<(Rule, usize)>, +} + +impl Parser { + pub const fn new() -> Self { + Parser { rules: Vec::new() } + } + + fn reset(&mut self) { + self.rules.clear(); + } + + fn push_rule_span(&mut self, rule: Rule, lexer: &mut Lexer<'_>) { + self.rules.push((rule, lexer.start_byte_offset())); + } + + fn pop_rule_span(&mut self, lexer: &Lexer<'_>) -> Span { + let (_, initial) = self.rules.pop().unwrap(); + lexer.span_from(initial) + } + + fn peek_rule_span(&mut self, lexer: &Lexer<'_>) -> Span { + let &(_, initial) = self.rules.last().unwrap(); + lexer.span_from(initial) + } + + fn switch_value<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result, Error<'a>> { + if let Token::Word("default") = lexer.peek().0 { + let _ = lexer.next(); + return Ok(ast::SwitchValue::Default); + } + + let expr = self.general_expression(lexer, ctx)?; + Ok(ast::SwitchValue::Expr(expr)) + } + + /// Decide if we're looking at a construction expression, and return its + /// type if so. + /// + /// If the identifier `word` is a [type-defining keyword], then return a + /// [`ConstructorType`] value describing the type to build. Return an error + /// if the type is not constructible (like `sampler`). + /// + /// If `word` isn't a type name, then return `None`. + /// + /// [type-defining keyword]: https://gpuweb.github.io/gpuweb/wgsl/#type-defining-keywords + /// [`ConstructorType`]: ast::ConstructorType + fn constructor_type<'a>( + &mut self, + lexer: &mut Lexer<'a>, + word: &'a str, + span: Span, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + if let Some(scalar) = conv::get_scalar_type(word) { + return Ok(Some(ast::ConstructorType::Scalar(scalar))); + } + + let partial = match word { + "vec2" => ast::ConstructorType::PartialVector { + size: crate::VectorSize::Bi, + }, + "vec2i" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Bi, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, + })) + } + "vec2u" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Bi, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, + })) + } + "vec2f" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Bi, + scalar: Scalar::F32, + })) + } + "vec3" => ast::ConstructorType::PartialVector { + size: crate::VectorSize::Tri, + }, + "vec3i" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Tri, + scalar: Scalar::I32, + })) + } + "vec3u" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Tri, + scalar: Scalar::U32, + })) + } + "vec3f" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Tri, + scalar: Scalar::F32, + })) + } + "vec4" => ast::ConstructorType::PartialVector { + size: crate::VectorSize::Quad, + }, + "vec4i" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Quad, + scalar: Scalar::I32, + })) + } + "vec4u" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Quad, + scalar: Scalar::U32, + })) + } + "vec4f" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Quad, + scalar: Scalar::F32, + })) + } + "mat2x2" => ast::ConstructorType::PartialMatrix { + columns: crate::VectorSize::Bi, + rows: crate::VectorSize::Bi, + }, + "mat2x2f" => { + return Ok(Some(ast::ConstructorType::Matrix { + columns: crate::VectorSize::Bi, + rows: crate::VectorSize::Bi, + width: 4, + })) + } + "mat2x3" => ast::ConstructorType::PartialMatrix { + columns: crate::VectorSize::Bi, + rows: crate::VectorSize::Tri, + }, + "mat2x3f" => { + return Ok(Some(ast::ConstructorType::Matrix { + columns: crate::VectorSize::Bi, + rows: crate::VectorSize::Tri, + width: 4, + })) + } + "mat2x4" => ast::ConstructorType::PartialMatrix { + columns: crate::VectorSize::Bi, + rows: crate::VectorSize::Quad, + }, + "mat2x4f" => { + return Ok(Some(ast::ConstructorType::Matrix { + columns: crate::VectorSize::Bi, + rows: crate::VectorSize::Quad, + width: 4, + })) + } + "mat3x2" => ast::ConstructorType::PartialMatrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Bi, + }, + "mat3x2f" => { + return Ok(Some(ast::ConstructorType::Matrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Bi, + width: 4, + })) + } + "mat3x3" => ast::ConstructorType::PartialMatrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Tri, + }, + "mat3x3f" => { + return Ok(Some(ast::ConstructorType::Matrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Tri, + width: 4, + })) + } + "mat3x4" => ast::ConstructorType::PartialMatrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Quad, + }, + "mat3x4f" => { + return Ok(Some(ast::ConstructorType::Matrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Quad, + width: 4, + })) + } + "mat4x2" => ast::ConstructorType::PartialMatrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Bi, + }, + "mat4x2f" => { + return Ok(Some(ast::ConstructorType::Matrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Bi, + width: 4, + })) + } + "mat4x3" => ast::ConstructorType::PartialMatrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Tri, + }, + "mat4x3f" => { + return Ok(Some(ast::ConstructorType::Matrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Tri, + width: 4, + })) + } + "mat4x4" => ast::ConstructorType::PartialMatrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Quad, + }, + "mat4x4f" => { + return Ok(Some(ast::ConstructorType::Matrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Quad, + width: 4, + })) + } + "array" => ast::ConstructorType::PartialArray, + "atomic" + | "binding_array" + | "sampler" + | "sampler_comparison" + | "texture_1d" + | "texture_1d_array" + | "texture_2d" + | "texture_2d_array" + | "texture_3d" + | "texture_cube" + | "texture_cube_array" + | "texture_multisampled_2d" + | "texture_multisampled_2d_array" + | "texture_depth_2d" + | "texture_depth_2d_array" + | "texture_depth_cube" + | "texture_depth_cube_array" + | "texture_depth_multisampled_2d" + | "texture_storage_1d" + | "texture_storage_1d_array" + | "texture_storage_2d" + | "texture_storage_2d_array" + | "texture_storage_3d" => return Err(Error::TypeNotConstructible(span)), + _ => return Ok(None), + }; + + // parse component type if present + match (lexer.peek().0, partial) { + (Token::Paren('<'), ast::ConstructorType::PartialVector { size }) => { + let scalar = lexer.next_scalar_generic()?; + Ok(Some(ast::ConstructorType::Vector { size, scalar })) + } + (Token::Paren('<'), ast::ConstructorType::PartialMatrix { columns, rows }) => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + match scalar.kind { + crate::ScalarKind::Float => Ok(Some(ast::ConstructorType::Matrix { + columns, + rows, + width: scalar.width, + })), + _ => Err(Error::BadMatrixScalarKind(span, scalar)), + } + } + (Token::Paren('<'), ast::ConstructorType::PartialArray) => { + lexer.expect_generic_paren('<')?; + let base = self.type_decl(lexer, ctx)?; + let size = if lexer.skip(Token::Separator(',')) { + let expr = self.unary_expression(lexer, ctx)?; + ast::ArraySize::Constant(expr) + } else { + ast::ArraySize::Dynamic + }; + lexer.expect_generic_paren('>')?; + + Ok(Some(ast::ConstructorType::Array { base, size })) + } + (_, partial) => Ok(Some(partial)), + } + } + + /// Expects `name` to be consumed (not in lexer). + fn arguments<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>>, Error<'a>> { + lexer.open_arguments()?; + let mut arguments = Vec::new(); + loop { + if !arguments.is_empty() { + if !lexer.next_argument()? { + break; + } + } else if lexer.skip(Token::Paren(')')) { + break; + } + let arg = self.general_expression(lexer, ctx)?; + arguments.push(arg); + } + + Ok(arguments) + } + + /// Expects [`Rule::PrimaryExpr`] or [`Rule::SingularExpr`] on top; does not pop it. + /// Expects `name` to be consumed (not in lexer). + fn function_call<'a>( + &mut self, + lexer: &mut Lexer<'a>, + name: &'a str, + name_span: Span, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + assert!(self.rules.last().is_some()); + + let expr = match name { + // bitcast looks like a function call, but it's an operator and must be handled differently. + "bitcast" => { + lexer.expect_generic_paren('<')?; + let start = lexer.start_byte_offset(); + let to = self.type_decl(lexer, ctx)?; + let span = lexer.span_from(start); + lexer.expect_generic_paren('>')?; + + lexer.open_arguments()?; + let expr = self.general_expression(lexer, ctx)?; + lexer.close_arguments()?; + + ast::Expression::Bitcast { + expr, + to, + ty_span: span, + } + } + // everything else must be handled later, since they can be hidden by user-defined functions. + _ => { + let arguments = self.arguments(lexer, ctx)?; + ctx.unresolved.insert(ast::Dependency { + ident: name, + usage: name_span, + }); + ast::Expression::Call { + function: ast::Ident { + name, + span: name_span, + }, + arguments, + } + } + }; + + let span = self.peek_rule_span(lexer); + let expr = ctx.expressions.append(expr, span); + Ok(expr) + } + + fn ident_expr<'a>( + &mut self, + name: &'a str, + name_span: Span, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> ast::IdentExpr<'a> { + match ctx.local_table.lookup(name) { + Some(&local) => ast::IdentExpr::Local(local), + None => { + ctx.unresolved.insert(ast::Dependency { + ident: name, + usage: name_span, + }); + ast::IdentExpr::Unresolved(name) + } + } + } + + fn primary_expression<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + self.push_rule_span(Rule::PrimaryExpr, lexer); + + let expr = match lexer.peek() { + (Token::Paren('('), _) => { + let _ = lexer.next(); + let expr = self.general_expression(lexer, ctx)?; + lexer.expect(Token::Paren(')'))?; + self.pop_rule_span(lexer); + return Ok(expr); + } + (Token::Word("true"), _) => { + let _ = lexer.next(); + ast::Expression::Literal(ast::Literal::Bool(true)) + } + (Token::Word("false"), _) => { + let _ = lexer.next(); + ast::Expression::Literal(ast::Literal::Bool(false)) + } + (Token::Number(res), span) => { + let _ = lexer.next(); + let num = res.map_err(|err| Error::BadNumber(span, err))?; + ast::Expression::Literal(ast::Literal::Number(num)) + } + (Token::Word("RAY_FLAG_NONE"), _) => { + let _ = lexer.next(); + ast::Expression::Literal(ast::Literal::Number(Number::U32(0))) + } + (Token::Word("RAY_FLAG_TERMINATE_ON_FIRST_HIT"), _) => { + let _ = lexer.next(); + ast::Expression::Literal(ast::Literal::Number(Number::U32(4))) + } + (Token::Word("RAY_QUERY_INTERSECTION_NONE"), _) => { + let _ = lexer.next(); + ast::Expression::Literal(ast::Literal::Number(Number::U32(0))) + } + (Token::Word(word), span) => { + let start = lexer.start_byte_offset(); + let _ = lexer.next(); + + if let Some(ty) = self.constructor_type(lexer, word, span, ctx)? { + let ty_span = lexer.span_from(start); + let components = self.arguments(lexer, ctx)?; + ast::Expression::Construct { + ty, + ty_span, + components, + } + } else if let Token::Paren('(') = lexer.peek().0 { + self.pop_rule_span(lexer); + return self.function_call(lexer, word, span, ctx); + } else if word == "bitcast" { + self.pop_rule_span(lexer); + return self.function_call(lexer, word, span, ctx); + } else { + let ident = self.ident_expr(word, span, ctx); + ast::Expression::Ident(ident) + } + } + other => return Err(Error::Unexpected(other.1, ExpectedToken::PrimaryExpression)), + }; + + let span = self.pop_rule_span(lexer); + let expr = ctx.expressions.append(expr, span); + Ok(expr) + } + + fn postfix<'a>( + &mut self, + span_start: usize, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + expr: Handle>, + ) -> Result>, Error<'a>> { + let mut expr = expr; + + loop { + let expression = match lexer.peek().0 { + Token::Separator('.') => { + let _ = lexer.next(); + let field = lexer.next_ident()?; + + ast::Expression::Member { base: expr, field } + } + Token::Paren('[') => { + let _ = lexer.next(); + let index = self.general_expression(lexer, ctx)?; + lexer.expect(Token::Paren(']'))?; + + ast::Expression::Index { base: expr, index } + } + _ => break, + }; + + let span = lexer.span_from(span_start); + expr = ctx.expressions.append(expression, span); + } + + Ok(expr) + } + + /// Parse a `unary_expression`. + fn unary_expression<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + self.push_rule_span(Rule::UnaryExpr, lexer); + //TODO: refactor this to avoid backing up + let expr = match lexer.peek().0 { + Token::Operation('-') => { + let _ = lexer.next(); + let expr = self.unary_expression(lexer, ctx)?; + let expr = ast::Expression::Unary { + op: crate::UnaryOperator::Negate, + expr, + }; + let span = self.peek_rule_span(lexer); + ctx.expressions.append(expr, span) + } + Token::Operation('!') => { + let _ = lexer.next(); + let expr = self.unary_expression(lexer, ctx)?; + let expr = ast::Expression::Unary { + op: crate::UnaryOperator::LogicalNot, + expr, + }; + let span = self.peek_rule_span(lexer); + ctx.expressions.append(expr, span) + } + Token::Operation('~') => { + let _ = lexer.next(); + let expr = self.unary_expression(lexer, ctx)?; + let expr = ast::Expression::Unary { + op: crate::UnaryOperator::BitwiseNot, + expr, + }; + let span = self.peek_rule_span(lexer); + ctx.expressions.append(expr, span) + } + Token::Operation('*') => { + let _ = lexer.next(); + let expr = self.unary_expression(lexer, ctx)?; + let expr = ast::Expression::Deref(expr); + let span = self.peek_rule_span(lexer); + ctx.expressions.append(expr, span) + } + Token::Operation('&') => { + let _ = lexer.next(); + let expr = self.unary_expression(lexer, ctx)?; + let expr = ast::Expression::AddrOf(expr); + let span = self.peek_rule_span(lexer); + ctx.expressions.append(expr, span) + } + _ => self.singular_expression(lexer, ctx)?, + }; + + self.pop_rule_span(lexer); + Ok(expr) + } + + /// Parse a `singular_expression`. + fn singular_expression<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + let start = lexer.start_byte_offset(); + self.push_rule_span(Rule::SingularExpr, lexer); + let primary_expr = self.primary_expression(lexer, ctx)?; + let singular_expr = self.postfix(start, lexer, ctx, primary_expr)?; + self.pop_rule_span(lexer); + + Ok(singular_expr) + } + + fn equality_expression<'a>( + &mut self, + lexer: &mut Lexer<'a>, + context: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + // equality_expression + context.parse_binary_op( + lexer, + |token| match token { + Token::LogicalOperation('=') => Some(crate::BinaryOperator::Equal), + Token::LogicalOperation('!') => Some(crate::BinaryOperator::NotEqual), + _ => None, + }, + // relational_expression + |lexer, context| { + context.parse_binary_op( + lexer, + |token| match token { + Token::Paren('<') => Some(crate::BinaryOperator::Less), + Token::Paren('>') => Some(crate::BinaryOperator::Greater), + Token::LogicalOperation('<') => Some(crate::BinaryOperator::LessEqual), + Token::LogicalOperation('>') => Some(crate::BinaryOperator::GreaterEqual), + _ => None, + }, + // shift_expression + |lexer, context| { + context.parse_binary_op( + lexer, + |token| match token { + Token::ShiftOperation('<') => { + Some(crate::BinaryOperator::ShiftLeft) + } + Token::ShiftOperation('>') => { + Some(crate::BinaryOperator::ShiftRight) + } + _ => None, + }, + // additive_expression + |lexer, context| { + context.parse_binary_op( + lexer, + |token| match token { + Token::Operation('+') => Some(crate::BinaryOperator::Add), + Token::Operation('-') => { + Some(crate::BinaryOperator::Subtract) + } + _ => None, + }, + // multiplicative_expression + |lexer, context| { + context.parse_binary_op( + lexer, + |token| match token { + Token::Operation('*') => { + Some(crate::BinaryOperator::Multiply) + } + Token::Operation('/') => { + Some(crate::BinaryOperator::Divide) + } + Token::Operation('%') => { + Some(crate::BinaryOperator::Modulo) + } + _ => None, + }, + |lexer, context| self.unary_expression(lexer, context), + ) + }, + ) + }, + ) + }, + ) + }, + ) + } + + fn general_expression<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + self.general_expression_with_span(lexer, ctx) + .map(|(expr, _)| expr) + } + + fn general_expression_with_span<'a>( + &mut self, + lexer: &mut Lexer<'a>, + context: &mut ExpressionContext<'a, '_, '_>, + ) -> Result<(Handle>, Span), Error<'a>> { + self.push_rule_span(Rule::GeneralExpr, lexer); + // logical_or_expression + let handle = context.parse_binary_op( + lexer, + |token| match token { + Token::LogicalOperation('|') => Some(crate::BinaryOperator::LogicalOr), + _ => None, + }, + // logical_and_expression + |lexer, context| { + context.parse_binary_op( + lexer, + |token| match token { + Token::LogicalOperation('&') => Some(crate::BinaryOperator::LogicalAnd), + _ => None, + }, + // inclusive_or_expression + |lexer, context| { + context.parse_binary_op( + lexer, + |token| match token { + Token::Operation('|') => Some(crate::BinaryOperator::InclusiveOr), + _ => None, + }, + // exclusive_or_expression + |lexer, context| { + context.parse_binary_op( + lexer, + |token| match token { + Token::Operation('^') => { + Some(crate::BinaryOperator::ExclusiveOr) + } + _ => None, + }, + // and_expression + |lexer, context| { + context.parse_binary_op( + lexer, + |token| match token { + Token::Operation('&') => { + Some(crate::BinaryOperator::And) + } + _ => None, + }, + |lexer, context| { + self.equality_expression(lexer, context) + }, + ) + }, + ) + }, + ) + }, + ) + }, + )?; + Ok((handle, self.pop_rule_span(lexer))) + } + + fn variable_decl<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result, Error<'a>> { + self.push_rule_span(Rule::VariableDecl, lexer); + let mut space = crate::AddressSpace::Handle; + + if lexer.skip(Token::Paren('<')) { + let (class_str, span) = lexer.next_ident_with_span()?; + space = match class_str { + "storage" => { + let access = if lexer.skip(Token::Separator(',')) { + lexer.next_storage_access()? + } else { + // defaulting to `read` + crate::StorageAccess::LOAD + }; + crate::AddressSpace::Storage { access } + } + _ => conv::map_address_space(class_str, span)?, + }; + lexer.expect(Token::Paren('>'))?; + } + let name = lexer.next_ident()?; + lexer.expect(Token::Separator(':'))?; + let ty = self.type_decl(lexer, ctx)?; + + let init = if lexer.skip(Token::Operation('=')) { + let handle = self.general_expression(lexer, ctx)?; + Some(handle) + } else { + None + }; + lexer.expect(Token::Separator(';'))?; + self.pop_rule_span(lexer); + + Ok(ast::GlobalVariable { + name, + space, + binding: None, + ty, + init, + }) + } + + fn struct_body<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + let mut members = Vec::new(); + + lexer.expect(Token::Paren('{'))?; + let mut ready = true; + while !lexer.skip(Token::Paren('}')) { + if !ready { + return Err(Error::Unexpected( + lexer.next().1, + ExpectedToken::Token(Token::Separator(',')), + )); + } + let (mut size, mut align) = (ParsedAttribute::default(), ParsedAttribute::default()); + self.push_rule_span(Rule::Attribute, lexer); + let mut bind_parser = BindingParser::default(); + while lexer.skip(Token::Attribute) { + match lexer.next_ident_with_span()? { + ("size", name_span) => { + lexer.expect(Token::Paren('('))?; + let expr = self.general_expression(lexer, ctx)?; + lexer.expect(Token::Paren(')'))?; + size.set(expr, name_span)?; + } + ("align", name_span) => { + lexer.expect(Token::Paren('('))?; + let expr = self.general_expression(lexer, ctx)?; + lexer.expect(Token::Paren(')'))?; + align.set(expr, name_span)?; + } + (word, word_span) => bind_parser.parse(self, lexer, word, word_span, ctx)?, + } + } + + let bind_span = self.pop_rule_span(lexer); + let binding = bind_parser.finish(bind_span)?; + + let name = lexer.next_ident()?; + lexer.expect(Token::Separator(':'))?; + let ty = self.type_decl(lexer, ctx)?; + ready = lexer.skip(Token::Separator(',')); + + members.push(ast::StructMember { + name, + ty, + binding, + size: size.value, + align: align.value, + }); + } + + Ok(members) + } + + fn matrix_scalar_type<'a>( + &mut self, + lexer: &mut Lexer<'a>, + columns: crate::VectorSize, + rows: crate::VectorSize, + ) -> Result, Error<'a>> { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + match scalar.kind { + crate::ScalarKind::Float => Ok(ast::Type::Matrix { + columns, + rows, + width: scalar.width, + }), + _ => Err(Error::BadMatrixScalarKind(span, scalar)), + } + } + + fn type_decl_impl<'a>( + &mut self, + lexer: &mut Lexer<'a>, + word: &'a str, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + if let Some(scalar) = conv::get_scalar_type(word) { + return Ok(Some(ast::Type::Scalar(scalar))); + } + + Ok(Some(match word { + "vec2" => { + let scalar = lexer.next_scalar_generic()?; + ast::Type::Vector { + size: crate::VectorSize::Bi, + scalar, + } + } + "vec2i" => ast::Type::Vector { + size: crate::VectorSize::Bi, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, + }, + "vec2u" => ast::Type::Vector { + size: crate::VectorSize::Bi, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, + }, + "vec2f" => ast::Type::Vector { + size: crate::VectorSize::Bi, + scalar: Scalar::F32, + }, + "vec3" => { + let scalar = lexer.next_scalar_generic()?; + ast::Type::Vector { + size: crate::VectorSize::Tri, + scalar, + } + } + "vec3i" => ast::Type::Vector { + size: crate::VectorSize::Tri, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, + }, + "vec3u" => ast::Type::Vector { + size: crate::VectorSize::Tri, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, + }, + "vec3f" => ast::Type::Vector { + size: crate::VectorSize::Tri, + scalar: Scalar::F32, + }, + "vec4" => { + let scalar = lexer.next_scalar_generic()?; + ast::Type::Vector { + size: crate::VectorSize::Quad, + scalar, + } + } + "vec4i" => ast::Type::Vector { + size: crate::VectorSize::Quad, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, + }, + "vec4u" => ast::Type::Vector { + size: crate::VectorSize::Quad, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, + }, + "vec4f" => ast::Type::Vector { + size: crate::VectorSize::Quad, + scalar: Scalar::F32, + }, + "mat2x2" => { + self.matrix_scalar_type(lexer, crate::VectorSize::Bi, crate::VectorSize::Bi)? + } + "mat2x2f" => ast::Type::Matrix { + columns: crate::VectorSize::Bi, + rows: crate::VectorSize::Bi, + width: 4, + }, + "mat2x3" => { + self.matrix_scalar_type(lexer, crate::VectorSize::Bi, crate::VectorSize::Tri)? + } + "mat2x3f" => ast::Type::Matrix { + columns: crate::VectorSize::Bi, + rows: crate::VectorSize::Tri, + width: 4, + }, + "mat2x4" => { + self.matrix_scalar_type(lexer, crate::VectorSize::Bi, crate::VectorSize::Quad)? + } + "mat2x4f" => ast::Type::Matrix { + columns: crate::VectorSize::Bi, + rows: crate::VectorSize::Quad, + width: 4, + }, + "mat3x2" => { + self.matrix_scalar_type(lexer, crate::VectorSize::Tri, crate::VectorSize::Bi)? + } + "mat3x2f" => ast::Type::Matrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Bi, + width: 4, + }, + "mat3x3" => { + self.matrix_scalar_type(lexer, crate::VectorSize::Tri, crate::VectorSize::Tri)? + } + "mat3x3f" => ast::Type::Matrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Tri, + width: 4, + }, + "mat3x4" => { + self.matrix_scalar_type(lexer, crate::VectorSize::Tri, crate::VectorSize::Quad)? + } + "mat3x4f" => ast::Type::Matrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Quad, + width: 4, + }, + "mat4x2" => { + self.matrix_scalar_type(lexer, crate::VectorSize::Quad, crate::VectorSize::Bi)? + } + "mat4x2f" => ast::Type::Matrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Bi, + width: 4, + }, + "mat4x3" => { + self.matrix_scalar_type(lexer, crate::VectorSize::Quad, crate::VectorSize::Tri)? + } + "mat4x3f" => ast::Type::Matrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Tri, + width: 4, + }, + "mat4x4" => { + self.matrix_scalar_type(lexer, crate::VectorSize::Quad, crate::VectorSize::Quad)? + } + "mat4x4f" => ast::Type::Matrix { + columns: crate::VectorSize::Quad, + rows: crate::VectorSize::Quad, + width: 4, + }, + "atomic" => { + let scalar = lexer.next_scalar_generic()?; + ast::Type::Atomic(scalar) + } + "ptr" => { + lexer.expect_generic_paren('<')?; + let (ident, span) = lexer.next_ident_with_span()?; + let mut space = conv::map_address_space(ident, span)?; + lexer.expect(Token::Separator(','))?; + let base = self.type_decl(lexer, ctx)?; + if let crate::AddressSpace::Storage { ref mut access } = space { + *access = if lexer.skip(Token::Separator(',')) { + lexer.next_storage_access()? + } else { + crate::StorageAccess::LOAD + }; + } + lexer.expect_generic_paren('>')?; + ast::Type::Pointer { base, space } + } + "array" => { + lexer.expect_generic_paren('<')?; + let base = self.type_decl(lexer, ctx)?; + let size = if lexer.skip(Token::Separator(',')) { + let size = self.unary_expression(lexer, ctx)?; + ast::ArraySize::Constant(size) + } else { + ast::ArraySize::Dynamic + }; + lexer.expect_generic_paren('>')?; + + ast::Type::Array { base, size } + } + "binding_array" => { + lexer.expect_generic_paren('<')?; + let base = self.type_decl(lexer, ctx)?; + let size = if lexer.skip(Token::Separator(',')) { + let size = self.unary_expression(lexer, ctx)?; + ast::ArraySize::Constant(size) + } else { + ast::ArraySize::Dynamic + }; + lexer.expect_generic_paren('>')?; + + ast::Type::BindingArray { base, size } + } + "sampler" => ast::Type::Sampler { comparison: false }, + "sampler_comparison" => ast::Type::Sampler { comparison: true }, + "texture_1d" => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D1, + arrayed: false, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, + } + } + "texture_1d_array" => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D1, + arrayed: true, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, + } + } + "texture_2d" => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, + } + } + "texture_2d_array" => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: true, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, + } + } + "texture_3d" => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D3, + arrayed: false, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, + } + } + "texture_cube" => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; + ast::Type::Image { + dim: crate::ImageDimension::Cube, + arrayed: false, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, + } + } + "texture_cube_array" => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; + ast::Type::Image { + dim: crate::ImageDimension::Cube, + arrayed: true, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, + } + } + "texture_multisampled_2d" => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: true, + }, + } + } + "texture_multisampled_2d_array" => { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: true, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: true, + }, + } + } + "texture_depth_2d" => ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::Depth { multi: false }, + }, + "texture_depth_2d_array" => ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: true, + class: crate::ImageClass::Depth { multi: false }, + }, + "texture_depth_cube" => ast::Type::Image { + dim: crate::ImageDimension::Cube, + arrayed: false, + class: crate::ImageClass::Depth { multi: false }, + }, + "texture_depth_cube_array" => ast::Type::Image { + dim: crate::ImageDimension::Cube, + arrayed: true, + class: crate::ImageClass::Depth { multi: false }, + }, + "texture_depth_multisampled_2d" => ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::Depth { multi: true }, + }, + "texture_storage_1d" => { + let (format, access) = lexer.next_format_generic()?; + ast::Type::Image { + dim: crate::ImageDimension::D1, + arrayed: false, + class: crate::ImageClass::Storage { format, access }, + } + } + "texture_storage_1d_array" => { + let (format, access) = lexer.next_format_generic()?; + ast::Type::Image { + dim: crate::ImageDimension::D1, + arrayed: true, + class: crate::ImageClass::Storage { format, access }, + } + } + "texture_storage_2d" => { + let (format, access) = lexer.next_format_generic()?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::Storage { format, access }, + } + } + "texture_storage_2d_array" => { + let (format, access) = lexer.next_format_generic()?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: true, + class: crate::ImageClass::Storage { format, access }, + } + } + "texture_storage_3d" => { + let (format, access) = lexer.next_format_generic()?; + ast::Type::Image { + dim: crate::ImageDimension::D3, + arrayed: false, + class: crate::ImageClass::Storage { format, access }, + } + } + "acceleration_structure" => ast::Type::AccelerationStructure, + "ray_query" => ast::Type::RayQuery, + "RayDesc" => ast::Type::RayDesc, + "RayIntersection" => ast::Type::RayIntersection, + _ => return Ok(None), + })) + } + + const fn check_texture_sample_type(scalar: Scalar, span: Span) -> Result<(), Error<'static>> { + use crate::ScalarKind::*; + // Validate according to https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type + match scalar { + Scalar { + kind: Float | Sint | Uint, + width: 4, + } => Ok(()), + _ => Err(Error::BadTextureSampleType { span, scalar }), + } + } + + /// Parse type declaration of a given name. + fn type_decl<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + self.push_rule_span(Rule::TypeDecl, lexer); + + let (name, span) = lexer.next_ident_with_span()?; + + let ty = match self.type_decl_impl(lexer, name, ctx)? { + Some(ty) => ty, + None => { + ctx.unresolved.insert(ast::Dependency { + ident: name, + usage: span, + }); + ast::Type::User(ast::Ident { name, span }) + } + }; + + self.pop_rule_span(lexer); + + let handle = ctx.types.append(ty, Span::UNDEFINED); + Ok(handle) + } + + fn assignment_op_and_rhs<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + block: &mut ast::Block<'a>, + target: Handle>, + span_start: usize, + ) -> Result<(), Error<'a>> { + use crate::BinaryOperator as Bo; + + let op = lexer.next(); + let (op, value) = match op { + (Token::Operation('='), _) => { + let value = self.general_expression(lexer, ctx)?; + (None, value) + } + (Token::AssignmentOperation(c), _) => { + let op = match c { + '<' => Bo::ShiftLeft, + '>' => Bo::ShiftRight, + '+' => Bo::Add, + '-' => Bo::Subtract, + '*' => Bo::Multiply, + '/' => Bo::Divide, + '%' => Bo::Modulo, + '&' => Bo::And, + '|' => Bo::InclusiveOr, + '^' => Bo::ExclusiveOr, + // Note: `consume_token` shouldn't produce any other assignment ops + _ => unreachable!(), + }; + + let value = self.general_expression(lexer, ctx)?; + (Some(op), value) + } + token @ (Token::IncrementOperation | Token::DecrementOperation, _) => { + let op = match token.0 { + Token::IncrementOperation => ast::StatementKind::Increment, + Token::DecrementOperation => ast::StatementKind::Decrement, + _ => unreachable!(), + }; + + let span = lexer.span_from(span_start); + block.stmts.push(ast::Statement { + kind: op(target), + span, + }); + return Ok(()); + } + _ => return Err(Error::Unexpected(op.1, ExpectedToken::Assignment)), + }; + + let span = lexer.span_from(span_start); + block.stmts.push(ast::Statement { + kind: ast::StatementKind::Assign { target, op, value }, + span, + }); + Ok(()) + } + + /// Parse an assignment statement (will also parse increment and decrement statements) + fn assignment_statement<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + block: &mut ast::Block<'a>, + ) -> Result<(), Error<'a>> { + let span_start = lexer.start_byte_offset(); + let target = self.general_expression(lexer, ctx)?; + self.assignment_op_and_rhs(lexer, ctx, block, target, span_start) + } + + /// Parse a function call statement. + /// Expects `ident` to be consumed (not in the lexer). + fn function_statement<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ident: &'a str, + ident_span: Span, + span_start: usize, + context: &mut ExpressionContext<'a, '_, '_>, + block: &mut ast::Block<'a>, + ) -> Result<(), Error<'a>> { + self.push_rule_span(Rule::SingularExpr, lexer); + + context.unresolved.insert(ast::Dependency { + ident, + usage: ident_span, + }); + let arguments = self.arguments(lexer, context)?; + let span = lexer.span_from(span_start); + + block.stmts.push(ast::Statement { + kind: ast::StatementKind::Call { + function: ast::Ident { + name: ident, + span: ident_span, + }, + arguments, + }, + span, + }); + + self.pop_rule_span(lexer); + + Ok(()) + } + + fn function_call_or_assignment_statement<'a>( + &mut self, + lexer: &mut Lexer<'a>, + context: &mut ExpressionContext<'a, '_, '_>, + block: &mut ast::Block<'a>, + ) -> Result<(), Error<'a>> { + let span_start = lexer.start_byte_offset(); + match lexer.peek() { + (Token::Word(name), span) => { + // A little hack for 2 token lookahead. + let cloned = lexer.clone(); + let _ = lexer.next(); + match lexer.peek() { + (Token::Paren('('), _) => { + self.function_statement(lexer, name, span, span_start, context, block) + } + _ => { + *lexer = cloned; + self.assignment_statement(lexer, context, block) + } + } + } + _ => self.assignment_statement(lexer, context, block), + } + } + + fn statement<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + block: &mut ast::Block<'a>, + ) -> Result<(), Error<'a>> { + self.push_rule_span(Rule::Statement, lexer); + match lexer.peek() { + (Token::Separator(';'), _) => { + let _ = lexer.next(); + self.pop_rule_span(lexer); + return Ok(()); + } + (Token::Paren('{'), _) => { + let (inner, span) = self.block(lexer, ctx)?; + block.stmts.push(ast::Statement { + kind: ast::StatementKind::Block(inner), + span, + }); + self.pop_rule_span(lexer); + return Ok(()); + } + (Token::Word(word), _) => { + let kind = match word { + "_" => { + let _ = lexer.next(); + lexer.expect(Token::Operation('='))?; + let expr = self.general_expression(lexer, ctx)?; + lexer.expect(Token::Separator(';'))?; + + ast::StatementKind::Ignore(expr) + } + "let" => { + let _ = lexer.next(); + let name = lexer.next_ident()?; + + let given_ty = if lexer.skip(Token::Separator(':')) { + let ty = self.type_decl(lexer, ctx)?; + Some(ty) + } else { + None + }; + lexer.expect(Token::Operation('='))?; + let expr_id = self.general_expression(lexer, ctx)?; + lexer.expect(Token::Separator(';'))?; + + let handle = ctx.declare_local(name)?; + ast::StatementKind::LocalDecl(ast::LocalDecl::Let(ast::Let { + name, + ty: given_ty, + init: expr_id, + handle, + })) + } + "var" => { + let _ = lexer.next(); + + let name = lexer.next_ident()?; + let ty = if lexer.skip(Token::Separator(':')) { + let ty = self.type_decl(lexer, ctx)?; + Some(ty) + } else { + None + }; + + let init = if lexer.skip(Token::Operation('=')) { + let init = self.general_expression(lexer, ctx)?; + Some(init) + } else { + None + }; + + lexer.expect(Token::Separator(';'))?; + + let handle = ctx.declare_local(name)?; + ast::StatementKind::LocalDecl(ast::LocalDecl::Var(ast::LocalVariable { + name, + ty, + init, + handle, + })) + } + "return" => { + let _ = lexer.next(); + let value = if lexer.peek().0 != Token::Separator(';') { + let handle = self.general_expression(lexer, ctx)?; + Some(handle) + } else { + None + }; + lexer.expect(Token::Separator(';'))?; + ast::StatementKind::Return { value } + } + "if" => { + let _ = lexer.next(); + let condition = self.general_expression(lexer, ctx)?; + + let accept = self.block(lexer, ctx)?.0; + + let mut elsif_stack = Vec::new(); + let mut elseif_span_start = lexer.start_byte_offset(); + let mut reject = loop { + if !lexer.skip(Token::Word("else")) { + break ast::Block::default(); + } + + if !lexer.skip(Token::Word("if")) { + // ... else { ... } + break self.block(lexer, ctx)?.0; + } + + // ... else if (...) { ... } + let other_condition = self.general_expression(lexer, ctx)?; + let other_block = self.block(lexer, ctx)?; + elsif_stack.push((elseif_span_start, other_condition, other_block)); + elseif_span_start = lexer.start_byte_offset(); + }; + + // reverse-fold the else-if blocks + //Note: we may consider uplifting this to the IR + for (other_span_start, other_cond, other_block) in + elsif_stack.into_iter().rev() + { + let sub_stmt = ast::StatementKind::If { + condition: other_cond, + accept: other_block.0, + reject, + }; + reject = ast::Block::default(); + let span = lexer.span_from(other_span_start); + reject.stmts.push(ast::Statement { + kind: sub_stmt, + span, + }) + } + + ast::StatementKind::If { + condition, + accept, + reject, + } + } + "switch" => { + let _ = lexer.next(); + let selector = self.general_expression(lexer, ctx)?; + lexer.expect(Token::Paren('{'))?; + let mut cases = Vec::new(); + + loop { + // cases + default + match lexer.next() { + (Token::Word("case"), _) => { + // parse a list of values + let value = loop { + let value = self.switch_value(lexer, ctx)?; + if lexer.skip(Token::Separator(',')) { + if lexer.skip(Token::Separator(':')) { + break value; + } + } else { + lexer.skip(Token::Separator(':')); + break value; + } + cases.push(ast::SwitchCase { + value, + body: ast::Block::default(), + fall_through: true, + }); + }; + + let body = self.block(lexer, ctx)?.0; + + cases.push(ast::SwitchCase { + value, + body, + fall_through: false, + }); + } + (Token::Word("default"), _) => { + lexer.skip(Token::Separator(':')); + let body = self.block(lexer, ctx)?.0; + cases.push(ast::SwitchCase { + value: ast::SwitchValue::Default, + body, + fall_through: false, + }); + } + (Token::Paren('}'), _) => break, + (_, span) => { + return Err(Error::Unexpected(span, ExpectedToken::SwitchItem)) + } + } + } + + ast::StatementKind::Switch { selector, cases } + } + "loop" => self.r#loop(lexer, ctx)?, + "while" => { + let _ = lexer.next(); + let mut body = ast::Block::default(); + + let (condition, span) = lexer.capture_span(|lexer| { + let condition = self.general_expression(lexer, ctx)?; + Ok(condition) + })?; + let mut reject = ast::Block::default(); + reject.stmts.push(ast::Statement { + kind: ast::StatementKind::Break, + span, + }); + + body.stmts.push(ast::Statement { + kind: ast::StatementKind::If { + condition, + accept: ast::Block::default(), + reject, + }, + span, + }); + + let (block, span) = self.block(lexer, ctx)?; + body.stmts.push(ast::Statement { + kind: ast::StatementKind::Block(block), + span, + }); + + ast::StatementKind::Loop { + body, + continuing: ast::Block::default(), + break_if: None, + } + } + "for" => { + let _ = lexer.next(); + lexer.expect(Token::Paren('('))?; + + ctx.local_table.push_scope(); + + if !lexer.skip(Token::Separator(';')) { + let num_statements = block.stmts.len(); + let (_, span) = { + let ctx = &mut *ctx; + let block = &mut *block; + lexer.capture_span(|lexer| self.statement(lexer, ctx, block))? + }; + + if block.stmts.len() != num_statements { + match block.stmts.last().unwrap().kind { + ast::StatementKind::Call { .. } + | ast::StatementKind::Assign { .. } + | ast::StatementKind::LocalDecl(_) => {} + _ => return Err(Error::InvalidForInitializer(span)), + } + } + }; + + let mut body = ast::Block::default(); + if !lexer.skip(Token::Separator(';')) { + let (condition, span) = lexer.capture_span(|lexer| { + let condition = self.general_expression(lexer, ctx)?; + lexer.expect(Token::Separator(';'))?; + Ok(condition) + })?; + let mut reject = ast::Block::default(); + reject.stmts.push(ast::Statement { + kind: ast::StatementKind::Break, + span, + }); + body.stmts.push(ast::Statement { + kind: ast::StatementKind::If { + condition, + accept: ast::Block::default(), + reject, + }, + span, + }); + }; + + let mut continuing = ast::Block::default(); + if !lexer.skip(Token::Paren(')')) { + self.function_call_or_assignment_statement( + lexer, + ctx, + &mut continuing, + )?; + lexer.expect(Token::Paren(')'))?; + } + + let (block, span) = self.block(lexer, ctx)?; + body.stmts.push(ast::Statement { + kind: ast::StatementKind::Block(block), + span, + }); + + ctx.local_table.pop_scope(); + + ast::StatementKind::Loop { + body, + continuing, + break_if: None, + } + } + "break" => { + let (_, span) = lexer.next(); + // Check if the next token is an `if`, this indicates + // that the user tried to type out a `break if` which + // is illegal in this position. + let (peeked_token, peeked_span) = lexer.peek(); + if let Token::Word("if") = peeked_token { + let span = span.until(&peeked_span); + return Err(Error::InvalidBreakIf(span)); + } + lexer.expect(Token::Separator(';'))?; + ast::StatementKind::Break + } + "continue" => { + let _ = lexer.next(); + lexer.expect(Token::Separator(';'))?; + ast::StatementKind::Continue + } + "discard" => { + let _ = lexer.next(); + lexer.expect(Token::Separator(';'))?; + ast::StatementKind::Kill + } + // assignment or a function call + _ => { + self.function_call_or_assignment_statement(lexer, ctx, block)?; + lexer.expect(Token::Separator(';'))?; + self.pop_rule_span(lexer); + return Ok(()); + } + }; + + let span = self.pop_rule_span(lexer); + block.stmts.push(ast::Statement { kind, span }); + } + _ => { + self.assignment_statement(lexer, ctx, block)?; + lexer.expect(Token::Separator(';'))?; + self.pop_rule_span(lexer); + } + } + Ok(()) + } + + fn r#loop<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result, Error<'a>> { + let _ = lexer.next(); + let mut body = ast::Block::default(); + let mut continuing = ast::Block::default(); + let mut break_if = None; + + lexer.expect(Token::Paren('{'))?; + + ctx.local_table.push_scope(); + + loop { + if lexer.skip(Token::Word("continuing")) { + // Branch for the `continuing` block, this must be + // the last thing in the loop body + + // Expect a opening brace to start the continuing block + lexer.expect(Token::Paren('{'))?; + loop { + if lexer.skip(Token::Word("break")) { + // Branch for the `break if` statement, this statement + // has the form `break if ;` and must be the last + // statement in a continuing block + + // The break must be followed by an `if` to form + // the break if + lexer.expect(Token::Word("if"))?; + + let condition = self.general_expression(lexer, ctx)?; + // Set the condition of the break if to the newly parsed + // expression + break_if = Some(condition); + + // Expect a semicolon to close the statement + lexer.expect(Token::Separator(';'))?; + // Expect a closing brace to close the continuing block, + // since the break if must be the last statement + lexer.expect(Token::Paren('}'))?; + // Stop parsing the continuing block + break; + } else if lexer.skip(Token::Paren('}')) { + // If we encounter a closing brace it means we have reached + // the end of the continuing block and should stop processing + break; + } else { + // Otherwise try to parse a statement + self.statement(lexer, ctx, &mut continuing)?; + } + } + // Since the continuing block must be the last part of the loop body, + // we expect to see a closing brace to end the loop body + lexer.expect(Token::Paren('}'))?; + break; + } + if lexer.skip(Token::Paren('}')) { + // If we encounter a closing brace it means we have reached + // the end of the loop body and should stop processing + break; + } + // Otherwise try to parse a statement + self.statement(lexer, ctx, &mut body)?; + } + + ctx.local_table.pop_scope(); + + Ok(ast::StatementKind::Loop { + body, + continuing, + break_if, + }) + } + + /// compound_statement + fn block<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result<(ast::Block<'a>, Span), Error<'a>> { + self.push_rule_span(Rule::Block, lexer); + + ctx.local_table.push_scope(); + + lexer.expect(Token::Paren('{'))?; + let mut block = ast::Block::default(); + while !lexer.skip(Token::Paren('}')) { + self.statement(lexer, ctx, &mut block)?; + } + + ctx.local_table.pop_scope(); + + let span = self.pop_rule_span(lexer); + Ok((block, span)) + } + + fn varying_binding<'a>( + &mut self, + lexer: &mut Lexer<'a>, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + let mut bind_parser = BindingParser::default(); + self.push_rule_span(Rule::Attribute, lexer); + + while lexer.skip(Token::Attribute) { + let (word, span) = lexer.next_ident_with_span()?; + bind_parser.parse(self, lexer, word, span, ctx)?; + } + + let span = self.pop_rule_span(lexer); + bind_parser.finish(span) + } + + fn function_decl<'a>( + &mut self, + lexer: &mut Lexer<'a>, + out: &mut ast::TranslationUnit<'a>, + dependencies: &mut FastIndexSet>, + ) -> Result, Error<'a>> { + self.push_rule_span(Rule::FunctionDecl, lexer); + // read function name + let fun_name = lexer.next_ident()?; + + let mut locals = Arena::new(); + + let mut ctx = ExpressionContext { + expressions: &mut out.expressions, + local_table: &mut SymbolTable::default(), + locals: &mut locals, + types: &mut out.types, + unresolved: dependencies, + }; + + // start a scope that contains arguments as well as the function body + ctx.local_table.push_scope(); + + // read parameter list + let mut arguments = Vec::new(); + lexer.expect(Token::Paren('('))?; + let mut ready = true; + while !lexer.skip(Token::Paren(')')) { + if !ready { + return Err(Error::Unexpected( + lexer.next().1, + ExpectedToken::Token(Token::Separator(',')), + )); + } + let binding = self.varying_binding(lexer, &mut ctx)?; + + let param_name = lexer.next_ident()?; + + lexer.expect(Token::Separator(':'))?; + let param_type = self.type_decl(lexer, &mut ctx)?; + + let handle = ctx.declare_local(param_name)?; + arguments.push(ast::FunctionArgument { + name: param_name, + ty: param_type, + binding, + handle, + }); + ready = lexer.skip(Token::Separator(',')); + } + // read return type + let result = if lexer.skip(Token::Arrow) && !lexer.skip(Token::Word("void")) { + let binding = self.varying_binding(lexer, &mut ctx)?; + let ty = self.type_decl(lexer, &mut ctx)?; + Some(ast::FunctionResult { ty, binding }) + } else { + None + }; + + // do not use `self.block` here, since we must not push a new scope + lexer.expect(Token::Paren('{'))?; + let mut body = ast::Block::default(); + while !lexer.skip(Token::Paren('}')) { + self.statement(lexer, &mut ctx, &mut body)?; + } + + ctx.local_table.pop_scope(); + + let fun = ast::Function { + entry_point: None, + name: fun_name, + arguments, + result, + body, + locals, + }; + + // done + self.pop_rule_span(lexer); + + Ok(fun) + } + + fn global_decl<'a>( + &mut self, + lexer: &mut Lexer<'a>, + out: &mut ast::TranslationUnit<'a>, + ) -> Result<(), Error<'a>> { + // read attributes + let mut binding = None; + let mut stage = ParsedAttribute::default(); + let mut compute_span = Span::new(0, 0); + let mut workgroup_size = ParsedAttribute::default(); + let mut early_depth_test = ParsedAttribute::default(); + let (mut bind_index, mut bind_group) = + (ParsedAttribute::default(), ParsedAttribute::default()); + + let mut dependencies = FastIndexSet::default(); + let mut ctx = ExpressionContext { + expressions: &mut out.expressions, + local_table: &mut SymbolTable::default(), + locals: &mut Arena::new(), + types: &mut out.types, + unresolved: &mut dependencies, + }; + + self.push_rule_span(Rule::Attribute, lexer); + while lexer.skip(Token::Attribute) { + match lexer.next_ident_with_span()? { + ("binding", name_span) => { + lexer.expect(Token::Paren('('))?; + bind_index.set(self.general_expression(lexer, &mut ctx)?, name_span)?; + lexer.expect(Token::Paren(')'))?; + } + ("group", name_span) => { + lexer.expect(Token::Paren('('))?; + bind_group.set(self.general_expression(lexer, &mut ctx)?, name_span)?; + lexer.expect(Token::Paren(')'))?; + } + ("vertex", name_span) => { + stage.set(crate::ShaderStage::Vertex, name_span)?; + } + ("fragment", name_span) => { + stage.set(crate::ShaderStage::Fragment, name_span)?; + } + ("compute", name_span) => { + stage.set(crate::ShaderStage::Compute, name_span)?; + compute_span = name_span; + } + ("workgroup_size", name_span) => { + lexer.expect(Token::Paren('('))?; + let mut new_workgroup_size = [None; 3]; + for (i, size) in new_workgroup_size.iter_mut().enumerate() { + *size = Some(self.general_expression(lexer, &mut ctx)?); + match lexer.next() { + (Token::Paren(')'), _) => break, + (Token::Separator(','), _) if i != 2 => (), + other => { + return Err(Error::Unexpected( + other.1, + ExpectedToken::WorkgroupSizeSeparator, + )) + } + } + } + workgroup_size.set(new_workgroup_size, name_span)?; + } + ("early_depth_test", name_span) => { + let conservative = if lexer.skip(Token::Paren('(')) { + let (ident, ident_span) = lexer.next_ident_with_span()?; + let value = conv::map_conservative_depth(ident, ident_span)?; + lexer.expect(Token::Paren(')'))?; + Some(value) + } else { + None + }; + early_depth_test.set(crate::EarlyDepthTest { conservative }, name_span)?; + } + (_, word_span) => return Err(Error::UnknownAttribute(word_span)), + } + } + + let attrib_span = self.pop_rule_span(lexer); + match (bind_group.value, bind_index.value) { + (Some(group), Some(index)) => { + binding = Some(ast::ResourceBinding { + group, + binding: index, + }); + } + (Some(_), None) => return Err(Error::MissingAttribute("binding", attrib_span)), + (None, Some(_)) => return Err(Error::MissingAttribute("group", attrib_span)), + (None, None) => {} + } + + // read item + let start = lexer.start_byte_offset(); + let kind = match lexer.next() { + (Token::Separator(';'), _) => None, + (Token::Word("struct"), _) => { + let name = lexer.next_ident()?; + + let members = self.struct_body(lexer, &mut ctx)?; + Some(ast::GlobalDeclKind::Struct(ast::Struct { name, members })) + } + (Token::Word("alias"), _) => { + let name = lexer.next_ident()?; + + lexer.expect(Token::Operation('='))?; + let ty = self.type_decl(lexer, &mut ctx)?; + lexer.expect(Token::Separator(';'))?; + Some(ast::GlobalDeclKind::Type(ast::TypeAlias { name, ty })) + } + (Token::Word("const"), _) => { + let name = lexer.next_ident()?; + + let ty = if lexer.skip(Token::Separator(':')) { + let ty = self.type_decl(lexer, &mut ctx)?; + Some(ty) + } else { + None + }; + + lexer.expect(Token::Operation('='))?; + let init = self.general_expression(lexer, &mut ctx)?; + lexer.expect(Token::Separator(';'))?; + + Some(ast::GlobalDeclKind::Const(ast::Const { name, ty, init })) + } + (Token::Word("var"), _) => { + let mut var = self.variable_decl(lexer, &mut ctx)?; + var.binding = binding.take(); + Some(ast::GlobalDeclKind::Var(var)) + } + (Token::Word("fn"), _) => { + let function = self.function_decl(lexer, out, &mut dependencies)?; + Some(ast::GlobalDeclKind::Fn(ast::Function { + entry_point: if let Some(stage) = stage.value { + if stage == ShaderStage::Compute && workgroup_size.value.is_none() { + return Err(Error::MissingWorkgroupSize(compute_span)); + } + Some(ast::EntryPoint { + stage, + early_depth_test: early_depth_test.value, + workgroup_size: workgroup_size.value, + }) + } else { + None + }, + ..function + })) + } + (Token::End, _) => return Ok(()), + other => return Err(Error::Unexpected(other.1, ExpectedToken::GlobalItem)), + }; + + if let Some(kind) = kind { + out.decls.append( + ast::GlobalDecl { kind, dependencies }, + lexer.span_from(start), + ); + } + + if !self.rules.is_empty() { + log::error!("Reached the end of global decl, but rule stack is not empty"); + log::error!("Rules: {:?}", self.rules); + return Err(Error::Internal("rule stack is not empty")); + }; + + match binding { + None => Ok(()), + Some(_) => Err(Error::Internal("we had the attribute but no var?")), + } + } + + pub fn parse<'a>(&mut self, source: &'a str) -> Result, Error<'a>> { + self.reset(); + + let mut lexer = Lexer::new(source); + let mut tu = ast::TranslationUnit::default(); + loop { + match self.global_decl(&mut lexer, &mut tu) { + Err(error) => return Err(error), + Ok(()) => { + if lexer.peek().0 == Token::End { + break; + } + } + } + } + + Ok(tu) + } +} diff --git a/naga/src/front/wgsl/parse/number.rs b/naga/src/front/wgsl/parse/number.rs new file mode 100644 index 0000000000..7b09ac59bb --- /dev/null +++ b/naga/src/front/wgsl/parse/number.rs @@ -0,0 +1,420 @@ +use crate::front::wgsl::error::NumberError; +use crate::front::wgsl::parse::lexer::Token; + +/// When using this type assume no Abstract Int/Float for now +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Number { + /// Abstract Int (-2^63 ≤ i < 2^63) + AbstractInt(i64), + /// Abstract Float (IEEE-754 binary64) + AbstractFloat(f64), + /// Concrete i32 + I32(i32), + /// Concrete u32 + U32(u32), + /// Concrete f32 + F32(f32), + /// Concrete f64 + F64(f64), +} + +pub(in crate::front::wgsl) fn consume_number(input: &str) -> (Token<'_>, &str) { + let (result, rest) = parse(input); + (Token::Number(result), rest) +} + +enum Kind { + Int(IntKind), + Float(FloatKind), +} + +enum IntKind { + I32, + U32, +} + +#[derive(Debug)] +enum FloatKind { + F16, + F32, + F64, +} + +// The following regexes (from the WGSL spec) will be matched: + +// int_literal: +// | / 0 [iu]? / +// | / [1-9][0-9]* [iu]? / +// | / 0[xX][0-9a-fA-F]+ [iu]? / + +// decimal_float_literal: +// | / 0 [fh] / +// | / [1-9][0-9]* [fh] / +// | / [0-9]* \.[0-9]+ ([eE][+-]?[0-9]+)? [fh]? / +// | / [0-9]+ \.[0-9]* ([eE][+-]?[0-9]+)? [fh]? / +// | / [0-9]+ [eE][+-]?[0-9]+ [fh]? / + +// hex_float_literal: +// | / 0[xX][0-9a-fA-F]* \.[0-9a-fA-F]+ ([pP][+-]?[0-9]+ [fh]?)? / +// | / 0[xX][0-9a-fA-F]+ \.[0-9a-fA-F]* ([pP][+-]?[0-9]+ [fh]?)? / +// | / 0[xX][0-9a-fA-F]+ [pP][+-]?[0-9]+ [fh]? / + +// You could visualize the regex below via https://debuggex.com to get a rough idea what `parse` is doing +// (?:0[xX](?:([0-9a-fA-F]+\.[0-9a-fA-F]*|[0-9a-fA-F]*\.[0-9a-fA-F]+)(?:([pP][+-]?[0-9]+)([fh]?))?|([0-9a-fA-F]+)([pP][+-]?[0-9]+)([fh]?)|([0-9a-fA-F]+)([iu]?))|((?:[0-9]+[eE][+-]?[0-9]+|(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?))([fh]?)|((?:[0-9]|[1-9][0-9]+))([iufh]?)) + +// Leading signs are handled as unary operators. + +fn parse(input: &str) -> (Result, &str) { + /// returns `true` and consumes `X` bytes from the given byte buffer + /// if the given `X` nr of patterns are found at the start of the buffer + macro_rules! consume { + ($bytes:ident, $($pattern:pat),*) => { + match $bytes { + &[$($pattern),*, ref rest @ ..] => { $bytes = rest; true }, + _ => false, + } + }; + } + + /// consumes one byte from the given byte buffer + /// if one of the given patterns are found at the start of the buffer + /// returning the corresponding expr for the matched pattern + macro_rules! consume_map { + ($bytes:ident, [$( $($pattern:pat_param),* => $to:expr),* $(,)?]) => { + match $bytes { + $( &[ $($pattern),*, ref rest @ ..] => { $bytes = rest; Some($to) }, )* + _ => None, + } + }; + } + + /// consumes all consecutive bytes matched by the `0-9` pattern from the given byte buffer + /// returning the number of consumed bytes + macro_rules! consume_dec_digits { + ($bytes:ident) => {{ + let start_len = $bytes.len(); + while let &[b'0'..=b'9', ref rest @ ..] = $bytes { + $bytes = rest; + } + start_len - $bytes.len() + }}; + } + + /// consumes all consecutive bytes matched by the `0-9 | a-f | A-F` pattern from the given byte buffer + /// returning the number of consumed bytes + macro_rules! consume_hex_digits { + ($bytes:ident) => {{ + let start_len = $bytes.len(); + while let &[b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F', ref rest @ ..] = $bytes { + $bytes = rest; + } + start_len - $bytes.len() + }}; + } + + macro_rules! consume_float_suffix { + ($bytes:ident) => { + consume_map!($bytes, [ + b'h' => FloatKind::F16, + b'f' => FloatKind::F32, + b'l', b'f' => FloatKind::F64, + ]) + }; + } + + /// maps the given `&[u8]` (tail of the initial `input: &str`) to a `&str` + macro_rules! rest_to_str { + ($bytes:ident) => { + &input[input.len() - $bytes.len()..] + }; + } + + struct ExtractSubStr<'a>(&'a str); + + impl<'a> ExtractSubStr<'a> { + /// given an `input` and a `start` (tail of the `input`) + /// creates a new [`ExtractSubStr`](`Self`) + fn start(input: &'a str, start: &'a [u8]) -> Self { + let start = input.len() - start.len(); + Self(&input[start..]) + } + /// given an `end` (tail of the initial `input`) + /// returns a substring of `input` + fn end(&self, end: &'a [u8]) -> &'a str { + let end = self.0.len() - end.len(); + &self.0[..end] + } + } + + let mut bytes = input.as_bytes(); + + let general_extract = ExtractSubStr::start(input, bytes); + + if consume!(bytes, b'0', b'x' | b'X') { + let digits_extract = ExtractSubStr::start(input, bytes); + + let consumed = consume_hex_digits!(bytes); + + if consume!(bytes, b'.') { + let consumed_after_period = consume_hex_digits!(bytes); + + if consumed + consumed_after_period == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let significand = general_extract.end(bytes); + + if consume!(bytes, b'p' | b'P') { + consume!(bytes, b'+' | b'-'); + let consumed = consume_dec_digits!(bytes); + + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let number = general_extract.end(bytes); + + let kind = consume_float_suffix!(bytes); + + (parse_hex_float(number, kind), rest_to_str!(bytes)) + } else { + ( + parse_hex_float_missing_exponent(significand, None), + rest_to_str!(bytes), + ) + } + } else { + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let significand = general_extract.end(bytes); + let digits = digits_extract.end(bytes); + + let exp_extract = ExtractSubStr::start(input, bytes); + + if consume!(bytes, b'p' | b'P') { + consume!(bytes, b'+' | b'-'); + let consumed = consume_dec_digits!(bytes); + + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let exponent = exp_extract.end(bytes); + + let kind = consume_float_suffix!(bytes); + + ( + parse_hex_float_missing_period(significand, exponent, kind), + rest_to_str!(bytes), + ) + } else { + let kind = consume_map!(bytes, [b'i' => IntKind::I32, b'u' => IntKind::U32]); + + (parse_hex_int(digits, kind), rest_to_str!(bytes)) + } + } + } else { + let is_first_zero = bytes.first() == Some(&b'0'); + + let consumed = consume_dec_digits!(bytes); + + if consume!(bytes, b'.') { + let consumed_after_period = consume_dec_digits!(bytes); + + if consumed + consumed_after_period == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + if consume!(bytes, b'e' | b'E') { + consume!(bytes, b'+' | b'-'); + let consumed = consume_dec_digits!(bytes); + + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + } + + let number = general_extract.end(bytes); + + let kind = consume_float_suffix!(bytes); + + (parse_dec_float(number, kind), rest_to_str!(bytes)) + } else { + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + if consume!(bytes, b'e' | b'E') { + consume!(bytes, b'+' | b'-'); + let consumed = consume_dec_digits!(bytes); + + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let number = general_extract.end(bytes); + + let kind = consume_float_suffix!(bytes); + + (parse_dec_float(number, kind), rest_to_str!(bytes)) + } else { + // make sure the multi-digit numbers don't start with zero + if consumed > 1 && is_first_zero { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let digits = general_extract.end(bytes); + + let kind = consume_map!(bytes, [ + b'i' => Kind::Int(IntKind::I32), + b'u' => Kind::Int(IntKind::U32), + b'h' => Kind::Float(FloatKind::F16), + b'f' => Kind::Float(FloatKind::F32), + b'l', b'f' => Kind::Float(FloatKind::F64), + ]); + + (parse_dec(digits, kind), rest_to_str!(bytes)) + } + } + } +} + +fn parse_hex_float_missing_exponent( + // format: 0[xX] ( [0-9a-fA-F]+\.[0-9a-fA-F]* | [0-9a-fA-F]*\.[0-9a-fA-F]+ ) + significand: &str, + kind: Option, +) -> Result { + let hexf_input = format!("{}{}", significand, "p0"); + parse_hex_float(&hexf_input, kind) +} + +fn parse_hex_float_missing_period( + // format: 0[xX] [0-9a-fA-F]+ + significand: &str, + // format: [pP][+-]?[0-9]+ + exponent: &str, + kind: Option, +) -> Result { + let hexf_input = format!("{significand}.{exponent}"); + parse_hex_float(&hexf_input, kind) +} + +fn parse_hex_int( + // format: [0-9a-fA-F]+ + digits: &str, + kind: Option, +) -> Result { + parse_int(digits, kind, 16) +} + +fn parse_dec( + // format: ( [0-9] | [1-9][0-9]+ ) + digits: &str, + kind: Option, +) -> Result { + match kind { + None => parse_int(digits, None, 10), + Some(Kind::Int(kind)) => parse_int(digits, Some(kind), 10), + Some(Kind::Float(kind)) => parse_dec_float(digits, Some(kind)), + } +} + +// Float parsing notes + +// The following chapters of IEEE 754-2019 are relevant: +// +// 7.4 Overflow (largest finite number is exceeded by what would have been +// the rounded floating-point result were the exponent range unbounded) +// +// 7.5 Underflow (tiny non-zero result is detected; +// for decimal formats tininess is detected before rounding when a non-zero result +// computed as though both the exponent range and the precision were unbounded +// would lie strictly between 2^−126) +// +// 7.6 Inexact (rounded result differs from what would have been computed +// were both exponent range and precision unbounded) + +// The WGSL spec requires us to error: +// on overflow for decimal floating point literals +// on overflow and inexact for hexadecimal floating point literals +// (underflow is not mentioned) + +// hexf_parse errors on overflow, underflow, inexact +// rust std lib float from str handles overflow, underflow, inexact transparently (rounds and will not error) + +// Therefore we only check for overflow manually for decimal floating point literals + +// input format: 0[xX] ( [0-9a-fA-F]+\.[0-9a-fA-F]* | [0-9a-fA-F]*\.[0-9a-fA-F]+ ) [pP][+-]?[0-9]+ +fn parse_hex_float(input: &str, kind: Option) -> Result { + match kind { + None => match hexf_parse::parse_hexf64(input, false) { + Ok(num) => Ok(Number::AbstractFloat(num)), + // can only be ParseHexfErrorKind::Inexact but we can't check since it's private + _ => Err(NumberError::NotRepresentable), + }, + Some(FloatKind::F16) => Err(NumberError::UnimplementedF16), + Some(FloatKind::F32) => match hexf_parse::parse_hexf32(input, false) { + Ok(num) => Ok(Number::F32(num)), + // can only be ParseHexfErrorKind::Inexact but we can't check since it's private + _ => Err(NumberError::NotRepresentable), + }, + Some(FloatKind::F64) => match hexf_parse::parse_hexf64(input, false) { + Ok(num) => Ok(Number::F64(num)), + // can only be ParseHexfErrorKind::Inexact but we can't check since it's private + _ => Err(NumberError::NotRepresentable), + }, + } +} + +// input format: ( [0-9]+\.[0-9]* | [0-9]*\.[0-9]+ ) ([eE][+-]?[0-9]+)? +// | [0-9]+ [eE][+-]?[0-9]+ +fn parse_dec_float(input: &str, kind: Option) -> Result { + match kind { + None => { + let num = input.parse::().unwrap(); // will never fail + num.is_finite() + .then_some(Number::AbstractFloat(num)) + .ok_or(NumberError::NotRepresentable) + } + Some(FloatKind::F32) => { + let num = input.parse::().unwrap(); // will never fail + num.is_finite() + .then_some(Number::F32(num)) + .ok_or(NumberError::NotRepresentable) + } + Some(FloatKind::F64) => { + let num = input.parse::().unwrap(); // will never fail + num.is_finite() + .then_some(Number::F64(num)) + .ok_or(NumberError::NotRepresentable) + } + Some(FloatKind::F16) => Err(NumberError::UnimplementedF16), + } +} + +fn parse_int(input: &str, kind: Option, radix: u32) -> Result { + fn map_err(e: core::num::ParseIntError) -> NumberError { + match *e.kind() { + core::num::IntErrorKind::PosOverflow | core::num::IntErrorKind::NegOverflow => { + NumberError::NotRepresentable + } + _ => unreachable!(), + } + } + match kind { + None => match i64::from_str_radix(input, radix) { + Ok(num) => Ok(Number::AbstractInt(num)), + Err(e) => Err(map_err(e)), + }, + Some(IntKind::I32) => match i32::from_str_radix(input, radix) { + Ok(num) => Ok(Number::I32(num)), + Err(e) => Err(map_err(e)), + }, + Some(IntKind::U32) => match u32::from_str_radix(input, radix) { + Ok(num) => Ok(Number::U32(num)), + Err(e) => Err(map_err(e)), + }, + } +} diff --git a/naga/src/front/wgsl/tests.rs b/naga/src/front/wgsl/tests.rs new file mode 100644 index 0000000000..eb2f8a2eb3 --- /dev/null +++ b/naga/src/front/wgsl/tests.rs @@ -0,0 +1,637 @@ +use super::parse_str; + +#[test] +fn parse_comment() { + parse_str( + "// + //// + ///////////////////////////////////////////////////////// asda + //////////////////// dad ////////// / + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // + ", + ) + .unwrap(); +} + +#[test] +fn parse_types() { + parse_str("const a : i32 = 2;").unwrap(); + assert!(parse_str("const a : x32 = 2;").is_err()); + parse_str("var t: texture_2d;").unwrap(); + parse_str("var t: texture_cube_array;").unwrap(); + parse_str("var t: texture_multisampled_2d;").unwrap(); + parse_str("var t: texture_storage_1d;").unwrap(); + parse_str("var t: texture_storage_3d;").unwrap(); +} + +#[test] +fn parse_type_inference() { + parse_str( + " + fn foo() { + let a = 2u; + let b: u32 = a; + var x = 3.; + var y = vec2(1, 2); + }", + ) + .unwrap(); + assert!(parse_str( + " + fn foo() { let c : i32 = 2.0; }", + ) + .is_err()); +} + +#[test] +fn parse_type_cast() { + parse_str( + " + const a : i32 = 2; + fn main() { + var x: f32 = f32(a); + x = f32(i32(a + 1) / 2); + } + ", + ) + .unwrap(); + parse_str( + " + fn main() { + let x: vec2 = vec2(1.0, 2.0); + let y: vec2 = vec2(x); + } + ", + ) + .unwrap(); + parse_str( + " + fn main() { + let x: vec2 = vec2(0.0); + } + ", + ) + .unwrap(); + assert!(parse_str( + " + fn main() { + let x: vec2 = vec2(0i, 0i); + } + ", + ) + .is_err()); +} + +#[test] +fn parse_struct() { + parse_str( + " + struct Foo { x: i32 } + struct Bar { + @size(16) x: vec2, + @align(16) y: f32, + @size(32) @align(128) z: vec3, + }; + struct Empty {} + var s: Foo; + ", + ) + .unwrap(); +} + +#[test] +fn parse_standard_fun() { + parse_str( + " + fn main() { + var x: i32 = min(max(1, 2), 3); + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_statement() { + parse_str( + " + fn main() { + ; + {} + {;} + } + ", + ) + .unwrap(); + + parse_str( + " + fn foo() {} + fn bar() { foo(); } + ", + ) + .unwrap(); +} + +#[test] +fn parse_if() { + parse_str( + " + fn main() { + if true { + discard; + } else {} + if 0 != 1 {} + if false { + return; + } else if true { + return; + } else {} + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_parentheses_if() { + parse_str( + " + fn main() { + if (true) { + discard; + } else {} + if (0 != 1) {} + if (false) { + return; + } else if (true) { + return; + } else {} + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_loop() { + parse_str( + " + fn main() { + var i: i32 = 0; + loop { + if i == 1 { break; } + continuing { i = 1; } + } + loop { + if i == 0 { continue; } + break; + } + } + ", + ) + .unwrap(); + parse_str( + " + fn main() { + var found: bool = false; + var i: i32 = 0; + while !found { + if i == 10 { + found = true; + } + + i = i + 1; + } + } + ", + ) + .unwrap(); + parse_str( + " + fn main() { + while true { + break; + } + } + ", + ) + .unwrap(); + parse_str( + " + fn main() { + var a: i32 = 0; + for(var i: i32 = 0; i < 4; i = i + 1) { + a = a + 2; + } + } + ", + ) + .unwrap(); + parse_str( + " + fn main() { + for(;;) { + break; + } + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_switch() { + parse_str( + " + fn main() { + var pos: f32; + switch (3) { + case 0, 1: { pos = 0.0; } + case 2: { pos = 1.0; } + default: { pos = 3.0; } + } + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_switch_optional_colon_in_case() { + parse_str( + " + fn main() { + var pos: f32; + switch (3) { + case 0, 1 { pos = 0.0; } + case 2 { pos = 1.0; } + default { pos = 3.0; } + } + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_switch_default_in_case() { + parse_str( + " + fn main() { + var pos: f32; + switch (3) { + case 0, 1: { pos = 0.0; } + case 2: {} + case default, 3: { pos = 3.0; } + } + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_parentheses_switch() { + parse_str( + " + fn main() { + var pos: f32; + switch pos > 1.0 { + default: { pos = 3.0; } + } + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_texture_load() { + parse_str( + " + var t: texture_3d; + fn foo() { + let r: vec4 = textureLoad(t, vec3(0u, 1u, 2u), 1); + } + ", + ) + .unwrap(); + parse_str( + " + var t: texture_multisampled_2d_array; + fn foo() { + let r: vec4 = textureLoad(t, vec2(10, 20), 2, 3); + } + ", + ) + .unwrap(); + parse_str( + " + var t: texture_storage_1d_array; + fn foo() { + let r: vec4 = textureLoad(t, 10, 2); + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_texture_store() { + parse_str( + " + var t: texture_storage_2d; + fn foo() { + textureStore(t, vec2(10, 20), vec4(0.0, 1.0, 2.0, 3.0)); + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_texture_query() { + parse_str( + " + var t: texture_multisampled_2d_array; + fn foo() { + var dim: vec2 = textureDimensions(t); + dim = textureDimensions(t, 0); + let layers: u32 = textureNumLayers(t); + let samples: u32 = textureNumSamples(t); + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_postfix() { + parse_str( + "fn foo() { + let x: f32 = vec4(1.0, 2.0, 3.0, 4.0).xyz.rgbr.aaaa.wz.g; + let y: f32 = fract(vec2(0.5, x)).x; + }", + ) + .unwrap(); +} + +#[test] +fn parse_expressions() { + parse_str("fn foo() { + let x: f32 = select(0.0, 1.0, true); + let y: vec2 = select(vec2(1.0, 1.0), vec2(x, x), vec2(x < 0.5, x > 0.5)); + let z: bool = !(0.0 == 1.0); + }").unwrap(); +} + +#[test] +fn binary_expression_mixed_scalar_and_vector_operands() { + for (operand, expect_splat) in [ + ('<', false), + ('>', false), + ('&', false), + ('|', false), + ('+', true), + ('-', true), + ('*', false), + ('/', true), + ('%', true), + ] { + let module = parse_str(&format!( + " + @fragment + fn main(@location(0) some_vec: vec3) -> @location(0) vec4 {{ + if (all(1.0 {operand} some_vec)) {{ + return vec4(0.0); + }} + return vec4(1.0); + }} + " + )) + .unwrap(); + + let expressions = &&module.entry_points[0].function.expressions; + + let found_expressions = expressions + .iter() + .filter(|&(_, e)| { + if let crate::Expression::Binary { left, .. } = *e { + matches!( + (expect_splat, &expressions[left]), + (false, &crate::Expression::Literal(crate::Literal::F32(..))) + | (true, &crate::Expression::Splat { .. }) + ) + } else { + false + } + }) + .count(); + + assert_eq!( + found_expressions, + 1, + "expected `{operand}` expression {} splat", + if expect_splat { "with" } else { "without" } + ); + } + + let module = parse_str( + "@fragment + fn main(mat: mat3x3) { + let vec = vec3(1.0, 1.0, 1.0); + let result = mat / vec; + }", + ) + .unwrap(); + let expressions = &&module.entry_points[0].function.expressions; + let found_splat = expressions.iter().any(|(_, e)| { + if let crate::Expression::Binary { left, .. } = *e { + matches!(&expressions[left], &crate::Expression::Splat { .. }) + } else { + false + } + }); + assert!(!found_splat, "'mat / vec' should not be splatted"); +} + +#[test] +fn parse_pointers() { + parse_str( + "fn foo(a: ptr) -> f32 { return *a; } + fn bar() { + var x: f32 = 1.0; + let px = &x; + let py = foo(px); + }", + ) + .unwrap(); +} + +#[test] +fn parse_struct_instantiation() { + parse_str( + " + struct Foo { + a: f32, + b: vec3, + } + + @fragment + fn fs_main() { + var foo: Foo = Foo(0.0, vec3(0.0, 1.0, 42.0)); + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_array_length() { + parse_str( + " + struct Foo { + data: array + } // this is used as both input and output for convenience + + @group(0) @binding(0) + var foo: Foo; + + @group(0) @binding(1) + var bar: array; + + fn baz() { + var x: u32 = arrayLength(foo.data); + var y: u32 = arrayLength(bar); + } + ", + ) + .unwrap(); +} + +#[test] +fn parse_storage_buffers() { + parse_str( + " + @group(0) @binding(0) + var foo: array; + ", + ) + .unwrap(); + parse_str( + " + @group(0) @binding(0) + var foo: array; + ", + ) + .unwrap(); + parse_str( + " + @group(0) @binding(0) + var foo: array; + ", + ) + .unwrap(); + parse_str( + " + @group(0) @binding(0) + var foo: array; + ", + ) + .unwrap(); +} + +#[test] +fn parse_alias() { + parse_str( + " + alias Vec4 = vec4; + ", + ) + .unwrap(); +} + +#[test] +fn parse_texture_load_store_expecting_four_args() { + for (func, texture) in [ + ( + "textureStore", + "texture_storage_2d_array", + ), + ("textureLoad", "texture_2d_array"), + ] { + let error = parse_str(&format!( + " + @group(0) @binding(0) var tex_los_res: {texture}; + @compute + @workgroup_size(1) + fn main(@builtin(global_invocation_id) id: vec3) {{ + var color = vec4(1, 1, 1, 1); + {func}(tex_los_res, id, color); + }} + " + )) + .unwrap_err(); + assert_eq!( + error.message(), + "wrong number of arguments: expected 4, found 3" + ); + } +} + +#[test] +fn parse_repeated_attributes() { + use crate::{ + front::wgsl::{error::Error, Frontend}, + Span, + }; + + let template_vs = "@vertex fn vs() -> __REPLACE__ vec4 { return vec4(0.0); }"; + let template_struct = "struct A { __REPLACE__ data: vec3 }"; + let template_resource = "__REPLACE__ var tex_los_res: texture_2d_array;"; + let template_stage = "__REPLACE__ fn vs() -> vec4 { return vec4(0.0); }"; + for (attribute, template) in [ + ("align(16)", template_struct), + ("binding(0)", template_resource), + ("builtin(position)", template_vs), + ("compute", template_stage), + ("fragment", template_stage), + ("group(0)", template_resource), + ("interpolate(flat)", template_vs), + ("invariant", template_vs), + ("location(0)", template_vs), + ("size(16)", template_struct), + ("vertex", template_stage), + ("early_depth_test(less_equal)", template_resource), + ("workgroup_size(1)", template_stage), + ] { + let shader = template.replace("__REPLACE__", &format!("@{attribute} @{attribute}")); + let name_length = attribute.rfind('(').unwrap_or(attribute.len()) as u32; + let span_start = shader.rfind(attribute).unwrap() as u32; + let span_end = span_start + name_length; + let expected_span = Span::new(span_start, span_end); + + let result = Frontend::new().inner(&shader); + assert!(matches!( + result.unwrap_err(), + Error::RepeatedAttribute(span) if span == expected_span + )); + } +} + +#[test] +fn parse_missing_workgroup_size() { + use crate::{ + front::wgsl::{error::Error, Frontend}, + Span, + }; + + let shader = "@compute fn vs() -> vec4 { return vec4(0.0); }"; + let result = Frontend::new().inner(shader); + assert!(matches!( + result.unwrap_err(), + Error::MissingWorkgroupSize(span) if span == Span::new(1, 8) + )); +} diff --git a/naga/src/front/wgsl/to_wgsl.rs b/naga/src/front/wgsl/to_wgsl.rs new file mode 100644 index 0000000000..c8331ace09 --- /dev/null +++ b/naga/src/front/wgsl/to_wgsl.rs @@ -0,0 +1,283 @@ +//! Producing the WGSL forms of types, for use in error messages. + +use crate::proc::GlobalCtx; +use crate::Handle; + +impl crate::proc::TypeResolution { + pub fn to_wgsl(&self, gctx: &GlobalCtx) -> String { + match *self { + crate::proc::TypeResolution::Handle(handle) => handle.to_wgsl(gctx), + crate::proc::TypeResolution::Value(ref inner) => inner.to_wgsl(gctx), + } + } +} + +impl Handle { + /// Formats the type as it is written in wgsl. + /// + /// For example `vec3`. + pub fn to_wgsl(self, gctx: &GlobalCtx) -> String { + let ty = &gctx.types[self]; + match ty.name { + Some(ref name) => name.clone(), + None => ty.inner.to_wgsl(gctx), + } + } +} + +impl crate::TypeInner { + /// Formats the type as it is written in wgsl. + /// + /// For example `vec3`. + /// + /// Note: `TypeInner::Struct` doesn't include the name of the + /// struct type. Therefore this method will simply return "struct" + /// for them. + pub fn to_wgsl(&self, gctx: &GlobalCtx) -> String { + use crate::TypeInner as Ti; + + match *self { + Ti::Scalar(scalar) => scalar.to_wgsl(), + Ti::Vector { size, scalar } => { + format!("vec{}<{}>", size as u32, scalar.to_wgsl()) + } + Ti::Matrix { + columns, + rows, + scalar, + } => { + format!( + "mat{}x{}<{}>", + columns as u32, + rows as u32, + scalar.to_wgsl(), + ) + } + Ti::Atomic(scalar) => { + format!("atomic<{}>", scalar.to_wgsl()) + } + Ti::Pointer { base, .. } => { + let name = base.to_wgsl(gctx); + format!("ptr<{name}>") + } + Ti::ValuePointer { scalar, .. } => { + format!("ptr<{}>", scalar.to_wgsl()) + } + Ti::Array { base, size, .. } => { + let base = base.to_wgsl(gctx); + match size { + crate::ArraySize::Constant(size) => format!("array<{base}, {size}>"), + crate::ArraySize::Dynamic => format!("array<{base}>"), + } + } + Ti::Struct { .. } => { + // TODO: Actually output the struct? + "struct".to_string() + } + Ti::Image { + dim, + arrayed, + class, + } => { + let dim_suffix = match dim { + crate::ImageDimension::D1 => "_1d", + crate::ImageDimension::D2 => "_2d", + crate::ImageDimension::D3 => "_3d", + crate::ImageDimension::Cube => "_cube", + }; + let array_suffix = if arrayed { "_array" } else { "" }; + + let class_suffix = match class { + crate::ImageClass::Sampled { multi: true, .. } => "_multisampled", + crate::ImageClass::Depth { multi: false } => "_depth", + crate::ImageClass::Depth { multi: true } => "_depth_multisampled", + crate::ImageClass::Sampled { multi: false, .. } + | crate::ImageClass::Storage { .. } => "", + }; + + let type_in_brackets = match class { + crate::ImageClass::Sampled { kind, .. } => { + // Note: The only valid widths are 4 bytes wide. + // The lexer has already verified this, so we can safely assume it here. + // https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type + let element_type = crate::Scalar { kind, width: 4 }.to_wgsl(); + format!("<{element_type}>") + } + crate::ImageClass::Depth { multi: _ } => String::new(), + crate::ImageClass::Storage { format, access } => { + if access.contains(crate::StorageAccess::STORE) { + format!("<{},write>", format.to_wgsl()) + } else { + format!("<{}>", format.to_wgsl()) + } + } + }; + + format!("texture{class_suffix}{dim_suffix}{array_suffix}{type_in_brackets}") + } + Ti::Sampler { .. } => "sampler".to_string(), + Ti::AccelerationStructure => "acceleration_structure".to_string(), + Ti::RayQuery => "ray_query".to_string(), + Ti::BindingArray { base, size, .. } => { + let member_type = &gctx.types[base]; + let base = member_type.name.as_deref().unwrap_or("unknown"); + match size { + crate::ArraySize::Constant(size) => format!("binding_array<{base}, {size}>"), + crate::ArraySize::Dynamic => format!("binding_array<{base}>"), + } + } + } + } +} + +impl crate::Scalar { + /// Format a scalar kind+width as a type is written in wgsl. + /// + /// Examples: `f32`, `u64`, `bool`. + pub fn to_wgsl(self) -> String { + let prefix = match self.kind { + crate::ScalarKind::Sint => "i", + crate::ScalarKind::Uint => "u", + crate::ScalarKind::Float => "f", + crate::ScalarKind::Bool => return "bool".to_string(), + crate::ScalarKind::AbstractInt => return "{AbstractInt}".to_string(), + crate::ScalarKind::AbstractFloat => return "{AbstractFloat}".to_string(), + }; + format!("{}{}", prefix, self.width * 8) + } +} + +impl crate::StorageFormat { + pub const fn to_wgsl(self) -> &'static str { + use crate::StorageFormat as Sf; + match self { + Sf::R8Unorm => "r8unorm", + Sf::R8Snorm => "r8snorm", + Sf::R8Uint => "r8uint", + Sf::R8Sint => "r8sint", + Sf::R16Uint => "r16uint", + Sf::R16Sint => "r16sint", + Sf::R16Float => "r16float", + Sf::Rg8Unorm => "rg8unorm", + Sf::Rg8Snorm => "rg8snorm", + Sf::Rg8Uint => "rg8uint", + Sf::Rg8Sint => "rg8sint", + Sf::R32Uint => "r32uint", + Sf::R32Sint => "r32sint", + Sf::R32Float => "r32float", + Sf::Rg16Uint => "rg16uint", + Sf::Rg16Sint => "rg16sint", + Sf::Rg16Float => "rg16float", + Sf::Rgba8Unorm => "rgba8unorm", + Sf::Rgba8Snorm => "rgba8snorm", + Sf::Rgba8Uint => "rgba8uint", + Sf::Rgba8Sint => "rgba8sint", + Sf::Bgra8Unorm => "bgra8unorm", + Sf::Rgb10a2Uint => "rgb10a2uint", + Sf::Rgb10a2Unorm => "rgb10a2unorm", + Sf::Rg11b10Float => "rg11b10float", + Sf::Rg32Uint => "rg32uint", + Sf::Rg32Sint => "rg32sint", + Sf::Rg32Float => "rg32float", + Sf::Rgba16Uint => "rgba16uint", + Sf::Rgba16Sint => "rgba16sint", + Sf::Rgba16Float => "rgba16float", + Sf::Rgba32Uint => "rgba32uint", + Sf::Rgba32Sint => "rgba32sint", + Sf::Rgba32Float => "rgba32float", + Sf::R16Unorm => "r16unorm", + Sf::R16Snorm => "r16snorm", + Sf::Rg16Unorm => "rg16unorm", + Sf::Rg16Snorm => "rg16snorm", + Sf::Rgba16Unorm => "rgba16unorm", + Sf::Rgba16Snorm => "rgba16snorm", + } + } +} + +mod tests { + #[test] + fn to_wgsl() { + use std::num::NonZeroU32; + + let mut types = crate::UniqueArena::new(); + + let mytype1 = types.insert( + crate::Type { + name: Some("MyType1".to_string()), + inner: crate::TypeInner::Struct { + members: vec![], + span: 0, + }, + }, + Default::default(), + ); + let mytype2 = types.insert( + crate::Type { + name: Some("MyType2".to_string()), + inner: crate::TypeInner::Struct { + members: vec![], + span: 0, + }, + }, + Default::default(), + ); + + let gctx = crate::proc::GlobalCtx { + types: &types, + constants: &crate::Arena::new(), + const_expressions: &crate::Arena::new(), + }; + let array = crate::TypeInner::Array { + base: mytype1, + stride: 4, + size: crate::ArraySize::Constant(unsafe { NonZeroU32::new_unchecked(32) }), + }; + assert_eq!(array.to_wgsl(&gctx), "array"); + + let mat = crate::TypeInner::Matrix { + rows: crate::VectorSize::Quad, + columns: crate::VectorSize::Bi, + scalar: crate::Scalar::F64, + }; + assert_eq!(mat.to_wgsl(&gctx), "mat2x4"); + + let ptr = crate::TypeInner::Pointer { + base: mytype2, + space: crate::AddressSpace::Storage { + access: crate::StorageAccess::default(), + }, + }; + assert_eq!(ptr.to_wgsl(&gctx), "ptr"); + + let img1 = crate::TypeInner::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::Sampled { + kind: crate::ScalarKind::Float, + multi: true, + }, + }; + assert_eq!(img1.to_wgsl(&gctx), "texture_multisampled_2d"); + + let img2 = crate::TypeInner::Image { + dim: crate::ImageDimension::Cube, + arrayed: true, + class: crate::ImageClass::Depth { multi: false }, + }; + assert_eq!(img2.to_wgsl(&gctx), "texture_depth_cube_array"); + + let img3 = crate::TypeInner::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::Depth { multi: true }, + }; + assert_eq!(img3.to_wgsl(&gctx), "texture_depth_multisampled_2d"); + + let array = crate::TypeInner::BindingArray { + base: mytype1, + size: crate::ArraySize::Constant(unsafe { NonZeroU32::new_unchecked(32) }), + }; + assert_eq!(array.to_wgsl(&gctx), "binding_array"); + } +} diff --git a/naga/src/keywords/mod.rs b/naga/src/keywords/mod.rs new file mode 100644 index 0000000000..d54a1704f7 --- /dev/null +++ b/naga/src/keywords/mod.rs @@ -0,0 +1,6 @@ +/*! +Lists of reserved keywords for each shading language with a [frontend][crate::front] or [backend][crate::back]. +*/ + +#[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] +pub mod wgsl; diff --git a/naga/src/keywords/wgsl.rs b/naga/src/keywords/wgsl.rs new file mode 100644 index 0000000000..7b47a13128 --- /dev/null +++ b/naga/src/keywords/wgsl.rs @@ -0,0 +1,229 @@ +/*! +Keywords for [WGSL][wgsl] (WebGPU Shading Language). + +[wgsl]: https://gpuweb.github.io/gpuweb/wgsl.html +*/ + +// https://gpuweb.github.io/gpuweb/wgsl/#keyword-summary +// last sync: https://github.com/gpuweb/gpuweb/blob/39f2321f547c8f0b7f473cf1d47fba30b1691303/wgsl/index.bs +pub const RESERVED: &[&str] = &[ + // Type-defining Keywords + "array", + "atomic", + "bool", + "f32", + "f16", + "i32", + "mat2x2", + "mat2x3", + "mat2x4", + "mat3x2", + "mat3x3", + "mat3x4", + "mat4x2", + "mat4x3", + "mat4x4", + "ptr", + "sampler", + "sampler_comparison", + "texture_1d", + "texture_2d", + "texture_2d_array", + "texture_3d", + "texture_cube", + "texture_cube_array", + "texture_multisampled_2d", + "texture_storage_1d", + "texture_storage_2d", + "texture_storage_2d_array", + "texture_storage_3d", + "texture_depth_2d", + "texture_depth_2d_array", + "texture_depth_cube", + "texture_depth_cube_array", + "texture_depth_multisampled_2d", + "u32", + "vec2", + "vec3", + "vec4", + // Other Keywords + "alias", + "bitcast", + "break", + "case", + "const", + "continue", + "continuing", + "default", + "discard", + "else", + "enable", + "false", + "fn", + "for", + "if", + "let", + "loop", + "override", + "return", + "static_assert", + "struct", + "switch", + "true", + "type", + "var", + "while", + // Reserved Words + "CompileShader", + "ComputeShader", + "DomainShader", + "GeometryShader", + "Hullshader", + "NULL", + "Self", + "abstract", + "active", + "alignas", + "alignof", + "as", + "asm", + "asm_fragment", + "async", + "attribute", + "auto", + "await", + "become", + "binding_array", + "cast", + "catch", + "class", + "co_await", + "co_return", + "co_yield", + "coherent", + "column_major", + "common", + "compile", + "compile_fragment", + "concept", + "const_cast", + "consteval", + "constexpr", + "constinit", + "crate", + "debugger", + "decltype", + "delete", + "demote", + "demote_to_helper", + "do", + "dynamic_cast", + "enum", + "explicit", + "export", + "extends", + "extern", + "external", + "fallthrough", + "filter", + "final", + "finally", + "friend", + "from", + "fxgroup", + "get", + "goto", + "groupshared", + "handle", + "highp", + "impl", + "implements", + "import", + "inline", + "inout", + "instanceof", + "interface", + "layout", + "lowp", + "macro", + "macro_rules", + "match", + "mediump", + "meta", + "mod", + "module", + "move", + "mut", + "mutable", + "namespace", + "new", + "nil", + "noexcept", + "noinline", + "nointerpolation", + "noperspective", + "null", + "nullptr", + "of", + "operator", + "package", + "packoffset", + "partition", + "pass", + "patch", + "pixelfragment", + "precise", + "precision", + "premerge", + "priv", + "protected", + "pub", + "public", + "readonly", + "ref", + "regardless", + "register", + "reinterpret_cast", + "requires", + "resource", + "restrict", + "self", + "set", + "shared", + "signed", + "sizeof", + "smooth", + "snorm", + "static", + "static_assert", + "static_cast", + "std", + "subroutine", + "super", + "target", + "template", + "this", + "thread_local", + "throw", + "trait", + "try", + "typedef", + "typeid", + "typename", + "typeof", + "union", + "unless", + "unorm", + "unsafe", + "unsized", + "use", + "using", + "varying", + "virtual", + "volatile", + "wgsl", + "where", + "with", + "writeonly", + "yield", +]; diff --git a/naga/src/lib.rs b/naga/src/lib.rs new file mode 100644 index 0000000000..b27ebc6764 --- /dev/null +++ b/naga/src/lib.rs @@ -0,0 +1,2070 @@ +/*! Universal shader translator. + +The central structure of the crate is [`Module`]. A `Module` contains: + +- [`Function`]s, which have arguments, a return type, local variables, and a body, + +- [`EntryPoint`]s, which are specialized functions that can serve as the entry + point for pipeline stages like vertex shading or fragment shading, + +- [`Constant`]s and [`GlobalVariable`]s used by `EntryPoint`s and `Function`s, and + +- [`Type`]s used by the above. + +The body of an `EntryPoint` or `Function` is represented using two types: + +- An [`Expression`] produces a value, but has no side effects or control flow. + `Expressions` include variable references, unary and binary operators, and so + on. + +- A [`Statement`] can have side effects and structured control flow. + `Statement`s do not produce a value, other than by storing one in some + designated place. `Statements` include blocks, conditionals, and loops, but also + operations that have side effects, like stores and function calls. + +`Statement`s form a tree, with pointers into the DAG of `Expression`s. + +Restricting side effects to statements simplifies analysis and code generation. +A Naga backend can generate code to evaluate an `Expression` however and +whenever it pleases, as long as it is certain to observe the side effects of all +previously executed `Statement`s. + +Many `Statement` variants use the [`Block`] type, which is `Vec`, +with optional span info, representing a series of statements executed in order. The body of an +`EntryPoint`s or `Function` is a `Block`, and `Statement` has a +[`Block`][Statement::Block] variant. + +If the `clone` feature is enabled, [`Arena`], [`UniqueArena`], [`Type`], [`TypeInner`], +[`Constant`], [`Function`], [`EntryPoint`] and [`Module`] can be cloned. + +## Arenas + +To improve translator performance and reduce memory usage, most structures are +stored in an [`Arena`]. An `Arena` stores a series of `T` values, indexed by +[`Handle`](Handle) values, which are just wrappers around integer indexes. +For example, a `Function`'s expressions are stored in an `Arena`, +and compound expressions refer to their sub-expressions via `Handle` +values. (When examining the serialized form of a `Module`, note that the first +element of an `Arena` has an index of 1, not 0.) + +A [`UniqueArena`] is just like an `Arena`, except that it stores only a single +instance of each value. The value type must implement `Eq` and `Hash`. Like an +`Arena`, inserting a value into a `UniqueArena` returns a `Handle` which can be +used to efficiently access the value, without a hash lookup. Inserting a value +multiple times returns the same `Handle`. + +If the `span` feature is enabled, both `Arena` and `UniqueArena` can associate a +source code span with each element. + +## Function Calls + +Naga's representation of function calls is unusual. Most languages treat +function calls as expressions, but because calls may have side effects, Naga +represents them as a kind of statement, [`Statement::Call`]. If the function +returns a value, a call statement designates a particular [`Expression::CallResult`] +expression to represent its return value, for use by subsequent statements and +expressions. + +## `Expression` evaluation time + +It is essential to know when an [`Expression`] should be evaluated, because its +value may depend on previous [`Statement`]s' effects. But whereas the order of +execution for a tree of `Statement`s is apparent from its structure, it is not +so clear for `Expressions`, since an expression may be referred to by any number +of `Statement`s and other `Expression`s. + +Naga's rules for when `Expression`s are evaluated are as follows: + +- [`Literal`], [`Constant`], and [`ZeroValue`] expressions are + considered to be implicitly evaluated before execution begins. + +- [`FunctionArgument`] and [`LocalVariable`] expressions are considered + implicitly evaluated upon entry to the function to which they belong. + Function arguments cannot be assigned to, and `LocalVariable` expressions + produce a *pointer to* the variable's value (for use with [`Load`] and + [`Store`]). Neither varies while the function executes, so it suffices to + consider these expressions evaluated once on entry. + +- Similarly, [`GlobalVariable`] expressions are considered implicitly + evaluated before execution begins, since their value does not change while + code executes, for one of two reasons: + + - Most `GlobalVariable` expressions produce a pointer to the variable's + value, for use with [`Load`] and [`Store`], as `LocalVariable` + expressions do. Although the variable's value may change, its address + does not. + + - A `GlobalVariable` expression referring to a global in the + [`AddressSpace::Handle`] address space produces the value directly, not + a pointer. Such global variables hold opaque types like shaders or + images, and cannot be assigned to. + +- A [`CallResult`] expression that is the `result` of a [`Statement::Call`], + representing the call's return value, is evaluated when the `Call` statement + is executed. + +- Similarly, an [`AtomicResult`] expression that is the `result` of an + [`Atomic`] statement, representing the result of the atomic operation, is + evaluated when the `Atomic` statement is executed. + +- A [`RayQueryProceedResult`] expression, which is a boolean + indicating if the ray query is finished, is evaluated when the + [`RayQuery`] statement whose [`Proceed::result`] points to it is + executed. + +- All other expressions are evaluated when the (unique) [`Statement::Emit`] + statement that covers them is executed. + +Now, strictly speaking, not all `Expression` variants actually care when they're +evaluated. For example, you can evaluate a [`BinaryOperator::Add`] expression +any time you like, as long as you give it the right operands. It's really only a +very small set of expressions that are affected by timing: + +- [`Load`], [`ImageSample`], and [`ImageLoad`] expressions are influenced by + stores to the variables or images they access, and must execute at the + proper time relative to them. + +- [`Derivative`] expressions are sensitive to control flow uniformity: they + must not be moved out of an area of uniform control flow into a non-uniform + area. + +- More generally, any expression that's used by more than one other expression + or statement should probably be evaluated only once, and then stored in a + variable to be cited at each point of use. + +Naga tries to help back ends handle all these cases correctly in a somewhat +circuitous way. The [`ModuleInfo`] structure returned by [`Validator::validate`] +provides a reference count for each expression in each function in the module. +Naturally, any expression with a reference count of two or more deserves to be +evaluated and stored in a temporary variable at the point that the `Emit` +statement covering it is executed. But if we selectively lower the reference +count threshold to _one_ for the sensitive expression types listed above, so +that we _always_ generate a temporary variable and save their value, then the +same code that manages multiply referenced expressions will take care of +introducing temporaries for time-sensitive expressions as well. The +`Expression::bake_ref_count` method (private to the back ends) is meant to help +with this. + +## `Expression` scope + +Each `Expression` has a *scope*, which is the region of the function within +which it can be used by `Statement`s and other `Expression`s. It is a validation +error to use an `Expression` outside its scope. + +An expression's scope is defined as follows: + +- The scope of a [`Constant`], [`GlobalVariable`], [`FunctionArgument`] or + [`LocalVariable`] expression covers the entire `Function` in which it + occurs. + +- The scope of an expression evaluated by an [`Emit`] statement covers the + subsequent expressions in that `Emit`, the subsequent statements in the `Block` + to which that `Emit` belongs (if any) and their sub-statements (if any). + +- The `result` expression of a [`Call`] or [`Atomic`] statement has a scope + covering the subsequent statements in the `Block` in which the statement + occurs (if any) and their sub-statements (if any). + +For example, this implies that an expression evaluated by some statement in a +nested `Block` is not available in the `Block`'s parents. Such a value would +need to be stored in a local variable to be carried upwards in the statement +tree. + +## Constant expressions + +A Naga *constant expression* is one of the following [`Expression`] +variants, whose operands (if any) are also constant expressions: +- [`Literal`] +- [`Constant`], for [`Constant`s][const_type] whose [`override`] is [`None`] +- [`ZeroValue`], for fixed-size types +- [`Compose`] +- [`Access`] +- [`AccessIndex`] +- [`Splat`] +- [`Swizzle`] +- [`Unary`] +- [`Binary`] +- [`Select`] +- [`Relational`] +- [`Math`] +- [`As`] + +A constant expression can be evaluated at module translation time. + +## Override expressions + +A Naga *override expression* is the same as a [constant expression], +except that it is also allowed to refer to [`Constant`s][const_type] +whose [`override`] is something other than [`None`]. + +An override expression can be evaluated at pipeline creation time. + +[`AtomicResult`]: Expression::AtomicResult +[`RayQueryProceedResult`]: Expression::RayQueryProceedResult +[`CallResult`]: Expression::CallResult +[`Constant`]: Expression::Constant +[`ZeroValue`]: Expression::ZeroValue +[`Literal`]: Expression::Literal +[`Derivative`]: Expression::Derivative +[`FunctionArgument`]: Expression::FunctionArgument +[`GlobalVariable`]: Expression::GlobalVariable +[`ImageLoad`]: Expression::ImageLoad +[`ImageSample`]: Expression::ImageSample +[`Load`]: Expression::Load +[`LocalVariable`]: Expression::LocalVariable + +[`Atomic`]: Statement::Atomic +[`Call`]: Statement::Call +[`Emit`]: Statement::Emit +[`Store`]: Statement::Store +[`RayQuery`]: Statement::RayQuery + +[`Proceed::result`]: RayQueryFunction::Proceed::result + +[`Validator::validate`]: valid::Validator::validate +[`ModuleInfo`]: valid::ModuleInfo + +[`Literal`]: Expression::Literal +[`ZeroValue`]: Expression::ZeroValue +[`Compose`]: Expression::Compose +[`Access`]: Expression::Access +[`AccessIndex`]: Expression::AccessIndex +[`Splat`]: Expression::Splat +[`Swizzle`]: Expression::Swizzle +[`Unary`]: Expression::Unary +[`Binary`]: Expression::Binary +[`Select`]: Expression::Select +[`Relational`]: Expression::Relational +[`Math`]: Expression::Math +[`As`]: Expression::As + +[const_type]: Constant +[`override`]: Constant::override +[`None`]: Override::None + +[constant expression]: index.html#constant-expressions +*/ + +#![allow( + clippy::new_without_default, + clippy::unneeded_field_pattern, + clippy::match_like_matches_macro, + clippy::collapsible_if, + clippy::derive_partial_eq_without_eq, + clippy::needless_borrowed_reference, + clippy::single_match +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_extern_crates, + unused_qualifications, + clippy::pattern_type_mismatch, + clippy::missing_const_for_fn, + clippy::rest_pat_in_fully_bound_structs, + clippy::match_wildcard_for_single_variants +)] +#![deny(clippy::exit)] +#![cfg_attr( + not(test), + warn( + clippy::dbg_macro, + clippy::panic, + clippy::print_stderr, + clippy::print_stdout, + clippy::todo + ) +)] + +mod arena; +pub mod back; +mod block; +#[cfg(feature = "compact")] +pub mod compact; +pub mod front; +pub mod keywords; +pub mod proc; +mod span; +pub mod valid; + +pub use crate::arena::{Arena, Handle, Range, UniqueArena}; + +pub use crate::span::{SourceLocation, Span, SpanContext, WithSpan}; +#[cfg(feature = "arbitrary")] +use arbitrary::Arbitrary; +#[cfg(feature = "deserialize")] +use serde::Deserialize; +#[cfg(feature = "serialize")] +use serde::Serialize; + +/// Width of a boolean type, in bytes. +pub const BOOL_WIDTH: Bytes = 1; + +/// Width of abstract types, in bytes. +pub const ABSTRACT_WIDTH: Bytes = 8; + +/// Hash map that is faster but not resilient to DoS attacks. +pub type FastHashMap = rustc_hash::FxHashMap; +/// Hash set that is faster but not resilient to DoS attacks. +pub type FastHashSet = rustc_hash::FxHashSet; + +/// Insertion-order-preserving hash set (`IndexSet`), but with the same +/// hasher as `FastHashSet` (faster but not resilient to DoS attacks). +pub type FastIndexSet = + indexmap::IndexSet>; + +/// Insertion-order-preserving hash map (`IndexMap`), but with the same +/// hasher as `FastHashMap` (faster but not resilient to DoS attacks). +pub type FastIndexMap = + indexmap::IndexMap>; + +/// Map of expressions that have associated variable names +pub(crate) type NamedExpressions = FastIndexMap, String>; + +/// Early fragment tests. +/// +/// In a standard situation, if a driver determines that it is possible to switch on early depth test, it will. +/// +/// Typical situations when early depth test is switched off: +/// - Calling `discard` in a shader. +/// - Writing to the depth buffer, unless ConservativeDepth is enabled. +/// +/// To use in a shader: +/// - GLSL: `layout(early_fragment_tests) in;` +/// - HLSL: `Attribute earlydepthstencil` +/// - SPIR-V: `ExecutionMode EarlyFragmentTests` +/// - WGSL: `@early_depth_test` +/// +/// For more, see: +/// - +/// - +/// - +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct EarlyDepthTest { + pub conservative: Option, +} +/// Enables adjusting depth without disabling early Z. +/// +/// To use in a shader: +/// - GLSL: `layout (depth_) out float gl_FragDepth;` +/// - `depth_any` option behaves as if the layout qualifier was not present. +/// - HLSL: `SV_DepthGreaterEqual`/`SV_DepthLessEqual`/`SV_Depth` +/// - SPIR-V: `ExecutionMode Depth` +/// - WGSL: `@early_depth_test(greater_equal/less_equal/unchanged)` +/// +/// For more, see: +/// - +/// - +/// - +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum ConservativeDepth { + /// Shader may rewrite depth only with a value greater than calculated. + GreaterEqual, + + /// Shader may rewrite depth smaller than one that would have been written without the modification. + LessEqual, + + /// Shader may not rewrite depth value. + Unchanged, +} + +/// Stage of the programmable pipeline. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +#[allow(missing_docs)] // The names are self evident +pub enum ShaderStage { + Vertex, + Fragment, + Compute, +} + +/// Addressing space of variables. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum AddressSpace { + /// Function locals. + Function, + /// Private data, per invocation, mutable. + Private, + /// Workgroup shared data, mutable. + WorkGroup, + /// Uniform buffer data. + Uniform, + /// Storage buffer data, potentially mutable. + Storage { access: StorageAccess }, + /// Opaque handles, such as samplers and images. + Handle, + /// Push constants. + PushConstant, +} + +/// Built-in inputs and outputs. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum BuiltIn { + Position { invariant: bool }, + ViewIndex, + // vertex + BaseInstance, + BaseVertex, + ClipDistance, + CullDistance, + InstanceIndex, + PointSize, + VertexIndex, + // fragment + FragDepth, + PointCoord, + FrontFacing, + PrimitiveIndex, + SampleIndex, + SampleMask, + // compute + GlobalInvocationId, + LocalInvocationId, + LocalInvocationIndex, + WorkGroupId, + WorkGroupSize, + NumWorkGroups, +} + +/// Number of bytes per scalar. +pub type Bytes = u8; + +/// Number of components in a vector. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum VectorSize { + /// 2D vector + Bi = 2, + /// 3D vector + Tri = 3, + /// 4D vector + Quad = 4, +} + +/// Primitive type for a scalar. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum ScalarKind { + /// Signed integer type. + Sint, + /// Unsigned integer type. + Uint, + /// Floating point type. + Float, + /// Boolean type. + Bool, + + /// WGSL abstract integer type. + /// + /// These are forbidden by validation, and should never reach backends. + AbstractInt, + + /// Abstract floating-point type. + /// + /// These are forbidden by validation, and should never reach backends. + AbstractFloat, +} + +/// Characteristics of a scalar type. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct Scalar { + /// How the value's bits are to be interpreted. + pub kind: ScalarKind, + + /// This size of the value in bytes. + pub width: Bytes, +} + +/// Size of an array. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum ArraySize { + /// The array size is constant. + Constant(std::num::NonZeroU32), + /// The array size can change at runtime. + Dynamic, +} + +/// The interpolation qualifier of a binding or struct field. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum Interpolation { + /// The value will be interpolated in a perspective-correct fashion. + /// Also known as "smooth" in glsl. + Perspective, + /// Indicates that linear, non-perspective, correct + /// interpolation must be used. + /// Also known as "no_perspective" in glsl. + Linear, + /// Indicates that no interpolation will be performed. + Flat, +} + +/// The sampling qualifiers of a binding or struct field. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum Sampling { + /// Interpolate the value at the center of the pixel. + Center, + + /// Interpolate the value at a point that lies within all samples covered by + /// the fragment within the current primitive. In multisampling, use a + /// single value for all samples in the primitive. + Centroid, + + /// Interpolate the value at each sample location. In multisampling, invoke + /// the fragment shader once per sample. + Sample, +} + +/// Member of a user-defined structure. +// Clone is used only for error reporting and is not intended for end users +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct StructMember { + pub name: Option, + /// Type of the field. + pub ty: Handle, + /// For I/O structs, defines the binding. + pub binding: Option, + /// Offset from the beginning from the struct. + pub offset: u32, +} + +/// The number of dimensions an image has. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum ImageDimension { + /// 1D image + D1, + /// 2D image + D2, + /// 3D image + D3, + /// Cube map + Cube, +} + +bitflags::bitflags! { + /// Flags describing an image. + #[cfg_attr(feature = "serialize", derive(Serialize))] + #[cfg_attr(feature = "deserialize", derive(Deserialize))] + #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] + #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct StorageAccess: u32 { + /// Storage can be used as a source for load ops. + const LOAD = 0x1; + /// Storage can be used as a target for store ops. + const STORE = 0x2; + } +} + +/// Image storage format. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum StorageFormat { + // 8-bit formats + R8Unorm, + R8Snorm, + R8Uint, + R8Sint, + + // 16-bit formats + R16Uint, + R16Sint, + R16Float, + Rg8Unorm, + Rg8Snorm, + Rg8Uint, + Rg8Sint, + + // 32-bit formats + R32Uint, + R32Sint, + R32Float, + Rg16Uint, + Rg16Sint, + Rg16Float, + Rgba8Unorm, + Rgba8Snorm, + Rgba8Uint, + Rgba8Sint, + Bgra8Unorm, + + // Packed 32-bit formats + Rgb10a2Uint, + Rgb10a2Unorm, + Rg11b10Float, + + // 64-bit formats + Rg32Uint, + Rg32Sint, + Rg32Float, + Rgba16Uint, + Rgba16Sint, + Rgba16Float, + + // 128-bit formats + Rgba32Uint, + Rgba32Sint, + Rgba32Float, + + // Normalized 16-bit per channel formats + R16Unorm, + R16Snorm, + Rg16Unorm, + Rg16Snorm, + Rgba16Unorm, + Rgba16Snorm, +} + +/// Sub-class of the image type. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum ImageClass { + /// Regular sampled image. + Sampled { + /// Kind of values to sample. + kind: ScalarKind, + /// Multi-sampled image. + /// + /// A multi-sampled image holds several samples per texel. Multi-sampled + /// images cannot have mipmaps. + multi: bool, + }, + /// Depth comparison image. + Depth { + /// Multi-sampled depth image. + multi: bool, + }, + /// Storage image. + Storage { + format: StorageFormat, + access: StorageAccess, + }, +} + +/// A data type declared in the module. +#[derive(Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct Type { + /// The name of the type, if any. + pub name: Option, + /// Inner structure that depends on the kind of the type. + pub inner: TypeInner, +} + +/// Enum with additional information, depending on the kind of type. +#[derive(Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum TypeInner { + /// Number of integral or floating-point kind. + Scalar(Scalar), + /// Vector of numbers. + Vector { size: VectorSize, scalar: Scalar }, + /// Matrix of numbers. + Matrix { + columns: VectorSize, + rows: VectorSize, + scalar: Scalar, + }, + /// Atomic scalar. + Atomic(Scalar), + /// Pointer to another type. + /// + /// Pointers to scalars and vectors should be treated as equivalent to + /// [`ValuePointer`] types. Use the [`TypeInner::equivalent`] method to + /// compare types in a way that treats pointers correctly. + /// + /// ## Pointers to non-`SIZED` types + /// + /// The `base` type of a pointer may be a non-[`SIZED`] type like a + /// dynamically-sized [`Array`], or a [`Struct`] whose last member is a + /// dynamically sized array. Such pointers occur as the types of + /// [`GlobalVariable`] or [`AccessIndex`] expressions referring to + /// dynamically-sized arrays. + /// + /// However, among pointers to non-`SIZED` types, only pointers to `Struct`s + /// are [`DATA`]. Pointers to dynamically sized `Array`s cannot be passed as + /// arguments, stored in variables, or held in arrays or structures. Their + /// only use is as the types of `AccessIndex` expressions. + /// + /// [`SIZED`]: valid::TypeFlags::SIZED + /// [`DATA`]: valid::TypeFlags::DATA + /// [`Array`]: TypeInner::Array + /// [`Struct`]: TypeInner::Struct + /// [`ValuePointer`]: TypeInner::ValuePointer + /// [`GlobalVariable`]: Expression::GlobalVariable + /// [`AccessIndex`]: Expression::AccessIndex + Pointer { + base: Handle, + space: AddressSpace, + }, + + /// Pointer to a scalar or vector. + /// + /// A `ValuePointer` type is equivalent to a `Pointer` whose `base` is a + /// `Scalar` or `Vector` type. This is for use in [`TypeResolution::Value`] + /// variants; see the documentation for [`TypeResolution`] for details. + /// + /// Use the [`TypeInner::equivalent`] method to compare types that could be + /// pointers, to ensure that `Pointer` and `ValuePointer` types are + /// recognized as equivalent. + /// + /// [`TypeResolution`]: proc::TypeResolution + /// [`TypeResolution::Value`]: proc::TypeResolution::Value + ValuePointer { + size: Option, + scalar: Scalar, + space: AddressSpace, + }, + + /// Homogenous list of elements. + /// + /// The `base` type must be a [`SIZED`], [`DATA`] type. + /// + /// ## Dynamically sized arrays + /// + /// An `Array` is [`SIZED`] unless its `size` is [`Dynamic`]. + /// Dynamically-sized arrays may only appear in a few situations: + /// + /// - They may appear as the type of a [`GlobalVariable`], or as the last + /// member of a [`Struct`]. + /// + /// - They may appear as the base type of a [`Pointer`]. An + /// [`AccessIndex`] expression referring to a struct's final + /// unsized array member would have such a pointer type. However, such + /// pointer types may only appear as the types of such intermediate + /// expressions. They are not [`DATA`], and cannot be stored in + /// variables, held in arrays or structs, or passed as parameters. + /// + /// [`SIZED`]: crate::valid::TypeFlags::SIZED + /// [`DATA`]: crate::valid::TypeFlags::DATA + /// [`Dynamic`]: ArraySize::Dynamic + /// [`Struct`]: TypeInner::Struct + /// [`Pointer`]: TypeInner::Pointer + /// [`AccessIndex`]: Expression::AccessIndex + Array { + base: Handle, + size: ArraySize, + stride: u32, + }, + + /// User-defined structure. + /// + /// There must always be at least one member. + /// + /// A `Struct` type is [`DATA`], and the types of its members must be + /// `DATA` as well. + /// + /// Member types must be [`SIZED`], except for the final member of a + /// struct, which may be a dynamically sized [`Array`]. The + /// `Struct` type itself is `SIZED` when all its members are `SIZED`. + /// + /// [`DATA`]: crate::valid::TypeFlags::DATA + /// [`SIZED`]: crate::valid::TypeFlags::SIZED + /// [`Array`]: TypeInner::Array + Struct { + members: Vec, + //TODO: should this be unaligned? + span: u32, + }, + /// Possibly multidimensional array of texels. + Image { + dim: ImageDimension, + arrayed: bool, + //TODO: consider moving `multisampled: bool` out + class: ImageClass, + }, + /// Can be used to sample values from images. + Sampler { comparison: bool }, + + /// Opaque object representing an acceleration structure of geometry. + AccelerationStructure, + + /// Locally used handle for ray queries. + RayQuery, + + /// Array of bindings. + /// + /// A `BindingArray` represents an array where each element draws its value + /// from a separate bound resource. The array's element type `base` may be + /// [`Image`], [`Sampler`], or any type that would be permitted for a global + /// in the [`Uniform`] or [`Storage`] address spaces. Only global variables + /// may be binding arrays; on the host side, their values are provided by + /// [`TextureViewArray`], [`SamplerArray`], or [`BufferArray`] + /// bindings. + /// + /// Since each element comes from a distinct resource, a binding array of + /// images could have images of varying sizes (but not varying dimensions; + /// they must all have the same `Image` type). Or, a binding array of + /// buffers could have elements that are dynamically sized arrays, each with + /// a different length. + /// + /// Binding arrays are in the same address spaces as their underlying type. + /// As such, referring to an array of images produces an [`Image`] value + /// directly (as opposed to a pointer). The only operation permitted on + /// `BindingArray` values is indexing, which works transparently: indexing + /// a binding array of samplers yields a [`Sampler`], indexing a pointer to the + /// binding array of storage buffers produces a pointer to the storage struct. + /// + /// Unlike textures and samplers, binding arrays are not [`ARGUMENT`], so + /// they cannot be passed as arguments to functions. + /// + /// Naga's WGSL front end supports binding arrays with the type syntax + /// `binding_array`. + /// + /// [`Image`]: TypeInner::Image + /// [`Sampler`]: TypeInner::Sampler + /// [`Uniform`]: AddressSpace::Uniform + /// [`Storage`]: AddressSpace::Storage + /// [`TextureViewArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.TextureViewArray + /// [`SamplerArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.SamplerArray + /// [`BufferArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.BufferArray + /// [`DATA`]: crate::valid::TypeFlags::DATA + /// [`ARGUMENT`]: crate::valid::TypeFlags::ARGUMENT + /// [naga#1864]: https://github.com/gfx-rs/naga/issues/1864 + BindingArray { base: Handle, size: ArraySize }, +} + +#[derive(Debug, Clone, Copy, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum Literal { + /// May not be NaN or infinity. + F64(f64), + /// May not be NaN or infinity. + F32(f32), + U32(u32), + I32(i32), + I64(i64), + Bool(bool), + AbstractInt(i64), + AbstractFloat(f64), +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum Override { + None, + ByName, + ByNameOrId(u32), +} + +/// Constant value. +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct Constant { + pub name: Option, + pub r#override: Override, + pub ty: Handle, + + /// The value of the constant. + /// + /// This [`Handle`] refers to [`Module::const_expressions`], not + /// any [`Function::expressions`] arena. + /// + /// If [`override`] is [`None`], then this must be a Naga + /// [constant expression]. Otherwise, this may be a Naga + /// [override expression] or [constant expression]. + /// + /// [`override`]: Constant::override + /// [`None`]: Override::None + /// [constant expression]: index.html#constant-expressions + /// [override expression]: index.html#override-expressions + pub init: Handle, +} + +/// Describes how an input/output variable is to be bound. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum Binding { + /// Built-in shader variable. + BuiltIn(BuiltIn), + + /// Indexed location. + /// + /// Values passed from the [`Vertex`] stage to the [`Fragment`] stage must + /// have their `interpolation` defaulted (i.e. not `None`) by the front end + /// as appropriate for that language. + /// + /// For other stages, we permit interpolations even though they're ignored. + /// When a front end is parsing a struct type, it usually doesn't know what + /// stages will be using it for IO, so it's easiest if it can apply the + /// defaults to anything with a `Location` binding, just in case. + /// + /// For anything other than floating-point scalars and vectors, the + /// interpolation must be `Flat`. + /// + /// [`Vertex`]: crate::ShaderStage::Vertex + /// [`Fragment`]: crate::ShaderStage::Fragment + Location { + location: u32, + /// Indicates the 2nd input to the blender when dual-source blending. + second_blend_source: bool, + interpolation: Option, + sampling: Option, + }, +} + +/// Pipeline binding information for global resources. +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct ResourceBinding { + /// The bind group index. + pub group: u32, + /// Binding number within the group. + pub binding: u32, +} + +/// Variable defined at module level. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct GlobalVariable { + /// Name of the variable, if any. + pub name: Option, + /// How this variable is to be stored. + pub space: AddressSpace, + /// For resources, defines the binding point. + pub binding: Option, + /// The type of this variable. + pub ty: Handle, + /// Initial value for this variable. + /// + /// Expression handle lives in const_expressions + pub init: Option>, +} + +/// Variable defined at function level. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct LocalVariable { + /// Name of the variable, if any. + pub name: Option, + /// The type of this variable. + pub ty: Handle, + /// Initial value for this variable. + /// + /// This handle refers to this `LocalVariable`'s function's + /// [`expressions`] arena, but it is required to be an evaluated + /// constant expression. + /// + /// [`expressions`]: Function::expressions + pub init: Option>, +} + +/// Operation that can be applied on a single value. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum UnaryOperator { + Negate, + LogicalNot, + BitwiseNot, +} + +/// Operation that can be applied on two values. +/// +/// ## Arithmetic type rules +/// +/// The arithmetic operations `Add`, `Subtract`, `Multiply`, `Divide`, and +/// `Modulo` can all be applied to [`Scalar`] types other than [`Bool`], or +/// [`Vector`]s thereof. Both operands must have the same type. +/// +/// `Add` and `Subtract` can also be applied to [`Matrix`] values. Both operands +/// must have the same type. +/// +/// `Multiply` supports additional cases: +/// +/// - A [`Matrix`] or [`Vector`] can be multiplied by a scalar [`Float`], +/// either on the left or the right. +/// +/// - A [`Matrix`] on the left can be multiplied by a [`Vector`] on the right +/// if the matrix has as many columns as the vector has components (`matCxR +/// * VecC`). +/// +/// - A [`Vector`] on the left can be multiplied by a [`Matrix`] on the right +/// if the matrix has as many rows as the vector has components (`VecR * +/// matCxR`). +/// +/// - Two matrices can be multiplied if the left operand has as many columns +/// as the right operand has rows (`matNxR * matCxN`). +/// +/// In all the above `Multiply` cases, the byte widths of the underlying scalar +/// types of both operands must be the same. +/// +/// Note that `Multiply` supports mixed vector and scalar operations directly, +/// whereas the other arithmetic operations require an explicit [`Splat`] for +/// mixed-type use. +/// +/// [`Scalar`]: TypeInner::Scalar +/// [`Vector`]: TypeInner::Vector +/// [`Matrix`]: TypeInner::Matrix +/// [`Float`]: ScalarKind::Float +/// [`Bool`]: ScalarKind::Bool +/// [`Splat`]: Expression::Splat +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum BinaryOperator { + Add, + Subtract, + Multiply, + Divide, + /// Equivalent of the WGSL's `%` operator or SPIR-V's `OpFRem` + Modulo, + Equal, + NotEqual, + Less, + LessEqual, + Greater, + GreaterEqual, + And, + ExclusiveOr, + InclusiveOr, + LogicalAnd, + LogicalOr, + ShiftLeft, + /// Right shift carries the sign of signed integers only. + ShiftRight, +} + +/// Function on an atomic value. +/// +/// Note: these do not include load/store, which use the existing +/// [`Expression::Load`] and [`Statement::Store`]. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum AtomicFunction { + Add, + Subtract, + And, + ExclusiveOr, + InclusiveOr, + Min, + Max, + Exchange { compare: Option> }, +} + +/// Hint at which precision to compute a derivative. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum DerivativeControl { + Coarse, + Fine, + None, +} + +/// Axis on which to compute a derivative. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum DerivativeAxis { + X, + Y, + Width, +} + +/// Built-in shader function for testing relation between values. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum RelationalFunction { + All, + Any, + IsNan, + IsInf, +} + +/// Built-in shader function for math. +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum MathFunction { + // comparison + Abs, + Min, + Max, + Clamp, + Saturate, + // trigonometry + Cos, + Cosh, + Sin, + Sinh, + Tan, + Tanh, + Acos, + Asin, + Atan, + Atan2, + Asinh, + Acosh, + Atanh, + Radians, + Degrees, + // decomposition + Ceil, + Floor, + Round, + Fract, + Trunc, + Modf, + Frexp, + Ldexp, + // exponent + Exp, + Exp2, + Log, + Log2, + Pow, + // geometry + Dot, + Outer, + Cross, + Distance, + Length, + Normalize, + FaceForward, + Reflect, + Refract, + // computational + Sign, + Fma, + Mix, + Step, + SmoothStep, + Sqrt, + InverseSqrt, + Inverse, + Transpose, + Determinant, + // bits + CountTrailingZeros, + CountLeadingZeros, + CountOneBits, + ReverseBits, + ExtractBits, + InsertBits, + FindLsb, + FindMsb, + // data packing + Pack4x8snorm, + Pack4x8unorm, + Pack2x16snorm, + Pack2x16unorm, + Pack2x16float, + // data unpacking + Unpack4x8snorm, + Unpack4x8unorm, + Unpack2x16snorm, + Unpack2x16unorm, + Unpack2x16float, +} + +/// Sampling modifier to control the level of detail. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum SampleLevel { + Auto, + Zero, + Exact(Handle), + Bias(Handle), + Gradient { + x: Handle, + y: Handle, + }, +} + +/// Type of an image query. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum ImageQuery { + /// Get the size at the specified level. + Size { + /// If `None`, the base level is considered. + level: Option>, + }, + /// Get the number of mipmap levels. + NumLevels, + /// Get the number of array layers. + NumLayers, + /// Get the number of samples. + NumSamples, +} + +/// Component selection for a vector swizzle. +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum SwizzleComponent { + /// + X = 0, + /// + Y = 1, + /// + Z = 2, + /// + W = 3, +} + +bitflags::bitflags! { + /// Memory barrier flags. + #[cfg_attr(feature = "serialize", derive(Serialize))] + #[cfg_attr(feature = "deserialize", derive(Deserialize))] + #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct Barrier: u32 { + /// Barrier affects all `AddressSpace::Storage` accesses. + const STORAGE = 0x1; + /// Barrier affects all `AddressSpace::WorkGroup` accesses. + const WORK_GROUP = 0x2; + } +} + +/// An expression that can be evaluated to obtain a value. +/// +/// This is a Single Static Assignment (SSA) scheme similar to SPIR-V. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum Expression { + /// Literal. + Literal(Literal), + /// Constant value. + Constant(Handle), + /// Zero value of a type. + ZeroValue(Handle), + /// Composite expression. + Compose { + ty: Handle, + components: Vec>, + }, + + /// Array access with a computed index. + /// + /// ## Typing rules + /// + /// The `base` operand must be some composite type: [`Vector`], [`Matrix`], + /// [`Array`], a [`Pointer`] to one of those, or a [`ValuePointer`] with a + /// `size`. + /// + /// The `index` operand must be an integer, signed or unsigned. + /// + /// Indexing a [`Vector`] or [`Array`] produces a value of its element type. + /// Indexing a [`Matrix`] produces a [`Vector`]. + /// + /// Indexing a [`Pointer`] to any of the above produces a pointer to the + /// element/component type, in the same [`space`]. In the case of [`Array`], + /// the result is an actual [`Pointer`], but for vectors and matrices, there + /// may not be any type in the arena representing the component's type, so + /// those produce [`ValuePointer`] types equivalent to the appropriate + /// [`Pointer`]. + /// + /// ## Dynamic indexing restrictions + /// + /// To accommodate restrictions in some of the shader languages that Naga + /// targets, it is not permitted to subscript a matrix or array with a + /// dynamically computed index unless that matrix or array appears behind a + /// pointer. In other words, if the inner type of `base` is [`Array`] or + /// [`Matrix`], then `index` must be a constant. But if the type of `base` + /// is a [`Pointer`] to an array or matrix or a [`ValuePointer`] with a + /// `size`, then the index may be any expression of integer type. + /// + /// You can use the [`Expression::is_dynamic_index`] method to determine + /// whether a given index expression requires matrix or array base operands + /// to be behind a pointer. + /// + /// (It would be simpler to always require the use of `AccessIndex` when + /// subscripting arrays and matrices that are not behind pointers, but to + /// accommodate existing front ends, Naga also permits `Access`, with a + /// restricted `index`.) + /// + /// [`Vector`]: TypeInner::Vector + /// [`Matrix`]: TypeInner::Matrix + /// [`Array`]: TypeInner::Array + /// [`Pointer`]: TypeInner::Pointer + /// [`space`]: TypeInner::Pointer::space + /// [`ValuePointer`]: TypeInner::ValuePointer + /// [`Float`]: ScalarKind::Float + Access { + base: Handle, + index: Handle, + }, + /// Access the same types as [`Access`], plus [`Struct`] with a known index. + /// + /// [`Access`]: Expression::Access + /// [`Struct`]: TypeInner::Struct + AccessIndex { + base: Handle, + index: u32, + }, + /// Splat scalar into a vector. + Splat { + size: VectorSize, + value: Handle, + }, + /// Vector swizzle. + Swizzle { + size: VectorSize, + vector: Handle, + pattern: [SwizzleComponent; 4], + }, + + /// Reference a function parameter, by its index. + /// + /// A `FunctionArgument` expression evaluates to a pointer to the argument's + /// value. You must use a [`Load`] expression to retrieve its value, or a + /// [`Store`] statement to assign it a new value. + /// + /// [`Load`]: Expression::Load + /// [`Store`]: Statement::Store + FunctionArgument(u32), + + /// Reference a global variable. + /// + /// If the given `GlobalVariable`'s [`space`] is [`AddressSpace::Handle`], + /// then the variable stores some opaque type like a sampler or an image, + /// and a `GlobalVariable` expression referring to it produces the + /// variable's value directly. + /// + /// For any other address space, a `GlobalVariable` expression produces a + /// pointer to the variable's value. You must use a [`Load`] expression to + /// retrieve its value, or a [`Store`] statement to assign it a new value. + /// + /// [`space`]: GlobalVariable::space + /// [`Load`]: Expression::Load + /// [`Store`]: Statement::Store + GlobalVariable(Handle), + + /// Reference a local variable. + /// + /// A `LocalVariable` expression evaluates to a pointer to the variable's value. + /// You must use a [`Load`](Expression::Load) expression to retrieve its value, + /// or a [`Store`](Statement::Store) statement to assign it a new value. + LocalVariable(Handle), + + /// Load a value indirectly. + /// + /// For [`TypeInner::Atomic`] the result is a corresponding scalar. + /// For other types behind the `pointer`, the result is `T`. + Load { pointer: Handle }, + /// Sample a point from a sampled or a depth image. + ImageSample { + image: Handle, + sampler: Handle, + /// If Some(), this operation is a gather operation + /// on the selected component. + gather: Option, + coordinate: Handle, + array_index: Option>, + /// Expression handle lives in const_expressions + offset: Option>, + level: SampleLevel, + depth_ref: Option>, + }, + + /// Load a texel from an image. + /// + /// For most images, this returns a four-element vector of the same + /// [`ScalarKind`] as the image. If the format of the image does not have + /// four components, default values are provided: the first three components + /// (typically R, G, and B) default to zero, and the final component + /// (typically alpha) defaults to one. + /// + /// However, if the image's [`class`] is [`Depth`], then this returns a + /// [`Float`] scalar value. + /// + /// [`ScalarKind`]: ScalarKind + /// [`class`]: TypeInner::Image::class + /// [`Depth`]: ImageClass::Depth + /// [`Float`]: ScalarKind::Float + ImageLoad { + /// The image to load a texel from. This must have type [`Image`]. (This + /// will necessarily be a [`GlobalVariable`] or [`FunctionArgument`] + /// expression, since no other expressions are allowed to have that + /// type.) + /// + /// [`Image`]: TypeInner::Image + /// [`GlobalVariable`]: Expression::GlobalVariable + /// [`FunctionArgument`]: Expression::FunctionArgument + image: Handle, + + /// The coordinate of the texel we wish to load. This must be a scalar + /// for [`D1`] images, a [`Bi`] vector for [`D2`] images, and a [`Tri`] + /// vector for [`D3`] images. (Array indices, sample indices, and + /// explicit level-of-detail values are supplied separately.) Its + /// component type must be [`Sint`]. + /// + /// [`D1`]: ImageDimension::D1 + /// [`D2`]: ImageDimension::D2 + /// [`D3`]: ImageDimension::D3 + /// [`Bi`]: VectorSize::Bi + /// [`Tri`]: VectorSize::Tri + /// [`Sint`]: ScalarKind::Sint + coordinate: Handle, + + /// The index into an arrayed image. If the [`arrayed`] flag in + /// `image`'s type is `true`, then this must be `Some(expr)`, where + /// `expr` is a [`Sint`] scalar. Otherwise, it must be `None`. + /// + /// [`arrayed`]: TypeInner::Image::arrayed + /// [`Sint`]: ScalarKind::Sint + array_index: Option>, + + /// A sample index, for multisampled [`Sampled`] and [`Depth`] images. + /// + /// [`Sampled`]: ImageClass::Sampled + /// [`Depth`]: ImageClass::Depth + sample: Option>, + + /// A level of detail, for mipmapped images. + /// + /// This must be present when accessing non-multisampled + /// [`Sampled`] and [`Depth`] images, even if only the + /// full-resolution level is present (in which case the only + /// valid level is zero). + /// + /// [`Sampled`]: ImageClass::Sampled + /// [`Depth`]: ImageClass::Depth + level: Option>, + }, + + /// Query information from an image. + ImageQuery { + image: Handle, + query: ImageQuery, + }, + /// Apply an unary operator. + Unary { + op: UnaryOperator, + expr: Handle, + }, + /// Apply a binary operator. + Binary { + op: BinaryOperator, + left: Handle, + right: Handle, + }, + /// Select between two values based on a condition. + /// + /// Note that, because expressions have no side effects, it is unobservable + /// whether the non-selected branch is evaluated. + Select { + /// Boolean expression + condition: Handle, + accept: Handle, + reject: Handle, + }, + /// Compute the derivative on an axis. + Derivative { + axis: DerivativeAxis, + ctrl: DerivativeControl, + expr: Handle, + }, + /// Call a relational function. + Relational { + fun: RelationalFunction, + argument: Handle, + }, + /// Call a math function + Math { + fun: MathFunction, + arg: Handle, + arg1: Option>, + arg2: Option>, + arg3: Option>, + }, + /// Cast a simple type to another kind. + As { + /// Source expression, which can only be a scalar or a vector. + expr: Handle, + /// Target scalar kind. + kind: ScalarKind, + /// If provided, converts to the specified byte width. + /// Otherwise, bitcast. + convert: Option, + }, + /// Result of calling another function. + CallResult(Handle), + /// Result of an atomic operation. + AtomicResult { ty: Handle, comparison: bool }, + /// Result of a [`WorkGroupUniformLoad`] statement. + /// + /// [`WorkGroupUniformLoad`]: Statement::WorkGroupUniformLoad + WorkGroupUniformLoadResult { + /// The type of the result + ty: Handle, + }, + /// Get the length of an array. + /// The expression must resolve to a pointer to an array with a dynamic size. + /// + /// This doesn't match the semantics of spirv's `OpArrayLength`, which must be passed + /// a pointer to a structure containing a runtime array in its' last field. + ArrayLength(Handle), + + /// Result of a [`Proceed`] [`RayQuery`] statement. + /// + /// [`Proceed`]: RayQueryFunction::Proceed + /// [`RayQuery`]: Statement::RayQuery + RayQueryProceedResult, + + /// Return an intersection found by `query`. + /// + /// If `committed` is true, return the committed result available when + RayQueryGetIntersection { + query: Handle, + committed: bool, + }, +} + +pub use block::Block; + +/// The value of the switch case. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum SwitchValue { + I32(i32), + U32(u32), + Default, +} + +/// A case for a switch statement. +// Clone is used only for error reporting and is not intended for end users +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct SwitchCase { + /// Value, upon which the case is considered true. + pub value: SwitchValue, + /// Body of the case. + pub body: Block, + /// If true, the control flow continues to the next case in the list, + /// or default. + pub fall_through: bool, +} + +/// An operation that a [`RayQuery` statement] applies to its [`query`] operand. +/// +/// [`RayQuery` statement]: Statement::RayQuery +/// [`query`]: Statement::RayQuery::query +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum RayQueryFunction { + /// Initialize the `RayQuery` object. + Initialize { + /// The acceleration structure within which this query should search for hits. + /// + /// The expression must be an [`AccelerationStructure`]. + /// + /// [`AccelerationStructure`]: TypeInner::AccelerationStructure + acceleration_structure: Handle, + + #[allow(rustdoc::private_intra_doc_links)] + /// A struct of detailed parameters for the ray query. + /// + /// This expression should have the struct type given in + /// [`SpecialTypes::ray_desc`]. This is available in the WGSL + /// front end as the `RayDesc` type. + descriptor: Handle, + }, + + /// Start or continue the query given by the statement's [`query`] operand. + /// + /// After executing this statement, the `result` expression is a + /// [`Bool`] scalar indicating whether there are more intersection + /// candidates to consider. + /// + /// [`query`]: Statement::RayQuery::query + /// [`Bool`]: ScalarKind::Bool + Proceed { + result: Handle, + }, + + Terminate, +} + +//TODO: consider removing `Clone`. It's not valid to clone `Statement::Emit` anyway. +/// Instructions which make up an executable block. +// Clone is used only for error reporting and is not intended for end users +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum Statement { + /// Emit a range of expressions, visible to all statements that follow in this block. + /// + /// See the [module-level documentation][emit] for details. + /// + /// [emit]: index.html#expression-evaluation-time + Emit(Range), + /// A block containing more statements, to be executed sequentially. + Block(Block), + /// Conditionally executes one of two blocks, based on the value of the condition. + If { + condition: Handle, //bool + accept: Block, + reject: Block, + }, + /// Conditionally executes one of multiple blocks, based on the value of the selector. + /// + /// Each case must have a distinct [`value`], exactly one of which must be + /// [`Default`]. The `Default` may appear at any position, and covers all + /// values not explicitly appearing in other cases. A `Default` appearing in + /// the midst of the list of cases does not shadow the cases that follow. + /// + /// Some backend languages don't support fallthrough (HLSL due to FXC, + /// WGSL), and may translate fallthrough cases in the IR by duplicating + /// code. However, all backend languages do support cases selected by + /// multiple values, like `case 1: case 2: case 3: { ... }`. This is + /// represented in the IR as a series of fallthrough cases with empty + /// bodies, except for the last. + /// + /// [`value`]: SwitchCase::value + /// [`body`]: SwitchCase::body + /// [`Default`]: SwitchValue::Default + Switch { + selector: Handle, + cases: Vec, + }, + + /// Executes a block repeatedly. + /// + /// Each iteration of the loop executes the `body` block, followed by the + /// `continuing` block. + /// + /// Executing a [`Break`], [`Return`] or [`Kill`] statement exits the loop. + /// + /// A [`Continue`] statement in `body` jumps to the `continuing` block. The + /// `continuing` block is meant to be used to represent structures like the + /// third expression of a C-style `for` loop head, to which `continue` + /// statements in the loop's body jump. + /// + /// The `continuing` block and its substatements must not contain `Return` + /// or `Kill` statements, or any `Break` or `Continue` statements targeting + /// this loop. (It may have `Break` and `Continue` statements targeting + /// loops or switches nested within the `continuing` block.) Expressions + /// emitted in `body` are in scope in `continuing`. + /// + /// If present, `break_if` is an expression which is evaluated after the + /// continuing block. Expressions emitted in `body` or `continuing` are + /// considered to be in scope. If the expression's value is true, control + /// continues after the `Loop` statement, rather than branching back to the + /// top of body as usual. The `break_if` expression corresponds to a "break + /// if" statement in WGSL, or a loop whose back edge is an + /// `OpBranchConditional` instruction in SPIR-V. + /// + /// [`Break`]: Statement::Break + /// [`Continue`]: Statement::Continue + /// [`Kill`]: Statement::Kill + /// [`Return`]: Statement::Return + /// [`break if`]: Self::Loop::break_if + Loop { + body: Block, + continuing: Block, + break_if: Option>, + }, + + /// Exits the innermost enclosing [`Loop`] or [`Switch`]. + /// + /// A `Break` statement may only appear within a [`Loop`] or [`Switch`] + /// statement. It may not break out of a [`Loop`] from within the loop's + /// `continuing` block. + /// + /// [`Loop`]: Statement::Loop + /// [`Switch`]: Statement::Switch + Break, + + /// Skips to the `continuing` block of the innermost enclosing [`Loop`]. + /// + /// A `Continue` statement may only appear within the `body` block of the + /// innermost enclosing [`Loop`] statement. It must not appear within that + /// loop's `continuing` block. + /// + /// [`Loop`]: Statement::Loop + Continue, + + /// Returns from the function (possibly with a value). + /// + /// `Return` statements are forbidden within the `continuing` block of a + /// [`Loop`] statement. + /// + /// [`Loop`]: Statement::Loop + Return { value: Option> }, + + /// Aborts the current shader execution. + /// + /// `Kill` statements are forbidden within the `continuing` block of a + /// [`Loop`] statement. + /// + /// [`Loop`]: Statement::Loop + Kill, + + /// Synchronize invocations within the work group. + /// The `Barrier` flags control which memory accesses should be synchronized. + /// If empty, this becomes purely an execution barrier. + Barrier(Barrier), + /// Stores a value at an address. + /// + /// For [`TypeInner::Atomic`] type behind the pointer, the value + /// has to be a corresponding scalar. + /// For other types behind the `pointer`, the value is `T`. + /// + /// This statement is a barrier for any operations on the + /// `Expression::LocalVariable` or `Expression::GlobalVariable` + /// that is the destination of an access chain, started + /// from the `pointer`. + Store { + pointer: Handle, + value: Handle, + }, + /// Stores a texel value to an image. + /// + /// The `image`, `coordinate`, and `array_index` fields have the same + /// meanings as the corresponding operands of an [`ImageLoad`] expression; + /// see that documentation for details. Storing into multisampled images or + /// images with mipmaps is not supported, so there are no `level` or + /// `sample` operands. + /// + /// This statement is a barrier for any operations on the corresponding + /// [`Expression::GlobalVariable`] for this image. + /// + /// [`ImageLoad`]: Expression::ImageLoad + ImageStore { + image: Handle, + coordinate: Handle, + array_index: Option>, + value: Handle, + }, + /// Atomic function. + Atomic { + /// Pointer to an atomic value. + pointer: Handle, + /// Function to run on the atomic. + fun: AtomicFunction, + /// Value to use in the function. + value: Handle, + /// [`AtomicResult`] expression representing this function's result. + /// + /// [`AtomicResult`]: crate::Expression::AtomicResult + result: Handle, + }, + /// Load uniformly from a uniform pointer in the workgroup address space. + /// + /// Corresponds to the [`workgroupUniformLoad`](https://www.w3.org/TR/WGSL/#workgroupUniformLoad-builtin) + /// built-in function of wgsl, and has the same barrier semantics + WorkGroupUniformLoad { + /// This must be of type [`Pointer`] in the [`WorkGroup`] address space + /// + /// [`Pointer`]: TypeInner::Pointer + /// [`WorkGroup`]: AddressSpace::WorkGroup + pointer: Handle, + /// The [`WorkGroupUniformLoadResult`] expression representing this load's result. + /// + /// [`WorkGroupUniformLoadResult`]: Expression::WorkGroupUniformLoadResult + result: Handle, + }, + /// Calls a function. + /// + /// If the `result` is `Some`, the corresponding expression has to be + /// `Expression::CallResult`, and this statement serves as a barrier for any + /// operations on that expression. + Call { + function: Handle, + arguments: Vec>, + result: Option>, + }, + RayQuery { + /// The [`RayQuery`] object this statement operates on. + /// + /// [`RayQuery`]: TypeInner::RayQuery + query: Handle, + + /// The specific operation we're performing on `query`. + fun: RayQueryFunction, + }, +} + +/// A function argument. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct FunctionArgument { + /// Name of the argument, if any. + pub name: Option, + /// Type of the argument. + pub ty: Handle, + /// For entry points, an argument has to have a binding + /// unless it's a structure. + pub binding: Option, +} + +/// A function result. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct FunctionResult { + /// Type of the result. + pub ty: Handle, + /// For entry points, the result has to have a binding + /// unless it's a structure. + pub binding: Option, +} + +/// A function defined in the module. +#[derive(Debug, Default)] +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct Function { + /// Name of the function, if any. + pub name: Option, + /// Information about function argument. + pub arguments: Vec, + /// The result of this function, if any. + pub result: Option, + /// Local variables defined and used in the function. + pub local_variables: Arena, + /// Expressions used inside this function. + /// + /// An `Expression` must occur before all other `Expression`s that use its + /// value. + pub expressions: Arena, + /// Map of expressions that have associated variable names + pub named_expressions: NamedExpressions, + /// Block of instructions comprising the body of the function. + pub body: Block, +} + +/// The main function for a pipeline stage. +/// +/// An [`EntryPoint`] is a [`Function`] that serves as the main function for a +/// graphics or compute pipeline stage. For example, an `EntryPoint` whose +/// [`stage`] is [`ShaderStage::Vertex`] can serve as a graphics pipeline's +/// vertex shader. +/// +/// Since an entry point is called directly by the graphics or compute pipeline, +/// not by other WGSL functions, you must specify what the pipeline should pass +/// as the entry point's arguments, and what values it will return. For example, +/// a vertex shader needs a vertex's attributes as its arguments, but if it's +/// used for instanced draw calls, it will also want to know the instance id. +/// The vertex shader's return value will usually include an output vertex +/// position, and possibly other attributes to be interpolated and passed along +/// to a fragment shader. +/// +/// To specify this, the arguments and result of an `EntryPoint`'s [`function`] +/// must each have a [`Binding`], or be structs whose members all have +/// `Binding`s. This associates every value passed to or returned from the entry +/// point with either a [`BuiltIn`] or a [`Location`]: +/// +/// - A [`BuiltIn`] has special semantics, usually specific to its pipeline +/// stage. For example, the result of a vertex shader can include a +/// [`BuiltIn::Position`] value, which determines the position of a vertex +/// of a rendered primitive. Or, a compute shader might take an argument +/// whose binding is [`BuiltIn::WorkGroupSize`], through which the compute +/// pipeline would pass the number of invocations in your workgroup. +/// +/// - A [`Location`] indicates user-defined IO to be passed from one pipeline +/// stage to the next. For example, a vertex shader might also produce a +/// `uv` texture location as a user-defined IO value. +/// +/// In other words, the pipeline stage's input and output interface are +/// determined by the bindings of the arguments and result of the `EntryPoint`'s +/// [`function`]. +/// +/// [`Function`]: crate::Function +/// [`Location`]: Binding::Location +/// [`function`]: EntryPoint::function +/// [`stage`]: EntryPoint::stage +#[derive(Debug)] +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct EntryPoint { + /// Name of this entry point, visible externally. + /// + /// Entry point names for a given `stage` must be distinct within a module. + pub name: String, + /// Shader stage. + pub stage: ShaderStage, + /// Early depth test for fragment stages. + pub early_depth_test: Option, + /// Workgroup size for compute stages + pub workgroup_size: [u32; 3], + /// The entrance function. + pub function: Function, +} + +/// Return types predeclared for the frexp, modf, and atomicCompareExchangeWeak built-in functions. +/// +/// These cannot be spelled in WGSL source. +/// +/// Stored in [`SpecialTypes::predeclared_types`] and created by [`Module::generate_predeclared_type`]. +#[derive(Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum PredeclaredType { + AtomicCompareExchangeWeakResult(Scalar), + ModfResult { + size: Option, + width: Bytes, + }, + FrexpResult { + size: Option, + width: Bytes, + }, +} + +/// Set of special types that can be optionally generated by the frontends. +#[derive(Debug, Default)] +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct SpecialTypes { + /// Type for `RayDesc`. + /// + /// Call [`Module::generate_ray_desc_type`] to populate this if + /// needed and return the handle. + pub ray_desc: Option>, + + /// Type for `RayIntersection`. + /// + /// Call [`Module::generate_ray_intersection_type`] to populate + /// this if needed and return the handle. + pub ray_intersection: Option>, + + /// Types for predeclared wgsl types instantiated on demand. + /// + /// Call [`Module::generate_predeclared_type`] to populate this if + /// needed and return the handle. + pub predeclared_types: FastIndexMap>, +} + +/// Shader module. +/// +/// A module is a set of constants, global variables and functions, as well as +/// the types required to define them. +/// +/// Some functions are marked as entry points, to be used in a certain shader stage. +/// +/// To create a new module, use the `Default` implementation. +/// Alternatively, you can load an existing shader using one of the [available front ends][front]. +/// +/// When finished, you can export modules using one of the [available backends][back]. +#[derive(Debug, Default)] +#[cfg_attr(feature = "clone", derive(Clone))] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct Module { + /// Arena for the types defined in this module. + pub types: UniqueArena, + /// Dictionary of special type handles. + pub special_types: SpecialTypes, + /// Arena for the constants defined in this module. + pub constants: Arena, + /// Arena for the global variables defined in this module. + pub global_variables: Arena, + /// [Constant expressions] and [override expressions] used by this module. + /// + /// Each `Expression` must occur in the arena before any + /// `Expression` that uses its value. + /// + /// [Constant expressions]: index.html#constant-expressions + /// [override expressions]: index.html#override-expressions + pub const_expressions: Arena, + /// Arena for the functions defined in this module. + /// + /// Each function must appear in this arena strictly before all its callers. + /// Recursion is not supported. + pub functions: Arena, + /// Entry points. + pub entry_points: Vec, +} diff --git a/naga/src/proc/constant_evaluator.rs b/naga/src/proc/constant_evaluator.rs new file mode 100644 index 0000000000..82efeece4e --- /dev/null +++ b/naga/src/proc/constant_evaluator.rs @@ -0,0 +1,2198 @@ +use crate::{ + arena::{Arena, Handle, UniqueArena}, + ArraySize, BinaryOperator, Constant, Expression, Literal, ScalarKind, Span, Type, TypeInner, + UnaryOperator, +}; + +#[derive(Debug)] +enum Behavior { + Wgsl, + Glsl, +} + +/// A context for evaluating constant expressions. +/// +/// A `ConstantEvaluator` points at an expression arena to which it can append +/// newly evaluated expressions: you pass [`try_eval_and_append`] whatever kind +/// of Naga [`Expression`] you like, and if its value can be computed at compile +/// time, `try_eval_and_append` appends an expression representing the computed +/// value - a tree of [`Literal`], [`Compose`], [`ZeroValue`], and [`Swizzle`] +/// expressions - to the arena. See the [`try_eval_and_append`] method for details. +/// +/// A `ConstantEvaluator` also holds whatever information we need to carry out +/// that evaluation: types, other constants, and so on. +/// +/// [`try_eval_and_append`]: ConstantEvaluator::try_eval_and_append +/// [`Compose`]: Expression::Compose +/// [`ZeroValue`]: Expression::ZeroValue +/// [`Literal`]: Expression::Literal +/// [`Swizzle`]: Expression::Swizzle +#[derive(Debug)] +pub struct ConstantEvaluator<'a> { + /// Which language's evaluation rules we should follow. + behavior: Behavior, + + /// The module's type arena. + /// + /// Because expressions like [`Splat`] contain type handles, we need to be + /// able to add new types to produce those expressions. + /// + /// [`Splat`]: Expression::Splat + types: &'a mut UniqueArena, + + /// The module's constant arena. + constants: &'a Arena, + + /// The arena to which we are contributing expressions. + expressions: &'a mut Arena, + + /// When `self.expressions` refers to a function's local expression + /// arena, this needs to be populated + function_local_data: Option>, +} + +#[derive(Debug)] +struct FunctionLocalData<'a> { + /// Global constant expressions + const_expressions: &'a Arena, + /// Tracks the constness of expressions residing in `ConstantEvaluator.expressions` + expression_constness: &'a mut ExpressionConstnessTracker, + emitter: &'a mut super::Emitter, + block: &'a mut crate::Block, +} + +#[derive(Debug)] +pub struct ExpressionConstnessTracker { + inner: bit_set::BitSet, +} + +impl ExpressionConstnessTracker { + pub fn new() -> Self { + Self { + inner: bit_set::BitSet::new(), + } + } + + /// Forces the the expression to not be const + pub fn force_non_const(&mut self, value: Handle) { + self.inner.remove(value.index()); + } + + fn insert(&mut self, value: Handle) { + self.inner.insert(value.index()); + } + + pub fn is_const(&self, value: Handle) -> bool { + self.inner.contains(value.index()) + } + + pub fn from_arena(arena: &Arena) -> Self { + let mut tracker = Self::new(); + for (handle, expr) in arena.iter() { + let insert = match *expr { + crate::Expression::Literal(_) + | crate::Expression::ZeroValue(_) + | crate::Expression::Constant(_) => true, + crate::Expression::Compose { ref components, .. } => { + components.iter().all(|h| tracker.is_const(*h)) + } + crate::Expression::Splat { value, .. } => tracker.is_const(value), + _ => false, + }; + if insert { + tracker.insert(handle); + } + } + tracker + } +} + +#[derive(Clone, Debug, thiserror::Error)] +pub enum ConstantEvaluatorError { + #[error("Constants cannot access function arguments")] + FunctionArg, + #[error("Constants cannot access global variables")] + GlobalVariable, + #[error("Constants cannot access local variables")] + LocalVariable, + #[error("Cannot get the array length of a non array type")] + InvalidArrayLengthArg, + #[error("Constants cannot get the array length of a dynamically sized array")] + ArrayLengthDynamic, + #[error("Constants cannot call functions")] + Call, + #[error("Constants don't support workGroupUniformLoad")] + WorkGroupUniformLoadResult, + #[error("Constants don't support atomic functions")] + Atomic, + #[error("Constants don't support derivative functions")] + Derivative, + #[error("Constants don't support load expressions")] + Load, + #[error("Constants don't support image expressions")] + ImageExpression, + #[error("Constants don't support ray query expressions")] + RayQueryExpression, + #[error("Cannot access the type")] + InvalidAccessBase, + #[error("Cannot access at the index")] + InvalidAccessIndex, + #[error("Cannot access with index of type")] + InvalidAccessIndexTy, + #[error("Constants don't support array length expressions")] + ArrayLength, + #[error("Cannot cast scalar components of expression `{from}` to type `{to}`")] + InvalidCastArg { from: String, to: String }, + #[error("Cannot apply the unary op to the argument")] + InvalidUnaryOpArg, + #[error("Cannot apply the binary op to the arguments")] + InvalidBinaryOpArgs, + #[error("Cannot apply math function to type")] + InvalidMathArg, + #[error("{0:?} built-in function expects {1:?} arguments but {2:?} were supplied")] + InvalidMathArgCount(crate::MathFunction, usize, usize), + #[error("value of `low` is greater than `high` for clamp built-in function")] + InvalidClamp, + #[error("Splat is defined only on scalar values")] + SplatScalarOnly, + #[error("Can only swizzle vector constants")] + SwizzleVectorOnly, + #[error("swizzle component not present in source expression")] + SwizzleOutOfBounds, + #[error("Type is not constructible")] + TypeNotConstructible, + #[error("Subexpression(s) are not constant")] + SubexpressionsAreNotConstant, + #[error("Not implemented as constant expression: {0}")] + NotImplemented(String), + #[error("{0} operation overflowed")] + Overflow(String), + #[error( + "the concrete type `{to_type}` cannot represent the abstract value `{value}` accurately" + )] + AutomaticConversionLossy { + value: String, + to_type: &'static str, + }, + #[error("abstract floating-point values cannot be automatically converted to integers")] + AutomaticConversionFloatToInt { to_type: &'static str }, + #[error("Division by zero")] + DivisionByZero, + #[error("Remainder by zero")] + RemainderByZero, + #[error("RHS of shift operation is greater than or equal to 32")] + ShiftedMoreThan32Bits, + #[error(transparent)] + Literal(#[from] crate::valid::LiteralError), +} + +impl<'a> ConstantEvaluator<'a> { + /// Return a [`ConstantEvaluator`] that will add expressions to `module`'s + /// constant expression arena. + /// + /// Report errors according to WGSL's rules for constant evaluation. + pub fn for_wgsl_module(module: &'a mut crate::Module) -> Self { + Self::for_module(Behavior::Wgsl, module) + } + + /// Return a [`ConstantEvaluator`] that will add expressions to `module`'s + /// constant expression arena. + /// + /// Report errors according to GLSL's rules for constant evaluation. + pub fn for_glsl_module(module: &'a mut crate::Module) -> Self { + Self::for_module(Behavior::Glsl, module) + } + + fn for_module(behavior: Behavior, module: &'a mut crate::Module) -> Self { + Self { + behavior, + types: &mut module.types, + constants: &module.constants, + expressions: &mut module.const_expressions, + function_local_data: None, + } + } + + /// Return a [`ConstantEvaluator`] that will add expressions to `function`'s + /// expression arena. + /// + /// Report errors according to WGSL's rules for constant evaluation. + pub fn for_wgsl_function( + module: &'a mut crate::Module, + expressions: &'a mut Arena, + expression_constness: &'a mut ExpressionConstnessTracker, + emitter: &'a mut super::Emitter, + block: &'a mut crate::Block, + ) -> Self { + Self::for_function( + Behavior::Wgsl, + module, + expressions, + expression_constness, + emitter, + block, + ) + } + + /// Return a [`ConstantEvaluator`] that will add expressions to `function`'s + /// expression arena. + /// + /// Report errors according to GLSL's rules for constant evaluation. + pub fn for_glsl_function( + module: &'a mut crate::Module, + expressions: &'a mut Arena, + expression_constness: &'a mut ExpressionConstnessTracker, + emitter: &'a mut super::Emitter, + block: &'a mut crate::Block, + ) -> Self { + Self::for_function( + Behavior::Glsl, + module, + expressions, + expression_constness, + emitter, + block, + ) + } + + fn for_function( + behavior: Behavior, + module: &'a mut crate::Module, + expressions: &'a mut Arena, + expression_constness: &'a mut ExpressionConstnessTracker, + emitter: &'a mut super::Emitter, + block: &'a mut crate::Block, + ) -> Self { + Self { + behavior, + types: &mut module.types, + constants: &module.constants, + expressions, + function_local_data: Some(FunctionLocalData { + const_expressions: &module.const_expressions, + expression_constness, + emitter, + block, + }), + } + } + + pub fn to_ctx(&self) -> crate::proc::GlobalCtx { + crate::proc::GlobalCtx { + types: self.types, + constants: self.constants, + const_expressions: match self.function_local_data { + Some(ref data) => data.const_expressions, + None => self.expressions, + }, + } + } + + fn check(&self, expr: Handle) -> Result<(), ConstantEvaluatorError> { + if let Some(ref function_local_data) = self.function_local_data { + if !function_local_data.expression_constness.is_const(expr) { + log::debug!("check: SubexpressionsAreNotConstant"); + return Err(ConstantEvaluatorError::SubexpressionsAreNotConstant); + } + } + Ok(()) + } + + fn check_and_get( + &mut self, + expr: Handle, + ) -> Result, ConstantEvaluatorError> { + match self.expressions[expr] { + Expression::Constant(c) => { + // Are we working in a function's expression arena, or the + // module's constant expression arena? + if let Some(ref function_local_data) = self.function_local_data { + // Deep-copy the constant's value into our arena. + self.copy_from( + self.constants[c].init, + function_local_data.const_expressions, + ) + } else { + // "See through" the constant and use its initializer. + Ok(self.constants[c].init) + } + } + _ => { + self.check(expr)?; + Ok(expr) + } + } + } + + /// Try to evaluate `expr` at compile time. + /// + /// The `expr` argument can be any sort of Naga [`Expression`] you like. If + /// we can determine its value at compile time, we append an expression + /// representing its value - a tree of [`Literal`], [`Compose`], + /// [`ZeroValue`], and [`Swizzle`] expressions - to the expression arena + /// `self` contributes to. + /// + /// If `expr`'s value cannot be determined at compile time, return a an + /// error. If it's acceptable to evaluate `expr` at runtime, this error can + /// be ignored, and the caller can append `expr` to the arena itself. + /// + /// We only consider `expr` itself, without recursing into its operands. Its + /// operands must all have been produced by prior calls to + /// `try_eval_and_append`, to ensure that they have already been reduced to + /// an evaluated form if possible. + /// + /// [`Literal`]: Expression::Literal + /// [`Compose`]: Expression::Compose + /// [`ZeroValue`]: Expression::ZeroValue + /// [`Swizzle`]: Expression::Swizzle + pub fn try_eval_and_append( + &mut self, + expr: &Expression, + span: Span, + ) -> Result, ConstantEvaluatorError> { + log::trace!("try_eval_and_append: {:?}", expr); + match *expr { + Expression::Constant(c) if self.function_local_data.is_none() => { + // "See through" the constant and use its initializer. + // This is mainly done to avoid having constants pointing to other constants. + Ok(self.constants[c].init) + } + Expression::Literal(_) | Expression::ZeroValue(_) | Expression::Constant(_) => { + self.register_evaluated_expr(expr.clone(), span) + } + Expression::Compose { ty, ref components } => { + let components = components + .iter() + .map(|component| self.check_and_get(*component)) + .collect::, _>>()?; + self.register_evaluated_expr(Expression::Compose { ty, components }, span) + } + Expression::Splat { size, value } => { + let value = self.check_and_get(value)?; + self.register_evaluated_expr(Expression::Splat { size, value }, span) + } + Expression::AccessIndex { base, index } => { + let base = self.check_and_get(base)?; + + self.access(base, index as usize, span) + } + Expression::Access { base, index } => { + let base = self.check_and_get(base)?; + let index = self.check_and_get(index)?; + + self.access(base, self.constant_index(index)?, span) + } + Expression::Swizzle { + size, + vector, + pattern, + } => { + let vector = self.check_and_get(vector)?; + + self.swizzle(size, span, vector, pattern) + } + Expression::Unary { expr, op } => { + let expr = self.check_and_get(expr)?; + + self.unary_op(op, expr, span) + } + Expression::Binary { left, right, op } => { + let left = self.check_and_get(left)?; + let right = self.check_and_get(right)?; + + self.binary_op(op, left, right, span) + } + Expression::Math { + fun, + arg, + arg1, + arg2, + arg3, + } => { + let arg = self.check_and_get(arg)?; + let arg1 = arg1.map(|arg| self.check_and_get(arg)).transpose()?; + let arg2 = arg2.map(|arg| self.check_and_get(arg)).transpose()?; + let arg3 = arg3.map(|arg| self.check_and_get(arg)).transpose()?; + + self.math(arg, arg1, arg2, arg3, fun, span) + } + Expression::As { + convert, + expr, + kind, + } => { + let expr = self.check_and_get(expr)?; + + match convert { + Some(width) => self.cast(expr, crate::Scalar { kind, width }, span), + None => Err(ConstantEvaluatorError::NotImplemented( + "bitcast built-in function".into(), + )), + } + } + Expression::Select { .. } => Err(ConstantEvaluatorError::NotImplemented( + "select built-in function".into(), + )), + Expression::Relational { fun, .. } => Err(ConstantEvaluatorError::NotImplemented( + format!("{fun:?} built-in function"), + )), + Expression::ArrayLength(expr) => match self.behavior { + Behavior::Wgsl => Err(ConstantEvaluatorError::ArrayLength), + Behavior::Glsl => { + let expr = self.check_and_get(expr)?; + self.array_length(expr, span) + } + }, + Expression::Load { .. } => Err(ConstantEvaluatorError::Load), + Expression::LocalVariable(_) => Err(ConstantEvaluatorError::LocalVariable), + Expression::Derivative { .. } => Err(ConstantEvaluatorError::Derivative), + Expression::CallResult { .. } => Err(ConstantEvaluatorError::Call), + Expression::WorkGroupUniformLoadResult { .. } => { + Err(ConstantEvaluatorError::WorkGroupUniformLoadResult) + } + Expression::AtomicResult { .. } => Err(ConstantEvaluatorError::Atomic), + Expression::FunctionArgument(_) => Err(ConstantEvaluatorError::FunctionArg), + Expression::GlobalVariable(_) => Err(ConstantEvaluatorError::GlobalVariable), + Expression::ImageSample { .. } + | Expression::ImageLoad { .. } + | Expression::ImageQuery { .. } => Err(ConstantEvaluatorError::ImageExpression), + Expression::RayQueryProceedResult | Expression::RayQueryGetIntersection { .. } => { + Err(ConstantEvaluatorError::RayQueryExpression) + } + } + } + + /// Splat `value` to `size`, without using [`Splat`] expressions. + /// + /// This constructs [`Compose`] or [`ZeroValue`] expressions to + /// build a vector with the given `size` whose components are all + /// `value`. + /// + /// Use `span` as the span of the inserted expressions and + /// resulting types. + /// + /// [`Splat`]: Expression::Splat + /// [`Compose`]: Expression::Compose + /// [`ZeroValue`]: Expression::ZeroValue + fn splat( + &mut self, + value: Handle, + size: crate::VectorSize, + span: Span, + ) -> Result, ConstantEvaluatorError> { + match self.expressions[value] { + Expression::Literal(literal) => { + let scalar = literal.scalar(); + let ty = self.types.insert( + Type { + name: None, + inner: TypeInner::Vector { size, scalar }, + }, + span, + ); + let expr = Expression::Compose { + ty, + components: vec![value; size as usize], + }; + self.register_evaluated_expr(expr, span) + } + Expression::ZeroValue(ty) => { + let inner = match self.types[ty].inner { + TypeInner::Scalar(scalar) => TypeInner::Vector { size, scalar }, + _ => return Err(ConstantEvaluatorError::SplatScalarOnly), + }; + let res_ty = self.types.insert(Type { name: None, inner }, span); + let expr = Expression::ZeroValue(res_ty); + self.register_evaluated_expr(expr, span) + } + _ => Err(ConstantEvaluatorError::SplatScalarOnly), + } + } + + fn swizzle( + &mut self, + size: crate::VectorSize, + span: Span, + src_constant: Handle, + pattern: [crate::SwizzleComponent; 4], + ) -> Result, ConstantEvaluatorError> { + let mut get_dst_ty = |ty| match self.types[ty].inner { + crate::TypeInner::Vector { size: _, scalar } => Ok(self.types.insert( + Type { + name: None, + inner: crate::TypeInner::Vector { size, scalar }, + }, + span, + )), + _ => Err(ConstantEvaluatorError::SwizzleVectorOnly), + }; + + match self.expressions[src_constant] { + Expression::ZeroValue(ty) => { + let dst_ty = get_dst_ty(ty)?; + let expr = Expression::ZeroValue(dst_ty); + self.register_evaluated_expr(expr, span) + } + Expression::Splat { value, .. } => { + let expr = Expression::Splat { size, value }; + self.register_evaluated_expr(expr, span) + } + Expression::Compose { ty, ref components } => { + let dst_ty = get_dst_ty(ty)?; + + let mut flattened = [src_constant; 4]; // dummy value + let len = + crate::proc::flatten_compose(ty, components, self.expressions, self.types) + .zip(flattened.iter_mut()) + .map(|(component, elt)| *elt = component) + .count(); + let flattened = &flattened[..len]; + + let swizzled_components = pattern[..size as usize] + .iter() + .map(|&sc| { + let sc = sc as usize; + if let Some(elt) = flattened.get(sc) { + Ok(*elt) + } else { + Err(ConstantEvaluatorError::SwizzleOutOfBounds) + } + }) + .collect::>, _>>()?; + let expr = Expression::Compose { + ty: dst_ty, + components: swizzled_components, + }; + self.register_evaluated_expr(expr, span) + } + _ => Err(ConstantEvaluatorError::SwizzleVectorOnly), + } + } + + fn math( + &mut self, + arg: Handle, + arg1: Option>, + arg2: Option>, + arg3: Option>, + fun: crate::MathFunction, + span: Span, + ) -> Result, ConstantEvaluatorError> { + let expected = fun.argument_count(); + let given = Some(arg) + .into_iter() + .chain(arg1) + .chain(arg2) + .chain(arg3) + .count(); + if expected != given { + return Err(ConstantEvaluatorError::InvalidMathArgCount( + fun, expected, given, + )); + } + + match fun { + crate::MathFunction::Pow => self.math_pow(arg, arg1.unwrap(), span), + crate::MathFunction::Clamp => self.math_clamp(arg, arg1.unwrap(), arg2.unwrap(), span), + fun => Err(ConstantEvaluatorError::NotImplemented(format!( + "{fun:?} built-in function" + ))), + } + } + + fn math_pow( + &mut self, + e1: Handle, + e2: Handle, + span: Span, + ) -> Result, ConstantEvaluatorError> { + let e1 = self.eval_zero_value_and_splat(e1, span)?; + let e2 = self.eval_zero_value_and_splat(e2, span)?; + + let expr = match (&self.expressions[e1], &self.expressions[e2]) { + (&Expression::Literal(Literal::F32(a)), &Expression::Literal(Literal::F32(b))) => { + Expression::Literal(Literal::F32(a.powf(b))) + } + ( + &Expression::Compose { + components: ref src_components0, + ty: ty0, + }, + &Expression::Compose { + components: ref src_components1, + ty: ty1, + }, + ) if ty0 == ty1 + && matches!( + self.types[ty0].inner, + crate::TypeInner::Vector { + scalar: crate::Scalar { + kind: ScalarKind::Float, + .. + }, + .. + } + ) => + { + let mut components: Vec<_> = crate::proc::flatten_compose( + ty0, + src_components0, + self.expressions, + self.types, + ) + .chain(crate::proc::flatten_compose( + ty1, + src_components1, + self.expressions, + self.types, + )) + .collect(); + + let mid = components.len() / 2; + let (first, last) = components.split_at_mut(mid); + for (a, b) in first.iter_mut().zip(&*last) { + *a = self.math_pow(*a, *b, span)?; + } + components.truncate(mid); + + Expression::Compose { + ty: ty0, + components, + } + } + _ => return Err(ConstantEvaluatorError::InvalidMathArg), + }; + + self.register_evaluated_expr(expr, span) + } + + fn math_clamp( + &mut self, + e: Handle, + low: Handle, + high: Handle, + span: Span, + ) -> Result, ConstantEvaluatorError> { + let e = self.eval_zero_value_and_splat(e, span)?; + let low = self.eval_zero_value_and_splat(low, span)?; + let high = self.eval_zero_value_and_splat(high, span)?; + + let expr = match ( + &self.expressions[e], + &self.expressions[low], + &self.expressions[high], + ) { + (&Expression::Literal(e), &Expression::Literal(low), &Expression::Literal(high)) => { + let literal = match (e, low, high) { + (Literal::I32(e), Literal::I32(low), Literal::I32(high)) => { + if low > high { + return Err(ConstantEvaluatorError::InvalidClamp); + } else { + Literal::I32(e.clamp(low, high)) + } + } + (Literal::U32(e), Literal::U32(low), Literal::U32(high)) => { + if low > high { + return Err(ConstantEvaluatorError::InvalidClamp); + } else { + Literal::U32(e.clamp(low, high)) + } + } + (Literal::F32(e), Literal::F32(low), Literal::F32(high)) => { + if low > high { + return Err(ConstantEvaluatorError::InvalidClamp); + } else { + Literal::F32(e.clamp(low, high)) + } + } + _ => return Err(ConstantEvaluatorError::InvalidMathArg), + }; + Expression::Literal(literal) + } + ( + &Expression::Compose { + components: ref src_components0, + ty: ty0, + }, + &Expression::Compose { + components: ref src_components1, + ty: ty1, + }, + &Expression::Compose { + components: ref src_components2, + ty: ty2, + }, + ) if ty0 == ty1 + && ty0 == ty2 + && matches!( + self.types[ty0].inner, + crate::TypeInner::Vector { + scalar: crate::Scalar { + kind: ScalarKind::Float, + .. + }, + .. + } + ) => + { + let mut components: Vec<_> = crate::proc::flatten_compose( + ty0, + src_components0, + self.expressions, + self.types, + ) + .chain(crate::proc::flatten_compose( + ty1, + src_components1, + self.expressions, + self.types, + )) + .chain(crate::proc::flatten_compose( + ty2, + src_components2, + self.expressions, + self.types, + )) + .collect(); + + let chunk_size = components.len() / 3; + let (es, rem) = components.split_at_mut(chunk_size); + let (lows, highs) = rem.split_at(chunk_size); + for ((e, low), high) in es.iter_mut().zip(lows).zip(highs) { + *e = self.math_clamp(*e, *low, *high, span)?; + } + components.truncate(chunk_size); + + Expression::Compose { + ty: ty0, + components, + } + } + _ => return Err(ConstantEvaluatorError::InvalidMathArg), + }; + + self.register_evaluated_expr(expr, span) + } + + fn array_length( + &mut self, + array: Handle, + span: Span, + ) -> Result, ConstantEvaluatorError> { + match self.expressions[array] { + Expression::ZeroValue(ty) | Expression::Compose { ty, .. } => { + match self.types[ty].inner { + TypeInner::Array { size, .. } => match size { + crate::ArraySize::Constant(len) => { + let expr = Expression::Literal(Literal::U32(len.get())); + self.register_evaluated_expr(expr, span) + } + crate::ArraySize::Dynamic => { + Err(ConstantEvaluatorError::ArrayLengthDynamic) + } + }, + _ => Err(ConstantEvaluatorError::InvalidArrayLengthArg), + } + } + _ => Err(ConstantEvaluatorError::InvalidArrayLengthArg), + } + } + + fn access( + &mut self, + base: Handle, + index: usize, + span: Span, + ) -> Result, ConstantEvaluatorError> { + match self.expressions[base] { + Expression::ZeroValue(ty) => { + let ty_inner = &self.types[ty].inner; + let components = ty_inner + .components() + .ok_or(ConstantEvaluatorError::InvalidAccessBase)?; + + if index >= components as usize { + Err(ConstantEvaluatorError::InvalidAccessBase) + } else { + let ty_res = ty_inner + .component_type(index) + .ok_or(ConstantEvaluatorError::InvalidAccessIndex)?; + let ty = match ty_res { + crate::proc::TypeResolution::Handle(ty) => ty, + crate::proc::TypeResolution::Value(inner) => { + self.types.insert(Type { name: None, inner }, span) + } + }; + self.register_evaluated_expr(Expression::ZeroValue(ty), span) + } + } + Expression::Splat { size, value } => { + if index >= size as usize { + Err(ConstantEvaluatorError::InvalidAccessBase) + } else { + Ok(value) + } + } + Expression::Compose { ty, ref components } => { + let _ = self.types[ty] + .inner + .components() + .ok_or(ConstantEvaluatorError::InvalidAccessBase)?; + + crate::proc::flatten_compose(ty, components, self.expressions, self.types) + .nth(index) + .ok_or(ConstantEvaluatorError::InvalidAccessIndex) + } + _ => Err(ConstantEvaluatorError::InvalidAccessBase), + } + } + + fn constant_index(&self, expr: Handle) -> Result { + match self.expressions[expr] { + Expression::ZeroValue(ty) + if matches!( + self.types[ty].inner, + crate::TypeInner::Scalar(crate::Scalar { + kind: ScalarKind::Uint, + .. + }) + ) => + { + Ok(0) + } + Expression::Literal(Literal::U32(index)) => Ok(index as usize), + _ => Err(ConstantEvaluatorError::InvalidAccessIndexTy), + } + } + + /// Lower [`ZeroValue`] and [`Splat`] expressions to [`Literal`] and [`Compose`] expressions. + /// + /// [`ZeroValue`]: Expression::ZeroValue + /// [`Splat`]: Expression::Splat + /// [`Literal`]: Expression::Literal + /// [`Compose`]: Expression::Compose + fn eval_zero_value_and_splat( + &mut self, + expr: Handle, + span: Span, + ) -> Result, ConstantEvaluatorError> { + match self.expressions[expr] { + Expression::ZeroValue(ty) => self.eval_zero_value_impl(ty, span), + Expression::Splat { size, value } => self.splat(value, size, span), + _ => Ok(expr), + } + } + + /// Lower [`ZeroValue`] expressions to [`Literal`] and [`Compose`] expressions. + /// + /// [`ZeroValue`]: Expression::ZeroValue + /// [`Literal`]: Expression::Literal + /// [`Compose`]: Expression::Compose + fn eval_zero_value( + &mut self, + expr: Handle, + span: Span, + ) -> Result, ConstantEvaluatorError> { + match self.expressions[expr] { + Expression::ZeroValue(ty) => self.eval_zero_value_impl(ty, span), + _ => Ok(expr), + } + } + + /// Lower [`ZeroValue`] expressions to [`Literal`] and [`Compose`] expressions. + /// + /// [`ZeroValue`]: Expression::ZeroValue + /// [`Literal`]: Expression::Literal + /// [`Compose`]: Expression::Compose + fn eval_zero_value_impl( + &mut self, + ty: Handle, + span: Span, + ) -> Result, ConstantEvaluatorError> { + match self.types[ty].inner { + TypeInner::Scalar(scalar) => { + let expr = Expression::Literal( + Literal::zero(scalar).ok_or(ConstantEvaluatorError::TypeNotConstructible)?, + ); + self.register_evaluated_expr(expr, span) + } + TypeInner::Vector { size, scalar } => { + let scalar_ty = self.types.insert( + Type { + name: None, + inner: TypeInner::Scalar(scalar), + }, + span, + ); + let el = self.eval_zero_value_impl(scalar_ty, span)?; + let expr = Expression::Compose { + ty, + components: vec![el; size as usize], + }; + self.register_evaluated_expr(expr, span) + } + TypeInner::Matrix { + columns, + rows, + scalar, + } => { + let vec_ty = self.types.insert( + Type { + name: None, + inner: TypeInner::Vector { size: rows, scalar }, + }, + span, + ); + let el = self.eval_zero_value_impl(vec_ty, span)?; + let expr = Expression::Compose { + ty, + components: vec![el; columns as usize], + }; + self.register_evaluated_expr(expr, span) + } + TypeInner::Array { + base, + size: ArraySize::Constant(size), + .. + } => { + let el = self.eval_zero_value_impl(base, span)?; + let expr = Expression::Compose { + ty, + components: vec![el; size.get() as usize], + }; + self.register_evaluated_expr(expr, span) + } + TypeInner::Struct { ref members, .. } => { + let types: Vec<_> = members.iter().map(|m| m.ty).collect(); + let mut components = Vec::with_capacity(members.len()); + for ty in types { + components.push(self.eval_zero_value_impl(ty, span)?); + } + let expr = Expression::Compose { ty, components }; + self.register_evaluated_expr(expr, span) + } + _ => Err(ConstantEvaluatorError::TypeNotConstructible), + } + } + + /// Convert the scalar components of `expr` to `target`. + /// + /// Treat `span` as the location of the resulting expression. + pub fn cast( + &mut self, + expr: Handle, + target: crate::Scalar, + span: Span, + ) -> Result, ConstantEvaluatorError> { + use crate::Scalar as Sc; + + let expr = self.eval_zero_value(expr, span)?; + + let make_error = || -> Result<_, ConstantEvaluatorError> { + let from = format!("{:?} {:?}", expr, self.expressions[expr]); + + #[cfg(feature = "wgsl-in")] + let to = target.to_wgsl(); + + #[cfg(not(feature = "wgsl-in"))] + let to = format!("{target:?}"); + + Err(ConstantEvaluatorError::InvalidCastArg { from, to }) + }; + + let expr = match self.expressions[expr] { + Expression::Literal(literal) => { + let literal = match target { + Sc::I32 => Literal::I32(match literal { + Literal::I32(v) => v, + Literal::U32(v) => v as i32, + Literal::F32(v) => v as i32, + Literal::Bool(v) => v as i32, + Literal::F64(_) | Literal::I64(_) => { + return make_error(); + } + Literal::AbstractInt(v) => i32::try_from_abstract(v)?, + Literal::AbstractFloat(v) => i32::try_from_abstract(v)?, + }), + Sc::U32 => Literal::U32(match literal { + Literal::I32(v) => v as u32, + Literal::U32(v) => v, + Literal::F32(v) => v as u32, + Literal::Bool(v) => v as u32, + Literal::F64(_) | Literal::I64(_) => { + return make_error(); + } + Literal::AbstractInt(v) => u32::try_from_abstract(v)?, + Literal::AbstractFloat(v) => u32::try_from_abstract(v)?, + }), + Sc::F32 => Literal::F32(match literal { + Literal::I32(v) => v as f32, + Literal::U32(v) => v as f32, + Literal::F32(v) => v, + Literal::Bool(v) => v as u32 as f32, + Literal::F64(_) | Literal::I64(_) => { + return make_error(); + } + Literal::AbstractInt(v) => f32::try_from_abstract(v)?, + Literal::AbstractFloat(v) => f32::try_from_abstract(v)?, + }), + Sc::F64 => Literal::F64(match literal { + Literal::I32(v) => v as f64, + Literal::U32(v) => v as f64, + Literal::F32(v) => v as f64, + Literal::F64(v) => v, + Literal::Bool(v) => v as u32 as f64, + Literal::I64(_) => return make_error(), + Literal::AbstractInt(v) => f64::try_from_abstract(v)?, + Literal::AbstractFloat(v) => f64::try_from_abstract(v)?, + }), + Sc::BOOL => Literal::Bool(match literal { + Literal::I32(v) => v != 0, + Literal::U32(v) => v != 0, + Literal::F32(v) => v != 0.0, + Literal::Bool(v) => v, + Literal::F64(_) + | Literal::I64(_) + | Literal::AbstractInt(_) + | Literal::AbstractFloat(_) => { + return make_error(); + } + }), + Sc::ABSTRACT_FLOAT => Literal::AbstractFloat(match literal { + Literal::AbstractInt(v) => { + // Overflow is forbidden, but inexact conversions + // are fine. The range of f64 is far larger than + // that of i64, so we don't have to check anything + // here. + v as f64 + } + Literal::AbstractFloat(v) => v, + _ => return make_error(), + }), + _ => { + log::debug!("Constant evaluator refused to convert value to {target:?}"); + return make_error(); + } + }; + Expression::Literal(literal) + } + Expression::Compose { + ty, + components: ref src_components, + } => { + let ty_inner = match self.types[ty].inner { + TypeInner::Vector { size, .. } => TypeInner::Vector { + size, + scalar: target, + }, + TypeInner::Matrix { columns, rows, .. } => TypeInner::Matrix { + columns, + rows, + scalar: target, + }, + _ => return make_error(), + }; + + let mut components = src_components.clone(); + for component in &mut components { + *component = self.cast(*component, target, span)?; + } + + let ty = self.types.insert( + Type { + name: None, + inner: ty_inner, + }, + span, + ); + + Expression::Compose { ty, components } + } + Expression::Splat { size, value } => { + let value_span = self.expressions.get_span(value); + let cast_value = self.cast(value, target, value_span)?; + Expression::Splat { + size, + value: cast_value, + } + } + _ => return make_error(), + }; + + self.register_evaluated_expr(expr, span) + } + + /// Convert the scalar leaves of `expr` to `target`, handling arrays. + /// + /// `expr` must be a `Compose` expression whose type is a scalar, vector, + /// matrix, or nested arrays of such. + /// + /// This is basically the same as the [`cast`] method, except that that + /// should only handle Naga [`As`] expressions, which cannot convert arrays. + /// + /// Treat `span` as the location of the resulting expression. + /// + /// [`cast`]: ConstantEvaluator::cast + /// [`As`]: crate::Expression::As + pub fn cast_array( + &mut self, + expr: Handle, + target: crate::Scalar, + span: Span, + ) -> Result, ConstantEvaluatorError> { + let Expression::Compose { ty, ref components } = self.expressions[expr] else { + return self.cast(expr, target, span); + }; + + let crate::TypeInner::Array { base: _, size, stride: _ } = self.types[ty].inner else { + return self.cast(expr, target, span); + }; + + let mut components = components.clone(); + for component in &mut components { + *component = self.cast_array(*component, target, span)?; + } + + let first = components.first().unwrap(); + let new_base = match self.resolve_type(*first)? { + crate::proc::TypeResolution::Handle(ty) => ty, + crate::proc::TypeResolution::Value(inner) => { + self.types.insert(Type { name: None, inner }, span) + } + }; + let new_base_stride = self.types[new_base].inner.size(self.to_ctx()); + let new_array_ty = self.types.insert( + Type { + name: None, + inner: TypeInner::Array { + base: new_base, + size, + stride: new_base_stride, + }, + }, + span, + ); + + let compose = Expression::Compose { + ty: new_array_ty, + components, + }; + self.register_evaluated_expr(compose, span) + } + + fn unary_op( + &mut self, + op: UnaryOperator, + expr: Handle, + span: Span, + ) -> Result, ConstantEvaluatorError> { + let expr = self.eval_zero_value_and_splat(expr, span)?; + + let expr = match self.expressions[expr] { + Expression::Literal(value) => Expression::Literal(match op { + UnaryOperator::Negate => match value { + Literal::I32(v) => Literal::I32(v.wrapping_neg()), + Literal::F32(v) => Literal::F32(-v), + Literal::AbstractInt(v) => Literal::AbstractInt(v.wrapping_neg()), + Literal::AbstractFloat(v) => Literal::AbstractFloat(-v), + _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), + }, + UnaryOperator::LogicalNot => match value { + Literal::Bool(v) => Literal::Bool(!v), + _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), + }, + UnaryOperator::BitwiseNot => match value { + Literal::I32(v) => Literal::I32(!v), + Literal::U32(v) => Literal::U32(!v), + Literal::AbstractInt(v) => Literal::AbstractInt(!v), + _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), + }, + }), + Expression::Compose { + ty, + components: ref src_components, + } => { + match self.types[ty].inner { + TypeInner::Vector { .. } | TypeInner::Matrix { .. } => (), + _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), + } + + let mut components = src_components.clone(); + for component in &mut components { + *component = self.unary_op(op, *component, span)?; + } + + Expression::Compose { ty, components } + } + _ => return Err(ConstantEvaluatorError::InvalidUnaryOpArg), + }; + + self.register_evaluated_expr(expr, span) + } + + fn binary_op( + &mut self, + op: BinaryOperator, + left: Handle, + right: Handle, + span: Span, + ) -> Result, ConstantEvaluatorError> { + let left = self.eval_zero_value_and_splat(left, span)?; + let right = self.eval_zero_value_and_splat(right, span)?; + + let expr = match (&self.expressions[left], &self.expressions[right]) { + (&Expression::Literal(left_value), &Expression::Literal(right_value)) => { + let literal = match op { + BinaryOperator::Equal => Literal::Bool(left_value == right_value), + BinaryOperator::NotEqual => Literal::Bool(left_value != right_value), + BinaryOperator::Less => Literal::Bool(left_value < right_value), + BinaryOperator::LessEqual => Literal::Bool(left_value <= right_value), + BinaryOperator::Greater => Literal::Bool(left_value > right_value), + BinaryOperator::GreaterEqual => Literal::Bool(left_value >= right_value), + + _ => match (left_value, right_value) { + (Literal::I32(a), Literal::I32(b)) => Literal::I32(match op { + BinaryOperator::Add => a.checked_add(b).ok_or_else(|| { + ConstantEvaluatorError::Overflow("addition".into()) + })?, + BinaryOperator::Subtract => a.checked_sub(b).ok_or_else(|| { + ConstantEvaluatorError::Overflow("subtraction".into()) + })?, + BinaryOperator::Multiply => a.checked_mul(b).ok_or_else(|| { + ConstantEvaluatorError::Overflow("multiplication".into()) + })?, + BinaryOperator::Divide => a.checked_div(b).ok_or_else(|| { + if b == 0 { + ConstantEvaluatorError::DivisionByZero + } else { + ConstantEvaluatorError::Overflow("division".into()) + } + })?, + BinaryOperator::Modulo => a.checked_rem(b).ok_or_else(|| { + if b == 0 { + ConstantEvaluatorError::RemainderByZero + } else { + ConstantEvaluatorError::Overflow("remainder".into()) + } + })?, + BinaryOperator::And => a & b, + BinaryOperator::ExclusiveOr => a ^ b, + BinaryOperator::InclusiveOr => a | b, + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }), + (Literal::I32(a), Literal::U32(b)) => Literal::I32(match op { + BinaryOperator::ShiftLeft => a + .checked_shl(b) + .ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?, + BinaryOperator::ShiftRight => a + .checked_shr(b) + .ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?, + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }), + (Literal::U32(a), Literal::U32(b)) => Literal::U32(match op { + BinaryOperator::Add => a.checked_add(b).ok_or_else(|| { + ConstantEvaluatorError::Overflow("addition".into()) + })?, + BinaryOperator::Subtract => a.checked_sub(b).ok_or_else(|| { + ConstantEvaluatorError::Overflow("subtraction".into()) + })?, + BinaryOperator::Multiply => a.checked_mul(b).ok_or_else(|| { + ConstantEvaluatorError::Overflow("multiplication".into()) + })?, + BinaryOperator::Divide => a + .checked_div(b) + .ok_or(ConstantEvaluatorError::DivisionByZero)?, + BinaryOperator::Modulo => a + .checked_rem(b) + .ok_or(ConstantEvaluatorError::RemainderByZero)?, + BinaryOperator::And => a & b, + BinaryOperator::ExclusiveOr => a ^ b, + BinaryOperator::InclusiveOr => a | b, + BinaryOperator::ShiftLeft => a + .checked_shl(b) + .ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?, + BinaryOperator::ShiftRight => a + .checked_shr(b) + .ok_or(ConstantEvaluatorError::ShiftedMoreThan32Bits)?, + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }), + (Literal::F32(a), Literal::F32(b)) => Literal::F32(match op { + BinaryOperator::Add => a + b, + BinaryOperator::Subtract => a - b, + BinaryOperator::Multiply => a * b, + BinaryOperator::Divide => a / b, + BinaryOperator::Modulo => a % b, + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }), + (Literal::AbstractInt(a), Literal::AbstractInt(b)) => { + Literal::AbstractInt(match op { + BinaryOperator::Add => a.checked_add(b).ok_or_else(|| { + ConstantEvaluatorError::Overflow("addition".into()) + })?, + BinaryOperator::Subtract => a.checked_sub(b).ok_or_else(|| { + ConstantEvaluatorError::Overflow("subtraction".into()) + })?, + BinaryOperator::Multiply => a.checked_mul(b).ok_or_else(|| { + ConstantEvaluatorError::Overflow("multiplication".into()) + })?, + BinaryOperator::Divide => a.checked_div(b).ok_or_else(|| { + if b == 0 { + ConstantEvaluatorError::DivisionByZero + } else { + ConstantEvaluatorError::Overflow("division".into()) + } + })?, + BinaryOperator::Modulo => a.checked_rem(b).ok_or_else(|| { + if b == 0 { + ConstantEvaluatorError::RemainderByZero + } else { + ConstantEvaluatorError::Overflow("remainder".into()) + } + })?, + BinaryOperator::And => a & b, + BinaryOperator::ExclusiveOr => a ^ b, + BinaryOperator::InclusiveOr => a | b, + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }) + } + (Literal::AbstractFloat(a), Literal::AbstractFloat(b)) => { + Literal::AbstractFloat(match op { + BinaryOperator::Add => a + b, + BinaryOperator::Subtract => a - b, + BinaryOperator::Multiply => a * b, + BinaryOperator::Divide => a / b, + BinaryOperator::Modulo => a % b, + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }) + } + (Literal::Bool(a), Literal::Bool(b)) => Literal::Bool(match op { + BinaryOperator::LogicalAnd => a && b, + BinaryOperator::LogicalOr => a || b, + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }), + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }, + }; + Expression::Literal(literal) + } + ( + &Expression::Compose { + components: ref src_components, + ty, + }, + &Expression::Literal(_), + ) => { + let mut components = src_components.clone(); + for component in &mut components { + *component = self.binary_op(op, *component, right, span)?; + } + Expression::Compose { ty, components } + } + ( + &Expression::Literal(_), + &Expression::Compose { + components: ref src_components, + ty, + }, + ) => { + let mut components = src_components.clone(); + for component in &mut components { + *component = self.binary_op(op, left, *component, span)?; + } + Expression::Compose { ty, components } + } + ( + &Expression::Compose { + components: ref left_components, + ty: left_ty, + }, + &Expression::Compose { + components: ref right_components, + ty: right_ty, + }, + ) => { + // We have to make a copy of the component lists, because the + // call to `binary_op_vector` needs `&mut self`, but `self` owns + // the component lists. + let left_flattened = crate::proc::flatten_compose( + left_ty, + left_components, + self.expressions, + self.types, + ); + let right_flattened = crate::proc::flatten_compose( + right_ty, + right_components, + self.expressions, + self.types, + ); + + // `flatten_compose` doesn't return an `ExactSizeIterator`, so + // make a reasonable guess of the capacity we'll need. + let mut flattened = Vec::with_capacity(left_components.len()); + flattened.extend(left_flattened.zip(right_flattened)); + + match (&self.types[left_ty].inner, &self.types[right_ty].inner) { + ( + &TypeInner::Vector { + size: left_size, .. + }, + &TypeInner::Vector { + size: right_size, .. + }, + ) if left_size == right_size => { + self.binary_op_vector(op, left_size, &flattened, left_ty, span)? + } + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + } + } + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }; + + self.register_evaluated_expr(expr, span) + } + + fn binary_op_vector( + &mut self, + op: BinaryOperator, + size: crate::VectorSize, + components: &[(Handle, Handle)], + left_ty: Handle, + span: Span, + ) -> Result { + let ty = match op { + // Relational operators produce vectors of booleans. + BinaryOperator::Equal + | BinaryOperator::NotEqual + | BinaryOperator::Less + | BinaryOperator::LessEqual + | BinaryOperator::Greater + | BinaryOperator::GreaterEqual => self.types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size, + scalar: crate::Scalar::BOOL, + }, + }, + span, + ), + + // Other operators produce the same type as their left + // operand. + BinaryOperator::Add + | BinaryOperator::Subtract + | BinaryOperator::Multiply + | BinaryOperator::Divide + | BinaryOperator::Modulo + | BinaryOperator::And + | BinaryOperator::ExclusiveOr + | BinaryOperator::InclusiveOr + | BinaryOperator::LogicalAnd + | BinaryOperator::LogicalOr + | BinaryOperator::ShiftLeft + | BinaryOperator::ShiftRight => left_ty, + }; + + let components = components + .iter() + .map(|&(left, right)| self.binary_op(op, left, right, span)) + .collect::, _>>()?; + + Ok(Expression::Compose { ty, components }) + } + + /// Deep copy `expr` from `expressions` into `self.expressions`. + /// + /// Return the root of the new copy. + /// + /// This is used when we're evaluating expressions in a function's + /// expression arena that refer to a constant: we need to copy the + /// constant's value into the function's arena so we can operate on it. + fn copy_from( + &mut self, + expr: Handle, + expressions: &Arena, + ) -> Result, ConstantEvaluatorError> { + let span = expressions.get_span(expr); + match expressions[expr] { + ref expr @ (Expression::Literal(_) + | Expression::Constant(_) + | Expression::ZeroValue(_)) => self.register_evaluated_expr(expr.clone(), span), + Expression::Compose { ty, ref components } => { + let mut components = components.clone(); + for component in &mut components { + *component = self.copy_from(*component, expressions)?; + } + self.register_evaluated_expr(Expression::Compose { ty, components }, span) + } + Expression::Splat { size, value } => { + let value = self.copy_from(value, expressions)?; + self.register_evaluated_expr(Expression::Splat { size, value }, span) + } + _ => { + log::debug!("copy_from: SubexpressionsAreNotConstant"); + Err(ConstantEvaluatorError::SubexpressionsAreNotConstant) + } + } + } + + fn register_evaluated_expr( + &mut self, + expr: Expression, + span: Span, + ) -> Result, ConstantEvaluatorError> { + // It suffices to only check literals, since we only register one + // expression at a time, `Compose` expressions can only refer to other + // expressions, and `ZeroValue` expressions are always okay. + if let Expression::Literal(literal) = expr { + crate::valid::check_literal_value(literal)?; + } + + if let Some(FunctionLocalData { + ref mut emitter, + ref mut block, + ref mut expression_constness, + .. + }) = self.function_local_data + { + let is_running = emitter.is_running(); + let needs_pre_emit = expr.needs_pre_emit(); + if is_running && needs_pre_emit { + block.extend(emitter.finish(self.expressions)); + let h = self.expressions.append(expr, span); + emitter.start(self.expressions); + expression_constness.insert(h); + Ok(h) + } else { + let h = self.expressions.append(expr, span); + expression_constness.insert(h); + Ok(h) + } + } else { + Ok(self.expressions.append(expr, span)) + } + } + + fn resolve_type( + &self, + expr: Handle, + ) -> Result { + use crate::proc::TypeResolution as Tr; + use crate::Expression as Ex; + let resolution = match self.expressions[expr] { + Ex::Literal(ref literal) => Tr::Value(literal.ty_inner()), + Ex::Constant(c) => Tr::Handle(self.constants[c].ty), + Ex::ZeroValue(ty) | Ex::Compose { ty, .. } => Tr::Handle(ty), + Ex::Splat { size, value } => { + let Tr::Value(TypeInner::Scalar(scalar)) = self.resolve_type(value)? else { + return Err(ConstantEvaluatorError::SplatScalarOnly); + }; + Tr::Value(TypeInner::Vector { scalar, size }) + } + _ => { + log::debug!("resolve_type: SubexpressionsAreNotConstant"); + return Err(ConstantEvaluatorError::SubexpressionsAreNotConstant); + } + }; + + Ok(resolution) + } +} + +#[cfg(test)] +mod tests { + use std::vec; + + use crate::{ + Arena, Constant, Expression, Literal, ScalarKind, Type, TypeInner, UnaryOperator, + UniqueArena, VectorSize, + }; + + use super::{Behavior, ConstantEvaluator}; + + #[test] + fn unary_op() { + let mut types = UniqueArena::new(); + let mut constants = Arena::new(); + let mut const_expressions = Arena::new(); + + let scalar_ty = types.insert( + Type { + name: None, + inner: TypeInner::Scalar(crate::Scalar::I32), + }, + Default::default(), + ); + + let vec_ty = types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: VectorSize::Bi, + scalar: crate::Scalar::I32, + }, + }, + Default::default(), + ); + + let h = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: scalar_ty, + init: const_expressions + .append(Expression::Literal(Literal::I32(4)), Default::default()), + }, + Default::default(), + ); + + let h1 = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: scalar_ty, + init: const_expressions + .append(Expression::Literal(Literal::I32(8)), Default::default()), + }, + Default::default(), + ); + + let vec_h = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: vec_ty, + init: const_expressions.append( + Expression::Compose { + ty: vec_ty, + components: vec![constants[h].init, constants[h1].init], + }, + Default::default(), + ), + }, + Default::default(), + ); + + let expr = const_expressions.append(Expression::Constant(h), Default::default()); + let expr1 = const_expressions.append(Expression::Constant(vec_h), Default::default()); + + let expr2 = Expression::Unary { + op: UnaryOperator::Negate, + expr, + }; + + let expr3 = Expression::Unary { + op: UnaryOperator::BitwiseNot, + expr, + }; + + let expr4 = Expression::Unary { + op: UnaryOperator::BitwiseNot, + expr: expr1, + }; + + let mut solver = ConstantEvaluator { + behavior: Behavior::Wgsl, + types: &mut types, + constants: &constants, + expressions: &mut const_expressions, + function_local_data: None, + }; + + let res1 = solver + .try_eval_and_append(&expr2, Default::default()) + .unwrap(); + let res2 = solver + .try_eval_and_append(&expr3, Default::default()) + .unwrap(); + let res3 = solver + .try_eval_and_append(&expr4, Default::default()) + .unwrap(); + + assert_eq!( + const_expressions[res1], + Expression::Literal(Literal::I32(-4)) + ); + + assert_eq!( + const_expressions[res2], + Expression::Literal(Literal::I32(!4)) + ); + + let res3_inner = &const_expressions[res3]; + + match *res3_inner { + Expression::Compose { + ref ty, + ref components, + } => { + assert_eq!(*ty, vec_ty); + let mut components_iter = components.iter().copied(); + assert_eq!( + const_expressions[components_iter.next().unwrap()], + Expression::Literal(Literal::I32(!4)) + ); + assert_eq!( + const_expressions[components_iter.next().unwrap()], + Expression::Literal(Literal::I32(!8)) + ); + assert!(components_iter.next().is_none()); + } + _ => panic!("Expected vector"), + } + } + + #[test] + fn cast() { + let mut types = UniqueArena::new(); + let mut constants = Arena::new(); + let mut const_expressions = Arena::new(); + + let scalar_ty = types.insert( + Type { + name: None, + inner: TypeInner::Scalar(crate::Scalar::I32), + }, + Default::default(), + ); + + let h = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: scalar_ty, + init: const_expressions + .append(Expression::Literal(Literal::I32(4)), Default::default()), + }, + Default::default(), + ); + + let expr = const_expressions.append(Expression::Constant(h), Default::default()); + + let root = Expression::As { + expr, + kind: ScalarKind::Bool, + convert: Some(crate::BOOL_WIDTH), + }; + + let mut solver = ConstantEvaluator { + behavior: Behavior::Wgsl, + types: &mut types, + constants: &constants, + expressions: &mut const_expressions, + function_local_data: None, + }; + + let res = solver + .try_eval_and_append(&root, Default::default()) + .unwrap(); + + assert_eq!( + const_expressions[res], + Expression::Literal(Literal::Bool(true)) + ); + } + + #[test] + fn access() { + let mut types = UniqueArena::new(); + let mut constants = Arena::new(); + let mut const_expressions = Arena::new(); + + let matrix_ty = types.insert( + Type { + name: None, + inner: TypeInner::Matrix { + columns: VectorSize::Bi, + rows: VectorSize::Tri, + scalar: crate::Scalar::F32, + }, + }, + Default::default(), + ); + + let vec_ty = types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: VectorSize::Tri, + scalar: crate::Scalar::F32, + }, + }, + Default::default(), + ); + + let mut vec1_components = Vec::with_capacity(3); + let mut vec2_components = Vec::with_capacity(3); + + for i in 0..3 { + let h = const_expressions.append( + Expression::Literal(Literal::F32(i as f32)), + Default::default(), + ); + + vec1_components.push(h) + } + + for i in 3..6 { + let h = const_expressions.append( + Expression::Literal(Literal::F32(i as f32)), + Default::default(), + ); + + vec2_components.push(h) + } + + let vec1 = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: vec_ty, + init: const_expressions.append( + Expression::Compose { + ty: vec_ty, + components: vec1_components, + }, + Default::default(), + ), + }, + Default::default(), + ); + + let vec2 = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: vec_ty, + init: const_expressions.append( + Expression::Compose { + ty: vec_ty, + components: vec2_components, + }, + Default::default(), + ), + }, + Default::default(), + ); + + let h = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: matrix_ty, + init: const_expressions.append( + Expression::Compose { + ty: matrix_ty, + components: vec![constants[vec1].init, constants[vec2].init], + }, + Default::default(), + ), + }, + Default::default(), + ); + + let base = const_expressions.append(Expression::Constant(h), Default::default()); + + let mut solver = ConstantEvaluator { + behavior: Behavior::Wgsl, + types: &mut types, + constants: &constants, + expressions: &mut const_expressions, + function_local_data: None, + }; + + let root1 = Expression::AccessIndex { base, index: 1 }; + + let res1 = solver + .try_eval_and_append(&root1, Default::default()) + .unwrap(); + + let root2 = Expression::AccessIndex { + base: res1, + index: 2, + }; + + let res2 = solver + .try_eval_and_append(&root2, Default::default()) + .unwrap(); + + match const_expressions[res1] { + Expression::Compose { + ref ty, + ref components, + } => { + assert_eq!(*ty, vec_ty); + let mut components_iter = components.iter().copied(); + assert_eq!( + const_expressions[components_iter.next().unwrap()], + Expression::Literal(Literal::F32(3.)) + ); + assert_eq!( + const_expressions[components_iter.next().unwrap()], + Expression::Literal(Literal::F32(4.)) + ); + assert_eq!( + const_expressions[components_iter.next().unwrap()], + Expression::Literal(Literal::F32(5.)) + ); + assert!(components_iter.next().is_none()); + } + _ => panic!("Expected vector"), + } + + assert_eq!( + const_expressions[res2], + Expression::Literal(Literal::F32(5.)) + ); + } + + #[test] + fn compose_of_constants() { + let mut types = UniqueArena::new(); + let mut constants = Arena::new(); + let mut const_expressions = Arena::new(); + + let i32_ty = types.insert( + Type { + name: None, + inner: TypeInner::Scalar(crate::Scalar::I32), + }, + Default::default(), + ); + + let vec2_i32_ty = types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: VectorSize::Bi, + scalar: crate::Scalar::I32, + }, + }, + Default::default(), + ); + + let h = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: i32_ty, + init: const_expressions + .append(Expression::Literal(Literal::I32(4)), Default::default()), + }, + Default::default(), + ); + + let h_expr = const_expressions.append(Expression::Constant(h), Default::default()); + + let mut solver = ConstantEvaluator { + behavior: Behavior::Wgsl, + types: &mut types, + constants: &constants, + expressions: &mut const_expressions, + function_local_data: None, + }; + + let solved_compose = solver + .try_eval_and_append( + &Expression::Compose { + ty: vec2_i32_ty, + components: vec![h_expr, h_expr], + }, + Default::default(), + ) + .unwrap(); + let solved_negate = solver + .try_eval_and_append( + &Expression::Unary { + op: UnaryOperator::Negate, + expr: solved_compose, + }, + Default::default(), + ) + .unwrap(); + + let pass = match const_expressions[solved_negate] { + Expression::Compose { ty, ref components } => { + ty == vec2_i32_ty + && components.iter().all(|&component| { + let component = &const_expressions[component]; + matches!(*component, Expression::Literal(Literal::I32(-4))) + }) + } + _ => false, + }; + if !pass { + panic!("unexpected evaluation result") + } + } + + #[test] + fn splat_of_constant() { + let mut types = UniqueArena::new(); + let mut constants = Arena::new(); + let mut const_expressions = Arena::new(); + + let i32_ty = types.insert( + Type { + name: None, + inner: TypeInner::Scalar(crate::Scalar::I32), + }, + Default::default(), + ); + + let vec2_i32_ty = types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: VectorSize::Bi, + scalar: crate::Scalar::I32, + }, + }, + Default::default(), + ); + + let h = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: i32_ty, + init: const_expressions + .append(Expression::Literal(Literal::I32(4)), Default::default()), + }, + Default::default(), + ); + + let h_expr = const_expressions.append(Expression::Constant(h), Default::default()); + + let mut solver = ConstantEvaluator { + behavior: Behavior::Wgsl, + types: &mut types, + constants: &constants, + expressions: &mut const_expressions, + function_local_data: None, + }; + + let solved_compose = solver + .try_eval_and_append( + &Expression::Splat { + size: VectorSize::Bi, + value: h_expr, + }, + Default::default(), + ) + .unwrap(); + let solved_negate = solver + .try_eval_and_append( + &Expression::Unary { + op: UnaryOperator::Negate, + expr: solved_compose, + }, + Default::default(), + ) + .unwrap(); + + let pass = match const_expressions[solved_negate] { + Expression::Compose { ty, ref components } => { + ty == vec2_i32_ty + && components.iter().all(|&component| { + let component = &const_expressions[component]; + matches!(*component, Expression::Literal(Literal::I32(-4))) + }) + } + _ => false, + }; + if !pass { + panic!("unexpected evaluation result") + } + } +} + +/// Trait for conversions of abstract values to concrete types. +trait TryFromAbstract: Sized { + /// Convert an abstract literal `value` to `Self`. + /// + /// Since Naga's `AbstractInt` and `AbstractFloat` exist to support + /// WGSL, we follow WGSL's conversion rules here: + /// + /// - WGSL §6.1.2. Conversion Rank says that automatic conversions + /// to integers are either lossless or an error. + /// + /// - WGSL §14.6.4 Floating Point Conversion says that conversions + /// to floating point in constant expressions and override + /// expressions are errors if the value is out of range for the + /// destination type, but rounding is okay. + /// + /// [`AbstractInt`]: crate::Literal::AbstractInt + /// [`Float`]: crate::Literal::Float + fn try_from_abstract(value: T) -> Result; +} + +impl TryFromAbstract for i32 { + fn try_from_abstract(value: i64) -> Result { + i32::try_from(value).map_err(|_| ConstantEvaluatorError::AutomaticConversionLossy { + value: format!("{value:?}"), + to_type: "i32", + }) + } +} + +impl TryFromAbstract for u32 { + fn try_from_abstract(value: i64) -> Result { + u32::try_from(value).map_err(|_| ConstantEvaluatorError::AutomaticConversionLossy { + value: format!("{value:?}"), + to_type: "u32", + }) + } +} + +impl TryFromAbstract for f32 { + fn try_from_abstract(value: i64) -> Result { + let f = value as f32; + // The range of `i64` is roughly ±18 × 10¹⁸, whereas the range of + // `f32` is roughly ±3.4 × 10³⁸, so there's no opportunity for + // overflow here. + Ok(f) + } +} + +impl TryFromAbstract for f32 { + fn try_from_abstract(value: f64) -> Result { + let f = value as f32; + if f.is_infinite() { + return Err(ConstantEvaluatorError::AutomaticConversionLossy { + value: format!("{value:?}"), + to_type: "f32", + }); + } + Ok(f) + } +} + +impl TryFromAbstract for f64 { + fn try_from_abstract(value: i64) -> Result { + let f = value as f64; + // The range of `i64` is roughly ±18 × 10¹⁸, whereas the range of + // `f64` is roughly ±1.8 × 10³⁰⁸, so there's no opportunity for + // overflow here. + Ok(f) + } +} + +impl TryFromAbstract for f64 { + fn try_from_abstract(value: f64) -> Result { + Ok(value) + } +} + +impl TryFromAbstract for i32 { + fn try_from_abstract(_: f64) -> Result { + Err(ConstantEvaluatorError::AutomaticConversionFloatToInt { to_type: "i32" }) + } +} + +impl TryFromAbstract for u32 { + fn try_from_abstract(_: f64) -> Result { + Err(ConstantEvaluatorError::AutomaticConversionFloatToInt { to_type: "u32" }) + } +} diff --git a/naga/src/proc/emitter.rs b/naga/src/proc/emitter.rs new file mode 100644 index 0000000000..0df804fff2 --- /dev/null +++ b/naga/src/proc/emitter.rs @@ -0,0 +1,39 @@ +use crate::arena::Arena; + +/// Helper class to emit expressions +#[allow(dead_code)] +#[derive(Default, Debug)] +pub struct Emitter { + start_len: Option, +} + +#[allow(dead_code)] +impl Emitter { + pub fn start(&mut self, arena: &Arena) { + if self.start_len.is_some() { + unreachable!("Emitting has already started!"); + } + self.start_len = Some(arena.len()); + } + pub const fn is_running(&self) -> bool { + self.start_len.is_some() + } + #[must_use] + pub fn finish( + &mut self, + arena: &Arena, + ) -> Option<(crate::Statement, crate::span::Span)> { + let start_len = self.start_len.take().unwrap(); + if start_len != arena.len() { + #[allow(unused_mut)] + let mut span = crate::span::Span::default(); + let range = arena.range_from(start_len); + for handle in range.clone() { + span.subsume(arena.get_span(handle)) + } + Some((crate::Statement::Emit(range), span)) + } else { + None + } + } +} diff --git a/naga/src/proc/index.rs b/naga/src/proc/index.rs new file mode 100644 index 0000000000..af3221c0fe --- /dev/null +++ b/naga/src/proc/index.rs @@ -0,0 +1,435 @@ +/*! +Definitions for index bounds checking. +*/ + +use crate::{valid, Handle, UniqueArena}; +use bit_set::BitSet; + +/// How should code generated by Naga do bounds checks? +/// +/// When a vector, matrix, or array index is out of bounds—either negative, or +/// greater than or equal to the number of elements in the type—WGSL requires +/// that some other index of the implementation's choice that is in bounds is +/// used instead. (There are no types with zero elements.) +/// +/// Similarly, when out-of-bounds coordinates, array indices, or sample indices +/// are presented to the WGSL `textureLoad` and `textureStore` operations, the +/// operation is redirected to do something safe. +/// +/// Different users of Naga will prefer different defaults: +/// +/// - When used as part of a WebGPU implementation, the WGSL specification +/// requires the `Restrict` behavior for array, vector, and matrix accesses, +/// and either the `Restrict` or `ReadZeroSkipWrite` behaviors for texture +/// accesses. +/// +/// - When used by the `wgpu` crate for native development, `wgpu` selects +/// `ReadZeroSkipWrite` as its default. +/// +/// - Naga's own default is `Unchecked`, so that shader translations +/// are as faithful to the original as possible. +/// +/// Sometimes the underlying hardware and drivers can perform bounds checks +/// themselves, in a way that performs better than the checks Naga would inject. +/// If you're using native checks like this, then having Naga inject its own +/// checks as well would be redundant, and the `Unchecked` policy is +/// appropriate. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub enum BoundsCheckPolicy { + /// Replace out-of-bounds indexes with some arbitrary in-bounds index. + /// + /// (This does not necessarily mean clamping. For example, interpreting the + /// index as unsigned and taking the minimum with the largest valid index + /// would also be a valid implementation. That would map negative indices to + /// the last element, not the first.) + Restrict, + + /// Out-of-bounds reads return zero, and writes have no effect. + /// + /// When applied to a chain of accesses, like `a[i][j].b[k]`, all index + /// expressions are evaluated, regardless of whether prior or later index + /// expressions were in bounds. But all the accesses per se are skipped + /// if any index is out of bounds. + ReadZeroSkipWrite, + + /// Naga adds no checks to indexing operations. Generate the fastest code + /// possible. This is the default for Naga, as a translator, but consumers + /// should consider defaulting to a safer behavior. + Unchecked, +} + +/// Policies for injecting bounds checks during code generation. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct BoundsCheckPolicies { + /// How should the generated code handle array, vector, or matrix indices + /// that are out of range? + #[cfg_attr(feature = "deserialize", serde(default))] + pub index: BoundsCheckPolicy, + + /// How should the generated code handle array, vector, or matrix indices + /// that are out of range, when those values live in a [`GlobalVariable`] in + /// the [`Storage`] or [`Uniform`] address spaces? + /// + /// Some graphics hardware provides "robust buffer access", a feature that + /// ensures that using a pointer cannot access memory outside the 'buffer' + /// that it was derived from. In Naga terms, this means that the hardware + /// ensures that pointers computed by applying [`Access`] and + /// [`AccessIndex`] expressions to a [`GlobalVariable`] whose [`space`] is + /// [`Storage`] or [`Uniform`] will never read or write memory outside that + /// global variable. + /// + /// When hardware offers such a feature, it is probably undesirable to have + /// Naga inject bounds checking code for such accesses, since the hardware + /// can probably provide the same protection more efficiently. However, + /// bounds checks are still needed on accesses to indexable values that do + /// not live in buffers, like local variables. + /// + /// So, this option provides a separate policy that applies only to accesses + /// to storage and uniform globals. When depending on hardware bounds + /// checking, this policy can be `Unchecked` to avoid unnecessary overhead. + /// + /// When special hardware support is not available, this should probably be + /// the same as `index_bounds_check_policy`. + /// + /// [`GlobalVariable`]: crate::GlobalVariable + /// [`space`]: crate::GlobalVariable::space + /// [`Restrict`]: crate::back::BoundsCheckPolicy::Restrict + /// [`ReadZeroSkipWrite`]: crate::back::BoundsCheckPolicy::ReadZeroSkipWrite + /// [`Access`]: crate::Expression::Access + /// [`AccessIndex`]: crate::Expression::AccessIndex + /// [`Storage`]: crate::AddressSpace::Storage + /// [`Uniform`]: crate::AddressSpace::Uniform + #[cfg_attr(feature = "deserialize", serde(default))] + pub buffer: BoundsCheckPolicy, + + /// How should the generated code handle image texel loads that are out + /// of range? + /// + /// This controls the behavior of [`ImageLoad`] expressions when a coordinate, + /// texture array index, level of detail, or multisampled sample number is out of range. + /// + /// [`ImageLoad`]: crate::Expression::ImageLoad + #[cfg_attr(feature = "deserialize", serde(default))] + pub image_load: BoundsCheckPolicy, + + /// How should the generated code handle image texel stores that are out + /// of range? + /// + /// This controls the behavior of [`ImageStore`] statements when a coordinate, + /// texture array index, level of detail, or multisampled sample number is out of range. + /// + /// This policy should't be needed since all backends should ignore OOB writes. + /// + /// [`ImageStore`]: crate::Statement::ImageStore + #[cfg_attr(feature = "deserialize", serde(default))] + pub image_store: BoundsCheckPolicy, + + /// How should the generated code handle binding array indexes that are out of bounds. + #[cfg_attr(feature = "deserialize", serde(default))] + pub binding_array: BoundsCheckPolicy, +} + +/// The default `BoundsCheckPolicy` is `Unchecked`. +impl Default for BoundsCheckPolicy { + fn default() -> Self { + BoundsCheckPolicy::Unchecked + } +} + +impl BoundsCheckPolicies { + /// Determine which policy applies to `base`. + /// + /// `base` is the "base" expression (the expression being indexed) of a `Access` + /// and `AccessIndex` expression. This is either a pointer, a value, being directly + /// indexed, or a binding array. + /// + /// See the documentation for [`BoundsCheckPolicy`] for details about + /// when each policy applies. + pub fn choose_policy( + &self, + base: Handle, + types: &UniqueArena, + info: &valid::FunctionInfo, + ) -> BoundsCheckPolicy { + let ty = info[base].ty.inner_with(types); + + if let crate::TypeInner::BindingArray { .. } = *ty { + return self.binding_array; + } + + match ty.pointer_space() { + Some(crate::AddressSpace::Storage { access: _ } | crate::AddressSpace::Uniform) => { + self.buffer + } + // This covers other address spaces, but also accessing vectors and + // matrices by value, where no pointer is involved. + _ => self.index, + } + } + + /// Return `true` if any of `self`'s policies are `policy`. + pub fn contains(&self, policy: BoundsCheckPolicy) -> bool { + self.index == policy + || self.buffer == policy + || self.image_load == policy + || self.image_store == policy + } +} + +/// An index that may be statically known, or may need to be computed at runtime. +/// +/// This enum lets us handle both [`Access`] and [`AccessIndex`] expressions +/// with the same code. +/// +/// [`Access`]: crate::Expression::Access +/// [`AccessIndex`]: crate::Expression::AccessIndex +#[derive(Clone, Copy, Debug)] +pub enum GuardedIndex { + Known(u32), + Expression(Handle), +} + +/// Build a set of expressions used as indices, to cache in temporary variables when +/// emitted. +/// +/// Given the bounds-check policies `policies`, construct a `BitSet` containing the handle +/// indices of all the expressions in `function` that are ever used as guarded indices +/// under the [`ReadZeroSkipWrite`] policy. The `module` argument must be the module to +/// which `function` belongs, and `info` should be that function's analysis results. +/// +/// Such index expressions will be used twice in the generated code: first for the +/// comparison to see if the index is in bounds, and then for the access itself, should +/// the comparison succeed. To avoid computing the expressions twice, the generated code +/// should cache them in temporary variables. +/// +/// Why do we need to build such a set in advance, instead of just processing access +/// expressions as we encounter them? Whether an expression needs to be cached depends on +/// whether it appears as something like the [`index`] operand of an [`Access`] expression +/// or the [`level`] operand of an [`ImageLoad`] expression, and on the index bounds check +/// policies that apply to those accesses. But [`Emit`] statements just identify a range +/// of expressions by index; there's no good way to tell what an expression is used +/// for. The only way to do it is to just iterate over all the expressions looking for +/// relevant `Access` expressions --- which is what this function does. +/// +/// Simple expressions like variable loads and constants don't make sense to cache: it's +/// no better than just re-evaluating them. But constants are not covered by `Emit` +/// statements, and `Load`s are always cached to ensure they occur at the right time, so +/// we don't bother filtering them out from this set. +/// +/// Fortunately, we don't need to deal with [`ImageStore`] statements here. When we emit +/// code for a statement, the writer isn't in the middle of an expression, so we can just +/// emit declarations for temporaries, initialized appropriately. +/// +/// None of these concerns apply for SPIR-V output, since it's easy to just reuse an +/// instruction ID in two places; that has the same semantics as a temporary variable, and +/// it's inherent in the design of SPIR-V. This function is more useful for text-based +/// back ends. +/// +/// [`ReadZeroSkipWrite`]: BoundsCheckPolicy::ReadZeroSkipWrite +/// [`index`]: crate::Expression::Access::index +/// [`Access`]: crate::Expression::Access +/// [`level`]: crate::Expression::ImageLoad::level +/// [`ImageLoad`]: crate::Expression::ImageLoad +/// [`Emit`]: crate::Statement::Emit +/// [`ImageStore`]: crate::Statement::ImageStore +pub fn find_checked_indexes( + module: &crate::Module, + function: &crate::Function, + info: &crate::valid::FunctionInfo, + policies: BoundsCheckPolicies, +) -> BitSet { + use crate::Expression as Ex; + + let mut guarded_indices = BitSet::new(); + + // Don't bother scanning if we never need `ReadZeroSkipWrite`. + if policies.contains(BoundsCheckPolicy::ReadZeroSkipWrite) { + for (_handle, expr) in function.expressions.iter() { + // There's no need to handle `AccessIndex` expressions, as their + // indices never need to be cached. + match *expr { + Ex::Access { base, index } => { + if policies.choose_policy(base, &module.types, info) + == BoundsCheckPolicy::ReadZeroSkipWrite + && access_needs_check( + base, + GuardedIndex::Expression(index), + module, + function, + info, + ) + .is_some() + { + guarded_indices.insert(index.index()); + } + } + Ex::ImageLoad { + coordinate, + array_index, + sample, + level, + .. + } => { + if policies.image_load == BoundsCheckPolicy::ReadZeroSkipWrite { + guarded_indices.insert(coordinate.index()); + if let Some(array_index) = array_index { + guarded_indices.insert(array_index.index()); + } + if let Some(sample) = sample { + guarded_indices.insert(sample.index()); + } + if let Some(level) = level { + guarded_indices.insert(level.index()); + } + } + } + _ => {} + } + } + } + + guarded_indices +} + +/// Determine whether `index` is statically known to be in bounds for `base`. +/// +/// If we can't be sure that the index is in bounds, return the limit within +/// which valid indices must fall. +/// +/// The return value is one of the following: +/// +/// - `Some(Known(n))` indicates that `n` is the largest valid index. +/// +/// - `Some(Computed(global))` indicates that the largest valid index is one +/// less than the length of the array that is the last member of the +/// struct held in `global`. +/// +/// - `None` indicates that the index need not be checked, either because it +/// is statically known to be in bounds, or because the applicable policy +/// is `Unchecked`. +/// +/// This function only handles subscriptable types: arrays, vectors, and +/// matrices. It does not handle struct member indices; those never require +/// run-time checks, so it's best to deal with them further up the call +/// chain. +pub fn access_needs_check( + base: Handle, + mut index: GuardedIndex, + module: &crate::Module, + function: &crate::Function, + info: &crate::valid::FunctionInfo, +) -> Option { + let base_inner = info[base].ty.inner_with(&module.types); + // Unwrap safety: `Err` here indicates unindexable base types and invalid + // length constants, but `access_needs_check` is only used by back ends, so + // validation should have caught those problems. + let length = base_inner.indexable_length(module).unwrap(); + index.try_resolve_to_constant(function, module); + if let (&GuardedIndex::Known(index), &IndexableLength::Known(length)) = (&index, &length) { + if index < length { + // Index is statically known to be in bounds, no check needed. + return None; + } + }; + + Some(length) +} + +impl GuardedIndex { + /// Make a `GuardedIndex::Known` from a `GuardedIndex::Expression` if possible. + /// + /// Return values that are already `Known` unchanged. + fn try_resolve_to_constant(&mut self, function: &crate::Function, module: &crate::Module) { + if let GuardedIndex::Expression(expr) = *self { + if let Ok(value) = module + .to_ctx() + .eval_expr_to_u32_from(expr, &function.expressions) + { + *self = GuardedIndex::Known(value); + } + } + } +} + +#[derive(Clone, Copy, Debug, thiserror::Error, PartialEq)] +pub enum IndexableLengthError { + #[error("Type is not indexable, and has no length (validation error)")] + TypeNotIndexable, + #[error("Array length constant {0:?} is invalid")] + InvalidArrayLength(Handle), +} + +impl crate::TypeInner { + /// Return the length of a subscriptable type. + /// + /// The `self` parameter should be a handle to a vector, matrix, or array + /// type, a pointer to one of those, or a value pointer. Arrays may be + /// fixed-size, dynamically sized, or sized by a specializable constant. + /// This function does not handle struct member references, as with + /// `AccessIndex`. + /// + /// The value returned is appropriate for bounds checks on subscripting. + /// + /// Return an error if `self` does not describe a subscriptable type at all. + pub fn indexable_length( + &self, + module: &crate::Module, + ) -> Result { + use crate::TypeInner as Ti; + let known_length = match *self { + Ti::Vector { size, .. } => size as _, + Ti::Matrix { columns, .. } => columns as _, + Ti::Array { size, .. } | Ti::BindingArray { size, .. } => { + return size.to_indexable_length(module); + } + Ti::ValuePointer { + size: Some(size), .. + } => size as _, + Ti::Pointer { base, .. } => { + // When assigning types to expressions, ResolveContext::Resolve + // does a separate sub-match here instead of a full recursion, + // so we'll do the same. + let base_inner = &module.types[base].inner; + match *base_inner { + Ti::Vector { size, .. } => size as _, + Ti::Matrix { columns, .. } => columns as _, + Ti::Array { size, .. } | Ti::BindingArray { size, .. } => { + return size.to_indexable_length(module) + } + _ => return Err(IndexableLengthError::TypeNotIndexable), + } + } + _ => return Err(IndexableLengthError::TypeNotIndexable), + }; + Ok(IndexableLength::Known(known_length)) + } +} + +/// The number of elements in an indexable type. +/// +/// This summarizes the length of vectors, matrices, and arrays in a way that is +/// convenient for indexing and bounds-checking code. +#[derive(Debug)] +pub enum IndexableLength { + /// Values of this type always have the given number of elements. + Known(u32), + + /// The number of elements is determined at runtime. + Dynamic, +} + +impl crate::ArraySize { + pub const fn to_indexable_length( + self, + _module: &crate::Module, + ) -> Result { + Ok(match self { + Self::Constant(length) => IndexableLength::Known(length.get()), + Self::Dynamic => IndexableLength::Dynamic, + }) + } +} diff --git a/naga/src/proc/layouter.rs b/naga/src/proc/layouter.rs new file mode 100644 index 0000000000..1c78a594d1 --- /dev/null +++ b/naga/src/proc/layouter.rs @@ -0,0 +1,251 @@ +use crate::arena::Handle; +use std::{fmt::Display, num::NonZeroU32, ops}; + +/// A newtype struct where its only valid values are powers of 2 +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct Alignment(NonZeroU32); + +impl Alignment { + pub const ONE: Self = Self(unsafe { NonZeroU32::new_unchecked(1) }); + pub const TWO: Self = Self(unsafe { NonZeroU32::new_unchecked(2) }); + pub const FOUR: Self = Self(unsafe { NonZeroU32::new_unchecked(4) }); + pub const EIGHT: Self = Self(unsafe { NonZeroU32::new_unchecked(8) }); + pub const SIXTEEN: Self = Self(unsafe { NonZeroU32::new_unchecked(16) }); + + pub const MIN_UNIFORM: Self = Self::SIXTEEN; + + pub const fn new(n: u32) -> Option { + if n.is_power_of_two() { + // SAFETY: value can't be 0 since we just checked if it's a power of 2 + Some(Self(unsafe { NonZeroU32::new_unchecked(n) })) + } else { + None + } + } + + /// # Panics + /// If `width` is not a power of 2 + pub fn from_width(width: u8) -> Self { + Self::new(width as u32).unwrap() + } + + /// Returns whether or not `n` is a multiple of this alignment. + pub const fn is_aligned(&self, n: u32) -> bool { + // equivalent to: `n % self.0.get() == 0` but much faster + n & (self.0.get() - 1) == 0 + } + + /// Round `n` up to the nearest alignment boundary. + pub const fn round_up(&self, n: u32) -> u32 { + // equivalent to: + // match n % self.0.get() { + // 0 => n, + // rem => n + (self.0.get() - rem), + // } + let mask = self.0.get() - 1; + (n + mask) & !mask + } +} + +impl Display for Alignment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.get().fmt(f) + } +} + +impl ops::Mul for Alignment { + type Output = u32; + + fn mul(self, rhs: u32) -> Self::Output { + self.0.get() * rhs + } +} + +impl ops::Mul for Alignment { + type Output = Alignment; + + fn mul(self, rhs: Alignment) -> Self::Output { + // SAFETY: both lhs and rhs are powers of 2, the result will be a power of 2 + Self(unsafe { NonZeroU32::new_unchecked(self.0.get() * rhs.0.get()) }) + } +} + +impl From for Alignment { + fn from(size: crate::VectorSize) -> Self { + match size { + crate::VectorSize::Bi => Alignment::TWO, + crate::VectorSize::Tri => Alignment::FOUR, + crate::VectorSize::Quad => Alignment::FOUR, + } + } +} + +/// Size and alignment information for a type. +#[derive(Clone, Copy, Debug, Hash, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct TypeLayout { + pub size: u32, + pub alignment: Alignment, +} + +impl TypeLayout { + /// Produce the stride as if this type is a base of an array. + pub const fn to_stride(&self) -> u32 { + self.alignment.round_up(self.size) + } +} + +/// Helper processor that derives the sizes of all types. +/// +/// `Layouter` uses the default layout algorithm/table, described in +/// [WGSL §4.3.7, "Memory Layout"] +/// +/// A `Layouter` may be indexed by `Handle` values: `layouter[handle]` is the +/// layout of the type whose handle is `handle`. +/// +/// [WGSL §4.3.7, "Memory Layout"](https://gpuweb.github.io/gpuweb/wgsl/#memory-layouts) +#[derive(Debug, Default)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct Layouter { + /// Layouts for types in an arena, indexed by `Handle` index. + layouts: Vec, +} + +impl ops::Index> for Layouter { + type Output = TypeLayout; + fn index(&self, handle: Handle) -> &TypeLayout { + &self.layouts[handle.index()] + } +} + +#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)] +pub enum LayoutErrorInner { + #[error("Array element type {0:?} doesn't exist")] + InvalidArrayElementType(Handle), + #[error("Struct member[{0}] type {1:?} doesn't exist")] + InvalidStructMemberType(u32, Handle), + #[error("Type width must be a power of two")] + NonPowerOfTwoWidth, +} + +#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)] +#[error("Error laying out type {ty:?}: {inner}")] +pub struct LayoutError { + pub ty: Handle, + pub inner: LayoutErrorInner, +} + +impl LayoutErrorInner { + const fn with(self, ty: Handle) -> LayoutError { + LayoutError { ty, inner: self } + } +} + +impl Layouter { + /// Remove all entries from this `Layouter`, retaining storage. + pub fn clear(&mut self) { + self.layouts.clear(); + } + + /// Extend this `Layouter` with layouts for any new entries in `gctx.types`. + /// + /// Ensure that every type in `gctx.types` has a corresponding [TypeLayout] + /// in [`self.layouts`]. + /// + /// Some front ends need to be able to compute layouts for existing types + /// while module construction is still in progress and new types are still + /// being added. This function assumes that the `TypeLayout` values already + /// present in `self.layouts` cover their corresponding entries in `types`, + /// and extends `self.layouts` as needed to cover the rest. Thus, a front + /// end can call this function at any time, passing its current type and + /// constant arenas, and then assume that layouts are available for all + /// types. + #[allow(clippy::or_fun_call)] + pub fn update(&mut self, gctx: super::GlobalCtx) -> Result<(), LayoutError> { + use crate::TypeInner as Ti; + + for (ty_handle, ty) in gctx.types.iter().skip(self.layouts.len()) { + let size = ty.inner.size(gctx); + let layout = match ty.inner { + Ti::Scalar(scalar) | Ti::Atomic(scalar) => { + let alignment = Alignment::new(scalar.width as u32) + .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; + TypeLayout { size, alignment } + } + Ti::Vector { + size: vec_size, + scalar, + } => { + let alignment = Alignment::new(scalar.width as u32) + .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; + TypeLayout { + size, + alignment: Alignment::from(vec_size) * alignment, + } + } + Ti::Matrix { + columns: _, + rows, + scalar, + } => { + let alignment = Alignment::new(scalar.width as u32) + .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; + TypeLayout { + size, + alignment: Alignment::from(rows) * alignment, + } + } + Ti::Pointer { .. } | Ti::ValuePointer { .. } => TypeLayout { + size, + alignment: Alignment::ONE, + }, + Ti::Array { + base, + stride: _, + size: _, + } => TypeLayout { + size, + alignment: if base < ty_handle { + self[base].alignment + } else { + return Err(LayoutErrorInner::InvalidArrayElementType(base).with(ty_handle)); + }, + }, + Ti::Struct { span, ref members } => { + let mut alignment = Alignment::ONE; + for (index, member) in members.iter().enumerate() { + alignment = if member.ty < ty_handle { + alignment.max(self[member.ty].alignment) + } else { + return Err(LayoutErrorInner::InvalidStructMemberType( + index as u32, + member.ty, + ) + .with(ty_handle)); + }; + } + TypeLayout { + size: span, + alignment, + } + } + Ti::Image { .. } + | Ti::Sampler { .. } + | Ti::AccelerationStructure + | Ti::RayQuery + | Ti::BindingArray { .. } => TypeLayout { + size, + alignment: Alignment::ONE, + }, + }; + debug_assert!(size <= layout.size); + self.layouts.push(layout); + } + + Ok(()) + } +} diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs new file mode 100644 index 0000000000..deddc7033d --- /dev/null +++ b/naga/src/proc/mod.rs @@ -0,0 +1,809 @@ +/*! +[`Module`](super::Module) processing functionality. +*/ + +mod constant_evaluator; +mod emitter; +pub mod index; +mod layouter; +mod namer; +mod terminator; +mod typifier; + +pub use constant_evaluator::{ + ConstantEvaluator, ConstantEvaluatorError, ExpressionConstnessTracker, +}; +pub use emitter::Emitter; +pub use index::{BoundsCheckPolicies, BoundsCheckPolicy, IndexableLength, IndexableLengthError}; +pub use layouter::{Alignment, LayoutError, LayoutErrorInner, Layouter, TypeLayout}; +pub use namer::{EntryPointIndex, NameKey, Namer}; +pub use terminator::ensure_block_returns; +pub use typifier::{ResolveContext, ResolveError, TypeResolution}; + +impl From for super::ScalarKind { + fn from(format: super::StorageFormat) -> Self { + use super::{ScalarKind as Sk, StorageFormat as Sf}; + match format { + Sf::R8Unorm => Sk::Float, + Sf::R8Snorm => Sk::Float, + Sf::R8Uint => Sk::Uint, + Sf::R8Sint => Sk::Sint, + Sf::R16Uint => Sk::Uint, + Sf::R16Sint => Sk::Sint, + Sf::R16Float => Sk::Float, + Sf::Rg8Unorm => Sk::Float, + Sf::Rg8Snorm => Sk::Float, + Sf::Rg8Uint => Sk::Uint, + Sf::Rg8Sint => Sk::Sint, + Sf::R32Uint => Sk::Uint, + Sf::R32Sint => Sk::Sint, + Sf::R32Float => Sk::Float, + Sf::Rg16Uint => Sk::Uint, + Sf::Rg16Sint => Sk::Sint, + Sf::Rg16Float => Sk::Float, + Sf::Rgba8Unorm => Sk::Float, + Sf::Rgba8Snorm => Sk::Float, + Sf::Rgba8Uint => Sk::Uint, + Sf::Rgba8Sint => Sk::Sint, + Sf::Bgra8Unorm => Sk::Float, + Sf::Rgb10a2Uint => Sk::Uint, + Sf::Rgb10a2Unorm => Sk::Float, + Sf::Rg11b10Float => Sk::Float, + Sf::Rg32Uint => Sk::Uint, + Sf::Rg32Sint => Sk::Sint, + Sf::Rg32Float => Sk::Float, + Sf::Rgba16Uint => Sk::Uint, + Sf::Rgba16Sint => Sk::Sint, + Sf::Rgba16Float => Sk::Float, + Sf::Rgba32Uint => Sk::Uint, + Sf::Rgba32Sint => Sk::Sint, + Sf::Rgba32Float => Sk::Float, + Sf::R16Unorm => Sk::Float, + Sf::R16Snorm => Sk::Float, + Sf::Rg16Unorm => Sk::Float, + Sf::Rg16Snorm => Sk::Float, + Sf::Rgba16Unorm => Sk::Float, + Sf::Rgba16Snorm => Sk::Float, + } + } +} + +impl super::ScalarKind { + pub const fn is_numeric(self) -> bool { + match self { + crate::ScalarKind::Sint + | crate::ScalarKind::Uint + | crate::ScalarKind::Float + | crate::ScalarKind::AbstractInt + | crate::ScalarKind::AbstractFloat => true, + crate::ScalarKind::Bool => false, + } + } +} + +impl super::Scalar { + pub const I32: Self = Self { + kind: crate::ScalarKind::Sint, + width: 4, + }; + pub const U32: Self = Self { + kind: crate::ScalarKind::Uint, + width: 4, + }; + pub const F32: Self = Self { + kind: crate::ScalarKind::Float, + width: 4, + }; + pub const F64: Self = Self { + kind: crate::ScalarKind::Float, + width: 8, + }; + pub const I64: Self = Self { + kind: crate::ScalarKind::Sint, + width: 8, + }; + pub const BOOL: Self = Self { + kind: crate::ScalarKind::Bool, + width: crate::BOOL_WIDTH, + }; + pub const ABSTRACT_INT: Self = Self { + kind: crate::ScalarKind::AbstractInt, + width: crate::ABSTRACT_WIDTH, + }; + pub const ABSTRACT_FLOAT: Self = Self { + kind: crate::ScalarKind::AbstractFloat, + width: crate::ABSTRACT_WIDTH, + }; + + pub const fn is_abstract(self) -> bool { + match self.kind { + crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => true, + crate::ScalarKind::Sint + | crate::ScalarKind::Uint + | crate::ScalarKind::Float + | crate::ScalarKind::Bool => false, + } + } + + /// Construct a float `Scalar` with the given width. + /// + /// This is especially common when dealing with + /// `TypeInner::Matrix`, where the scalar kind is implicit. + pub const fn float(width: crate::Bytes) -> Self { + Self { + kind: crate::ScalarKind::Float, + width, + } + } + + pub const fn to_inner_scalar(self) -> crate::TypeInner { + crate::TypeInner::Scalar(self) + } + + pub const fn to_inner_vector(self, size: crate::VectorSize) -> crate::TypeInner { + crate::TypeInner::Vector { size, scalar: self } + } + + pub const fn to_inner_atomic(self) -> crate::TypeInner { + crate::TypeInner::Atomic(self) + } +} + +impl PartialEq for crate::Literal { + fn eq(&self, other: &Self) -> bool { + match (*self, *other) { + (Self::F64(a), Self::F64(b)) => a.to_bits() == b.to_bits(), + (Self::F32(a), Self::F32(b)) => a.to_bits() == b.to_bits(), + (Self::U32(a), Self::U32(b)) => a == b, + (Self::I32(a), Self::I32(b)) => a == b, + (Self::I64(a), Self::I64(b)) => a == b, + (Self::Bool(a), Self::Bool(b)) => a == b, + _ => false, + } + } +} +impl Eq for crate::Literal {} +impl std::hash::Hash for crate::Literal { + fn hash(&self, hasher: &mut H) { + match *self { + Self::F64(v) | Self::AbstractFloat(v) => { + hasher.write_u8(0); + v.to_bits().hash(hasher); + } + Self::F32(v) => { + hasher.write_u8(1); + v.to_bits().hash(hasher); + } + Self::U32(v) => { + hasher.write_u8(2); + v.hash(hasher); + } + Self::I32(v) => { + hasher.write_u8(3); + v.hash(hasher); + } + Self::Bool(v) => { + hasher.write_u8(4); + v.hash(hasher); + } + Self::I64(v) | Self::AbstractInt(v) => { + hasher.write_u8(5); + v.hash(hasher); + } + } + } +} + +impl crate::Literal { + pub const fn new(value: u8, scalar: crate::Scalar) -> Option { + match (value, scalar.kind, scalar.width) { + (value, crate::ScalarKind::Float, 8) => Some(Self::F64(value as _)), + (value, crate::ScalarKind::Float, 4) => Some(Self::F32(value as _)), + (value, crate::ScalarKind::Uint, 4) => Some(Self::U32(value as _)), + (value, crate::ScalarKind::Sint, 4) => Some(Self::I32(value as _)), + (value, crate::ScalarKind::Sint, 8) => Some(Self::I64(value as _)), + (1, crate::ScalarKind::Bool, 4) => Some(Self::Bool(true)), + (0, crate::ScalarKind::Bool, 4) => Some(Self::Bool(false)), + _ => None, + } + } + + pub const fn zero(scalar: crate::Scalar) -> Option { + Self::new(0, scalar) + } + + pub const fn one(scalar: crate::Scalar) -> Option { + Self::new(1, scalar) + } + + pub const fn width(&self) -> crate::Bytes { + match *self { + Self::F64(_) | Self::I64(_) => 8, + Self::F32(_) | Self::U32(_) | Self::I32(_) => 4, + Self::Bool(_) => crate::BOOL_WIDTH, + Self::AbstractInt(_) | Self::AbstractFloat(_) => crate::ABSTRACT_WIDTH, + } + } + pub const fn scalar(&self) -> crate::Scalar { + match *self { + Self::F64(_) => crate::Scalar::F64, + Self::F32(_) => crate::Scalar::F32, + Self::U32(_) => crate::Scalar::U32, + Self::I32(_) => crate::Scalar::I32, + Self::I64(_) => crate::Scalar::I64, + Self::Bool(_) => crate::Scalar::BOOL, + Self::AbstractInt(_) => crate::Scalar::ABSTRACT_INT, + Self::AbstractFloat(_) => crate::Scalar::ABSTRACT_FLOAT, + } + } + pub const fn scalar_kind(&self) -> crate::ScalarKind { + self.scalar().kind + } + pub const fn ty_inner(&self) -> crate::TypeInner { + crate::TypeInner::Scalar(self.scalar()) + } +} + +pub const POINTER_SPAN: u32 = 4; + +impl super::TypeInner { + /// Return the scalar type of `self`. + /// + /// If `inner` is a scalar, vector, or matrix type, return + /// its scalar type. Otherwise, return `None`. + pub const fn scalar(&self) -> Option { + use crate::TypeInner as Ti; + match *self { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => Some(scalar), + Ti::Matrix { scalar, .. } => Some(scalar), + _ => None, + } + } + + pub fn scalar_kind(&self) -> Option { + self.scalar().map(|scalar| scalar.kind) + } + + pub fn scalar_width(&self) -> Option { + self.scalar().map(|scalar| scalar.width * 8) + } + + pub const fn pointer_space(&self) -> Option { + match *self { + Self::Pointer { space, .. } => Some(space), + Self::ValuePointer { space, .. } => Some(space), + _ => None, + } + } + + pub fn is_atomic_pointer(&self, types: &crate::UniqueArena) -> bool { + match *self { + crate::TypeInner::Pointer { base, .. } => match types[base].inner { + crate::TypeInner::Atomic { .. } => true, + _ => false, + }, + _ => false, + } + } + + /// Get the size of this type. + pub fn size(&self, _gctx: GlobalCtx) -> u32 { + match *self { + Self::Scalar(scalar) | Self::Atomic(scalar) => scalar.width as u32, + Self::Vector { size, scalar } => size as u32 * scalar.width as u32, + // matrices are treated as arrays of aligned columns + Self::Matrix { + columns, + rows, + scalar, + } => Alignment::from(rows) * scalar.width as u32 * columns as u32, + Self::Pointer { .. } | Self::ValuePointer { .. } => POINTER_SPAN, + Self::Array { + base: _, + size, + stride, + } => { + let count = match size { + super::ArraySize::Constant(count) => count.get(), + // A dynamically-sized array has to have at least one element + super::ArraySize::Dynamic => 1, + }; + count * stride + } + Self::Struct { span, .. } => span, + Self::Image { .. } + | Self::Sampler { .. } + | Self::AccelerationStructure + | Self::RayQuery + | Self::BindingArray { .. } => 0, + } + } + + /// Return the canonical form of `self`, or `None` if it's already in + /// canonical form. + /// + /// Certain types have multiple representations in `TypeInner`. This + /// function converts all forms of equivalent types to a single + /// representative of their class, so that simply applying `Eq` to the + /// result indicates whether the types are equivalent, as far as Naga IR is + /// concerned. + pub fn canonical_form( + &self, + types: &crate::UniqueArena, + ) -> Option { + use crate::TypeInner as Ti; + match *self { + Ti::Pointer { base, space } => match types[base].inner { + Ti::Scalar(scalar) => Some(Ti::ValuePointer { + size: None, + scalar, + space, + }), + Ti::Vector { size, scalar } => Some(Ti::ValuePointer { + size: Some(size), + scalar, + space, + }), + _ => None, + }, + _ => None, + } + } + + /// Compare `self` and `rhs` as types. + /// + /// This is mostly the same as `::eq`, but it treats + /// `ValuePointer` and `Pointer` types as equivalent. + /// + /// When you know that one side of the comparison is never a pointer, it's + /// fine to not bother with canonicalization, and just compare `TypeInner` + /// values with `==`. + pub fn equivalent( + &self, + rhs: &crate::TypeInner, + types: &crate::UniqueArena, + ) -> bool { + let left = self.canonical_form(types); + let right = rhs.canonical_form(types); + left.as_ref().unwrap_or(self) == right.as_ref().unwrap_or(rhs) + } + + pub fn is_dynamically_sized(&self, types: &crate::UniqueArena) -> bool { + use crate::TypeInner as Ti; + match *self { + Ti::Array { size, .. } => size == crate::ArraySize::Dynamic, + Ti::Struct { ref members, .. } => members + .last() + .map(|last| types[last.ty].inner.is_dynamically_sized(types)) + .unwrap_or(false), + _ => false, + } + } + + pub fn components(&self) -> Option { + Some(match *self { + Self::Vector { size, .. } => size as u32, + Self::Matrix { columns, .. } => columns as u32, + Self::Array { + size: crate::ArraySize::Constant(len), + .. + } => len.get(), + Self::Struct { ref members, .. } => members.len() as u32, + _ => return None, + }) + } + + pub fn component_type(&self, index: usize) -> Option { + Some(match *self { + Self::Vector { scalar, .. } => TypeResolution::Value(crate::TypeInner::Scalar(scalar)), + Self::Matrix { rows, scalar, .. } => { + TypeResolution::Value(crate::TypeInner::Vector { size: rows, scalar }) + } + Self::Array { + base, + size: crate::ArraySize::Constant(_), + .. + } => TypeResolution::Handle(base), + Self::Struct { ref members, .. } => TypeResolution::Handle(members[index].ty), + _ => return None, + }) + } +} + +impl super::AddressSpace { + pub fn access(self) -> crate::StorageAccess { + use crate::StorageAccess as Sa; + match self { + crate::AddressSpace::Function + | crate::AddressSpace::Private + | crate::AddressSpace::WorkGroup => Sa::LOAD | Sa::STORE, + crate::AddressSpace::Uniform => Sa::LOAD, + crate::AddressSpace::Storage { access } => access, + crate::AddressSpace::Handle => Sa::LOAD, + crate::AddressSpace::PushConstant => Sa::LOAD, + } + } +} + +impl super::MathFunction { + pub const fn argument_count(&self) -> usize { + match *self { + // comparison + Self::Abs => 1, + Self::Min => 2, + Self::Max => 2, + Self::Clamp => 3, + Self::Saturate => 1, + // trigonometry + Self::Cos => 1, + Self::Cosh => 1, + Self::Sin => 1, + Self::Sinh => 1, + Self::Tan => 1, + Self::Tanh => 1, + Self::Acos => 1, + Self::Asin => 1, + Self::Atan => 1, + Self::Atan2 => 2, + Self::Asinh => 1, + Self::Acosh => 1, + Self::Atanh => 1, + Self::Radians => 1, + Self::Degrees => 1, + // decomposition + Self::Ceil => 1, + Self::Floor => 1, + Self::Round => 1, + Self::Fract => 1, + Self::Trunc => 1, + Self::Modf => 1, + Self::Frexp => 1, + Self::Ldexp => 2, + // exponent + Self::Exp => 1, + Self::Exp2 => 1, + Self::Log => 1, + Self::Log2 => 1, + Self::Pow => 2, + // geometry + Self::Dot => 2, + Self::Outer => 2, + Self::Cross => 2, + Self::Distance => 2, + Self::Length => 1, + Self::Normalize => 1, + Self::FaceForward => 3, + Self::Reflect => 2, + Self::Refract => 3, + // computational + Self::Sign => 1, + Self::Fma => 3, + Self::Mix => 3, + Self::Step => 2, + Self::SmoothStep => 3, + Self::Sqrt => 1, + Self::InverseSqrt => 1, + Self::Inverse => 1, + Self::Transpose => 1, + Self::Determinant => 1, + // bits + Self::CountTrailingZeros => 1, + Self::CountLeadingZeros => 1, + Self::CountOneBits => 1, + Self::ReverseBits => 1, + Self::ExtractBits => 3, + Self::InsertBits => 4, + Self::FindLsb => 1, + Self::FindMsb => 1, + // data packing + Self::Pack4x8snorm => 1, + Self::Pack4x8unorm => 1, + Self::Pack2x16snorm => 1, + Self::Pack2x16unorm => 1, + Self::Pack2x16float => 1, + // data unpacking + Self::Unpack4x8snorm => 1, + Self::Unpack4x8unorm => 1, + Self::Unpack2x16snorm => 1, + Self::Unpack2x16unorm => 1, + Self::Unpack2x16float => 1, + } + } +} + +impl crate::Expression { + /// Returns true if the expression is considered emitted at the start of a function. + pub const fn needs_pre_emit(&self) -> bool { + match *self { + Self::Literal(_) + | Self::Constant(_) + | Self::ZeroValue(_) + | Self::FunctionArgument(_) + | Self::GlobalVariable(_) + | Self::LocalVariable(_) => true, + _ => false, + } + } + + /// Return true if this expression is a dynamic array index, for [`Access`]. + /// + /// This method returns true if this expression is a dynamically computed + /// index, and as such can only be used to index matrices and arrays when + /// they appear behind a pointer. See the documentation for [`Access`] for + /// details. + /// + /// Note, this does not check the _type_ of the given expression. It's up to + /// the caller to establish that the `Access` expression is well-typed + /// through other means, like [`ResolveContext`]. + /// + /// [`Access`]: crate::Expression::Access + /// [`ResolveContext`]: crate::proc::ResolveContext + pub fn is_dynamic_index(&self, module: &crate::Module) -> bool { + match *self { + Self::Literal(_) | Self::ZeroValue(_) => false, + Self::Constant(handle) => { + let constant = &module.constants[handle]; + !matches!(constant.r#override, crate::Override::None) + } + _ => true, + } + } +} + +impl crate::Function { + /// Return the global variable being accessed by the expression `pointer`. + /// + /// Assuming that `pointer` is a series of `Access` and `AccessIndex` + /// expressions that ultimately access some part of a `GlobalVariable`, + /// return a handle for that global. + /// + /// If the expression does not ultimately access a global variable, return + /// `None`. + pub fn originating_global( + &self, + mut pointer: crate::Handle, + ) -> Option> { + loop { + pointer = match self.expressions[pointer] { + crate::Expression::Access { base, .. } => base, + crate::Expression::AccessIndex { base, .. } => base, + crate::Expression::GlobalVariable(handle) => return Some(handle), + crate::Expression::LocalVariable(_) => return None, + crate::Expression::FunctionArgument(_) => return None, + // There are no other expressions that produce pointer values. + _ => unreachable!(), + } + } + } +} + +impl crate::SampleLevel { + pub const fn implicit_derivatives(&self) -> bool { + match *self { + Self::Auto | Self::Bias(_) => true, + Self::Zero | Self::Exact(_) | Self::Gradient { .. } => false, + } + } +} + +impl crate::Binding { + pub const fn to_built_in(&self) -> Option { + match *self { + crate::Binding::BuiltIn(built_in) => Some(built_in), + Self::Location { .. } => None, + } + } +} + +impl super::SwizzleComponent { + pub const XYZW: [Self; 4] = [Self::X, Self::Y, Self::Z, Self::W]; + + pub const fn index(&self) -> u32 { + match *self { + Self::X => 0, + Self::Y => 1, + Self::Z => 2, + Self::W => 3, + } + } + pub const fn from_index(idx: u32) -> Self { + match idx { + 0 => Self::X, + 1 => Self::Y, + 2 => Self::Z, + _ => Self::W, + } + } +} + +impl super::ImageClass { + pub const fn is_multisampled(self) -> bool { + match self { + crate::ImageClass::Sampled { multi, .. } | crate::ImageClass::Depth { multi } => multi, + crate::ImageClass::Storage { .. } => false, + } + } + + pub const fn is_mipmapped(self) -> bool { + match self { + crate::ImageClass::Sampled { multi, .. } | crate::ImageClass::Depth { multi } => !multi, + crate::ImageClass::Storage { .. } => false, + } + } +} + +impl crate::Module { + pub const fn to_ctx(&self) -> GlobalCtx<'_> { + GlobalCtx { + types: &self.types, + constants: &self.constants, + const_expressions: &self.const_expressions, + } + } +} + +#[derive(Debug)] +pub(super) enum U32EvalError { + NonConst, + Negative, +} + +#[derive(Clone, Copy)] +pub struct GlobalCtx<'a> { + pub types: &'a crate::UniqueArena, + pub constants: &'a crate::Arena, + pub const_expressions: &'a crate::Arena, +} + +impl GlobalCtx<'_> { + /// Try to evaluate the expression in `self.const_expressions` using its `handle` and return it as a `u32`. + #[allow(dead_code)] + pub(super) fn eval_expr_to_u32( + &self, + handle: crate::Handle, + ) -> Result { + self.eval_expr_to_u32_from(handle, self.const_expressions) + } + + /// Try to evaluate the expression in the `arena` using its `handle` and return it as a `u32`. + pub(super) fn eval_expr_to_u32_from( + &self, + handle: crate::Handle, + arena: &crate::Arena, + ) -> Result { + match self.eval_expr_to_literal_from(handle, arena) { + Some(crate::Literal::U32(value)) => Ok(value), + Some(crate::Literal::I32(value)) => { + value.try_into().map_err(|_| U32EvalError::Negative) + } + _ => Err(U32EvalError::NonConst), + } + } + + #[allow(dead_code)] + pub(crate) fn eval_expr_to_literal( + &self, + handle: crate::Handle, + ) -> Option { + self.eval_expr_to_literal_from(handle, self.const_expressions) + } + + fn eval_expr_to_literal_from( + &self, + handle: crate::Handle, + arena: &crate::Arena, + ) -> Option { + fn get( + gctx: GlobalCtx, + handle: crate::Handle, + arena: &crate::Arena, + ) -> Option { + match arena[handle] { + crate::Expression::Literal(literal) => Some(literal), + crate::Expression::ZeroValue(ty) => match gctx.types[ty].inner { + crate::TypeInner::Scalar(scalar) => crate::Literal::zero(scalar), + _ => None, + }, + _ => None, + } + } + match arena[handle] { + crate::Expression::Constant(c) => { + get(*self, self.constants[c].init, self.const_expressions) + } + _ => get(*self, handle, arena), + } + } +} + +/// Return an iterator over the individual components assembled by a +/// `Compose` expression. +/// +/// Given `ty` and `components` from an `Expression::Compose`, return an +/// iterator over the components of the resulting value. +/// +/// Normally, this would just be an iterator over `components`. However, +/// `Compose` expressions can concatenate vectors, in which case the i'th +/// value being composed is not generally the i'th element of `components`. +/// This function consults `ty` to decide if this concatenation is occuring, +/// and returns an iterator that produces the components of the result of +/// the `Compose` expression in either case. +pub fn flatten_compose<'arenas>( + ty: crate::Handle, + components: &'arenas [crate::Handle], + expressions: &'arenas crate::Arena, + types: &'arenas crate::UniqueArena, +) -> impl Iterator> + 'arenas { + // Returning `impl Iterator` is a bit tricky. We may or may not + // want to flatten the components, but we have to settle on a + // single concrete type to return. This function returns a single + // iterator chain that handles both the flattening and + // non-flattening cases. + let (size, is_vector) = if let crate::TypeInner::Vector { size, .. } = types[ty].inner { + (size as usize, true) + } else { + (components.len(), false) + }; + + /// Flatten `Compose` expressions if `is_vector` is true. + fn flatten_compose<'c>( + component: &'c crate::Handle, + is_vector: bool, + expressions: &'c crate::Arena, + ) -> &'c [crate::Handle] { + if is_vector { + if let crate::Expression::Compose { + ty: _, + components: ref subcomponents, + } = expressions[*component] + { + return subcomponents; + } + } + std::slice::from_ref(component) + } + + /// Flatten `Splat` expressions if `is_vector` is true. + fn flatten_splat<'c>( + component: &'c crate::Handle, + is_vector: bool, + expressions: &'c crate::Arena, + ) -> impl Iterator> { + let mut expr = *component; + let mut count = 1; + if is_vector { + if let crate::Expression::Splat { size, value } = expressions[expr] { + expr = value; + count = size as usize; + } + } + std::iter::repeat(expr).take(count) + } + + // Expressions like `vec4(vec3(vec2(6, 7), 8), 9)` require us to + // flatten up to two levels of `Compose` expressions. + // + // Expressions like `vec4(vec3(1.0), 1.0)` require us to flatten + // `Splat` expressions. Fortunately, the operand of a `Splat` must + // be a scalar, so we can stop there. + components + .iter() + .flat_map(move |component| flatten_compose(component, is_vector, expressions)) + .flat_map(move |component| flatten_compose(component, is_vector, expressions)) + .flat_map(move |component| flatten_splat(component, is_vector, expressions)) + .take(size) +} + +#[test] +fn test_matrix_size() { + let module = crate::Module::default(); + assert_eq!( + crate::TypeInner::Matrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Tri, + scalar: crate::Scalar::F32, + } + .size(module.to_ctx()), + 48, + ); +} diff --git a/naga/src/proc/namer.rs b/naga/src/proc/namer.rs new file mode 100644 index 0000000000..8afacb593d --- /dev/null +++ b/naga/src/proc/namer.rs @@ -0,0 +1,281 @@ +use crate::{arena::Handle, FastHashMap, FastHashSet}; +use std::borrow::Cow; +use std::hash::{Hash, Hasher}; + +pub type EntryPointIndex = u16; +const SEPARATOR: char = '_'; + +#[derive(Debug, Eq, Hash, PartialEq)] +pub enum NameKey { + Constant(Handle), + GlobalVariable(Handle), + Type(Handle), + StructMember(Handle, u32), + Function(Handle), + FunctionArgument(Handle, u32), + FunctionLocal(Handle, Handle), + EntryPoint(EntryPointIndex), + EntryPointLocal(EntryPointIndex, Handle), + EntryPointArgument(EntryPointIndex, u32), +} + +/// This processor assigns names to all the things in a module +/// that may need identifiers in a textual backend. +#[derive(Default)] +pub struct Namer { + /// The last numeric suffix used for each base name. Zero means "no suffix". + unique: FastHashMap, + keywords: FastHashSet<&'static str>, + keywords_case_insensitive: FastHashSet>, + reserved_prefixes: Vec<&'static str>, +} + +impl Namer { + /// Return a form of `string` suitable for use as the base of an identifier. + /// + /// - Drop leading digits. + /// - Retain only alphanumeric and `_` characters. + /// - Avoid prefixes in [`Namer::reserved_prefixes`]. + /// - Replace consecutive `_` characters with a single `_` character. + /// + /// The return value is a valid identifier prefix in all of Naga's output languages, + /// and it never ends with a `SEPARATOR` character. + /// It is used as a key into the unique table. + fn sanitize<'s>(&self, string: &'s str) -> Cow<'s, str> { + let string = string + .trim_start_matches(|c: char| c.is_numeric()) + .trim_end_matches(SEPARATOR); + + let base = if !string.is_empty() + && !string.contains("__") + && string + .chars() + .all(|c: char| c.is_ascii_alphanumeric() || c == '_') + { + Cow::Borrowed(string) + } else { + let mut filtered = string + .chars() + .filter(|&c| c.is_ascii_alphanumeric() || c == '_') + .fold(String::new(), |mut s, c| { + if s.ends_with('_') && c == '_' { + return s; + } + s.push(c); + s + }); + let stripped_len = filtered.trim_end_matches(SEPARATOR).len(); + filtered.truncate(stripped_len); + if filtered.is_empty() { + filtered.push_str("unnamed"); + } + Cow::Owned(filtered) + }; + + for prefix in &self.reserved_prefixes { + if base.starts_with(prefix) { + return format!("gen_{base}").into(); + } + } + + base + } + + /// Return a new identifier based on `label_raw`. + /// + /// The result: + /// - is a valid identifier even if `label_raw` is not + /// - conflicts with no keywords listed in `Namer::keywords`, and + /// - is different from any identifier previously constructed by this + /// `Namer`. + /// + /// Guarantee uniqueness by applying a numeric suffix when necessary. If `label_raw` + /// itself ends with digits, separate them from the suffix with an underscore. + pub fn call(&mut self, label_raw: &str) -> String { + use std::fmt::Write as _; // for write!-ing to Strings + + let base = self.sanitize(label_raw); + debug_assert!(!base.is_empty() && !base.ends_with(SEPARATOR)); + + // This would seem to be a natural place to use `HashMap::entry`. However, `entry` + // requires an owned key, and we'd like to avoid heap-allocating strings we're + // just going to throw away. The approach below double-hashes only when we create + // a new entry, in which case the heap allocation of the owned key was more + // expensive anyway. + match self.unique.get_mut(base.as_ref()) { + Some(count) => { + *count += 1; + // Add the suffix. This may fit in base's existing allocation. + let mut suffixed = base.into_owned(); + write!(suffixed, "{}{}", SEPARATOR, *count).unwrap(); + suffixed + } + None => { + let mut suffixed = base.to_string(); + if base.ends_with(char::is_numeric) + || self.keywords.contains(base.as_ref()) + || self + .keywords_case_insensitive + .contains(&AsciiUniCase(base.as_ref())) + { + suffixed.push(SEPARATOR); + } + debug_assert!(!self.keywords.contains::(&suffixed)); + // `self.unique` wants to own its keys. This allocates only if we haven't + // already done so earlier. + self.unique.insert(base.into_owned(), 0); + suffixed + } + } + } + + pub fn call_or(&mut self, label: &Option, fallback: &str) -> String { + self.call(match *label { + Some(ref name) => name, + None => fallback, + }) + } + + /// Enter a local namespace for things like structs. + /// + /// Struct member names only need to be unique amongst themselves, not + /// globally. This function temporarily establishes a fresh, empty naming + /// context for the duration of the call to `body`. + fn namespace(&mut self, capacity: usize, body: impl FnOnce(&mut Self)) { + let fresh = FastHashMap::with_capacity_and_hasher(capacity, Default::default()); + let outer = std::mem::replace(&mut self.unique, fresh); + body(self); + self.unique = outer; + } + + pub fn reset( + &mut self, + module: &crate::Module, + reserved_keywords: &[&'static str], + extra_reserved_keywords: &[&'static str], + reserved_keywords_case_insensitive: &[&'static str], + reserved_prefixes: &[&'static str], + output: &mut FastHashMap, + ) { + self.reserved_prefixes.clear(); + self.reserved_prefixes.extend(reserved_prefixes.iter()); + + self.unique.clear(); + self.keywords.clear(); + self.keywords.extend(reserved_keywords.iter()); + self.keywords.extend(extra_reserved_keywords.iter()); + + debug_assert!(reserved_keywords_case_insensitive + .iter() + .all(|s| s.is_ascii())); + self.keywords_case_insensitive.clear(); + self.keywords_case_insensitive.extend( + reserved_keywords_case_insensitive + .iter() + .map(|string| (AsciiUniCase(*string))), + ); + + let mut temp = String::new(); + + for (ty_handle, ty) in module.types.iter() { + let ty_name = self.call_or(&ty.name, "type"); + output.insert(NameKey::Type(ty_handle), ty_name); + + if let crate::TypeInner::Struct { ref members, .. } = ty.inner { + // struct members have their own namespace, because access is always prefixed + self.namespace(members.len(), |namer| { + for (index, member) in members.iter().enumerate() { + let name = namer.call_or(&member.name, "member"); + output.insert(NameKey::StructMember(ty_handle, index as u32), name); + } + }) + } + } + + for (ep_index, ep) in module.entry_points.iter().enumerate() { + let ep_name = self.call(&ep.name); + output.insert(NameKey::EntryPoint(ep_index as _), ep_name); + for (index, arg) in ep.function.arguments.iter().enumerate() { + let name = self.call_or(&arg.name, "param"); + output.insert( + NameKey::EntryPointArgument(ep_index as _, index as u32), + name, + ); + } + for (handle, var) in ep.function.local_variables.iter() { + let name = self.call_or(&var.name, "local"); + output.insert(NameKey::EntryPointLocal(ep_index as _, handle), name); + } + } + + for (fun_handle, fun) in module.functions.iter() { + let fun_name = self.call_or(&fun.name, "function"); + output.insert(NameKey::Function(fun_handle), fun_name); + for (index, arg) in fun.arguments.iter().enumerate() { + let name = self.call_or(&arg.name, "param"); + output.insert(NameKey::FunctionArgument(fun_handle, index as u32), name); + } + for (handle, var) in fun.local_variables.iter() { + let name = self.call_or(&var.name, "local"); + output.insert(NameKey::FunctionLocal(fun_handle, handle), name); + } + } + + for (handle, var) in module.global_variables.iter() { + let name = self.call_or(&var.name, "global"); + output.insert(NameKey::GlobalVariable(handle), name); + } + + for (handle, constant) in module.constants.iter() { + let label = match constant.name { + Some(ref name) => name, + None => { + use std::fmt::Write; + // Try to be more descriptive about the constant values + temp.clear(); + write!(temp, "const_{}", output[&NameKey::Type(constant.ty)]).unwrap(); + &temp + } + }; + let name = self.call(label); + output.insert(NameKey::Constant(handle), name); + } + } +} + +/// A string wrapper type with an ascii case insensitive Eq and Hash impl +struct AsciiUniCase + ?Sized>(S); + +impl> PartialEq for AsciiUniCase { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.0.as_ref().eq_ignore_ascii_case(other.0.as_ref()) + } +} + +impl> Eq for AsciiUniCase {} + +impl> Hash for AsciiUniCase { + #[inline] + fn hash(&self, hasher: &mut H) { + for byte in self + .0 + .as_ref() + .as_bytes() + .iter() + .map(|b| b.to_ascii_lowercase()) + { + hasher.write_u8(byte); + } + } +} + +#[test] +fn test() { + let mut namer = Namer::default(); + assert_eq!(namer.call("x"), "x"); + assert_eq!(namer.call("x"), "x_1"); + assert_eq!(namer.call("x1"), "x1_"); + assert_eq!(namer.call("__x"), "_x"); + assert_eq!(namer.call("1___x"), "_x_1"); +} diff --git a/naga/src/proc/terminator.rs b/naga/src/proc/terminator.rs new file mode 100644 index 0000000000..a5239d4eca --- /dev/null +++ b/naga/src/proc/terminator.rs @@ -0,0 +1,44 @@ +/// Ensure that the given block has return statements +/// at the end of its control flow. +/// +/// Note: we don't want to blindly append a return statement +/// to the end, because it may be either redundant or invalid, +/// e.g. when the user already has returns in if/else branches. +pub fn ensure_block_returns(block: &mut crate::Block) { + use crate::Statement as S; + match block.last_mut() { + Some(&mut S::Block(ref mut b)) => { + ensure_block_returns(b); + } + Some(&mut S::If { + condition: _, + ref mut accept, + ref mut reject, + }) => { + ensure_block_returns(accept); + ensure_block_returns(reject); + } + Some(&mut S::Switch { + selector: _, + ref mut cases, + }) => { + for case in cases.iter_mut() { + if !case.fall_through { + ensure_block_returns(&mut case.body); + } + } + } + Some(&mut (S::Emit(_) | S::Break | S::Continue | S::Return { .. } | S::Kill)) => (), + Some( + &mut (S::Loop { .. } + | S::Store { .. } + | S::ImageStore { .. } + | S::Call { .. } + | S::RayQuery { .. } + | S::Atomic { .. } + | S::WorkGroupUniformLoad { .. } + | S::Barrier(_)), + ) + | None => block.push(S::Return { value: None }, Default::default()), + } +} diff --git a/naga/src/proc/typifier.rs b/naga/src/proc/typifier.rs new file mode 100644 index 0000000000..9c4403445c --- /dev/null +++ b/naga/src/proc/typifier.rs @@ -0,0 +1,893 @@ +use crate::arena::{Arena, Handle, UniqueArena}; + +use thiserror::Error; + +/// The result of computing an expression's type. +/// +/// This is the (Rust) type returned by [`ResolveContext::resolve`] to represent +/// the (Naga) type it ascribes to some expression. +/// +/// You might expect such a function to simply return a `Handle`. However, +/// we want type resolution to be a read-only process, and that would limit the +/// possible results to types already present in the expression's associated +/// `UniqueArena`. Naga IR does have certain expressions whose types are +/// not certain to be present. +/// +/// So instead, type resolution returns a `TypeResolution` enum: either a +/// [`Handle`], referencing some type in the arena, or a [`Value`], holding a +/// free-floating [`TypeInner`]. This extends the range to cover anything that +/// can be represented with a `TypeInner` referring to the existing arena. +/// +/// What sorts of expressions can have types not available in the arena? +/// +/// - An [`Access`] or [`AccessIndex`] expression applied to a [`Vector`] or +/// [`Matrix`] must have a [`Scalar`] or [`Vector`] type. But since `Vector` +/// and `Matrix` represent their element and column types implicitly, not +/// via a handle, there may not be a suitable type in the expression's +/// associated arena. Instead, resolving such an expression returns a +/// `TypeResolution::Value(TypeInner::X { ... })`, where `X` is `Scalar` or +/// `Vector`. +/// +/// - Similarly, the type of an [`Access`] or [`AccessIndex`] expression +/// applied to a *pointer to* a vector or matrix must produce a *pointer to* +/// a scalar or vector type. These cannot be represented with a +/// [`TypeInner::Pointer`], since the `Pointer`'s `base` must point into the +/// arena, and as before, we cannot assume that a suitable scalar or vector +/// type is there. So we take things one step further and provide +/// [`TypeInner::ValuePointer`], specifically for the case of pointers to +/// scalars or vectors. This type fits in a `TypeInner` and is exactly +/// equivalent to a `Pointer` to a `Vector` or `Scalar`. +/// +/// So, for example, the type of an `Access` expression applied to a value of type: +/// +/// ```ignore +/// TypeInner::Matrix { columns, rows, width } +/// ``` +/// +/// might be: +/// +/// ```ignore +/// TypeResolution::Value(TypeInner::Vector { +/// size: rows, +/// kind: ScalarKind::Float, +/// width, +/// }) +/// ``` +/// +/// and the type of an access to a pointer of address space `space` to such a +/// matrix might be: +/// +/// ```ignore +/// TypeResolution::Value(TypeInner::ValuePointer { +/// size: Some(rows), +/// kind: ScalarKind::Float, +/// width, +/// space, +/// }) +/// ``` +/// +/// [`Handle`]: TypeResolution::Handle +/// [`Value`]: TypeResolution::Value +/// +/// [`Access`]: crate::Expression::Access +/// [`AccessIndex`]: crate::Expression::AccessIndex +/// +/// [`TypeInner`]: crate::TypeInner +/// [`Matrix`]: crate::TypeInner::Matrix +/// [`Pointer`]: crate::TypeInner::Pointer +/// [`Scalar`]: crate::TypeInner::Scalar +/// [`ValuePointer`]: crate::TypeInner::ValuePointer +/// [`Vector`]: crate::TypeInner::Vector +/// +/// [`TypeInner::Pointer`]: crate::TypeInner::Pointer +/// [`TypeInner::ValuePointer`]: crate::TypeInner::ValuePointer +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub enum TypeResolution { + /// A type stored in the associated arena. + Handle(Handle), + + /// A free-floating [`TypeInner`], representing a type that may not be + /// available in the associated arena. However, the `TypeInner` itself may + /// contain `Handle` values referring to types from the arena. + /// + /// [`TypeInner`]: crate::TypeInner + Value(crate::TypeInner), +} + +impl TypeResolution { + pub const fn handle(&self) -> Option> { + match *self { + Self::Handle(handle) => Some(handle), + Self::Value(_) => None, + } + } + + pub fn inner_with<'a>(&'a self, arena: &'a UniqueArena) -> &'a crate::TypeInner { + match *self { + Self::Handle(handle) => &arena[handle].inner, + Self::Value(ref inner) => inner, + } + } +} + +// Clone is only implemented for numeric variants of `TypeInner`. +impl Clone for TypeResolution { + fn clone(&self) -> Self { + use crate::TypeInner as Ti; + match *self { + Self::Handle(handle) => Self::Handle(handle), + Self::Value(ref v) => Self::Value(match *v { + Ti::Scalar(scalar) => Ti::Scalar(scalar), + Ti::Vector { size, scalar } => Ti::Vector { size, scalar }, + Ti::Matrix { + rows, + columns, + scalar, + } => Ti::Matrix { + rows, + columns, + scalar, + }, + Ti::Pointer { base, space } => Ti::Pointer { base, space }, + Ti::ValuePointer { + size, + scalar, + space, + } => Ti::ValuePointer { + size, + scalar, + space, + }, + _ => unreachable!("Unexpected clone type: {:?}", v), + }), + } + } +} + +#[derive(Clone, Debug, Error, PartialEq)] +pub enum ResolveError { + #[error("Index {index} is out of bounds for expression {expr:?}")] + OutOfBoundsIndex { + expr: Handle, + index: u32, + }, + #[error("Invalid access into expression {expr:?}, indexed: {indexed}")] + InvalidAccess { + expr: Handle, + indexed: bool, + }, + #[error("Invalid sub-access into type {ty:?}, indexed: {indexed}")] + InvalidSubAccess { + ty: Handle, + indexed: bool, + }, + #[error("Invalid scalar {0:?}")] + InvalidScalar(Handle), + #[error("Invalid vector {0:?}")] + InvalidVector(Handle), + #[error("Invalid pointer {0:?}")] + InvalidPointer(Handle), + #[error("Invalid image {0:?}")] + InvalidImage(Handle), + #[error("Function {name} not defined")] + FunctionNotDefined { name: String }, + #[error("Function without return type")] + FunctionReturnsVoid, + #[error("Incompatible operands: {0}")] + IncompatibleOperands(String), + #[error("Function argument {0} doesn't exist")] + FunctionArgumentNotFound(u32), + #[error("Special type is not registered within the module")] + MissingSpecialType, +} + +pub struct ResolveContext<'a> { + pub constants: &'a Arena, + pub types: &'a UniqueArena, + pub special_types: &'a crate::SpecialTypes, + pub global_vars: &'a Arena, + pub local_vars: &'a Arena, + pub functions: &'a Arena, + pub arguments: &'a [crate::FunctionArgument], +} + +impl<'a> ResolveContext<'a> { + /// Initialize a resolve context from the module. + pub const fn with_locals( + module: &'a crate::Module, + local_vars: &'a Arena, + arguments: &'a [crate::FunctionArgument], + ) -> Self { + Self { + constants: &module.constants, + types: &module.types, + special_types: &module.special_types, + global_vars: &module.global_variables, + local_vars, + functions: &module.functions, + arguments, + } + } + + /// Determine the type of `expr`. + /// + /// The `past` argument must be a closure that can resolve the types of any + /// expressions that `expr` refers to. These can be gathered by caching the + /// results of prior calls to `resolve`, perhaps as done by the + /// [`front::Typifier`] utility type. + /// + /// Type resolution is a read-only process: this method takes `self` by + /// shared reference. However, this means that we cannot add anything to + /// `self.types` that we might need to describe `expr`. To work around this, + /// this method returns a [`TypeResolution`], rather than simply returning a + /// `Handle`; see the documentation for [`TypeResolution`] for + /// details. + /// + /// [`front::Typifier`]: crate::front::Typifier + pub fn resolve( + &self, + expr: &crate::Expression, + past: impl Fn(Handle) -> Result<&'a TypeResolution, ResolveError>, + ) -> Result { + use crate::TypeInner as Ti; + let types = self.types; + Ok(match *expr { + crate::Expression::Access { base, .. } => match *past(base)?.inner_with(types) { + // Arrays and matrices can only be indexed dynamically behind a + // pointer, but that's a validation error, not a type error, so + // go ahead provide a type here. + Ti::Array { base, .. } => TypeResolution::Handle(base), + Ti::Matrix { rows, scalar, .. } => { + TypeResolution::Value(Ti::Vector { size: rows, scalar }) + } + Ti::Vector { size: _, scalar } => TypeResolution::Value(Ti::Scalar(scalar)), + Ti::ValuePointer { + size: Some(_), + scalar, + space, + } => TypeResolution::Value(Ti::ValuePointer { + size: None, + scalar, + space, + }), + Ti::Pointer { base, space } => { + TypeResolution::Value(match types[base].inner { + Ti::Array { base, .. } => Ti::Pointer { base, space }, + Ti::Vector { size: _, scalar } => Ti::ValuePointer { + size: None, + scalar, + space, + }, + // Matrices are only dynamically indexed behind a pointer + Ti::Matrix { + columns: _, + rows, + scalar, + } => Ti::ValuePointer { + size: Some(rows), + scalar, + space, + }, + Ti::BindingArray { base, .. } => Ti::Pointer { base, space }, + ref other => { + log::error!("Access sub-type {:?}", other); + return Err(ResolveError::InvalidSubAccess { + ty: base, + indexed: false, + }); + } + }) + } + Ti::BindingArray { base, .. } => TypeResolution::Handle(base), + ref other => { + log::error!("Access type {:?}", other); + return Err(ResolveError::InvalidAccess { + expr: base, + indexed: false, + }); + } + }, + crate::Expression::AccessIndex { base, index } => { + match *past(base)?.inner_with(types) { + Ti::Vector { size, scalar } => { + if index >= size as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + TypeResolution::Value(Ti::Scalar(scalar)) + } + Ti::Matrix { + columns, + rows, + scalar, + } => { + if index >= columns as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + TypeResolution::Value(crate::TypeInner::Vector { size: rows, scalar }) + } + Ti::Array { base, .. } => TypeResolution::Handle(base), + Ti::Struct { ref members, .. } => { + let member = members + .get(index as usize) + .ok_or(ResolveError::OutOfBoundsIndex { expr: base, index })?; + TypeResolution::Handle(member.ty) + } + Ti::ValuePointer { + size: Some(size), + scalar, + space, + } => { + if index >= size as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + TypeResolution::Value(Ti::ValuePointer { + size: None, + scalar, + space, + }) + } + Ti::Pointer { + base: ty_base, + space, + } => TypeResolution::Value(match types[ty_base].inner { + Ti::Array { base, .. } => Ti::Pointer { base, space }, + Ti::Vector { size, scalar } => { + if index >= size as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + Ti::ValuePointer { + size: None, + scalar, + space, + } + } + Ti::Matrix { + rows, + columns, + scalar, + } => { + if index >= columns as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + Ti::ValuePointer { + size: Some(rows), + scalar, + space, + } + } + Ti::Struct { ref members, .. } => { + let member = members + .get(index as usize) + .ok_or(ResolveError::OutOfBoundsIndex { expr: base, index })?; + Ti::Pointer { + base: member.ty, + space, + } + } + Ti::BindingArray { base, .. } => Ti::Pointer { base, space }, + ref other => { + log::error!("Access index sub-type {:?}", other); + return Err(ResolveError::InvalidSubAccess { + ty: ty_base, + indexed: true, + }); + } + }), + Ti::BindingArray { base, .. } => TypeResolution::Handle(base), + ref other => { + log::error!("Access index type {:?}", other); + return Err(ResolveError::InvalidAccess { + expr: base, + indexed: true, + }); + } + } + } + crate::Expression::Splat { size, value } => match *past(value)?.inner_with(types) { + Ti::Scalar(scalar) => TypeResolution::Value(Ti::Vector { size, scalar }), + ref other => { + log::error!("Scalar type {:?}", other); + return Err(ResolveError::InvalidScalar(value)); + } + }, + crate::Expression::Swizzle { + size, + vector, + pattern: _, + } => match *past(vector)?.inner_with(types) { + Ti::Vector { size: _, scalar } => { + TypeResolution::Value(Ti::Vector { size, scalar }) + } + ref other => { + log::error!("Vector type {:?}", other); + return Err(ResolveError::InvalidVector(vector)); + } + }, + crate::Expression::Literal(lit) => TypeResolution::Value(lit.ty_inner()), + crate::Expression::Constant(h) => TypeResolution::Handle(self.constants[h].ty), + crate::Expression::ZeroValue(ty) => TypeResolution::Handle(ty), + crate::Expression::Compose { ty, .. } => TypeResolution::Handle(ty), + crate::Expression::FunctionArgument(index) => { + let arg = self + .arguments + .get(index as usize) + .ok_or(ResolveError::FunctionArgumentNotFound(index))?; + TypeResolution::Handle(arg.ty) + } + crate::Expression::GlobalVariable(h) => { + let var = &self.global_vars[h]; + if var.space == crate::AddressSpace::Handle { + TypeResolution::Handle(var.ty) + } else { + TypeResolution::Value(Ti::Pointer { + base: var.ty, + space: var.space, + }) + } + } + crate::Expression::LocalVariable(h) => { + let var = &self.local_vars[h]; + TypeResolution::Value(Ti::Pointer { + base: var.ty, + space: crate::AddressSpace::Function, + }) + } + crate::Expression::Load { pointer } => match *past(pointer)?.inner_with(types) { + Ti::Pointer { base, space: _ } => { + if let Ti::Atomic(scalar) = types[base].inner { + TypeResolution::Value(Ti::Scalar(scalar)) + } else { + TypeResolution::Handle(base) + } + } + Ti::ValuePointer { + size, + scalar, + space: _, + } => TypeResolution::Value(match size { + Some(size) => Ti::Vector { size, scalar }, + None => Ti::Scalar(scalar), + }), + ref other => { + log::error!("Pointer type {:?}", other); + return Err(ResolveError::InvalidPointer(pointer)); + } + }, + crate::Expression::ImageSample { + image, + gather: Some(_), + .. + } => match *past(image)?.inner_with(types) { + Ti::Image { class, .. } => TypeResolution::Value(Ti::Vector { + scalar: crate::Scalar { + kind: match class { + crate::ImageClass::Sampled { kind, multi: _ } => kind, + _ => crate::ScalarKind::Float, + }, + width: 4, + }, + size: crate::VectorSize::Quad, + }), + ref other => { + log::error!("Image type {:?}", other); + return Err(ResolveError::InvalidImage(image)); + } + }, + crate::Expression::ImageSample { image, .. } + | crate::Expression::ImageLoad { image, .. } => match *past(image)?.inner_with(types) { + Ti::Image { class, .. } => TypeResolution::Value(match class { + crate::ImageClass::Depth { multi: _ } => Ti::Scalar(crate::Scalar::F32), + crate::ImageClass::Sampled { kind, multi: _ } => Ti::Vector { + scalar: crate::Scalar { kind, width: 4 }, + size: crate::VectorSize::Quad, + }, + crate::ImageClass::Storage { format, .. } => Ti::Vector { + scalar: crate::Scalar { + kind: format.into(), + width: 4, + }, + size: crate::VectorSize::Quad, + }, + }), + ref other => { + log::error!("Image type {:?}", other); + return Err(ResolveError::InvalidImage(image)); + } + }, + crate::Expression::ImageQuery { image, query } => TypeResolution::Value(match query { + crate::ImageQuery::Size { level: _ } => match *past(image)?.inner_with(types) { + Ti::Image { dim, .. } => match dim { + crate::ImageDimension::D1 => Ti::Scalar(crate::Scalar::U32), + crate::ImageDimension::D2 | crate::ImageDimension::Cube => Ti::Vector { + size: crate::VectorSize::Bi, + scalar: crate::Scalar::U32, + }, + crate::ImageDimension::D3 => Ti::Vector { + size: crate::VectorSize::Tri, + scalar: crate::Scalar::U32, + }, + }, + ref other => { + log::error!("Image type {:?}", other); + return Err(ResolveError::InvalidImage(image)); + } + }, + crate::ImageQuery::NumLevels + | crate::ImageQuery::NumLayers + | crate::ImageQuery::NumSamples => Ti::Scalar(crate::Scalar::U32), + }), + crate::Expression::Unary { expr, .. } => past(expr)?.clone(), + crate::Expression::Binary { op, left, right } => match op { + crate::BinaryOperator::Add + | crate::BinaryOperator::Subtract + | crate::BinaryOperator::Divide + | crate::BinaryOperator::Modulo => past(left)?.clone(), + crate::BinaryOperator::Multiply => { + let (res_left, res_right) = (past(left)?, past(right)?); + match (res_left.inner_with(types), res_right.inner_with(types)) { + ( + &Ti::Matrix { + columns: _, + rows, + scalar, + }, + &Ti::Matrix { columns, .. }, + ) => TypeResolution::Value(Ti::Matrix { + columns, + rows, + scalar, + }), + ( + &Ti::Matrix { + columns: _, + rows, + scalar, + }, + &Ti::Vector { .. }, + ) => TypeResolution::Value(Ti::Vector { size: rows, scalar }), + ( + &Ti::Vector { .. }, + &Ti::Matrix { + columns, + rows: _, + scalar, + }, + ) => TypeResolution::Value(Ti::Vector { + size: columns, + scalar, + }), + (&Ti::Scalar { .. }, _) => res_right.clone(), + (_, &Ti::Scalar { .. }) => res_left.clone(), + (&Ti::Vector { .. }, &Ti::Vector { .. }) => res_left.clone(), + (tl, tr) => { + return Err(ResolveError::IncompatibleOperands(format!( + "{tl:?} * {tr:?}" + ))) + } + } + } + crate::BinaryOperator::Equal + | crate::BinaryOperator::NotEqual + | crate::BinaryOperator::Less + | crate::BinaryOperator::LessEqual + | crate::BinaryOperator::Greater + | crate::BinaryOperator::GreaterEqual + | crate::BinaryOperator::LogicalAnd + | crate::BinaryOperator::LogicalOr => { + let scalar = crate::Scalar::BOOL; + let inner = match *past(left)?.inner_with(types) { + Ti::Scalar { .. } => Ti::Scalar(scalar), + Ti::Vector { size, .. } => Ti::Vector { size, scalar }, + ref other => { + return Err(ResolveError::IncompatibleOperands(format!( + "{op:?}({other:?}, _)" + ))) + } + }; + TypeResolution::Value(inner) + } + crate::BinaryOperator::And + | crate::BinaryOperator::ExclusiveOr + | crate::BinaryOperator::InclusiveOr + | crate::BinaryOperator::ShiftLeft + | crate::BinaryOperator::ShiftRight => past(left)?.clone(), + }, + crate::Expression::AtomicResult { ty, .. } => TypeResolution::Handle(ty), + crate::Expression::WorkGroupUniformLoadResult { ty } => TypeResolution::Handle(ty), + crate::Expression::Select { accept, .. } => past(accept)?.clone(), + crate::Expression::Derivative { expr, .. } => past(expr)?.clone(), + crate::Expression::Relational { fun, argument } => match fun { + crate::RelationalFunction::All | crate::RelationalFunction::Any => { + TypeResolution::Value(Ti::Scalar(crate::Scalar::BOOL)) + } + crate::RelationalFunction::IsNan | crate::RelationalFunction::IsInf => { + match *past(argument)?.inner_with(types) { + Ti::Scalar { .. } => TypeResolution::Value(Ti::Scalar(crate::Scalar::BOOL)), + Ti::Vector { size, .. } => TypeResolution::Value(Ti::Vector { + scalar: crate::Scalar::BOOL, + size, + }), + ref other => { + return Err(ResolveError::IncompatibleOperands(format!( + "{fun:?}({other:?})" + ))) + } + } + } + }, + crate::Expression::Math { + fun, + arg, + arg1, + arg2: _, + arg3: _, + } => { + use crate::MathFunction as Mf; + let res_arg = past(arg)?; + match fun { + // comparison + Mf::Abs | + Mf::Min | + Mf::Max | + Mf::Clamp | + Mf::Saturate | + // trigonometry + Mf::Cos | + Mf::Cosh | + Mf::Sin | + Mf::Sinh | + Mf::Tan | + Mf::Tanh | + Mf::Acos | + Mf::Asin | + Mf::Atan | + Mf::Atan2 | + Mf::Asinh | + Mf::Acosh | + Mf::Atanh | + Mf::Radians | + Mf::Degrees | + // decomposition + Mf::Ceil | + Mf::Floor | + Mf::Round | + Mf::Fract | + Mf::Trunc | + Mf::Ldexp | + // exponent + Mf::Exp | + Mf::Exp2 | + Mf::Log | + Mf::Log2 | + Mf::Pow => res_arg.clone(), + Mf::Modf | Mf::Frexp => { + let (size, width) = match res_arg.inner_with(types) { + &Ti::Scalar(crate::Scalar { + kind: crate::ScalarKind::Float, + width, + }) => (None, width), + &Ti::Vector { + scalar: crate::Scalar { + kind: crate::ScalarKind::Float, + width, + }, + size, + } => (Some(size), width), + ref other => + return Err(ResolveError::IncompatibleOperands(format!("{fun:?}({other:?}, _)"))) + }; + let result = self + .special_types + .predeclared_types + .get(&if fun == Mf::Modf { + crate::PredeclaredType::ModfResult { size, width } + } else { + crate::PredeclaredType::FrexpResult { size, width } + }) + .ok_or(ResolveError::MissingSpecialType)?; + TypeResolution::Handle(*result) + }, + // geometry + Mf::Dot => match *res_arg.inner_with(types) { + Ti::Vector { + size: _, + scalar, + } => TypeResolution::Value(Ti::Scalar(scalar)), + ref other => + return Err(ResolveError::IncompatibleOperands( + format!("{fun:?}({other:?}, _)") + )), + }, + Mf::Outer => { + let arg1 = arg1.ok_or_else(|| ResolveError::IncompatibleOperands( + format!("{fun:?}(_, None)") + ))?; + match (res_arg.inner_with(types), past(arg1)?.inner_with(types)) { + ( + &Ti::Vector { size: columns, scalar }, + &Ti::Vector{ size: rows, .. } + ) => TypeResolution::Value(Ti::Matrix { + columns, + rows, + scalar, + }), + (left, right) => + return Err(ResolveError::IncompatibleOperands( + format!("{fun:?}({left:?}, {right:?})") + )), + } + }, + Mf::Cross => res_arg.clone(), + Mf::Distance | + Mf::Length => match *res_arg.inner_with(types) { + Ti::Scalar(scalar) | + Ti::Vector {scalar,size:_} => TypeResolution::Value(Ti::Scalar(scalar)), + ref other => return Err(ResolveError::IncompatibleOperands( + format!("{fun:?}({other:?})") + )), + }, + Mf::Normalize | + Mf::FaceForward | + Mf::Reflect | + Mf::Refract => res_arg.clone(), + // computational + Mf::Sign | + Mf::Fma | + Mf::Mix | + Mf::Step | + Mf::SmoothStep | + Mf::Sqrt | + Mf::InverseSqrt => res_arg.clone(), + Mf::Transpose => match *res_arg.inner_with(types) { + Ti::Matrix { + columns, + rows, + scalar, + } => TypeResolution::Value(Ti::Matrix { + columns: rows, + rows: columns, + scalar, + }), + ref other => return Err(ResolveError::IncompatibleOperands( + format!("{fun:?}({other:?})") + )), + }, + Mf::Inverse => match *res_arg.inner_with(types) { + Ti::Matrix { + columns, + rows, + scalar, + } if columns == rows => TypeResolution::Value(Ti::Matrix { + columns, + rows, + scalar, + }), + ref other => return Err(ResolveError::IncompatibleOperands( + format!("{fun:?}({other:?})") + )), + }, + Mf::Determinant => match *res_arg.inner_with(types) { + Ti::Matrix { + scalar, + .. + } => TypeResolution::Value(Ti::Scalar(scalar)), + ref other => return Err(ResolveError::IncompatibleOperands( + format!("{fun:?}({other:?})") + )), + }, + // bits + Mf::CountTrailingZeros | + Mf::CountLeadingZeros | + Mf::CountOneBits | + Mf::ReverseBits | + Mf::ExtractBits | + Mf::InsertBits | + Mf::FindLsb | + Mf::FindMsb => match *res_arg.inner_with(types) { + Ti::Scalar(scalar @ crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + .. + }) => TypeResolution::Value(Ti::Scalar(scalar)), + Ti::Vector { + size, + scalar: scalar @ crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + .. + } + } => TypeResolution::Value(Ti::Vector { size, scalar }), + ref other => return Err(ResolveError::IncompatibleOperands( + format!("{fun:?}({other:?})") + )), + }, + // data packing + Mf::Pack4x8snorm | + Mf::Pack4x8unorm | + Mf::Pack2x16snorm | + Mf::Pack2x16unorm | + Mf::Pack2x16float => TypeResolution::Value(Ti::Scalar(crate::Scalar::U32)), + // data unpacking + Mf::Unpack4x8snorm | + Mf::Unpack4x8unorm => TypeResolution::Value(Ti::Vector { + size: crate::VectorSize::Quad, + scalar: crate::Scalar::F32 + }), + Mf::Unpack2x16snorm | + Mf::Unpack2x16unorm | + Mf::Unpack2x16float => TypeResolution::Value(Ti::Vector { + size: crate::VectorSize::Bi, + scalar: crate::Scalar::F32 + }), + } + } + crate::Expression::As { + expr, + kind, + convert, + } => match *past(expr)?.inner_with(types) { + Ti::Scalar(crate::Scalar { width, .. }) => { + TypeResolution::Value(Ti::Scalar(crate::Scalar { + kind, + width: convert.unwrap_or(width), + })) + } + Ti::Vector { + size, + scalar: crate::Scalar { kind: _, width }, + } => TypeResolution::Value(Ti::Vector { + size, + scalar: crate::Scalar { + kind, + width: convert.unwrap_or(width), + }, + }), + Ti::Matrix { + columns, + rows, + mut scalar, + } => { + if let Some(width) = convert { + scalar.width = width; + } + TypeResolution::Value(Ti::Matrix { + columns, + rows, + scalar, + }) + } + ref other => { + return Err(ResolveError::IncompatibleOperands(format!( + "{other:?} as {kind:?}" + ))) + } + }, + crate::Expression::CallResult(function) => { + let result = self.functions[function] + .result + .as_ref() + .ok_or(ResolveError::FunctionReturnsVoid)?; + TypeResolution::Handle(result.ty) + } + crate::Expression::ArrayLength(_) => { + TypeResolution::Value(Ti::Scalar(crate::Scalar::U32)) + } + crate::Expression::RayQueryProceedResult => { + TypeResolution::Value(Ti::Scalar(crate::Scalar::BOOL)) + } + crate::Expression::RayQueryGetIntersection { .. } => { + let result = self + .special_types + .ray_intersection + .ok_or(ResolveError::MissingSpecialType)?; + TypeResolution::Handle(result) + } + }) + } +} + +#[test] +fn test_error_size() { + use std::mem::size_of; + assert_eq!(size_of::(), 32); +} diff --git a/naga/src/span.rs b/naga/src/span.rs new file mode 100644 index 0000000000..53246b25d6 --- /dev/null +++ b/naga/src/span.rs @@ -0,0 +1,501 @@ +use crate::{Arena, Handle, UniqueArena}; +use std::{error::Error, fmt, ops::Range}; + +/// A source code span, used for error reporting. +#[derive(Clone, Copy, Debug, PartialEq, Default)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct Span { + start: u32, + end: u32, +} + +impl Span { + pub const UNDEFINED: Self = Self { start: 0, end: 0 }; + /// Creates a new `Span` from a range of byte indices + /// + /// Note: end is exclusive, it doesn't belong to the `Span` + pub const fn new(start: u32, end: u32) -> Self { + Span { start, end } + } + + /// Returns a new `Span` starting at `self` and ending at `other` + pub const fn until(&self, other: &Self) -> Self { + Span { + start: self.start, + end: other.end, + } + } + + /// Modifies `self` to contain the smallest `Span` possible that + /// contains both `self` and `other` + pub fn subsume(&mut self, other: Self) { + *self = if !self.is_defined() { + // self isn't defined so use other + other + } else if !other.is_defined() { + // other isn't defined so don't try to subsume + *self + } else { + // Both self and other are defined so calculate the span that contains them both + Span { + start: self.start.min(other.start), + end: self.end.max(other.end), + } + } + } + + /// Returns the smallest `Span` possible that contains all the `Span`s + /// defined in the `from` iterator + pub fn total_span>(from: T) -> Self { + let mut span: Self = Default::default(); + for other in from { + span.subsume(other); + } + span + } + + /// Converts `self` to a range if the span is not unknown + pub fn to_range(self) -> Option> { + if self.is_defined() { + Some(self.start as usize..self.end as usize) + } else { + None + } + } + + /// Check whether `self` was defined or is a default/unknown span + pub fn is_defined(&self) -> bool { + *self != Self::default() + } + + /// Return a [`SourceLocation`] for this span in the provided source. + pub fn location(&self, source: &str) -> SourceLocation { + let prefix = &source[..self.start as usize]; + let line_number = prefix.matches('\n').count() as u32 + 1; + let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0); + let line_position = source[line_start..self.start as usize].chars().count() as u32 + 1; + + SourceLocation { + line_number, + line_position, + offset: self.start, + length: self.end - self.start, + } + } +} + +impl From> for Span { + fn from(range: Range) -> Self { + Span { + start: range.start as u32, + end: range.end as u32, + } + } +} + +impl std::ops::Index for str { + type Output = str; + + #[inline] + fn index(&self, span: Span) -> &str { + &self[span.start as usize..span.end as usize] + } +} + +/// A human-readable representation for a span, tailored for text source. +/// +/// Corresponds to the positional members of [`GPUCompilationMessage`][gcm] from +/// the WebGPU specification, except that `offset` and `length` are in bytes +/// (UTF-8 code units), instead of UTF-16 code units. +/// +/// [gcm]: https://www.w3.org/TR/webgpu/#gpucompilationmessage +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct SourceLocation { + /// 1-based line number. + pub line_number: u32, + /// 1-based column of the start of this span + pub line_position: u32, + /// 0-based Offset in code units (in bytes) of the start of the span. + pub offset: u32, + /// Length in code units (in bytes) of the span. + pub length: u32, +} + +/// A source code span together with "context", a user-readable description of what part of the error it refers to. +pub type SpanContext = (Span, String); + +/// Wrapper class for [`Error`], augmenting it with a list of [`SpanContext`]s. +#[derive(Debug, Clone)] +pub struct WithSpan { + inner: E, + spans: Vec, +} + +impl fmt::Display for WithSpan +where + E: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +#[cfg(test)] +impl PartialEq for WithSpan +where + E: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.inner.eq(&other.inner) + } +} + +impl Error for WithSpan +where + E: Error, +{ + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.inner.source() + } +} + +impl WithSpan { + /// Create a new [`WithSpan`] from an [`Error`], containing no spans. + pub const fn new(inner: E) -> Self { + Self { + inner, + spans: Vec::new(), + } + } + + /// Reverse of [`Self::new`], discards span information and returns an inner error. + #[allow(clippy::missing_const_for_fn)] // ignore due to requirement of #![feature(const_precise_live_drops)] + pub fn into_inner(self) -> E { + self.inner + } + + pub const fn as_inner(&self) -> &E { + &self.inner + } + + /// Iterator over stored [`SpanContext`]s. + pub fn spans(&self) -> impl ExactSizeIterator { + self.spans.iter() + } + + /// Add a new span with description. + pub fn with_span(mut self, span: Span, description: S) -> Self + where + S: ToString, + { + if span.is_defined() { + self.spans.push((span, description.to_string())); + } + self + } + + /// Add a [`SpanContext`]. + pub fn with_context(self, span_context: SpanContext) -> Self { + let (span, description) = span_context; + self.with_span(span, description) + } + + /// Add a [`Handle`] from either [`Arena`] or [`UniqueArena`], borrowing its span information from there + /// and annotating with a type and the handle representation. + pub(crate) fn with_handle>(self, handle: Handle, arena: &A) -> Self { + self.with_context(arena.get_span_context(handle)) + } + + /// Convert inner error using [`From`]. + pub fn into_other(self) -> WithSpan + where + E2: From, + { + WithSpan { + inner: self.inner.into(), + spans: self.spans, + } + } + + /// Convert inner error into another type. Joins span information contained in `self` + /// with what is returned from `func`. + pub fn and_then(self, func: F) -> WithSpan + where + F: FnOnce(E) -> WithSpan, + { + let mut res = func(self.inner); + res.spans.extend(self.spans); + res + } + + /// Return a [`SourceLocation`] for our first span, if we have one. + pub fn location(&self, source: &str) -> Option { + if self.spans.is_empty() { + return None; + } + + Some(self.spans[0].0.location(source)) + } + + fn diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<()> + where + E: Error, + { + use codespan_reporting::diagnostic::{Diagnostic, Label}; + let diagnostic = Diagnostic::error() + .with_message(self.inner.to_string()) + .with_labels( + self.spans() + .map(|&(span, ref desc)| { + Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned()) + }) + .collect(), + ) + .with_notes({ + let mut notes = Vec::new(); + let mut source: &dyn Error = &self.inner; + while let Some(next) = Error::source(source) { + notes.push(next.to_string()); + source = next; + } + notes + }); + diagnostic + } + + /// Emits a summary of the error to standard error stream. + pub fn emit_to_stderr(&self, source: &str) + where + E: Error, + { + self.emit_to_stderr_with_path(source, "wgsl") + } + + /// Emits a summary of the error to standard error stream. + pub fn emit_to_stderr_with_path(&self, source: &str, path: &str) + where + E: Error, + { + use codespan_reporting::{files, term}; + use term::termcolor::{ColorChoice, StandardStream}; + + let files = files::SimpleFile::new(path, source); + let config = term::Config::default(); + let writer = StandardStream::stderr(ColorChoice::Auto); + term::emit(&mut writer.lock(), &config, &files, &self.diagnostic()) + .expect("cannot write error"); + } + + /// Emits a summary of the error to a string. + pub fn emit_to_string(&self, source: &str) -> String + where + E: Error, + { + self.emit_to_string_with_path(source, "wgsl") + } + + /// Emits a summary of the error to a string. + pub fn emit_to_string_with_path(&self, source: &str, path: &str) -> String + where + E: Error, + { + use codespan_reporting::{files, term}; + use term::termcolor::NoColor; + + let files = files::SimpleFile::new(path, source); + let config = codespan_reporting::term::Config::default(); + let mut writer = NoColor::new(Vec::new()); + term::emit(&mut writer, &config, &files, &self.diagnostic()).expect("cannot write error"); + String::from_utf8(writer.into_inner()).unwrap() + } +} + +/// Convenience trait for [`Error`] to be able to apply spans to anything. +pub(crate) trait AddSpan: Sized { + type Output; + /// See [`WithSpan::new`]. + fn with_span(self) -> Self::Output; + /// See [`WithSpan::with_span`]. + fn with_span_static(self, span: Span, description: &'static str) -> Self::Output; + /// See [`WithSpan::with_context`]. + fn with_span_context(self, span_context: SpanContext) -> Self::Output; + /// See [`WithSpan::with_handle`]. + fn with_span_handle>(self, handle: Handle, arena: &A) -> Self::Output; +} + +/// Trait abstracting over getting a span from an [`Arena`] or a [`UniqueArena`]. +pub(crate) trait SpanProvider { + fn get_span(&self, handle: Handle) -> Span; + fn get_span_context(&self, handle: Handle) -> SpanContext { + match self.get_span(handle) { + x if !x.is_defined() => (Default::default(), "".to_string()), + known => ( + known, + format!("{} {:?}", std::any::type_name::(), handle), + ), + } + } +} + +impl SpanProvider for Arena { + fn get_span(&self, handle: Handle) -> Span { + self.get_span(handle) + } +} + +impl SpanProvider for UniqueArena { + fn get_span(&self, handle: Handle) -> Span { + self.get_span(handle) + } +} + +impl AddSpan for E +where + E: Error, +{ + type Output = WithSpan; + fn with_span(self) -> WithSpan { + WithSpan::new(self) + } + + fn with_span_static(self, span: Span, description: &'static str) -> WithSpan { + WithSpan::new(self).with_span(span, description) + } + + fn with_span_context(self, span_context: SpanContext) -> WithSpan { + WithSpan::new(self).with_context(span_context) + } + + fn with_span_handle>( + self, + handle: Handle, + arena: &A, + ) -> WithSpan { + WithSpan::new(self).with_handle(handle, arena) + } +} + +/// Convenience trait for [`Result`], adding a [`MapErrWithSpan::map_err_inner`] +/// mapping to [`WithSpan::and_then`]. +pub trait MapErrWithSpan: Sized { + type Output: Sized; + fn map_err_inner(self, func: F) -> Self::Output + where + F: FnOnce(E) -> WithSpan, + E2: From; +} + +impl MapErrWithSpan for Result> { + type Output = Result>; + fn map_err_inner(self, func: F) -> Result> + where + F: FnOnce(E) -> WithSpan, + E2: From, + { + self.map_err(|e| e.and_then(func).into_other::()) + } +} + +#[test] +fn span_location() { + let source = "12\n45\n\n89\n"; + assert_eq!( + Span { start: 0, end: 1 }.location(source), + SourceLocation { + line_number: 1, + line_position: 1, + offset: 0, + length: 1 + } + ); + assert_eq!( + Span { start: 1, end: 2 }.location(source), + SourceLocation { + line_number: 1, + line_position: 2, + offset: 1, + length: 1 + } + ); + assert_eq!( + Span { start: 2, end: 3 }.location(source), + SourceLocation { + line_number: 1, + line_position: 3, + offset: 2, + length: 1 + } + ); + assert_eq!( + Span { start: 3, end: 5 }.location(source), + SourceLocation { + line_number: 2, + line_position: 1, + offset: 3, + length: 2 + } + ); + assert_eq!( + Span { start: 4, end: 6 }.location(source), + SourceLocation { + line_number: 2, + line_position: 2, + offset: 4, + length: 2 + } + ); + assert_eq!( + Span { start: 5, end: 6 }.location(source), + SourceLocation { + line_number: 2, + line_position: 3, + offset: 5, + length: 1 + } + ); + assert_eq!( + Span { start: 6, end: 7 }.location(source), + SourceLocation { + line_number: 3, + line_position: 1, + offset: 6, + length: 1 + } + ); + assert_eq!( + Span { start: 7, end: 8 }.location(source), + SourceLocation { + line_number: 4, + line_position: 1, + offset: 7, + length: 1 + } + ); + assert_eq!( + Span { start: 8, end: 9 }.location(source), + SourceLocation { + line_number: 4, + line_position: 2, + offset: 8, + length: 1 + } + ); + assert_eq!( + Span { start: 9, end: 10 }.location(source), + SourceLocation { + line_number: 4, + line_position: 3, + offset: 9, + length: 1 + } + ); + assert_eq!( + Span { start: 10, end: 11 }.location(source), + SourceLocation { + line_number: 5, + line_position: 1, + offset: 10, + length: 1 + } + ); +} diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs new file mode 100644 index 0000000000..df6fc5e9b0 --- /dev/null +++ b/naga/src/valid/analyzer.rs @@ -0,0 +1,1281 @@ +/*! Module analyzer. + +Figures out the following properties: + - control flow uniformity + - texture/sampler pairs + - expression reference counts +!*/ + +use super::{ExpressionError, FunctionError, ModuleInfo, ShaderStages, ValidationFlags}; +use crate::span::{AddSpan as _, WithSpan}; +use crate::{ + arena::{Arena, Handle}, + proc::{ResolveContext, TypeResolution}, +}; +use std::ops; + +pub type NonUniformResult = Option>; + +// Remove this once we update our uniformity analysis and +// add support for the `derivative_uniformity` diagnostic +const DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE: bool = true; + +bitflags::bitflags! { + /// Kinds of expressions that require uniform control flow. + #[cfg_attr(feature = "serialize", derive(serde::Serialize))] + #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct UniformityRequirements: u8 { + const WORK_GROUP_BARRIER = 0x1; + const DERIVATIVE = if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE { 0 } else { 0x2 }; + const IMPLICIT_LEVEL = if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE { 0 } else { 0x4 }; + } +} + +/// Uniform control flow characteristics. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +#[cfg_attr(test, derive(PartialEq))] +pub struct Uniformity { + /// A child expression with non-uniform result. + /// + /// This means, when the relevant invocations are scheduled on a compute unit, + /// they have to use vector registers to store an individual value + /// per invocation. + /// + /// Whenever the control flow is conditioned on such value, + /// the hardware needs to keep track of the mask of invocations, + /// and process all branches of the control flow. + /// + /// Any operations that depend on non-uniform results also produce non-uniform. + pub non_uniform_result: NonUniformResult, + /// If this expression requires uniform control flow, store the reason here. + pub requirements: UniformityRequirements, +} + +impl Uniformity { + const fn new() -> Self { + Uniformity { + non_uniform_result: None, + requirements: UniformityRequirements::empty(), + } + } +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, PartialEq)] + struct ExitFlags: u8 { + /// Control flow may return from the function, which makes all the + /// subsequent statements within the current function (only!) + /// to be executed in a non-uniform control flow. + const MAY_RETURN = 0x1; + /// Control flow may be killed. Anything after `Statement::Kill` is + /// considered inside non-uniform context. + const MAY_KILL = 0x2; + } +} + +/// Uniformity characteristics of a function. +#[cfg_attr(test, derive(Debug, PartialEq))] +struct FunctionUniformity { + result: Uniformity, + exit: ExitFlags, +} + +impl ops::BitOr for FunctionUniformity { + type Output = Self; + fn bitor(self, other: Self) -> Self { + FunctionUniformity { + result: Uniformity { + non_uniform_result: self + .result + .non_uniform_result + .or(other.result.non_uniform_result), + requirements: self.result.requirements | other.result.requirements, + }, + exit: self.exit | other.exit, + } + } +} + +impl FunctionUniformity { + const fn new() -> Self { + FunctionUniformity { + result: Uniformity::new(), + exit: ExitFlags::empty(), + } + } + + /// Returns a disruptor based on the stored exit flags, if any. + const fn exit_disruptor(&self) -> Option { + if self.exit.contains(ExitFlags::MAY_RETURN) { + Some(UniformityDisruptor::Return) + } else if self.exit.contains(ExitFlags::MAY_KILL) { + Some(UniformityDisruptor::Discard) + } else { + None + } + } +} + +bitflags::bitflags! { + /// Indicates how a global variable is used. + #[cfg_attr(feature = "serialize", derive(serde::Serialize))] + #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct GlobalUse: u8 { + /// Data will be read from the variable. + const READ = 0x1; + /// Data will be written to the variable. + const WRITE = 0x2; + /// The information about the data is queried. + const QUERY = 0x4; + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct SamplingKey { + pub image: Handle, + pub sampler: Handle, +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct ExpressionInfo { + pub uniformity: Uniformity, + pub ref_count: usize, + assignable_global: Option>, + pub ty: TypeResolution, +} + +impl ExpressionInfo { + const fn new() -> Self { + ExpressionInfo { + uniformity: Uniformity::new(), + ref_count: 0, + assignable_global: None, + // this doesn't matter at this point, will be overwritten + ty: TypeResolution::Value(crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Bool, + width: 0, + })), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +enum GlobalOrArgument { + Global(Handle), + Argument(u32), +} + +impl GlobalOrArgument { + fn from_expression( + expression_arena: &Arena, + expression: Handle, + ) -> Result { + Ok(match expression_arena[expression] { + crate::Expression::GlobalVariable(var) => GlobalOrArgument::Global(var), + crate::Expression::FunctionArgument(i) => GlobalOrArgument::Argument(i), + crate::Expression::Access { base, .. } + | crate::Expression::AccessIndex { base, .. } => match expression_arena[base] { + crate::Expression::GlobalVariable(var) => GlobalOrArgument::Global(var), + _ => return Err(ExpressionError::ExpectedGlobalOrArgument), + }, + _ => return Err(ExpressionError::ExpectedGlobalOrArgument), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +struct Sampling { + image: GlobalOrArgument, + sampler: GlobalOrArgument, +} + +#[derive(Debug)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct FunctionInfo { + /// Validation flags. + #[allow(dead_code)] + flags: ValidationFlags, + /// Set of shader stages where calling this function is valid. + pub available_stages: ShaderStages, + /// Uniformity characteristics. + pub uniformity: Uniformity, + /// Function may kill the invocation. + pub may_kill: bool, + + /// All pairs of (texture, sampler) globals that may be used together in + /// sampling operations by this function and its callees. This includes + /// pairings that arise when this function passes textures and samplers as + /// arguments to its callees. + /// + /// This table does not include uses of textures and samplers passed as + /// arguments to this function itself, since we do not know which globals + /// those will be. However, this table *is* exhaustive when computed for an + /// entry point function: entry points never receive textures or samplers as + /// arguments, so all an entry point's sampling can be reported in terms of + /// globals. + /// + /// The GLSL back end uses this table to construct reflection info that + /// clients need to construct texture-combined sampler values. + pub sampling_set: crate::FastHashSet, + + /// How this function and its callees use this module's globals. + /// + /// This is indexed by `Handle` indices. However, + /// `FunctionInfo` implements `std::ops::Index>`, + /// so you can simply index this struct with a global handle to retrieve + /// its usage information. + global_uses: Box<[GlobalUse]>, + + /// Information about each expression in this function's body. + /// + /// This is indexed by `Handle` indices. However, `FunctionInfo` + /// implements `std::ops::Index>`, so you can simply + /// index this struct with an expression handle to retrieve its + /// `ExpressionInfo`. + expressions: Box<[ExpressionInfo]>, + + /// All (texture, sampler) pairs that may be used together in sampling + /// operations by this function and its callees, whether they are accessed + /// as globals or passed as arguments. + /// + /// Participants are represented by [`GlobalVariable`] handles whenever + /// possible, and otherwise by indices of this function's arguments. + /// + /// When analyzing a function call, we combine this data about the callee + /// with the actual arguments being passed to produce the callers' own + /// `sampling_set` and `sampling` tables. + /// + /// [`GlobalVariable`]: crate::GlobalVariable + sampling: crate::FastHashSet, + + /// Indicates that the function is using dual source blending. + pub dual_source_blending: bool, +} + +impl FunctionInfo { + pub const fn global_variable_count(&self) -> usize { + self.global_uses.len() + } + pub const fn expression_count(&self) -> usize { + self.expressions.len() + } + pub fn dominates_global_use(&self, other: &Self) -> bool { + for (self_global_uses, other_global_uses) in + self.global_uses.iter().zip(other.global_uses.iter()) + { + if !self_global_uses.contains(*other_global_uses) { + return false; + } + } + true + } +} + +impl ops::Index> for FunctionInfo { + type Output = GlobalUse; + fn index(&self, handle: Handle) -> &GlobalUse { + &self.global_uses[handle.index()] + } +} + +impl ops::Index> for FunctionInfo { + type Output = ExpressionInfo; + fn index(&self, handle: Handle) -> &ExpressionInfo { + &self.expressions[handle.index()] + } +} + +/// Disruptor of the uniform control flow. +#[derive(Clone, Copy, Debug, thiserror::Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum UniformityDisruptor { + #[error("Expression {0:?} produced non-uniform result, and control flow depends on it")] + Expression(Handle), + #[error("There is a Return earlier in the control flow of the function")] + Return, + #[error("There is a Discard earlier in the entry point across all called functions")] + Discard, +} + +impl FunctionInfo { + /// Adds a value-type reference to an expression. + #[must_use] + fn add_ref_impl( + &mut self, + handle: Handle, + global_use: GlobalUse, + ) -> NonUniformResult { + let info = &mut self.expressions[handle.index()]; + info.ref_count += 1; + // mark the used global as read + if let Some(global) = info.assignable_global { + self.global_uses[global.index()] |= global_use; + } + info.uniformity.non_uniform_result + } + + /// Adds a value-type reference to an expression. + #[must_use] + fn add_ref(&mut self, handle: Handle) -> NonUniformResult { + self.add_ref_impl(handle, GlobalUse::READ) + } + + /// Adds a potentially assignable reference to an expression. + /// These are destinations for `Store` and `ImageStore` statements, + /// which can transit through `Access` and `AccessIndex`. + #[must_use] + fn add_assignable_ref( + &mut self, + handle: Handle, + assignable_global: &mut Option>, + ) -> NonUniformResult { + let info = &mut self.expressions[handle.index()]; + info.ref_count += 1; + // propagate the assignable global up the chain, till it either hits + // a value-type expression, or the assignment statement. + if let Some(global) = info.assignable_global { + if let Some(_old) = assignable_global.replace(global) { + unreachable!() + } + } + info.uniformity.non_uniform_result + } + + /// Inherit information from a called function. + fn process_call( + &mut self, + callee: &Self, + arguments: &[Handle], + expression_arena: &Arena, + ) -> Result> { + self.sampling_set + .extend(callee.sampling_set.iter().cloned()); + for sampling in callee.sampling.iter() { + // If the callee was passed the texture or sampler as an argument, + // we may now be able to determine which globals those referred to. + let image_storage = match sampling.image { + GlobalOrArgument::Global(var) => GlobalOrArgument::Global(var), + GlobalOrArgument::Argument(i) => { + let handle = arguments[i as usize]; + GlobalOrArgument::from_expression(expression_arena, handle).map_err( + |source| { + FunctionError::Expression { handle, source } + .with_span_handle(handle, expression_arena) + }, + )? + } + }; + + let sampler_storage = match sampling.sampler { + GlobalOrArgument::Global(var) => GlobalOrArgument::Global(var), + GlobalOrArgument::Argument(i) => { + let handle = arguments[i as usize]; + GlobalOrArgument::from_expression(expression_arena, handle).map_err( + |source| { + FunctionError::Expression { handle, source } + .with_span_handle(handle, expression_arena) + }, + )? + } + }; + + // If we've managed to pin both the image and sampler down to + // specific globals, record that in our `sampling_set`. Otherwise, + // record as much as we do know in our own `sampling` table, for our + // callers to sort out. + match (image_storage, sampler_storage) { + (GlobalOrArgument::Global(image), GlobalOrArgument::Global(sampler)) => { + self.sampling_set.insert(SamplingKey { image, sampler }); + } + (image, sampler) => { + self.sampling.insert(Sampling { image, sampler }); + } + } + } + + // Inherit global use from our callees. + for (mine, other) in self.global_uses.iter_mut().zip(callee.global_uses.iter()) { + *mine |= *other; + } + + Ok(FunctionUniformity { + result: callee.uniformity.clone(), + exit: if callee.may_kill { + ExitFlags::MAY_KILL + } else { + ExitFlags::empty() + }, + }) + } + + /// Compute the [`ExpressionInfo`] for `handle`. + /// + /// Replace the dummy entry in [`self.expressions`] for `handle` + /// with a real `ExpressionInfo` value describing that expression. + /// + /// This function is called as part of a forward sweep through the + /// arena, so we can assume that all earlier expressions in the + /// arena already have valid info. Since expressions only depend + /// on earlier expressions, this includes all our subexpressions. + /// + /// Adjust the reference counts on all expressions we use. + /// + /// Also populate the [`sampling_set`], [`sampling`] and + /// [`global_uses`] fields of `self`. + /// + /// [`self.expressions`]: FunctionInfo::expressions + /// [`sampling_set`]: FunctionInfo::sampling_set + /// [`sampling`]: FunctionInfo::sampling + /// [`global_uses`]: FunctionInfo::global_uses + #[allow(clippy::or_fun_call)] + fn process_expression( + &mut self, + handle: Handle, + expression_arena: &Arena, + other_functions: &[FunctionInfo], + resolve_context: &ResolveContext, + capabilities: super::Capabilities, + ) -> Result<(), ExpressionError> { + use crate::{Expression as E, SampleLevel as Sl}; + + let expression = &expression_arena[handle]; + let mut assignable_global = None; + let uniformity = match *expression { + E::Access { base, index } => { + let base_ty = self[base].ty.inner_with(resolve_context.types); + + // build up the caps needed if this is indexed non-uniformly + let mut needed_caps = super::Capabilities::empty(); + let is_binding_array = match *base_ty { + crate::TypeInner::BindingArray { + base: array_element_ty_handle, + .. + } => { + // these are nasty aliases, but these idents are too long and break rustfmt + let ub_st = super::Capabilities::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING; + let st_sb = super::Capabilities::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING; + let sampler = super::Capabilities::SAMPLER_NON_UNIFORM_INDEXING; + + // We're a binding array, so lets use the type of _what_ we are array of to determine if we can non-uniformly index it. + let array_element_ty = + &resolve_context.types[array_element_ty_handle].inner; + + needed_caps |= match *array_element_ty { + // If we're an image, use the appropriate limit. + crate::TypeInner::Image { class, .. } => match class { + crate::ImageClass::Storage { .. } => ub_st, + _ => st_sb, + }, + crate::TypeInner::Sampler { .. } => sampler, + // If we're anything but an image, assume we're a buffer and use the address space. + _ => { + if let E::GlobalVariable(global_handle) = expression_arena[base] { + let global = &resolve_context.global_vars[global_handle]; + match global.space { + crate::AddressSpace::Uniform => ub_st, + crate::AddressSpace::Storage { .. } => st_sb, + _ => unreachable!(), + } + } else { + unreachable!() + } + } + }; + + true + } + _ => false, + }; + + if self[index].uniformity.non_uniform_result.is_some() + && !capabilities.contains(needed_caps) + && is_binding_array + { + return Err(ExpressionError::MissingCapabilities(needed_caps)); + } + + Uniformity { + non_uniform_result: self + .add_assignable_ref(base, &mut assignable_global) + .or(self.add_ref(index)), + requirements: UniformityRequirements::empty(), + } + } + E::AccessIndex { base, .. } => Uniformity { + non_uniform_result: self.add_assignable_ref(base, &mut assignable_global), + requirements: UniformityRequirements::empty(), + }, + // always uniform + E::Splat { size: _, value } => Uniformity { + non_uniform_result: self.add_ref(value), + requirements: UniformityRequirements::empty(), + }, + E::Swizzle { vector, .. } => Uniformity { + non_uniform_result: self.add_ref(vector), + requirements: UniformityRequirements::empty(), + }, + E::Literal(_) | E::Constant(_) | E::ZeroValue(_) => Uniformity::new(), + E::Compose { ref components, .. } => { + let non_uniform_result = components + .iter() + .fold(None, |nur, &comp| nur.or(self.add_ref(comp))); + Uniformity { + non_uniform_result, + requirements: UniformityRequirements::empty(), + } + } + // depends on the builtin or interpolation + E::FunctionArgument(index) => { + let arg = &resolve_context.arguments[index as usize]; + let uniform = match arg.binding { + Some(crate::Binding::BuiltIn(built_in)) => match built_in { + // per-polygon built-ins are uniform + crate::BuiltIn::FrontFacing + // per-work-group built-ins are uniform + | crate::BuiltIn::WorkGroupId + | crate::BuiltIn::WorkGroupSize + | crate::BuiltIn::NumWorkGroups => true, + _ => false, + }, + // only flat inputs are uniform + Some(crate::Binding::Location { + interpolation: Some(crate::Interpolation::Flat), + .. + }) => true, + _ => false, + }; + Uniformity { + non_uniform_result: if uniform { None } else { Some(handle) }, + requirements: UniformityRequirements::empty(), + } + } + // depends on the address space + E::GlobalVariable(gh) => { + use crate::AddressSpace as As; + assignable_global = Some(gh); + let var = &resolve_context.global_vars[gh]; + let uniform = match var.space { + // local data is non-uniform + As::Function | As::Private => false, + // workgroup memory is exclusively accessed by the group + As::WorkGroup => true, + // uniform data + As::Uniform | As::PushConstant => true, + // storage data is only uniform when read-only + As::Storage { access } => !access.contains(crate::StorageAccess::STORE), + As::Handle => false, + }; + Uniformity { + non_uniform_result: if uniform { None } else { Some(handle) }, + requirements: UniformityRequirements::empty(), + } + } + E::LocalVariable(_) => Uniformity { + non_uniform_result: Some(handle), + requirements: UniformityRequirements::empty(), + }, + E::Load { pointer } => Uniformity { + non_uniform_result: self.add_ref(pointer), + requirements: UniformityRequirements::empty(), + }, + E::ImageSample { + image, + sampler, + gather: _, + coordinate, + array_index, + offset: _, + level, + depth_ref, + } => { + let image_storage = GlobalOrArgument::from_expression(expression_arena, image)?; + let sampler_storage = GlobalOrArgument::from_expression(expression_arena, sampler)?; + + match (image_storage, sampler_storage) { + (GlobalOrArgument::Global(image), GlobalOrArgument::Global(sampler)) => { + self.sampling_set.insert(SamplingKey { image, sampler }); + } + _ => { + self.sampling.insert(Sampling { + image: image_storage, + sampler: sampler_storage, + }); + } + } + + // "nur" == "Non-Uniform Result" + let array_nur = array_index.and_then(|h| self.add_ref(h)); + let level_nur = match level { + Sl::Auto | Sl::Zero => None, + Sl::Exact(h) | Sl::Bias(h) => self.add_ref(h), + Sl::Gradient { x, y } => self.add_ref(x).or(self.add_ref(y)), + }; + let dref_nur = depth_ref.and_then(|h| self.add_ref(h)); + Uniformity { + non_uniform_result: self + .add_ref(image) + .or(self.add_ref(sampler)) + .or(self.add_ref(coordinate)) + .or(array_nur) + .or(level_nur) + .or(dref_nur), + requirements: if level.implicit_derivatives() { + UniformityRequirements::IMPLICIT_LEVEL + } else { + UniformityRequirements::empty() + }, + } + } + E::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => { + let array_nur = array_index.and_then(|h| self.add_ref(h)); + let sample_nur = sample.and_then(|h| self.add_ref(h)); + let level_nur = level.and_then(|h| self.add_ref(h)); + Uniformity { + non_uniform_result: self + .add_ref(image) + .or(self.add_ref(coordinate)) + .or(array_nur) + .or(sample_nur) + .or(level_nur), + requirements: UniformityRequirements::empty(), + } + } + E::ImageQuery { image, query } => { + let query_nur = match query { + crate::ImageQuery::Size { level: Some(h) } => self.add_ref(h), + _ => None, + }; + Uniformity { + non_uniform_result: self.add_ref_impl(image, GlobalUse::QUERY).or(query_nur), + requirements: UniformityRequirements::empty(), + } + } + E::Unary { expr, .. } => Uniformity { + non_uniform_result: self.add_ref(expr), + requirements: UniformityRequirements::empty(), + }, + E::Binary { left, right, .. } => Uniformity { + non_uniform_result: self.add_ref(left).or(self.add_ref(right)), + requirements: UniformityRequirements::empty(), + }, + E::Select { + condition, + accept, + reject, + } => Uniformity { + non_uniform_result: self + .add_ref(condition) + .or(self.add_ref(accept)) + .or(self.add_ref(reject)), + requirements: UniformityRequirements::empty(), + }, + // explicit derivatives require uniform + E::Derivative { expr, .. } => Uniformity { + //Note: taking a derivative of a uniform doesn't make it non-uniform + non_uniform_result: self.add_ref(expr), + requirements: UniformityRequirements::DERIVATIVE, + }, + E::Relational { argument, .. } => Uniformity { + non_uniform_result: self.add_ref(argument), + requirements: UniformityRequirements::empty(), + }, + E::Math { + fun: _, + arg, + arg1, + arg2, + arg3, + } => { + let arg1_nur = arg1.and_then(|h| self.add_ref(h)); + let arg2_nur = arg2.and_then(|h| self.add_ref(h)); + let arg3_nur = arg3.and_then(|h| self.add_ref(h)); + Uniformity { + non_uniform_result: self.add_ref(arg).or(arg1_nur).or(arg2_nur).or(arg3_nur), + requirements: UniformityRequirements::empty(), + } + } + E::As { expr, .. } => Uniformity { + non_uniform_result: self.add_ref(expr), + requirements: UniformityRequirements::empty(), + }, + E::CallResult(function) => other_functions[function.index()].uniformity.clone(), + E::AtomicResult { .. } | E::RayQueryProceedResult => Uniformity { + non_uniform_result: Some(handle), + requirements: UniformityRequirements::empty(), + }, + E::WorkGroupUniformLoadResult { .. } => Uniformity { + // The result of WorkGroupUniformLoad is always uniform by definition + non_uniform_result: None, + // The call is what cares about uniformity, not the expression + // This expression is never emitted, so this requirement should never be used anyway? + requirements: UniformityRequirements::empty(), + }, + E::ArrayLength(expr) => Uniformity { + non_uniform_result: self.add_ref_impl(expr, GlobalUse::QUERY), + requirements: UniformityRequirements::empty(), + }, + E::RayQueryGetIntersection { + query, + committed: _, + } => Uniformity { + non_uniform_result: self.add_ref(query), + requirements: UniformityRequirements::empty(), + }, + }; + + let ty = resolve_context.resolve(expression, |h| Ok(&self[h].ty))?; + self.expressions[handle.index()] = ExpressionInfo { + uniformity, + ref_count: 0, + assignable_global, + ty, + }; + Ok(()) + } + + /// Analyzes the uniformity requirements of a block (as a sequence of statements). + /// Returns the uniformity characteristics at the *function* level, i.e. + /// whether or not the function requires to be called in uniform control flow, + /// and whether the produced result is not disrupting the control flow. + /// + /// The parent control flow is uniform if `disruptor.is_none()`. + /// + /// Returns a `NonUniformControlFlow` error if any of the expressions in the block + /// require uniformity, but the current flow is non-uniform. + #[allow(clippy::or_fun_call)] + fn process_block( + &mut self, + statements: &crate::Block, + other_functions: &[FunctionInfo], + mut disruptor: Option, + expression_arena: &Arena, + ) -> Result> { + use crate::Statement as S; + + let mut combined_uniformity = FunctionUniformity::new(); + for statement in statements { + let uniformity = match *statement { + S::Emit(ref range) => { + let mut requirements = UniformityRequirements::empty(); + for expr in range.clone() { + let req = self.expressions[expr.index()].uniformity.requirements; + if self + .flags + .contains(super::ValidationFlags::CONTROL_FLOW_UNIFORMITY) + && !req.is_empty() + { + if let Some(cause) = disruptor { + return Err(FunctionError::NonUniformControlFlow(req, expr, cause) + .with_span_handle(expr, expression_arena)); + } + } + requirements |= req; + } + FunctionUniformity { + result: Uniformity { + non_uniform_result: None, + requirements, + }, + exit: ExitFlags::empty(), + } + } + S::Break | S::Continue => FunctionUniformity::new(), + S::Kill => FunctionUniformity { + result: Uniformity::new(), + exit: if disruptor.is_some() { + ExitFlags::MAY_KILL + } else { + ExitFlags::empty() + }, + }, + S::Barrier(_) => FunctionUniformity { + result: Uniformity { + non_uniform_result: None, + requirements: UniformityRequirements::WORK_GROUP_BARRIER, + }, + exit: ExitFlags::empty(), + }, + S::WorkGroupUniformLoad { pointer, .. } => { + let _condition_nur = self.add_ref(pointer); + + // Don't check that this call occurs in uniform control flow until Naga implements WGSL's standard + // uniformity analysis (https://github.com/gfx-rs/naga/issues/1744). + // The uniformity analysis Naga uses now is less accurate than the one in the WGSL standard, + // causing Naga to reject correct uses of `workgroupUniformLoad` in some interesting programs. + + /* + if self + .flags + .contains(super::ValidationFlags::CONTROL_FLOW_UNIFORMITY) + { + let condition_nur = self.add_ref(pointer); + let this_disruptor = + disruptor.or(condition_nur.map(UniformityDisruptor::Expression)); + if let Some(cause) = this_disruptor { + return Err(FunctionError::NonUniformWorkgroupUniformLoad(cause) + .with_span_static(*span, "WorkGroupUniformLoad")); + } + } */ + FunctionUniformity { + result: Uniformity { + non_uniform_result: None, + requirements: UniformityRequirements::WORK_GROUP_BARRIER, + }, + exit: ExitFlags::empty(), + } + } + S::Block(ref b) => { + self.process_block(b, other_functions, disruptor, expression_arena)? + } + S::If { + condition, + ref accept, + ref reject, + } => { + let condition_nur = self.add_ref(condition); + let branch_disruptor = + disruptor.or(condition_nur.map(UniformityDisruptor::Expression)); + let accept_uniformity = self.process_block( + accept, + other_functions, + branch_disruptor, + expression_arena, + )?; + let reject_uniformity = self.process_block( + reject, + other_functions, + branch_disruptor, + expression_arena, + )?; + accept_uniformity | reject_uniformity + } + S::Switch { + selector, + ref cases, + } => { + let selector_nur = self.add_ref(selector); + let branch_disruptor = + disruptor.or(selector_nur.map(UniformityDisruptor::Expression)); + let mut uniformity = FunctionUniformity::new(); + let mut case_disruptor = branch_disruptor; + for case in cases.iter() { + let case_uniformity = self.process_block( + &case.body, + other_functions, + case_disruptor, + expression_arena, + )?; + case_disruptor = if case.fall_through { + case_disruptor.or(case_uniformity.exit_disruptor()) + } else { + branch_disruptor + }; + uniformity = uniformity | case_uniformity; + } + uniformity + } + S::Loop { + ref body, + ref continuing, + break_if, + } => { + let body_uniformity = + self.process_block(body, other_functions, disruptor, expression_arena)?; + let continuing_disruptor = disruptor.or(body_uniformity.exit_disruptor()); + let continuing_uniformity = self.process_block( + continuing, + other_functions, + continuing_disruptor, + expression_arena, + )?; + if let Some(expr) = break_if { + let _ = self.add_ref(expr); + } + body_uniformity | continuing_uniformity + } + S::Return { value } => FunctionUniformity { + result: Uniformity { + non_uniform_result: value.and_then(|expr| self.add_ref(expr)), + requirements: UniformityRequirements::empty(), + }, + exit: if disruptor.is_some() { + ExitFlags::MAY_RETURN + } else { + ExitFlags::empty() + }, + }, + // Here and below, the used expressions are already emitted, + // and their results do not affect the function return value, + // so we can ignore their non-uniformity. + S::Store { pointer, value } => { + let _ = self.add_ref_impl(pointer, GlobalUse::WRITE); + let _ = self.add_ref(value); + FunctionUniformity::new() + } + S::ImageStore { + image, + coordinate, + array_index, + value, + } => { + let _ = self.add_ref_impl(image, GlobalUse::WRITE); + if let Some(expr) = array_index { + let _ = self.add_ref(expr); + } + let _ = self.add_ref(coordinate); + let _ = self.add_ref(value); + FunctionUniformity::new() + } + S::Call { + function, + ref arguments, + result: _, + } => { + for &argument in arguments { + let _ = self.add_ref(argument); + } + let info = &other_functions[function.index()]; + //Note: the result is validated by the Validator, not here + self.process_call(info, arguments, expression_arena)? + } + S::Atomic { + pointer, + ref fun, + value, + result: _, + } => { + let _ = self.add_ref_impl(pointer, GlobalUse::WRITE); + let _ = self.add_ref(value); + if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun { + let _ = self.add_ref(cmp); + } + FunctionUniformity::new() + } + S::RayQuery { query, ref fun } => { + let _ = self.add_ref(query); + if let crate::RayQueryFunction::Initialize { + acceleration_structure, + descriptor, + } = *fun + { + let _ = self.add_ref(acceleration_structure); + let _ = self.add_ref(descriptor); + } + FunctionUniformity::new() + } + }; + + disruptor = disruptor.or(uniformity.exit_disruptor()); + combined_uniformity = combined_uniformity | uniformity; + } + Ok(combined_uniformity) + } +} + +impl ModuleInfo { + /// Populates `self.const_expression_types` + pub(super) fn process_const_expression( + &mut self, + handle: Handle, + resolve_context: &ResolveContext, + gctx: crate::proc::GlobalCtx, + ) -> Result<(), super::ConstExpressionError> { + self.const_expression_types[handle.index()] = + resolve_context.resolve(&gctx.const_expressions[handle], |h| Ok(&self[h]))?; + Ok(()) + } + + /// Builds the `FunctionInfo` based on the function, and validates the + /// uniform control flow if required by the expressions of this function. + pub(super) fn process_function( + &self, + fun: &crate::Function, + module: &crate::Module, + flags: ValidationFlags, + capabilities: super::Capabilities, + ) -> Result> { + let mut info = FunctionInfo { + flags, + available_stages: ShaderStages::all(), + uniformity: Uniformity::new(), + may_kill: false, + sampling_set: crate::FastHashSet::default(), + global_uses: vec![GlobalUse::empty(); module.global_variables.len()].into_boxed_slice(), + expressions: vec![ExpressionInfo::new(); fun.expressions.len()].into_boxed_slice(), + sampling: crate::FastHashSet::default(), + dual_source_blending: false, + }; + let resolve_context = + ResolveContext::with_locals(module, &fun.local_variables, &fun.arguments); + + for (handle, _) in fun.expressions.iter() { + if let Err(source) = info.process_expression( + handle, + &fun.expressions, + &self.functions, + &resolve_context, + capabilities, + ) { + return Err(FunctionError::Expression { handle, source } + .with_span_handle(handle, &fun.expressions)); + } + } + + for (_, expr) in fun.local_variables.iter() { + if let Some(init) = expr.init { + let _ = info.add_ref(init); + } + } + + let uniformity = info.process_block(&fun.body, &self.functions, None, &fun.expressions)?; + info.uniformity = uniformity.result; + info.may_kill = uniformity.exit.contains(ExitFlags::MAY_KILL); + + Ok(info) + } + + pub fn get_entry_point(&self, index: usize) -> &FunctionInfo { + &self.entry_points[index] + } +} + +#[test] +fn uniform_control_flow() { + use crate::{Expression as E, Statement as S}; + + let mut type_arena = crate::UniqueArena::new(); + let ty = type_arena.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size: crate::VectorSize::Bi, + scalar: crate::Scalar::F32, + }, + }, + Default::default(), + ); + let mut global_var_arena = Arena::new(); + let non_uniform_global = global_var_arena.append( + crate::GlobalVariable { + name: None, + init: None, + ty, + space: crate::AddressSpace::Handle, + binding: None, + }, + Default::default(), + ); + let uniform_global = global_var_arena.append( + crate::GlobalVariable { + name: None, + init: None, + ty, + binding: None, + space: crate::AddressSpace::Uniform, + }, + Default::default(), + ); + + let mut expressions = Arena::new(); + // checks the uniform control flow + let constant_expr = expressions.append(E::Literal(crate::Literal::U32(0)), Default::default()); + // checks the non-uniform control flow + let derivative_expr = expressions.append( + E::Derivative { + axis: crate::DerivativeAxis::X, + ctrl: crate::DerivativeControl::None, + expr: constant_expr, + }, + Default::default(), + ); + let emit_range_constant_derivative = expressions.range_from(0); + let non_uniform_global_expr = + expressions.append(E::GlobalVariable(non_uniform_global), Default::default()); + let uniform_global_expr = + expressions.append(E::GlobalVariable(uniform_global), Default::default()); + let emit_range_globals = expressions.range_from(2); + + // checks the QUERY flag + let query_expr = expressions.append(E::ArrayLength(uniform_global_expr), Default::default()); + // checks the transitive WRITE flag + let access_expr = expressions.append( + E::AccessIndex { + base: non_uniform_global_expr, + index: 1, + }, + Default::default(), + ); + let emit_range_query_access_globals = expressions.range_from(2); + + let mut info = FunctionInfo { + flags: ValidationFlags::all(), + available_stages: ShaderStages::all(), + uniformity: Uniformity::new(), + may_kill: false, + sampling_set: crate::FastHashSet::default(), + global_uses: vec![GlobalUse::empty(); global_var_arena.len()].into_boxed_slice(), + expressions: vec![ExpressionInfo::new(); expressions.len()].into_boxed_slice(), + sampling: crate::FastHashSet::default(), + dual_source_blending: false, + }; + let resolve_context = ResolveContext { + constants: &Arena::new(), + types: &type_arena, + special_types: &crate::SpecialTypes::default(), + global_vars: &global_var_arena, + local_vars: &Arena::new(), + functions: &Arena::new(), + arguments: &[], + }; + for (handle, _) in expressions.iter() { + info.process_expression( + handle, + &expressions, + &[], + &resolve_context, + super::Capabilities::empty(), + ) + .unwrap(); + } + assert_eq!(info[non_uniform_global_expr].ref_count, 1); + assert_eq!(info[uniform_global_expr].ref_count, 1); + assert_eq!(info[query_expr].ref_count, 0); + assert_eq!(info[access_expr].ref_count, 0); + assert_eq!(info[non_uniform_global], GlobalUse::empty()); + assert_eq!(info[uniform_global], GlobalUse::QUERY); + + let stmt_emit1 = S::Emit(emit_range_globals.clone()); + let stmt_if_uniform = S::If { + condition: uniform_global_expr, + accept: crate::Block::new(), + reject: vec![ + S::Emit(emit_range_constant_derivative.clone()), + S::Store { + pointer: constant_expr, + value: derivative_expr, + }, + ] + .into(), + }; + assert_eq!( + info.process_block( + &vec![stmt_emit1, stmt_if_uniform].into(), + &[], + None, + &expressions + ), + Ok(FunctionUniformity { + result: Uniformity { + non_uniform_result: None, + requirements: UniformityRequirements::DERIVATIVE, + }, + exit: ExitFlags::empty(), + }), + ); + assert_eq!(info[constant_expr].ref_count, 2); + assert_eq!(info[uniform_global], GlobalUse::READ | GlobalUse::QUERY); + + let stmt_emit2 = S::Emit(emit_range_globals.clone()); + let stmt_if_non_uniform = S::If { + condition: non_uniform_global_expr, + accept: vec![ + S::Emit(emit_range_constant_derivative), + S::Store { + pointer: constant_expr, + value: derivative_expr, + }, + ] + .into(), + reject: crate::Block::new(), + }; + { + let block_info = info.process_block( + &vec![stmt_emit2, stmt_if_non_uniform].into(), + &[], + None, + &expressions, + ); + if DISABLE_UNIFORMITY_REQ_FOR_FRAGMENT_STAGE { + assert_eq!(info[derivative_expr].ref_count, 2); + } else { + assert_eq!( + block_info, + Err(FunctionError::NonUniformControlFlow( + UniformityRequirements::DERIVATIVE, + derivative_expr, + UniformityDisruptor::Expression(non_uniform_global_expr) + ) + .with_span()), + ); + assert_eq!(info[derivative_expr].ref_count, 1); + } + } + assert_eq!(info[non_uniform_global], GlobalUse::READ); + + let stmt_emit3 = S::Emit(emit_range_globals); + let stmt_return_non_uniform = S::Return { + value: Some(non_uniform_global_expr), + }; + assert_eq!( + info.process_block( + &vec![stmt_emit3, stmt_return_non_uniform].into(), + &[], + Some(UniformityDisruptor::Return), + &expressions + ), + Ok(FunctionUniformity { + result: Uniformity { + non_uniform_result: Some(non_uniform_global_expr), + requirements: UniformityRequirements::empty(), + }, + exit: ExitFlags::MAY_RETURN, + }), + ); + assert_eq!(info[non_uniform_global_expr].ref_count, 3); + + // Check that uniformity requirements reach through a pointer + let stmt_emit4 = S::Emit(emit_range_query_access_globals); + let stmt_assign = S::Store { + pointer: access_expr, + value: query_expr, + }; + let stmt_return_pointer = S::Return { + value: Some(access_expr), + }; + let stmt_kill = S::Kill; + assert_eq!( + info.process_block( + &vec![stmt_emit4, stmt_assign, stmt_kill, stmt_return_pointer].into(), + &[], + Some(UniformityDisruptor::Discard), + &expressions + ), + Ok(FunctionUniformity { + result: Uniformity { + non_uniform_result: Some(non_uniform_global_expr), + requirements: UniformityRequirements::empty(), + }, + exit: ExitFlags::all(), + }), + ); + assert_eq!(info[non_uniform_global], GlobalUse::READ | GlobalUse::WRITE); +} diff --git a/naga/src/valid/compose.rs b/naga/src/valid/compose.rs new file mode 100644 index 0000000000..c21e98c6f2 --- /dev/null +++ b/naga/src/valid/compose.rs @@ -0,0 +1,128 @@ +use crate::proc::TypeResolution; + +use crate::arena::Handle; + +#[derive(Clone, Debug, thiserror::Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum ComposeError { + #[error("Composing of type {0:?} can't be done")] + Type(Handle), + #[error("Composing expects {expected} components but {given} were given")] + ComponentCount { given: u32, expected: u32 }, + #[error("Composing {index}'s component type is not expected")] + ComponentType { index: u32 }, +} + +pub fn validate_compose( + self_ty_handle: Handle, + gctx: crate::proc::GlobalCtx, + component_resolutions: impl ExactSizeIterator, +) -> Result<(), ComposeError> { + use crate::TypeInner as Ti; + + match gctx.types[self_ty_handle].inner { + // vectors are composed from scalars or other vectors + Ti::Vector { size, scalar } => { + let mut total = 0; + for (index, comp_res) in component_resolutions.enumerate() { + total += match *comp_res.inner_with(gctx.types) { + Ti::Scalar(comp_scalar) if comp_scalar == scalar => 1, + Ti::Vector { + size: comp_size, + scalar: comp_scalar, + } if comp_scalar == scalar => comp_size as u32, + ref other => { + log::error!( + "Vector component[{}] type {:?}, building {:?}", + index, + other, + scalar + ); + return Err(ComposeError::ComponentType { + index: index as u32, + }); + } + }; + } + if size as u32 != total { + return Err(ComposeError::ComponentCount { + expected: size as u32, + given: total, + }); + } + } + // matrix are composed from column vectors + Ti::Matrix { + columns, + rows, + scalar, + } => { + let inner = Ti::Vector { size: rows, scalar }; + if columns as usize != component_resolutions.len() { + return Err(ComposeError::ComponentCount { + expected: columns as u32, + given: component_resolutions.len() as u32, + }); + } + for (index, comp_res) in component_resolutions.enumerate() { + if comp_res.inner_with(gctx.types) != &inner { + log::error!("Matrix component[{}] type {:?}", index, comp_res); + return Err(ComposeError::ComponentType { + index: index as u32, + }); + } + } + } + Ti::Array { + base, + size: crate::ArraySize::Constant(count), + stride: _, + } => { + if count.get() as usize != component_resolutions.len() { + return Err(ComposeError::ComponentCount { + expected: count.get(), + given: component_resolutions.len() as u32, + }); + } + for (index, comp_res) in component_resolutions.enumerate() { + let base_inner = &gctx.types[base].inner; + let comp_res_inner = comp_res.inner_with(gctx.types); + // We don't support arrays of pointers, but it seems best not to + // embed that assumption here, so use `TypeInner::equivalent`. + if !base_inner.equivalent(comp_res_inner, gctx.types) { + log::error!("Array component[{}] type {:?}", index, comp_res); + return Err(ComposeError::ComponentType { + index: index as u32, + }); + } + } + } + Ti::Struct { ref members, .. } => { + if members.len() != component_resolutions.len() { + return Err(ComposeError::ComponentCount { + given: component_resolutions.len() as u32, + expected: members.len() as u32, + }); + } + for (index, (member, comp_res)) in members.iter().zip(component_resolutions).enumerate() + { + let member_inner = &gctx.types[member.ty].inner; + let comp_res_inner = comp_res.inner_with(gctx.types); + // We don't support pointers in structs, but it seems best not to embed + // that assumption here, so use `TypeInner::equivalent`. + if !comp_res_inner.equivalent(member_inner, gctx.types) { + log::error!("Struct component[{}] type {:?}", index, comp_res); + return Err(ComposeError::ComponentType { + index: index as u32, + }); + } + } + } + ref other => { + log::error!("Composing of {:?}", other); + return Err(ComposeError::Type(self_ty_handle)); + } + } + + Ok(()) +} diff --git a/naga/src/valid/expression.rs b/naga/src/valid/expression.rs new file mode 100644 index 0000000000..c82d60f062 --- /dev/null +++ b/naga/src/valid/expression.rs @@ -0,0 +1,1797 @@ +use super::{ + compose::validate_compose, validate_atomic_compare_exchange_struct, FunctionInfo, ModuleInfo, + ShaderStages, TypeFlags, +}; +use crate::arena::UniqueArena; + +use crate::{ + arena::Handle, + proc::{IndexableLengthError, ResolveError}, +}; + +#[derive(Clone, Debug, thiserror::Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum ExpressionError { + #[error("Doesn't exist")] + DoesntExist, + #[error("Used by a statement before it was introduced into the scope by any of the dominating blocks")] + NotInScope, + #[error("Base type {0:?} is not compatible with this expression")] + InvalidBaseType(Handle), + #[error("Accessing with index {0:?} can't be done")] + InvalidIndexType(Handle), + #[error("Accessing {0:?} via a negative index is invalid")] + NegativeIndex(Handle), + #[error("Accessing index {1} is out of {0:?} bounds")] + IndexOutOfBounds(Handle, u32), + #[error("The expression {0:?} may only be indexed by a constant")] + IndexMustBeConstant(Handle), + #[error("Function argument {0:?} doesn't exist")] + FunctionArgumentDoesntExist(u32), + #[error("Loading of {0:?} can't be done")] + InvalidPointerType(Handle), + #[error("Array length of {0:?} can't be done")] + InvalidArrayType(Handle), + #[error("Get intersection of {0:?} can't be done")] + InvalidRayQueryType(Handle), + #[error("Splatting {0:?} can't be done")] + InvalidSplatType(Handle), + #[error("Swizzling {0:?} can't be done")] + InvalidVectorType(Handle), + #[error("Swizzle component {0:?} is outside of vector size {1:?}")] + InvalidSwizzleComponent(crate::SwizzleComponent, crate::VectorSize), + #[error(transparent)] + Compose(#[from] super::ComposeError), + #[error(transparent)] + IndexableLength(#[from] IndexableLengthError), + #[error("Operation {0:?} can't work with {1:?}")] + InvalidUnaryOperandType(crate::UnaryOperator, Handle), + #[error("Operation {0:?} can't work with {1:?} and {2:?}")] + InvalidBinaryOperandTypes( + crate::BinaryOperator, + Handle, + Handle, + ), + #[error("Selecting is not possible")] + InvalidSelectTypes, + #[error("Relational argument {0:?} is not a boolean vector")] + InvalidBooleanVector(Handle), + #[error("Relational argument {0:?} is not a float")] + InvalidFloatArgument(Handle), + #[error("Type resolution failed")] + Type(#[from] ResolveError), + #[error("Not a global variable")] + ExpectedGlobalVariable, + #[error("Not a global variable or a function argument")] + ExpectedGlobalOrArgument, + #[error("Needs to be an binding array instead of {0:?}")] + ExpectedBindingArrayType(Handle), + #[error("Needs to be an image instead of {0:?}")] + ExpectedImageType(Handle), + #[error("Needs to be an image instead of {0:?}")] + ExpectedSamplerType(Handle), + #[error("Unable to operate on image class {0:?}")] + InvalidImageClass(crate::ImageClass), + #[error("Derivatives can only be taken from scalar and vector floats")] + InvalidDerivative, + #[error("Image array index parameter is misplaced")] + InvalidImageArrayIndex, + #[error("Inappropriate sample or level-of-detail index for texel access")] + InvalidImageOtherIndex, + #[error("Image array index type of {0:?} is not an integer scalar")] + InvalidImageArrayIndexType(Handle), + #[error("Image sample or level-of-detail index's type of {0:?} is not an integer scalar")] + InvalidImageOtherIndexType(Handle), + #[error("Image coordinate type of {1:?} does not match dimension {0:?}")] + InvalidImageCoordinateType(crate::ImageDimension, Handle), + #[error("Comparison sampling mismatch: image has class {image:?}, but the sampler is comparison={sampler}, and the reference was provided={has_ref}")] + ComparisonSamplingMismatch { + image: crate::ImageClass, + sampler: bool, + has_ref: bool, + }, + #[error("Sample offset constant {1:?} doesn't match the image dimension {0:?}")] + InvalidSampleOffset(crate::ImageDimension, Handle), + #[error("Depth reference {0:?} is not a scalar float")] + InvalidDepthReference(Handle), + #[error("Depth sample level can only be Auto or Zero")] + InvalidDepthSampleLevel, + #[error("Gather level can only be Zero")] + InvalidGatherLevel, + #[error("Gather component {0:?} doesn't exist in the image")] + InvalidGatherComponent(crate::SwizzleComponent), + #[error("Gather can't be done for image dimension {0:?}")] + InvalidGatherDimension(crate::ImageDimension), + #[error("Sample level (exact) type {0:?} is not a scalar float")] + InvalidSampleLevelExactType(Handle), + #[error("Sample level (bias) type {0:?} is not a scalar float")] + InvalidSampleLevelBiasType(Handle), + #[error("Sample level (gradient) of {1:?} doesn't match the image dimension {0:?}")] + InvalidSampleLevelGradientType(crate::ImageDimension, Handle), + #[error("Unable to cast")] + InvalidCastArgument, + #[error("Invalid argument count for {0:?}")] + WrongArgumentCount(crate::MathFunction), + #[error("Argument [{1}] to {0:?} as expression {2:?} has an invalid type.")] + InvalidArgumentType(crate::MathFunction, u32, Handle), + #[error("Atomic result type can't be {0:?}")] + InvalidAtomicResultType(Handle), + #[error( + "workgroupUniformLoad result type can't be {0:?}. It can only be a constructible type." + )] + InvalidWorkGroupUniformLoadResultType(Handle), + #[error("Shader requires capability {0:?}")] + MissingCapabilities(super::Capabilities), + #[error(transparent)] + Literal(#[from] LiteralError), +} + +#[derive(Clone, Debug, thiserror::Error)] +pub enum ConstExpressionError { + #[error("The expression is not a constant expression")] + NonConst, + #[error(transparent)] + Compose(#[from] super::ComposeError), + #[error("Splatting {0:?} can't be done")] + InvalidSplatType(Handle), + #[error("Type resolution failed")] + Type(#[from] ResolveError), + #[error(transparent)] + Literal(#[from] LiteralError), + #[error(transparent)] + Width(#[from] super::r#type::WidthError), +} + +#[derive(Clone, Debug, thiserror::Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum LiteralError { + #[error("Float literal is NaN")] + NaN, + #[error("Float literal is infinite")] + Infinity, + #[error(transparent)] + Width(#[from] super::r#type::WidthError), +} + +struct ExpressionTypeResolver<'a> { + root: Handle, + types: &'a UniqueArena, + info: &'a FunctionInfo, +} + +impl<'a> std::ops::Index> for ExpressionTypeResolver<'a> { + type Output = crate::TypeInner; + + #[allow(clippy::panic)] + fn index(&self, handle: Handle) -> &Self::Output { + if handle < self.root { + self.info[handle].ty.inner_with(self.types) + } else { + // `Validator::validate_module_handles` should have caught this. + panic!( + "Depends on {:?}, which has not been processed yet", + self.root + ) + } + } +} + +impl super::Validator { + pub(super) fn validate_const_expression( + &self, + handle: Handle, + gctx: crate::proc::GlobalCtx, + mod_info: &ModuleInfo, + ) -> Result<(), ConstExpressionError> { + use crate::Expression as E; + + match gctx.const_expressions[handle] { + E::Literal(literal) => { + self.validate_literal(literal)?; + } + E::Constant(_) | E::ZeroValue(_) => {} + E::Compose { ref components, ty } => { + validate_compose( + ty, + gctx, + components.iter().map(|&handle| mod_info[handle].clone()), + )?; + } + E::Splat { value, .. } => match *mod_info[value].inner_with(gctx.types) { + crate::TypeInner::Scalar { .. } => {} + _ => return Err(super::ConstExpressionError::InvalidSplatType(value)), + }, + _ => return Err(super::ConstExpressionError::NonConst), + } + + Ok(()) + } + + pub(super) fn validate_expression( + &self, + root: Handle, + expression: &crate::Expression, + function: &crate::Function, + module: &crate::Module, + info: &FunctionInfo, + mod_info: &ModuleInfo, + ) -> Result { + use crate::{Expression as E, Scalar as Sc, ScalarKind as Sk, TypeInner as Ti}; + + let resolver = ExpressionTypeResolver { + root, + types: &module.types, + info, + }; + + let stages = match *expression { + E::Access { base, index } => { + let base_type = &resolver[base]; + // See the documentation for `Expression::Access`. + let dynamic_indexing_restricted = match *base_type { + Ti::Vector { .. } => false, + Ti::Matrix { .. } | Ti::Array { .. } => true, + Ti::Pointer { .. } + | Ti::ValuePointer { size: Some(_), .. } + | Ti::BindingArray { .. } => false, + ref other => { + log::error!("Indexing of {:?}", other); + return Err(ExpressionError::InvalidBaseType(base)); + } + }; + match resolver[index] { + //TODO: only allow one of these + Ti::Scalar(Sc { + kind: Sk::Sint | Sk::Uint, + .. + }) => {} + ref other => { + log::error!("Indexing by {:?}", other); + return Err(ExpressionError::InvalidIndexType(index)); + } + } + if dynamic_indexing_restricted + && function.expressions[index].is_dynamic_index(module) + { + return Err(ExpressionError::IndexMustBeConstant(base)); + } + + // If we know both the length and the index, we can do the + // bounds check now. + if let crate::proc::IndexableLength::Known(known_length) = + base_type.indexable_length(module)? + { + match module + .to_ctx() + .eval_expr_to_u32_from(index, &function.expressions) + { + Ok(value) => { + if value >= known_length { + return Err(ExpressionError::IndexOutOfBounds(base, value)); + } + } + Err(crate::proc::U32EvalError::Negative) => { + return Err(ExpressionError::NegativeIndex(base)) + } + Err(crate::proc::U32EvalError::NonConst) => {} + } + } + + ShaderStages::all() + } + E::AccessIndex { base, index } => { + fn resolve_index_limit( + module: &crate::Module, + top: Handle, + ty: &crate::TypeInner, + top_level: bool, + ) -> Result { + let limit = match *ty { + Ti::Vector { size, .. } + | Ti::ValuePointer { + size: Some(size), .. + } => size as u32, + Ti::Matrix { columns, .. } => columns as u32, + Ti::Array { + size: crate::ArraySize::Constant(len), + .. + } => len.get(), + Ti::Array { .. } | Ti::BindingArray { .. } => u32::MAX, // can't statically know, but need run-time checks + Ti::Pointer { base, .. } if top_level => { + resolve_index_limit(module, top, &module.types[base].inner, false)? + } + Ti::Struct { ref members, .. } => members.len() as u32, + ref other => { + log::error!("Indexing of {:?}", other); + return Err(ExpressionError::InvalidBaseType(top)); + } + }; + Ok(limit) + } + + let limit = resolve_index_limit(module, base, &resolver[base], true)?; + if index >= limit { + return Err(ExpressionError::IndexOutOfBounds(base, limit)); + } + ShaderStages::all() + } + E::Splat { size: _, value } => match resolver[value] { + Ti::Scalar { .. } => ShaderStages::all(), + ref other => { + log::error!("Splat scalar type {:?}", other); + return Err(ExpressionError::InvalidSplatType(value)); + } + }, + E::Swizzle { + size, + vector, + pattern, + } => { + let vec_size = match resolver[vector] { + Ti::Vector { size: vec_size, .. } => vec_size, + ref other => { + log::error!("Swizzle vector type {:?}", other); + return Err(ExpressionError::InvalidVectorType(vector)); + } + }; + for &sc in pattern[..size as usize].iter() { + if sc as u8 >= vec_size as u8 { + return Err(ExpressionError::InvalidSwizzleComponent(sc, vec_size)); + } + } + ShaderStages::all() + } + E::Literal(literal) => { + self.validate_literal(literal)?; + ShaderStages::all() + } + E::Constant(_) | E::ZeroValue(_) => ShaderStages::all(), + E::Compose { ref components, ty } => { + validate_compose( + ty, + module.to_ctx(), + components.iter().map(|&handle| info[handle].ty.clone()), + )?; + ShaderStages::all() + } + E::FunctionArgument(index) => { + if index >= function.arguments.len() as u32 { + return Err(ExpressionError::FunctionArgumentDoesntExist(index)); + } + ShaderStages::all() + } + E::GlobalVariable(_handle) => ShaderStages::all(), + E::LocalVariable(_handle) => ShaderStages::all(), + E::Load { pointer } => { + match resolver[pointer] { + Ti::Pointer { base, .. } + if self.types[base.index()] + .flags + .contains(TypeFlags::SIZED | TypeFlags::DATA) => {} + Ti::ValuePointer { .. } => {} + ref other => { + log::error!("Loading {:?}", other); + return Err(ExpressionError::InvalidPointerType(pointer)); + } + } + ShaderStages::all() + } + E::ImageSample { + image, + sampler, + gather, + coordinate, + array_index, + offset, + level, + depth_ref, + } => { + // check the validity of expressions + let image_ty = Self::global_var_ty(module, function, image)?; + let sampler_ty = Self::global_var_ty(module, function, sampler)?; + + let comparison = match module.types[sampler_ty].inner { + Ti::Sampler { comparison } => comparison, + _ => return Err(ExpressionError::ExpectedSamplerType(sampler_ty)), + }; + + let (class, dim) = match module.types[image_ty].inner { + Ti::Image { + class, + arrayed, + dim, + } => { + // check the array property + if arrayed != array_index.is_some() { + return Err(ExpressionError::InvalidImageArrayIndex); + } + if let Some(expr) = array_index { + match resolver[expr] { + Ti::Scalar(Sc { + kind: Sk::Sint | Sk::Uint, + .. + }) => {} + _ => return Err(ExpressionError::InvalidImageArrayIndexType(expr)), + } + } + (class, dim) + } + _ => return Err(ExpressionError::ExpectedImageType(image_ty)), + }; + + // check sampling and comparison properties + let image_depth = match class { + crate::ImageClass::Sampled { + kind: crate::ScalarKind::Float, + multi: false, + } => false, + crate::ImageClass::Sampled { + kind: crate::ScalarKind::Uint | crate::ScalarKind::Sint, + multi: false, + } if gather.is_some() => false, + crate::ImageClass::Depth { multi: false } => true, + _ => return Err(ExpressionError::InvalidImageClass(class)), + }; + if comparison != depth_ref.is_some() || (comparison && !image_depth) { + return Err(ExpressionError::ComparisonSamplingMismatch { + image: class, + sampler: comparison, + has_ref: depth_ref.is_some(), + }); + } + + // check texture coordinates type + let num_components = match dim { + crate::ImageDimension::D1 => 1, + crate::ImageDimension::D2 => 2, + crate::ImageDimension::D3 | crate::ImageDimension::Cube => 3, + }; + match resolver[coordinate] { + Ti::Scalar(Sc { + kind: Sk::Float, .. + }) if num_components == 1 => {} + Ti::Vector { + size, + scalar: + Sc { + kind: Sk::Float, .. + }, + } if size as u32 == num_components => {} + _ => return Err(ExpressionError::InvalidImageCoordinateType(dim, coordinate)), + } + + // check constant offset + if let Some(const_expr) = offset { + match *mod_info[const_expr].inner_with(&module.types) { + Ti::Scalar(Sc { kind: Sk::Sint, .. }) if num_components == 1 => {} + Ti::Vector { + size, + scalar: Sc { kind: Sk::Sint, .. }, + } if size as u32 == num_components => {} + _ => { + return Err(ExpressionError::InvalidSampleOffset(dim, const_expr)); + } + } + } + + // check depth reference type + if let Some(expr) = depth_ref { + match resolver[expr] { + Ti::Scalar(Sc { + kind: Sk::Float, .. + }) => {} + _ => return Err(ExpressionError::InvalidDepthReference(expr)), + } + match level { + crate::SampleLevel::Auto | crate::SampleLevel::Zero => {} + _ => return Err(ExpressionError::InvalidDepthSampleLevel), + } + } + + if let Some(component) = gather { + match dim { + crate::ImageDimension::D2 | crate::ImageDimension::Cube => {} + crate::ImageDimension::D1 | crate::ImageDimension::D3 => { + return Err(ExpressionError::InvalidGatherDimension(dim)) + } + }; + let max_component = match class { + crate::ImageClass::Depth { .. } => crate::SwizzleComponent::X, + _ => crate::SwizzleComponent::W, + }; + if component > max_component { + return Err(ExpressionError::InvalidGatherComponent(component)); + } + match level { + crate::SampleLevel::Zero => {} + _ => return Err(ExpressionError::InvalidGatherLevel), + } + } + + // check level properties + match level { + crate::SampleLevel::Auto => ShaderStages::FRAGMENT, + crate::SampleLevel::Zero => ShaderStages::all(), + crate::SampleLevel::Exact(expr) => { + match resolver[expr] { + Ti::Scalar(Sc { + kind: Sk::Float, .. + }) => {} + _ => return Err(ExpressionError::InvalidSampleLevelExactType(expr)), + } + ShaderStages::all() + } + crate::SampleLevel::Bias(expr) => { + match resolver[expr] { + Ti::Scalar(Sc { + kind: Sk::Float, .. + }) => {} + _ => return Err(ExpressionError::InvalidSampleLevelBiasType(expr)), + } + ShaderStages::FRAGMENT + } + crate::SampleLevel::Gradient { x, y } => { + match resolver[x] { + Ti::Scalar(Sc { + kind: Sk::Float, .. + }) if num_components == 1 => {} + Ti::Vector { + size, + scalar: + Sc { + kind: Sk::Float, .. + }, + } if size as u32 == num_components => {} + _ => { + return Err(ExpressionError::InvalidSampleLevelGradientType(dim, x)) + } + } + match resolver[y] { + Ti::Scalar(Sc { + kind: Sk::Float, .. + }) if num_components == 1 => {} + Ti::Vector { + size, + scalar: + Sc { + kind: Sk::Float, .. + }, + } if size as u32 == num_components => {} + _ => { + return Err(ExpressionError::InvalidSampleLevelGradientType(dim, y)) + } + } + ShaderStages::all() + } + } + } + E::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => { + let ty = Self::global_var_ty(module, function, image)?; + match module.types[ty].inner { + Ti::Image { + class, + arrayed, + dim, + } => { + match resolver[coordinate].image_storage_coordinates() { + Some(coord_dim) if coord_dim == dim => {} + _ => { + return Err(ExpressionError::InvalidImageCoordinateType( + dim, coordinate, + )) + } + }; + if arrayed != array_index.is_some() { + return Err(ExpressionError::InvalidImageArrayIndex); + } + if let Some(expr) = array_index { + match resolver[expr] { + Ti::Scalar(Sc { + kind: Sk::Sint | Sk::Uint, + width: _, + }) => {} + _ => return Err(ExpressionError::InvalidImageArrayIndexType(expr)), + } + } + + match (sample, class.is_multisampled()) { + (None, false) => {} + (Some(sample), true) => { + if resolver[sample].scalar_kind() != Some(Sk::Sint) { + return Err(ExpressionError::InvalidImageOtherIndexType( + sample, + )); + } + } + _ => { + return Err(ExpressionError::InvalidImageOtherIndex); + } + } + + match (level, class.is_mipmapped()) { + (None, false) => {} + (Some(level), true) => { + if resolver[level].scalar_kind() != Some(Sk::Sint) { + return Err(ExpressionError::InvalidImageOtherIndexType(level)); + } + } + _ => { + return Err(ExpressionError::InvalidImageOtherIndex); + } + } + } + _ => return Err(ExpressionError::ExpectedImageType(ty)), + } + ShaderStages::all() + } + E::ImageQuery { image, query } => { + let ty = Self::global_var_ty(module, function, image)?; + match module.types[ty].inner { + Ti::Image { class, arrayed, .. } => { + let good = match query { + crate::ImageQuery::NumLayers => arrayed, + crate::ImageQuery::Size { level: None } => true, + crate::ImageQuery::Size { level: Some(_) } + | crate::ImageQuery::NumLevels => class.is_mipmapped(), + crate::ImageQuery::NumSamples => class.is_multisampled(), + }; + if !good { + return Err(ExpressionError::InvalidImageClass(class)); + } + } + _ => return Err(ExpressionError::ExpectedImageType(ty)), + } + ShaderStages::all() + } + E::Unary { op, expr } => { + use crate::UnaryOperator as Uo; + let inner = &resolver[expr]; + match (op, inner.scalar_kind()) { + (Uo::Negate, Some(Sk::Float | Sk::Sint)) + | (Uo::LogicalNot, Some(Sk::Bool)) + | (Uo::BitwiseNot, Some(Sk::Sint | Sk::Uint)) => {} + other => { + log::error!("Op {:?} kind {:?}", op, other); + return Err(ExpressionError::InvalidUnaryOperandType(op, expr)); + } + } + ShaderStages::all() + } + E::Binary { op, left, right } => { + use crate::BinaryOperator as Bo; + let left_inner = &resolver[left]; + let right_inner = &resolver[right]; + let good = match op { + Bo::Add | Bo::Subtract => match *left_inner { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => match scalar.kind { + Sk::Uint | Sk::Sint | Sk::Float => left_inner == right_inner, + Sk::Bool | Sk::AbstractInt | Sk::AbstractFloat => false, + }, + Ti::Matrix { .. } => left_inner == right_inner, + _ => false, + }, + Bo::Divide | Bo::Modulo => match *left_inner { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => match scalar.kind { + Sk::Uint | Sk::Sint | Sk::Float => left_inner == right_inner, + Sk::Bool | Sk::AbstractInt | Sk::AbstractFloat => false, + }, + _ => false, + }, + Bo::Multiply => { + let kind_allowed = match left_inner.scalar_kind() { + Some(Sk::Uint | Sk::Sint | Sk::Float) => true, + Some(Sk::Bool | Sk::AbstractInt | Sk::AbstractFloat) | None => false, + }; + let types_match = match (left_inner, right_inner) { + // Straight scalar and mixed scalar/vector. + (&Ti::Scalar(scalar1), &Ti::Scalar(scalar2)) + | ( + &Ti::Vector { + scalar: scalar1, .. + }, + &Ti::Scalar(scalar2), + ) + | ( + &Ti::Scalar(scalar1), + &Ti::Vector { + scalar: scalar2, .. + }, + ) => scalar1 == scalar2, + // Scalar/matrix. + ( + &Ti::Scalar(Sc { + kind: Sk::Float, .. + }), + &Ti::Matrix { .. }, + ) + | ( + &Ti::Matrix { .. }, + &Ti::Scalar(Sc { + kind: Sk::Float, .. + }), + ) => true, + // Vector/vector. + ( + &Ti::Vector { + size: size1, + scalar: scalar1, + }, + &Ti::Vector { + size: size2, + scalar: scalar2, + }, + ) => scalar1 == scalar2 && size1 == size2, + // Matrix * vector. + ( + &Ti::Matrix { columns, .. }, + &Ti::Vector { + size, + scalar: + Sc { + kind: Sk::Float, .. + }, + }, + ) => columns == size, + // Vector * matrix. + ( + &Ti::Vector { + size, + scalar: + Sc { + kind: Sk::Float, .. + }, + }, + &Ti::Matrix { rows, .. }, + ) => size == rows, + (&Ti::Matrix { columns, .. }, &Ti::Matrix { rows, .. }) => { + columns == rows + } + _ => false, + }; + let left_width = left_inner.scalar_width().unwrap_or(0); + let right_width = right_inner.scalar_width().unwrap_or(0); + kind_allowed && types_match && left_width == right_width + } + Bo::Equal | Bo::NotEqual => left_inner.is_sized() && left_inner == right_inner, + Bo::Less | Bo::LessEqual | Bo::Greater | Bo::GreaterEqual => { + match *left_inner { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => match scalar.kind { + Sk::Uint | Sk::Sint | Sk::Float => left_inner == right_inner, + Sk::Bool | Sk::AbstractInt | Sk::AbstractFloat => false, + }, + ref other => { + log::error!("Op {:?} left type {:?}", op, other); + false + } + } + } + Bo::LogicalAnd | Bo::LogicalOr => match *left_inner { + Ti::Scalar(Sc { kind: Sk::Bool, .. }) + | Ti::Vector { + scalar: Sc { kind: Sk::Bool, .. }, + .. + } => left_inner == right_inner, + ref other => { + log::error!("Op {:?} left type {:?}", op, other); + false + } + }, + Bo::And | Bo::InclusiveOr => match *left_inner { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => match scalar.kind { + Sk::Bool | Sk::Sint | Sk::Uint => left_inner == right_inner, + Sk::Float | Sk::AbstractInt | Sk::AbstractFloat => false, + }, + ref other => { + log::error!("Op {:?} left type {:?}", op, other); + false + } + }, + Bo::ExclusiveOr => match *left_inner { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => match scalar.kind { + Sk::Sint | Sk::Uint => left_inner == right_inner, + Sk::Bool | Sk::Float | Sk::AbstractInt | Sk::AbstractFloat => false, + }, + ref other => { + log::error!("Op {:?} left type {:?}", op, other); + false + } + }, + Bo::ShiftLeft | Bo::ShiftRight => { + let (base_size, base_scalar) = match *left_inner { + Ti::Scalar(scalar) => (Ok(None), scalar), + Ti::Vector { size, scalar } => (Ok(Some(size)), scalar), + ref other => { + log::error!("Op {:?} base type {:?}", op, other); + (Err(()), Sc::BOOL) + } + }; + let shift_size = match *right_inner { + Ti::Scalar(Sc { kind: Sk::Uint, .. }) => Ok(None), + Ti::Vector { + size, + scalar: Sc { kind: Sk::Uint, .. }, + } => Ok(Some(size)), + ref other => { + log::error!("Op {:?} shift type {:?}", op, other); + Err(()) + } + }; + match base_scalar.kind { + Sk::Sint | Sk::Uint => base_size.is_ok() && base_size == shift_size, + Sk::Float | Sk::AbstractInt | Sk::AbstractFloat | Sk::Bool => false, + } + } + }; + if !good { + log::error!( + "Left: {:?} of type {:?}", + function.expressions[left], + left_inner + ); + log::error!( + "Right: {:?} of type {:?}", + function.expressions[right], + right_inner + ); + return Err(ExpressionError::InvalidBinaryOperandTypes(op, left, right)); + } + ShaderStages::all() + } + E::Select { + condition, + accept, + reject, + } => { + let accept_inner = &resolver[accept]; + let reject_inner = &resolver[reject]; + let condition_good = match resolver[condition] { + Ti::Scalar(Sc { + kind: Sk::Bool, + width: _, + }) => { + // When `condition` is a single boolean, `accept` and + // `reject` can be vectors or scalars. + match *accept_inner { + Ti::Scalar { .. } | Ti::Vector { .. } => true, + _ => false, + } + } + Ti::Vector { + size, + scalar: + Sc { + kind: Sk::Bool, + width: _, + }, + } => match *accept_inner { + Ti::Vector { + size: other_size, .. + } => size == other_size, + _ => false, + }, + _ => false, + }; + if !condition_good || accept_inner != reject_inner { + return Err(ExpressionError::InvalidSelectTypes); + } + ShaderStages::all() + } + E::Derivative { expr, .. } => { + match resolver[expr] { + Ti::Scalar(Sc { + kind: Sk::Float, .. + }) + | Ti::Vector { + scalar: + Sc { + kind: Sk::Float, .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidDerivative), + } + ShaderStages::FRAGMENT + } + E::Relational { fun, argument } => { + use crate::RelationalFunction as Rf; + let argument_inner = &resolver[argument]; + match fun { + Rf::All | Rf::Any => match *argument_inner { + Ti::Vector { + scalar: Sc { kind: Sk::Bool, .. }, + .. + } => {} + ref other => { + log::error!("All/Any of type {:?}", other); + return Err(ExpressionError::InvalidBooleanVector(argument)); + } + }, + Rf::IsNan | Rf::IsInf => match *argument_inner { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } + if scalar.kind == Sk::Float => {} + ref other => { + log::error!("Float test of type {:?}", other); + return Err(ExpressionError::InvalidFloatArgument(argument)); + } + }, + } + ShaderStages::all() + } + E::Math { + fun, + arg, + arg1, + arg2, + arg3, + } => { + use crate::MathFunction as Mf; + + let resolve = |arg| &resolver[arg]; + let arg_ty = resolve(arg); + let arg1_ty = arg1.map(resolve); + let arg2_ty = arg2.map(resolve); + let arg3_ty = arg3.map(resolve); + match fun { + Mf::Abs => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + let good = match *arg_ty { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => { + scalar.kind != Sk::Bool + } + _ => false, + }; + if !good { + return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)); + } + } + Mf::Min | Mf::Max => { + let arg1_ty = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), None, None) => ty1, + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + let good = match *arg_ty { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => { + scalar.kind != Sk::Bool + } + _ => false, + }; + if !good { + return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)); + } + if arg1_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + } + Mf::Clamp => { + let (arg1_ty, arg2_ty) = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), Some(ty2), None) => (ty1, ty2), + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + let good = match *arg_ty { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => { + scalar.kind != Sk::Bool + } + _ => false, + }; + if !good { + return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)); + } + if arg1_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + if arg2_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg2.unwrap(), + )); + } + } + Mf::Saturate + | Mf::Cos + | Mf::Cosh + | Mf::Sin + | Mf::Sinh + | Mf::Tan + | Mf::Tanh + | Mf::Acos + | Mf::Asin + | Mf::Atan + | Mf::Asinh + | Mf::Acosh + | Mf::Atanh + | Mf::Radians + | Mf::Degrees + | Mf::Ceil + | Mf::Floor + | Mf::Round + | Mf::Fract + | Mf::Trunc + | Mf::Exp + | Mf::Exp2 + | Mf::Log + | Mf::Log2 + | Mf::Length + | Mf::Sqrt + | Mf::InverseSqrt => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + match *arg_ty { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } + if scalar.kind == Sk::Float => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } + Mf::Sign => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + match *arg_ty { + Ti::Scalar(Sc { + kind: Sk::Float | Sk::Sint, + .. + }) + | Ti::Vector { + scalar: + Sc { + kind: Sk::Float | Sk::Sint, + .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } + Mf::Atan2 | Mf::Pow | Mf::Distance | Mf::Step => { + let arg1_ty = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), None, None) => ty1, + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + match *arg_ty { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } + if scalar.kind == Sk::Float => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + if arg1_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + } + Mf::Modf | Mf::Frexp => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + if !matches!(*arg_ty, + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } + if scalar.kind == Sk::Float) + { + return Err(ExpressionError::InvalidArgumentType(fun, 1, arg)); + } + } + Mf::Ldexp => { + let arg1_ty = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), None, None) => ty1, + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + let size0 = match *arg_ty { + Ti::Scalar(Sc { + kind: Sk::Float, .. + }) => None, + Ti::Vector { + scalar: + Sc { + kind: Sk::Float, .. + }, + size, + } => Some(size), + _ => { + return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)); + } + }; + let good = match *arg1_ty { + Ti::Scalar(Sc { kind: Sk::Sint, .. }) if size0.is_none() => true, + Ti::Vector { + size, + scalar: Sc { kind: Sk::Sint, .. }, + } if Some(size) == size0 => true, + _ => false, + }; + if !good { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + } + Mf::Dot => { + let arg1_ty = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), None, None) => ty1, + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + match *arg_ty { + Ti::Vector { + scalar: + Sc { + kind: Sk::Float | Sk::Sint | Sk::Uint, + .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + if arg1_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + } + Mf::Outer | Mf::Cross | Mf::Reflect => { + let arg1_ty = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), None, None) => ty1, + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + match *arg_ty { + Ti::Vector { + scalar: + Sc { + kind: Sk::Float, .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + if arg1_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + } + Mf::Refract => { + let (arg1_ty, arg2_ty) = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), Some(ty2), None) => (ty1, ty2), + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + + match *arg_ty { + Ti::Vector { + scalar: + Sc { + kind: Sk::Float, .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + + if arg1_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + + match (arg_ty, arg2_ty) { + ( + &Ti::Vector { + scalar: + Sc { + width: vector_width, + .. + }, + .. + }, + &Ti::Scalar(Sc { + width: scalar_width, + kind: Sk::Float, + }), + ) if vector_width == scalar_width => {} + _ => { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg2.unwrap(), + )) + } + } + } + Mf::Normalize => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + match *arg_ty { + Ti::Vector { + scalar: + Sc { + kind: Sk::Float, .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } + Mf::FaceForward | Mf::Fma | Mf::SmoothStep => { + let (arg1_ty, arg2_ty) = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), Some(ty2), None) => (ty1, ty2), + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + match *arg_ty { + Ti::Scalar(Sc { + kind: Sk::Float, .. + }) + | Ti::Vector { + scalar: + Sc { + kind: Sk::Float, .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + if arg1_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + if arg2_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg2.unwrap(), + )); + } + } + Mf::Mix => { + let (arg1_ty, arg2_ty) = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), Some(ty2), None) => (ty1, ty2), + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + let arg_width = match *arg_ty { + Ti::Scalar(Sc { + kind: Sk::Float, + width, + }) + | Ti::Vector { + scalar: + Sc { + kind: Sk::Float, + width, + }, + .. + } => width, + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + }; + if arg1_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + // the last argument can always be a scalar + match *arg2_ty { + Ti::Scalar(Sc { + kind: Sk::Float, + width, + }) if width == arg_width => {} + _ if arg2_ty == arg_ty => {} + _ => { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg2.unwrap(), + )); + } + } + } + Mf::Inverse | Mf::Determinant => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + let good = match *arg_ty { + Ti::Matrix { columns, rows, .. } => columns == rows, + _ => false, + }; + if !good { + return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)); + } + } + Mf::Transpose => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + match *arg_ty { + Ti::Matrix { .. } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } + Mf::CountTrailingZeros + | Mf::CountLeadingZeros + | Mf::CountOneBits + | Mf::ReverseBits + | Mf::FindLsb + | Mf::FindMsb => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + match *arg_ty { + Ti::Scalar(Sc { + kind: Sk::Sint | Sk::Uint, + .. + }) + | Ti::Vector { + scalar: + Sc { + kind: Sk::Sint | Sk::Uint, + .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } + Mf::InsertBits => { + let (arg1_ty, arg2_ty, arg3_ty) = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), Some(ty2), Some(ty3)) => (ty1, ty2, ty3), + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + match *arg_ty { + Ti::Scalar(Sc { + kind: Sk::Sint | Sk::Uint, + .. + }) + | Ti::Vector { + scalar: + Sc { + kind: Sk::Sint | Sk::Uint, + .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + if arg1_ty != arg_ty { + return Err(ExpressionError::InvalidArgumentType( + fun, + 1, + arg1.unwrap(), + )); + } + match *arg2_ty { + Ti::Scalar(Sc { kind: Sk::Uint, .. }) => {} + _ => { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg2.unwrap(), + )) + } + } + match *arg3_ty { + Ti::Scalar(Sc { kind: Sk::Uint, .. }) => {} + _ => { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg3.unwrap(), + )) + } + } + } + Mf::ExtractBits => { + let (arg1_ty, arg2_ty) = match (arg1_ty, arg2_ty, arg3_ty) { + (Some(ty1), Some(ty2), None) => (ty1, ty2), + _ => return Err(ExpressionError::WrongArgumentCount(fun)), + }; + match *arg_ty { + Ti::Scalar(Sc { + kind: Sk::Sint | Sk::Uint, + .. + }) + | Ti::Vector { + scalar: + Sc { + kind: Sk::Sint | Sk::Uint, + .. + }, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + match *arg1_ty { + Ti::Scalar(Sc { kind: Sk::Uint, .. }) => {} + _ => { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg1.unwrap(), + )) + } + } + match *arg2_ty { + Ti::Scalar(Sc { kind: Sk::Uint, .. }) => {} + _ => { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg2.unwrap(), + )) + } + } + } + Mf::Pack2x16unorm | Mf::Pack2x16snorm | Mf::Pack2x16float => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + match *arg_ty { + Ti::Vector { + size: crate::VectorSize::Bi, + scalar: + Sc { + kind: Sk::Float, .. + }, + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } + Mf::Pack4x8snorm | Mf::Pack4x8unorm => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + match *arg_ty { + Ti::Vector { + size: crate::VectorSize::Quad, + scalar: + Sc { + kind: Sk::Float, .. + }, + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } + Mf::Unpack2x16float + | Mf::Unpack2x16snorm + | Mf::Unpack2x16unorm + | Mf::Unpack4x8snorm + | Mf::Unpack4x8unorm => { + if arg1_ty.is_some() || arg2_ty.is_some() || arg3_ty.is_some() { + return Err(ExpressionError::WrongArgumentCount(fun)); + } + match *arg_ty { + Ti::Scalar(Sc { kind: Sk::Uint, .. }) => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } + } + ShaderStages::all() + } + E::As { + expr, + kind, + convert, + } => { + let mut base_scalar = match resolver[expr] { + crate::TypeInner::Scalar(scalar) | crate::TypeInner::Vector { scalar, .. } => { + scalar + } + crate::TypeInner::Matrix { scalar, .. } => scalar, + _ => return Err(ExpressionError::InvalidCastArgument), + }; + base_scalar.kind = kind; + if let Some(width) = convert { + base_scalar.width = width; + } + if self.check_width(base_scalar).is_err() { + return Err(ExpressionError::InvalidCastArgument); + } + ShaderStages::all() + } + E::CallResult(function) => mod_info.functions[function.index()].available_stages, + E::AtomicResult { ty, comparison } => { + let scalar_predicate = |ty: &crate::TypeInner| match ty { + &crate::TypeInner::Scalar( + scalar @ Sc { + kind: crate::ScalarKind::Uint | crate::ScalarKind::Sint, + .. + }, + ) => self.check_width(scalar).is_ok(), + _ => false, + }; + let good = match &module.types[ty].inner { + ty if !comparison => scalar_predicate(ty), + &crate::TypeInner::Struct { ref members, .. } if comparison => { + validate_atomic_compare_exchange_struct( + &module.types, + members, + scalar_predicate, + ) + } + _ => false, + }; + if !good { + return Err(ExpressionError::InvalidAtomicResultType(ty)); + } + ShaderStages::all() + } + E::WorkGroupUniformLoadResult { ty } => { + if self.types[ty.index()] + .flags + // Sized | Constructible is exactly the types currently supported by + // WorkGroupUniformLoad + .contains(TypeFlags::SIZED | TypeFlags::CONSTRUCTIBLE) + { + ShaderStages::COMPUTE + } else { + return Err(ExpressionError::InvalidWorkGroupUniformLoadResultType(ty)); + } + } + E::ArrayLength(expr) => match resolver[expr] { + Ti::Pointer { base, .. } => { + let base_ty = &resolver.types[base]; + if let Ti::Array { + size: crate::ArraySize::Dynamic, + .. + } = base_ty.inner + { + ShaderStages::all() + } else { + return Err(ExpressionError::InvalidArrayType(expr)); + } + } + ref other => { + log::error!("Array length of {:?}", other); + return Err(ExpressionError::InvalidArrayType(expr)); + } + }, + E::RayQueryProceedResult => ShaderStages::all(), + E::RayQueryGetIntersection { + query, + committed: _, + } => match resolver[query] { + Ti::Pointer { + base, + space: crate::AddressSpace::Function, + } => match resolver.types[base].inner { + Ti::RayQuery => ShaderStages::all(), + ref other => { + log::error!("Intersection result of a pointer to {:?}", other); + return Err(ExpressionError::InvalidRayQueryType(query)); + } + }, + ref other => { + log::error!("Intersection result of {:?}", other); + return Err(ExpressionError::InvalidRayQueryType(query)); + } + }, + }; + Ok(stages) + } + + fn global_var_ty( + module: &crate::Module, + function: &crate::Function, + expr: Handle, + ) -> Result, ExpressionError> { + use crate::Expression as Ex; + + match function.expressions[expr] { + Ex::GlobalVariable(var_handle) => Ok(module.global_variables[var_handle].ty), + Ex::FunctionArgument(i) => Ok(function.arguments[i as usize].ty), + Ex::Access { base, .. } | Ex::AccessIndex { base, .. } => { + match function.expressions[base] { + Ex::GlobalVariable(var_handle) => { + let array_ty = module.global_variables[var_handle].ty; + + match module.types[array_ty].inner { + crate::TypeInner::BindingArray { base, .. } => Ok(base), + _ => Err(ExpressionError::ExpectedBindingArrayType(array_ty)), + } + } + _ => Err(ExpressionError::ExpectedGlobalVariable), + } + } + _ => Err(ExpressionError::ExpectedGlobalVariable), + } + } + + pub fn validate_literal(&self, literal: crate::Literal) -> Result<(), LiteralError> { + self.check_width(literal.scalar())?; + check_literal_value(literal)?; + + Ok(()) + } +} + +pub fn check_literal_value(literal: crate::Literal) -> Result<(), LiteralError> { + let is_nan = match literal { + crate::Literal::F64(v) => v.is_nan(), + crate::Literal::F32(v) => v.is_nan(), + _ => false, + }; + if is_nan { + return Err(LiteralError::NaN); + } + + let is_infinite = match literal { + crate::Literal::F64(v) => v.is_infinite(), + crate::Literal::F32(v) => v.is_infinite(), + _ => false, + }; + if is_infinite { + return Err(LiteralError::Infinity); + } + + Ok(()) +} + +#[cfg(all(test, feature = "validate"))] +/// Validate a module containing the given expression, expecting an error. +fn validate_with_expression( + expr: crate::Expression, + caps: super::Capabilities, +) -> Result> { + use crate::span::Span; + + let mut function = crate::Function::default(); + function.expressions.append(expr, Span::default()); + function.body.push( + crate::Statement::Emit(function.expressions.range_from(0)), + Span::default(), + ); + + let mut module = crate::Module::default(); + module.functions.append(function, Span::default()); + + let mut validator = super::Validator::new(super::ValidationFlags::EXPRESSIONS, caps); + + validator.validate(&module) +} + +#[cfg(all(test, feature = "validate"))] +/// Validate a module containing the given constant expression, expecting an error. +fn validate_with_const_expression( + expr: crate::Expression, + caps: super::Capabilities, +) -> Result> { + use crate::span::Span; + + let mut module = crate::Module::default(); + module.const_expressions.append(expr, Span::default()); + + let mut validator = super::Validator::new(super::ValidationFlags::CONSTANTS, caps); + + validator.validate(&module) +} + +/// Using F64 in a function's expression arena is forbidden. +#[cfg(feature = "validate")] +#[test] +fn f64_runtime_literals() { + let result = validate_with_expression( + crate::Expression::Literal(crate::Literal::F64(0.57721_56649)), + super::Capabilities::default(), + ); + let error = result.unwrap_err().into_inner(); + assert!(matches!( + error, + crate::valid::ValidationError::Function { + source: super::FunctionError::Expression { + source: super::ExpressionError::Literal(super::LiteralError::Width( + super::r#type::WidthError::MissingCapability { + name: "f64", + flag: "FLOAT64", + } + ),), + .. + }, + .. + } + )); + + let result = validate_with_expression( + crate::Expression::Literal(crate::Literal::F64(0.57721_56649)), + super::Capabilities::default() | super::Capabilities::FLOAT64, + ); + assert!(result.is_ok()); +} + +/// Using F64 in a module's constant expression arena is forbidden. +#[cfg(feature = "validate")] +#[test] +fn f64_const_literals() { + let result = validate_with_const_expression( + crate::Expression::Literal(crate::Literal::F64(0.57721_56649)), + super::Capabilities::default(), + ); + let error = result.unwrap_err().into_inner(); + assert!(matches!( + error, + crate::valid::ValidationError::ConstExpression { + source: super::ConstExpressionError::Literal(super::LiteralError::Width( + super::r#type::WidthError::MissingCapability { + name: "f64", + flag: "FLOAT64", + } + )), + .. + } + )); + + let result = validate_with_const_expression( + crate::Expression::Literal(crate::Literal::F64(0.57721_56649)), + super::Capabilities::default() | super::Capabilities::FLOAT64, + ); + assert!(result.is_ok()); +} + +/// Using I64 in a function's expression arena is forbidden. +#[cfg(feature = "validate")] +#[test] +fn i64_runtime_literals() { + let result = validate_with_expression( + crate::Expression::Literal(crate::Literal::I64(1729)), + // There is no capability that enables this. + super::Capabilities::all(), + ); + let error = result.unwrap_err().into_inner(); + assert!(matches!( + error, + crate::valid::ValidationError::Function { + source: super::FunctionError::Expression { + source: super::ExpressionError::Literal(super::LiteralError::Width( + super::r#type::WidthError::Unsupported64Bit + ),), + .. + }, + .. + } + )); +} + +/// Using I64 in a module's constant expression arena is forbidden. +#[cfg(feature = "validate")] +#[test] +fn i64_const_literals() { + let result = validate_with_const_expression( + crate::Expression::Literal(crate::Literal::I64(1729)), + // There is no capability that enables this. + super::Capabilities::all(), + ); + let error = result.unwrap_err().into_inner(); + assert!(matches!( + error, + crate::valid::ValidationError::ConstExpression { + source: super::ConstExpressionError::Literal(super::LiteralError::Width( + super::r#type::WidthError::Unsupported64Bit, + ),), + .. + } + )); +} diff --git a/naga/src/valid/function.rs b/naga/src/valid/function.rs new file mode 100644 index 0000000000..3b12e59067 --- /dev/null +++ b/naga/src/valid/function.rs @@ -0,0 +1,1056 @@ +use crate::arena::Handle; +use crate::arena::{Arena, UniqueArena}; + +use super::validate_atomic_compare_exchange_struct; + +use super::{ + analyzer::{UniformityDisruptor, UniformityRequirements}, + ExpressionError, FunctionInfo, ModuleInfo, +}; +use crate::span::WithSpan; +use crate::span::{AddSpan as _, MapErrWithSpan as _}; + +use bit_set::BitSet; + +#[derive(Clone, Debug, thiserror::Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum CallError { + #[error("Argument {index} expression is invalid")] + Argument { + index: usize, + source: ExpressionError, + }, + #[error("Result expression {0:?} has already been introduced earlier")] + ResultAlreadyInScope(Handle), + #[error("Result value is invalid")] + ResultValue(#[source] ExpressionError), + #[error("Requires {required} arguments, but {seen} are provided")] + ArgumentCount { required: usize, seen: usize }, + #[error("Argument {index} value {seen_expression:?} doesn't match the type {required:?}")] + ArgumentType { + index: usize, + required: Handle, + seen_expression: Handle, + }, + #[error("The emitted expression doesn't match the call")] + ExpressionMismatch(Option>), +} + +#[derive(Clone, Debug, thiserror::Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum AtomicError { + #[error("Pointer {0:?} to atomic is invalid.")] + InvalidPointer(Handle), + #[error("Operand {0:?} has invalid type.")] + InvalidOperand(Handle), + #[error("Result type for {0:?} doesn't match the statement")] + ResultTypeMismatch(Handle), +} + +#[derive(Clone, Debug, thiserror::Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum LocalVariableError { + #[error("Local variable has a type {0:?} that can't be stored in a local variable.")] + InvalidType(Handle), + #[error("Initializer doesn't match the variable type")] + InitializerType, + #[error("Initializer is not const")] + NonConstInitializer, +} + +#[derive(Clone, Debug, thiserror::Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum FunctionError { + #[error("Expression {handle:?} is invalid")] + Expression { + handle: Handle, + source: ExpressionError, + }, + #[error("Expression {0:?} can't be introduced - it's already in scope")] + ExpressionAlreadyInScope(Handle), + #[error("Local variable {handle:?} '{name}' is invalid")] + LocalVariable { + handle: Handle, + name: String, + source: LocalVariableError, + }, + #[error("Argument '{name}' at index {index} has a type that can't be passed into functions.")] + InvalidArgumentType { index: usize, name: String }, + #[error("The function's given return type cannot be returned from functions")] + NonConstructibleReturnType, + #[error("Argument '{name}' at index {index} is a pointer of space {space:?}, which can't be passed into functions.")] + InvalidArgumentPointerSpace { + index: usize, + name: String, + space: crate::AddressSpace, + }, + #[error("There are instructions after `return`/`break`/`continue`")] + InstructionsAfterReturn, + #[error("The `break` is used outside of a `loop` or `switch` context")] + BreakOutsideOfLoopOrSwitch, + #[error("The `continue` is used outside of a `loop` context")] + ContinueOutsideOfLoop, + #[error("The `return` is called within a `continuing` block")] + InvalidReturnSpot, + #[error("The `return` value {0:?} does not match the function return value")] + InvalidReturnType(Option>), + #[error("The `if` condition {0:?} is not a boolean scalar")] + InvalidIfType(Handle), + #[error("The `switch` value {0:?} is not an integer scalar")] + InvalidSwitchType(Handle), + #[error("Multiple `switch` cases for {0:?} are present")] + ConflictingSwitchCase(crate::SwitchValue), + #[error("The `switch` contains cases with conflicting types")] + ConflictingCaseType, + #[error("The `switch` is missing a `default` case")] + MissingDefaultCase, + #[error("Multiple `default` cases are present")] + MultipleDefaultCases, + #[error("The last `switch` case contains a `falltrough`")] + LastCaseFallTrough, + #[error("The pointer {0:?} doesn't relate to a valid destination for a store")] + InvalidStorePointer(Handle), + #[error("The value {0:?} can not be stored")] + InvalidStoreValue(Handle), + #[error("The type of {value:?} doesn't match the type stored in {pointer:?}")] + InvalidStoreTypes { + pointer: Handle, + value: Handle, + }, + #[error("Image store parameters are invalid")] + InvalidImageStore(#[source] ExpressionError), + #[error("Call to {function:?} is invalid")] + InvalidCall { + function: Handle, + #[source] + error: CallError, + }, + #[error("Atomic operation is invalid")] + InvalidAtomic(#[from] AtomicError), + #[error("Ray Query {0:?} is not a local variable")] + InvalidRayQueryExpression(Handle), + #[error("Acceleration structure {0:?} is not a matching expression")] + InvalidAccelerationStructure(Handle), + #[error("Ray descriptor {0:?} is not a matching expression")] + InvalidRayDescriptor(Handle), + #[error("Ray Query {0:?} does not have a matching type")] + InvalidRayQueryType(Handle), + #[error( + "Required uniformity of control flow for {0:?} in {1:?} is not fulfilled because of {2:?}" + )] + NonUniformControlFlow( + UniformityRequirements, + Handle, + UniformityDisruptor, + ), + #[error("Functions that are not entry points cannot have `@location` or `@builtin` attributes on their arguments: \"{name}\" has attributes")] + PipelineInputRegularFunction { name: String }, + #[error("Functions that are not entry points cannot have `@location` or `@builtin` attributes on their return value types")] + PipelineOutputRegularFunction, + #[error("Required uniformity for WorkGroupUniformLoad is not fulfilled because of {0:?}")] + // The actual load statement will be "pointed to" by the span + NonUniformWorkgroupUniformLoad(UniformityDisruptor), + // This is only possible with a misbehaving frontend + #[error("The expression {0:?} for a WorkGroupUniformLoad isn't a WorkgroupUniformLoadResult")] + WorkgroupUniformLoadExpressionMismatch(Handle), + #[error("The expression {0:?} is not valid as a WorkGroupUniformLoad argument. It should be a Pointer in Workgroup address space")] + WorkgroupUniformLoadInvalidPointer(Handle), +} + +bitflags::bitflags! { + #[repr(transparent)] + #[derive(Clone, Copy)] + struct ControlFlowAbility: u8 { + /// The control can return out of this block. + const RETURN = 0x1; + /// The control can break. + const BREAK = 0x2; + /// The control can continue. + const CONTINUE = 0x4; + } +} + +struct BlockInfo { + stages: super::ShaderStages, + finished: bool, +} + +struct BlockContext<'a> { + abilities: ControlFlowAbility, + info: &'a FunctionInfo, + expressions: &'a Arena, + types: &'a UniqueArena, + local_vars: &'a Arena, + global_vars: &'a Arena, + functions: &'a Arena, + special_types: &'a crate::SpecialTypes, + prev_infos: &'a [FunctionInfo], + return_type: Option>, +} + +impl<'a> BlockContext<'a> { + fn new( + fun: &'a crate::Function, + module: &'a crate::Module, + info: &'a FunctionInfo, + prev_infos: &'a [FunctionInfo], + ) -> Self { + Self { + abilities: ControlFlowAbility::RETURN, + info, + expressions: &fun.expressions, + types: &module.types, + local_vars: &fun.local_variables, + global_vars: &module.global_variables, + functions: &module.functions, + special_types: &module.special_types, + prev_infos, + return_type: fun.result.as_ref().map(|fr| fr.ty), + } + } + + const fn with_abilities(&self, abilities: ControlFlowAbility) -> Self { + BlockContext { abilities, ..*self } + } + + fn get_expression(&self, handle: Handle) -> &'a crate::Expression { + &self.expressions[handle] + } + + fn resolve_type_impl( + &self, + handle: Handle, + valid_expressions: &BitSet, + ) -> Result<&crate::TypeInner, WithSpan> { + if handle.index() >= self.expressions.len() { + Err(ExpressionError::DoesntExist.with_span()) + } else if !valid_expressions.contains(handle.index()) { + Err(ExpressionError::NotInScope.with_span_handle(handle, self.expressions)) + } else { + Ok(self.info[handle].ty.inner_with(self.types)) + } + } + + fn resolve_type( + &self, + handle: Handle, + valid_expressions: &BitSet, + ) -> Result<&crate::TypeInner, WithSpan> { + self.resolve_type_impl(handle, valid_expressions) + .map_err_inner(|source| FunctionError::Expression { handle, source }.with_span()) + } + + fn resolve_pointer_type( + &self, + handle: Handle, + ) -> Result<&crate::TypeInner, FunctionError> { + if handle.index() >= self.expressions.len() { + Err(FunctionError::Expression { + handle, + source: ExpressionError::DoesntExist, + }) + } else { + Ok(self.info[handle].ty.inner_with(self.types)) + } + } +} + +impl super::Validator { + fn validate_call( + &mut self, + function: Handle, + arguments: &[Handle], + result: Option>, + context: &BlockContext, + ) -> Result> { + let fun = &context.functions[function]; + if fun.arguments.len() != arguments.len() { + return Err(CallError::ArgumentCount { + required: fun.arguments.len(), + seen: arguments.len(), + } + .with_span()); + } + for (index, (arg, &expr)) in fun.arguments.iter().zip(arguments).enumerate() { + let ty = context + .resolve_type_impl(expr, &self.valid_expression_set) + .map_err_inner(|source| { + CallError::Argument { index, source } + .with_span_handle(expr, context.expressions) + })?; + let arg_inner = &context.types[arg.ty].inner; + if !ty.equivalent(arg_inner, context.types) { + return Err(CallError::ArgumentType { + index, + required: arg.ty, + seen_expression: expr, + } + .with_span_handle(expr, context.expressions)); + } + } + + if let Some(expr) = result { + if self.valid_expression_set.insert(expr.index()) { + self.valid_expression_list.push(expr); + } else { + return Err(CallError::ResultAlreadyInScope(expr) + .with_span_handle(expr, context.expressions)); + } + match context.expressions[expr] { + crate::Expression::CallResult(callee) + if fun.result.is_some() && callee == function => {} + _ => { + return Err(CallError::ExpressionMismatch(result) + .with_span_handle(expr, context.expressions)) + } + } + } else if fun.result.is_some() { + return Err(CallError::ExpressionMismatch(result).with_span()); + } + + let callee_info = &context.prev_infos[function.index()]; + Ok(callee_info.available_stages) + } + + fn emit_expression( + &mut self, + handle: Handle, + context: &BlockContext, + ) -> Result<(), WithSpan> { + if self.valid_expression_set.insert(handle.index()) { + self.valid_expression_list.push(handle); + Ok(()) + } else { + Err(FunctionError::ExpressionAlreadyInScope(handle) + .with_span_handle(handle, context.expressions)) + } + } + + fn validate_atomic( + &mut self, + pointer: Handle, + fun: &crate::AtomicFunction, + value: Handle, + result: Handle, + context: &BlockContext, + ) -> Result<(), WithSpan> { + let pointer_inner = context.resolve_type(pointer, &self.valid_expression_set)?; + let ptr_scalar = match *pointer_inner { + crate::TypeInner::Pointer { base, .. } => match context.types[base].inner { + crate::TypeInner::Atomic(scalar) => scalar, + ref other => { + log::error!("Atomic pointer to type {:?}", other); + return Err(AtomicError::InvalidPointer(pointer) + .with_span_handle(pointer, context.expressions) + .into_other()); + } + }, + ref other => { + log::error!("Atomic on type {:?}", other); + return Err(AtomicError::InvalidPointer(pointer) + .with_span_handle(pointer, context.expressions) + .into_other()); + } + }; + + let value_inner = context.resolve_type(value, &self.valid_expression_set)?; + match *value_inner { + crate::TypeInner::Scalar(scalar) if scalar == ptr_scalar => {} + ref other => { + log::error!("Atomic operand type {:?}", other); + return Err(AtomicError::InvalidOperand(value) + .with_span_handle(value, context.expressions) + .into_other()); + } + } + + if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun { + if context.resolve_type(cmp, &self.valid_expression_set)? != value_inner { + log::error!("Atomic exchange comparison has a different type from the value"); + return Err(AtomicError::InvalidOperand(cmp) + .with_span_handle(cmp, context.expressions) + .into_other()); + } + } + + self.emit_expression(result, context)?; + match context.expressions[result] { + crate::Expression::AtomicResult { ty, comparison } + if { + let scalar_predicate = + |ty: &crate::TypeInner| *ty == crate::TypeInner::Scalar(ptr_scalar); + match &context.types[ty].inner { + ty if !comparison => scalar_predicate(ty), + &crate::TypeInner::Struct { ref members, .. } if comparison => { + validate_atomic_compare_exchange_struct( + context.types, + members, + scalar_predicate, + ) + } + _ => false, + } + } => {} + _ => { + return Err(AtomicError::ResultTypeMismatch(result) + .with_span_handle(result, context.expressions) + .into_other()) + } + } + Ok(()) + } + + fn validate_block_impl( + &mut self, + statements: &crate::Block, + context: &BlockContext, + ) -> Result> { + use crate::{AddressSpace, Statement as S, TypeInner as Ti}; + let mut finished = false; + let mut stages = super::ShaderStages::all(); + for (statement, &span) in statements.span_iter() { + if finished { + return Err(FunctionError::InstructionsAfterReturn + .with_span_static(span, "instructions after return")); + } + match *statement { + S::Emit(ref range) => { + for handle in range.clone() { + self.emit_expression(handle, context)?; + } + } + S::Block(ref block) => { + let info = self.validate_block(block, context)?; + stages &= info.stages; + finished = info.finished; + } + S::If { + condition, + ref accept, + ref reject, + } => { + match *context.resolve_type(condition, &self.valid_expression_set)? { + Ti::Scalar(crate::Scalar { + kind: crate::ScalarKind::Bool, + width: _, + }) => {} + _ => { + return Err(FunctionError::InvalidIfType(condition) + .with_span_handle(condition, context.expressions)) + } + } + stages &= self.validate_block(accept, context)?.stages; + stages &= self.validate_block(reject, context)?.stages; + } + S::Switch { + selector, + ref cases, + } => { + let uint = match context + .resolve_type(selector, &self.valid_expression_set)? + .scalar_kind() + { + Some(crate::ScalarKind::Uint) => true, + Some(crate::ScalarKind::Sint) => false, + _ => { + return Err(FunctionError::InvalidSwitchType(selector) + .with_span_handle(selector, context.expressions)) + } + }; + self.switch_values.clear(); + for case in cases { + match case.value { + crate::SwitchValue::I32(_) if !uint => {} + crate::SwitchValue::U32(_) if uint => {} + crate::SwitchValue::Default => {} + _ => { + return Err(FunctionError::ConflictingCaseType.with_span_static( + case.body + .span_iter() + .next() + .map_or(Default::default(), |(_, s)| *s), + "conflicting switch arm here", + )); + } + }; + if !self.switch_values.insert(case.value) { + return Err(match case.value { + crate::SwitchValue::Default => FunctionError::MultipleDefaultCases + .with_span_static( + case.body + .span_iter() + .next() + .map_or(Default::default(), |(_, s)| *s), + "duplicated switch arm here", + ), + _ => FunctionError::ConflictingSwitchCase(case.value) + .with_span_static( + case.body + .span_iter() + .next() + .map_or(Default::default(), |(_, s)| *s), + "conflicting switch arm here", + ), + }); + } + } + if !self.switch_values.contains(&crate::SwitchValue::Default) { + return Err(FunctionError::MissingDefaultCase + .with_span_static(span, "missing default case")); + } + if let Some(case) = cases.last() { + if case.fall_through { + return Err(FunctionError::LastCaseFallTrough.with_span_static( + case.body + .span_iter() + .next() + .map_or(Default::default(), |(_, s)| *s), + "bad switch arm here", + )); + } + } + let pass_through_abilities = context.abilities + & (ControlFlowAbility::RETURN | ControlFlowAbility::CONTINUE); + let sub_context = + context.with_abilities(pass_through_abilities | ControlFlowAbility::BREAK); + for case in cases { + stages &= self.validate_block(&case.body, &sub_context)?.stages; + } + } + S::Loop { + ref body, + ref continuing, + break_if, + } => { + // special handling for block scoping is needed here, + // because the continuing{} block inherits the scope + let base_expression_count = self.valid_expression_list.len(); + let pass_through_abilities = context.abilities & ControlFlowAbility::RETURN; + stages &= self + .validate_block_impl( + body, + &context.with_abilities( + pass_through_abilities + | ControlFlowAbility::BREAK + | ControlFlowAbility::CONTINUE, + ), + )? + .stages; + stages &= self + .validate_block_impl( + continuing, + &context.with_abilities(ControlFlowAbility::empty()), + )? + .stages; + + if let Some(condition) = break_if { + match *context.resolve_type(condition, &self.valid_expression_set)? { + Ti::Scalar(crate::Scalar { + kind: crate::ScalarKind::Bool, + width: _, + }) => {} + _ => { + return Err(FunctionError::InvalidIfType(condition) + .with_span_handle(condition, context.expressions)) + } + } + } + + for handle in self.valid_expression_list.drain(base_expression_count..) { + self.valid_expression_set.remove(handle.index()); + } + } + S::Break => { + if !context.abilities.contains(ControlFlowAbility::BREAK) { + return Err(FunctionError::BreakOutsideOfLoopOrSwitch + .with_span_static(span, "invalid break")); + } + finished = true; + } + S::Continue => { + if !context.abilities.contains(ControlFlowAbility::CONTINUE) { + return Err(FunctionError::ContinueOutsideOfLoop + .with_span_static(span, "invalid continue")); + } + finished = true; + } + S::Return { value } => { + if !context.abilities.contains(ControlFlowAbility::RETURN) { + return Err(FunctionError::InvalidReturnSpot + .with_span_static(span, "invalid return")); + } + let value_ty = value + .map(|expr| context.resolve_type(expr, &self.valid_expression_set)) + .transpose()?; + let expected_ty = context.return_type.map(|ty| &context.types[ty].inner); + // We can't return pointers, but it seems best not to embed that + // assumption here, so use `TypeInner::equivalent` for comparison. + let okay = match (value_ty, expected_ty) { + (None, None) => true, + (Some(value_inner), Some(expected_inner)) => { + value_inner.equivalent(expected_inner, context.types) + } + (_, _) => false, + }; + + if !okay { + log::error!( + "Returning {:?} where {:?} is expected", + value_ty, + expected_ty + ); + if let Some(handle) = value { + return Err(FunctionError::InvalidReturnType(value) + .with_span_handle(handle, context.expressions)); + } else { + return Err(FunctionError::InvalidReturnType(value) + .with_span_static(span, "invalid return")); + } + } + finished = true; + } + S::Kill => { + stages &= super::ShaderStages::FRAGMENT; + finished = true; + } + S::Barrier(_) => { + stages &= super::ShaderStages::COMPUTE; + } + S::Store { pointer, value } => { + let mut current = pointer; + loop { + let _ = context + .resolve_pointer_type(current) + .map_err(|e| e.with_span())?; + match context.expressions[current] { + crate::Expression::Access { base, .. } + | crate::Expression::AccessIndex { base, .. } => current = base, + crate::Expression::LocalVariable(_) + | crate::Expression::GlobalVariable(_) + | crate::Expression::FunctionArgument(_) => break, + _ => { + return Err(FunctionError::InvalidStorePointer(current) + .with_span_handle(pointer, context.expressions)) + } + } + } + + let value_ty = context.resolve_type(value, &self.valid_expression_set)?; + match *value_ty { + Ti::Image { .. } | Ti::Sampler { .. } => { + return Err(FunctionError::InvalidStoreValue(value) + .with_span_handle(value, context.expressions)); + } + _ => {} + } + + let pointer_ty = context + .resolve_pointer_type(pointer) + .map_err(|e| e.with_span())?; + + let good = match *pointer_ty { + Ti::Pointer { base, space: _ } => match context.types[base].inner { + Ti::Atomic(scalar) => *value_ty == Ti::Scalar(scalar), + ref other => value_ty == other, + }, + Ti::ValuePointer { + size: Some(size), + scalar, + space: _, + } => *value_ty == Ti::Vector { size, scalar }, + Ti::ValuePointer { + size: None, + scalar, + space: _, + } => *value_ty == Ti::Scalar(scalar), + _ => false, + }; + if !good { + return Err(FunctionError::InvalidStoreTypes { pointer, value } + .with_span() + .with_handle(pointer, context.expressions) + .with_handle(value, context.expressions)); + } + + if let Some(space) = pointer_ty.pointer_space() { + if !space.access().contains(crate::StorageAccess::STORE) { + return Err(FunctionError::InvalidStorePointer(pointer) + .with_span_static( + context.expressions.get_span(pointer), + "writing to this location is not permitted", + )); + } + } + } + S::ImageStore { + image, + coordinate, + array_index, + value, + } => { + //Note: this code uses a lot of `FunctionError::InvalidImageStore`, + // and could probably be refactored. + let var = match *context.get_expression(image) { + crate::Expression::GlobalVariable(var_handle) => { + &context.global_vars[var_handle] + } + // We're looking at a binding index situation, so punch through the index and look at the global behind it. + crate::Expression::Access { base, .. } + | crate::Expression::AccessIndex { base, .. } => { + match *context.get_expression(base) { + crate::Expression::GlobalVariable(var_handle) => { + &context.global_vars[var_handle] + } + _ => { + return Err(FunctionError::InvalidImageStore( + ExpressionError::ExpectedGlobalVariable, + ) + .with_span_handle(image, context.expressions)) + } + } + } + _ => { + return Err(FunctionError::InvalidImageStore( + ExpressionError::ExpectedGlobalVariable, + ) + .with_span_handle(image, context.expressions)) + } + }; + + // Punch through a binding array to get the underlying type + let global_ty = match context.types[var.ty].inner { + Ti::BindingArray { base, .. } => &context.types[base].inner, + ref inner => inner, + }; + + let value_ty = match *global_ty { + Ti::Image { + class, + arrayed, + dim, + } => { + match context + .resolve_type(coordinate, &self.valid_expression_set)? + .image_storage_coordinates() + { + Some(coord_dim) if coord_dim == dim => {} + _ => { + return Err(FunctionError::InvalidImageStore( + ExpressionError::InvalidImageCoordinateType( + dim, coordinate, + ), + ) + .with_span_handle(coordinate, context.expressions)); + } + }; + if arrayed != array_index.is_some() { + return Err(FunctionError::InvalidImageStore( + ExpressionError::InvalidImageArrayIndex, + ) + .with_span_handle(coordinate, context.expressions)); + } + if let Some(expr) = array_index { + match *context.resolve_type(expr, &self.valid_expression_set)? { + Ti::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + width: _, + }) => {} + _ => { + return Err(FunctionError::InvalidImageStore( + ExpressionError::InvalidImageArrayIndexType(expr), + ) + .with_span_handle(expr, context.expressions)); + } + } + } + match class { + crate::ImageClass::Storage { format, .. } => { + crate::TypeInner::Vector { + size: crate::VectorSize::Quad, + scalar: crate::Scalar { + kind: format.into(), + width: 4, + }, + } + } + _ => { + return Err(FunctionError::InvalidImageStore( + ExpressionError::InvalidImageClass(class), + ) + .with_span_handle(image, context.expressions)); + } + } + } + _ => { + return Err(FunctionError::InvalidImageStore( + ExpressionError::ExpectedImageType(var.ty), + ) + .with_span() + .with_handle(var.ty, context.types) + .with_handle(image, context.expressions)) + } + }; + + if *context.resolve_type(value, &self.valid_expression_set)? != value_ty { + return Err(FunctionError::InvalidStoreValue(value) + .with_span_handle(value, context.expressions)); + } + } + S::Call { + function, + ref arguments, + result, + } => match self.validate_call(function, arguments, result, context) { + Ok(callee_stages) => stages &= callee_stages, + Err(error) => { + return Err(error.and_then(|error| { + FunctionError::InvalidCall { function, error } + .with_span_static(span, "invalid function call") + })) + } + }, + S::Atomic { + pointer, + ref fun, + value, + result, + } => { + self.validate_atomic(pointer, fun, value, result, context)?; + } + S::WorkGroupUniformLoad { pointer, result } => { + stages &= super::ShaderStages::COMPUTE; + let pointer_inner = + context.resolve_type(pointer, &self.valid_expression_set)?; + match *pointer_inner { + Ti::Pointer { + space: AddressSpace::WorkGroup, + .. + } => {} + Ti::ValuePointer { + space: AddressSpace::WorkGroup, + .. + } => {} + _ => { + return Err(FunctionError::WorkgroupUniformLoadInvalidPointer(pointer) + .with_span_static(span, "WorkGroupUniformLoad")) + } + } + self.emit_expression(result, context)?; + let ty = match &context.expressions[result] { + &crate::Expression::WorkGroupUniformLoadResult { ty } => ty, + _ => { + return Err(FunctionError::WorkgroupUniformLoadExpressionMismatch( + result, + ) + .with_span_static(span, "WorkGroupUniformLoad")); + } + }; + let expected_pointer_inner = Ti::Pointer { + base: ty, + space: AddressSpace::WorkGroup, + }; + if !expected_pointer_inner.equivalent(pointer_inner, context.types) { + return Err(FunctionError::WorkgroupUniformLoadInvalidPointer(pointer) + .with_span_static(span, "WorkGroupUniformLoad")); + } + } + S::RayQuery { query, ref fun } => { + let query_var = match *context.get_expression(query) { + crate::Expression::LocalVariable(var) => &context.local_vars[var], + ref other => { + log::error!("Unexpected ray query expression {other:?}"); + return Err(FunctionError::InvalidRayQueryExpression(query) + .with_span_static(span, "invalid query expression")); + } + }; + match context.types[query_var.ty].inner { + Ti::RayQuery => {} + ref other => { + log::error!("Unexpected ray query type {other:?}"); + return Err(FunctionError::InvalidRayQueryType(query_var.ty) + .with_span_static(span, "invalid query type")); + } + } + match *fun { + crate::RayQueryFunction::Initialize { + acceleration_structure, + descriptor, + } => { + match *context + .resolve_type(acceleration_structure, &self.valid_expression_set)? + { + Ti::AccelerationStructure => {} + _ => { + return Err(FunctionError::InvalidAccelerationStructure( + acceleration_structure, + ) + .with_span_static(span, "invalid acceleration structure")) + } + } + let desc_ty_given = + context.resolve_type(descriptor, &self.valid_expression_set)?; + let desc_ty_expected = context + .special_types + .ray_desc + .map(|handle| &context.types[handle].inner); + if Some(desc_ty_given) != desc_ty_expected { + return Err(FunctionError::InvalidRayDescriptor(descriptor) + .with_span_static(span, "invalid ray descriptor")); + } + } + crate::RayQueryFunction::Proceed { result } => { + self.emit_expression(result, context)?; + } + crate::RayQueryFunction::Terminate => {} + } + } + } + } + Ok(BlockInfo { stages, finished }) + } + + fn validate_block( + &mut self, + statements: &crate::Block, + context: &BlockContext, + ) -> Result> { + let base_expression_count = self.valid_expression_list.len(); + let info = self.validate_block_impl(statements, context)?; + for handle in self.valid_expression_list.drain(base_expression_count..) { + self.valid_expression_set.remove(handle.index()); + } + Ok(info) + } + + fn validate_local_var( + &self, + var: &crate::LocalVariable, + gctx: crate::proc::GlobalCtx, + fun_info: &FunctionInfo, + expression_constness: &crate::proc::ExpressionConstnessTracker, + ) -> Result<(), LocalVariableError> { + log::debug!("var {:?}", var); + let type_info = self + .types + .get(var.ty.index()) + .ok_or(LocalVariableError::InvalidType(var.ty))?; + if !type_info.flags.contains(super::TypeFlags::CONSTRUCTIBLE) { + return Err(LocalVariableError::InvalidType(var.ty)); + } + + if let Some(init) = var.init { + let decl_ty = &gctx.types[var.ty].inner; + let init_ty = fun_info[init].ty.inner_with(gctx.types); + if !decl_ty.equivalent(init_ty, gctx.types) { + return Err(LocalVariableError::InitializerType); + } + + if !expression_constness.is_const(init) { + return Err(LocalVariableError::NonConstInitializer); + } + } + + Ok(()) + } + + pub(super) fn validate_function( + &mut self, + fun: &crate::Function, + module: &crate::Module, + mod_info: &ModuleInfo, + entry_point: bool, + ) -> Result> { + let mut info = mod_info.process_function(fun, module, self.flags, self.capabilities)?; + + let expression_constness = + crate::proc::ExpressionConstnessTracker::from_arena(&fun.expressions); + + for (var_handle, var) in fun.local_variables.iter() { + self.validate_local_var(var, module.to_ctx(), &info, &expression_constness) + .map_err(|source| { + FunctionError::LocalVariable { + handle: var_handle, + name: var.name.clone().unwrap_or_default(), + source, + } + .with_span_handle(var.ty, &module.types) + .with_handle(var_handle, &fun.local_variables) + })?; + } + + for (index, argument) in fun.arguments.iter().enumerate() { + match module.types[argument.ty].inner.pointer_space() { + Some(crate::AddressSpace::Private | crate::AddressSpace::Function) | None => {} + Some(other) => { + return Err(FunctionError::InvalidArgumentPointerSpace { + index, + name: argument.name.clone().unwrap_or_default(), + space: other, + } + .with_span_handle(argument.ty, &module.types)) + } + } + // Check for the least informative error last. + if !self.types[argument.ty.index()] + .flags + .contains(super::TypeFlags::ARGUMENT) + { + return Err(FunctionError::InvalidArgumentType { + index, + name: argument.name.clone().unwrap_or_default(), + } + .with_span_handle(argument.ty, &module.types)); + } + + if !entry_point && argument.binding.is_some() { + return Err(FunctionError::PipelineInputRegularFunction { + name: argument.name.clone().unwrap_or_default(), + } + .with_span_handle(argument.ty, &module.types)); + } + } + + if let Some(ref result) = fun.result { + if !self.types[result.ty.index()] + .flags + .contains(super::TypeFlags::CONSTRUCTIBLE) + { + return Err(FunctionError::NonConstructibleReturnType + .with_span_handle(result.ty, &module.types)); + } + + if !entry_point && result.binding.is_some() { + return Err(FunctionError::PipelineOutputRegularFunction + .with_span_handle(result.ty, &module.types)); + } + } + + self.valid_expression_set.clear(); + self.valid_expression_list.clear(); + for (handle, expr) in fun.expressions.iter() { + if expr.needs_pre_emit() { + self.valid_expression_set.insert(handle.index()); + } + if self.flags.contains(super::ValidationFlags::EXPRESSIONS) { + match self.validate_expression(handle, expr, fun, module, &info, mod_info) { + Ok(stages) => info.available_stages &= stages, + Err(source) => { + return Err(FunctionError::Expression { handle, source } + .with_span_handle(handle, &fun.expressions)) + } + } + } + } + + if self.flags.contains(super::ValidationFlags::BLOCKS) { + let stages = self + .validate_block( + &fun.body, + &BlockContext::new(fun, module, &info, &mod_info.functions), + )? + .stages; + info.available_stages &= stages; + } + Ok(info) + } +} diff --git a/naga/src/valid/handles.rs b/naga/src/valid/handles.rs new file mode 100644 index 0000000000..e482f293bb --- /dev/null +++ b/naga/src/valid/handles.rs @@ -0,0 +1,699 @@ +//! Implementation of `Validator::validate_module_handles`. + +use crate::{ + arena::{BadHandle, BadRangeError}, + Handle, +}; + +use crate::{Arena, UniqueArena}; + +use super::ValidationError; + +use std::{convert::TryInto, hash::Hash, num::NonZeroU32}; + +impl super::Validator { + /// Validates that all handles within `module` are: + /// + /// * Valid, in the sense that they contain indices within each arena structure inside the + /// [`crate::Module`] type. + /// * No arena contents contain any items that have forward dependencies; that is, the value + /// associated with a handle only may contain references to handles in the same arena that + /// were constructed before it. + /// + /// By validating the above conditions, we free up subsequent logic to assume that handle + /// accesses are infallible. + /// + /// # Errors + /// + /// Errors returned by this method are intentionally sparse, for simplicity of implementation. + /// It is expected that only buggy frontends or fuzzers should ever emit IR that fails this + /// validation pass. + pub(super) fn validate_module_handles(module: &crate::Module) -> Result<(), ValidationError> { + let &crate::Module { + ref constants, + ref entry_points, + ref functions, + ref global_variables, + ref types, + ref special_types, + ref const_expressions, + } = module; + + // NOTE: Types being first is important. All other forms of validation depend on this. + for (this_handle, ty) in types.iter() { + match ty.inner { + crate::TypeInner::Scalar { .. } + | crate::TypeInner::Vector { .. } + | crate::TypeInner::Matrix { .. } + | crate::TypeInner::ValuePointer { .. } + | crate::TypeInner::Atomic { .. } + | crate::TypeInner::Image { .. } + | crate::TypeInner::Sampler { .. } + | crate::TypeInner::AccelerationStructure + | crate::TypeInner::RayQuery => (), + crate::TypeInner::Pointer { base, space: _ } => { + this_handle.check_dep(base)?; + } + crate::TypeInner::Array { base, .. } + | crate::TypeInner::BindingArray { base, .. } => { + this_handle.check_dep(base)?; + } + crate::TypeInner::Struct { + ref members, + span: _, + } => { + this_handle.check_dep_iter(members.iter().map(|m| m.ty))?; + } + } + } + + for handle_and_expr in const_expressions.iter() { + Self::validate_const_expression_handles(handle_and_expr, constants, types)?; + } + + let validate_type = |handle| Self::validate_type_handle(handle, types); + let validate_const_expr = + |handle| Self::validate_expression_handle(handle, const_expressions); + + for (_handle, constant) in constants.iter() { + let &crate::Constant { + name: _, + r#override: _, + ty, + init, + } = constant; + validate_type(ty)?; + validate_const_expr(init)?; + } + + for (_handle, global_variable) in global_variables.iter() { + let &crate::GlobalVariable { + name: _, + space: _, + binding: _, + ty, + init, + } = global_variable; + validate_type(ty)?; + if let Some(init_expr) = init { + validate_const_expr(init_expr)?; + } + } + + let validate_function = |function_handle, function: &_| -> Result<_, InvalidHandleError> { + let &crate::Function { + name: _, + ref arguments, + ref result, + ref local_variables, + ref expressions, + ref named_expressions, + ref body, + } = function; + + for arg in arguments.iter() { + let &crate::FunctionArgument { + name: _, + ty, + binding: _, + } = arg; + validate_type(ty)?; + } + + if let &Some(crate::FunctionResult { ty, binding: _ }) = result { + validate_type(ty)?; + } + + for (_handle, local_variable) in local_variables.iter() { + let &crate::LocalVariable { name: _, ty, init } = local_variable; + validate_type(ty)?; + if let Some(init) = init { + Self::validate_expression_handle(init, expressions)?; + } + } + + for handle in named_expressions.keys().copied() { + Self::validate_expression_handle(handle, expressions)?; + } + + for handle_and_expr in expressions.iter() { + Self::validate_expression_handles( + handle_and_expr, + constants, + const_expressions, + types, + local_variables, + global_variables, + functions, + function_handle, + )?; + } + + Self::validate_block_handles(body, expressions, functions)?; + + Ok(()) + }; + + for entry_point in entry_points.iter() { + validate_function(None, &entry_point.function)?; + } + + for (function_handle, function) in functions.iter() { + validate_function(Some(function_handle), function)?; + } + + if let Some(ty) = special_types.ray_desc { + validate_type(ty)?; + } + if let Some(ty) = special_types.ray_intersection { + validate_type(ty)?; + } + + Ok(()) + } + + fn validate_type_handle( + handle: Handle, + types: &UniqueArena, + ) -> Result<(), InvalidHandleError> { + handle.check_valid_for_uniq(types).map(|_| ()) + } + + fn validate_constant_handle( + handle: Handle, + constants: &Arena, + ) -> Result<(), InvalidHandleError> { + handle.check_valid_for(constants).map(|_| ()) + } + + fn validate_expression_handle( + handle: Handle, + expressions: &Arena, + ) -> Result<(), InvalidHandleError> { + handle.check_valid_for(expressions).map(|_| ()) + } + + fn validate_function_handle( + handle: Handle, + functions: &Arena, + ) -> Result<(), InvalidHandleError> { + handle.check_valid_for(functions).map(|_| ()) + } + + fn validate_const_expression_handles( + (handle, expression): (Handle, &crate::Expression), + constants: &Arena, + types: &UniqueArena, + ) -> Result<(), InvalidHandleError> { + let validate_constant = |handle| Self::validate_constant_handle(handle, constants); + let validate_type = |handle| Self::validate_type_handle(handle, types); + + match *expression { + crate::Expression::Literal(_) => {} + crate::Expression::Constant(constant) => { + validate_constant(constant)?; + handle.check_dep(constants[constant].init)?; + } + crate::Expression::ZeroValue(ty) => { + validate_type(ty)?; + } + crate::Expression::Compose { ty, ref components } => { + validate_type(ty)?; + handle.check_dep_iter(components.iter().copied())?; + } + _ => {} + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn validate_expression_handles( + (handle, expression): (Handle, &crate::Expression), + constants: &Arena, + const_expressions: &Arena, + types: &UniqueArena, + local_variables: &Arena, + global_variables: &Arena, + functions: &Arena, + // The handle of the current function or `None` if it's an entry point + current_function: Option>, + ) -> Result<(), InvalidHandleError> { + let validate_constant = |handle| Self::validate_constant_handle(handle, constants); + let validate_const_expr = + |handle| Self::validate_expression_handle(handle, const_expressions); + let validate_type = |handle| Self::validate_type_handle(handle, types); + + match *expression { + crate::Expression::Access { base, index } => { + handle.check_dep(base)?.check_dep(index)?; + } + crate::Expression::AccessIndex { base, .. } => { + handle.check_dep(base)?; + } + crate::Expression::Splat { value, .. } => { + handle.check_dep(value)?; + } + crate::Expression::Swizzle { vector, .. } => { + handle.check_dep(vector)?; + } + crate::Expression::Literal(_) => {} + crate::Expression::Constant(constant) => { + validate_constant(constant)?; + } + crate::Expression::ZeroValue(ty) => { + validate_type(ty)?; + } + crate::Expression::Compose { ty, ref components } => { + validate_type(ty)?; + handle.check_dep_iter(components.iter().copied())?; + } + crate::Expression::FunctionArgument(_arg_idx) => (), + crate::Expression::GlobalVariable(global_variable) => { + global_variable.check_valid_for(global_variables)?; + } + crate::Expression::LocalVariable(local_variable) => { + local_variable.check_valid_for(local_variables)?; + } + crate::Expression::Load { pointer } => { + handle.check_dep(pointer)?; + } + crate::Expression::ImageSample { + image, + sampler, + gather: _, + coordinate, + array_index, + offset, + level, + depth_ref, + } => { + if let Some(offset) = offset { + validate_const_expr(offset)?; + } + + handle + .check_dep(image)? + .check_dep(sampler)? + .check_dep(coordinate)? + .check_dep_opt(array_index)?; + + match level { + crate::SampleLevel::Auto | crate::SampleLevel::Zero => (), + crate::SampleLevel::Exact(expr) => { + handle.check_dep(expr)?; + } + crate::SampleLevel::Bias(expr) => { + handle.check_dep(expr)?; + } + crate::SampleLevel::Gradient { x, y } => { + handle.check_dep(x)?.check_dep(y)?; + } + }; + + handle.check_dep_opt(depth_ref)?; + } + crate::Expression::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => { + handle + .check_dep(image)? + .check_dep(coordinate)? + .check_dep_opt(array_index)? + .check_dep_opt(sample)? + .check_dep_opt(level)?; + } + crate::Expression::ImageQuery { image, query } => { + handle.check_dep(image)?; + match query { + crate::ImageQuery::Size { level } => { + handle.check_dep_opt(level)?; + } + crate::ImageQuery::NumLevels + | crate::ImageQuery::NumLayers + | crate::ImageQuery::NumSamples => (), + }; + } + crate::Expression::Unary { + op: _, + expr: operand, + } => { + handle.check_dep(operand)?; + } + crate::Expression::Binary { op: _, left, right } => { + handle.check_dep(left)?.check_dep(right)?; + } + crate::Expression::Select { + condition, + accept, + reject, + } => { + handle + .check_dep(condition)? + .check_dep(accept)? + .check_dep(reject)?; + } + crate::Expression::Derivative { expr: argument, .. } => { + handle.check_dep(argument)?; + } + crate::Expression::Relational { fun: _, argument } => { + handle.check_dep(argument)?; + } + crate::Expression::Math { + fun: _, + arg, + arg1, + arg2, + arg3, + } => { + handle + .check_dep(arg)? + .check_dep_opt(arg1)? + .check_dep_opt(arg2)? + .check_dep_opt(arg3)?; + } + crate::Expression::As { + expr: input, + kind: _, + convert: _, + } => { + handle.check_dep(input)?; + } + crate::Expression::CallResult(function) => { + Self::validate_function_handle(function, functions)?; + if let Some(handle) = current_function { + handle.check_dep(function)?; + } + } + crate::Expression::AtomicResult { .. } + | crate::Expression::RayQueryProceedResult + | crate::Expression::WorkGroupUniformLoadResult { .. } => (), + crate::Expression::ArrayLength(array) => { + handle.check_dep(array)?; + } + crate::Expression::RayQueryGetIntersection { + query, + committed: _, + } => { + handle.check_dep(query)?; + } + } + Ok(()) + } + + fn validate_block_handles( + block: &crate::Block, + expressions: &Arena, + functions: &Arena, + ) -> Result<(), InvalidHandleError> { + let validate_block = |block| Self::validate_block_handles(block, expressions, functions); + let validate_expr = |handle| Self::validate_expression_handle(handle, expressions); + let validate_expr_opt = |handle_opt| { + if let Some(handle) = handle_opt { + validate_expr(handle)?; + } + Ok(()) + }; + + block.iter().try_for_each(|stmt| match *stmt { + crate::Statement::Emit(ref expr_range) => { + expr_range.check_valid_for(expressions)?; + Ok(()) + } + crate::Statement::Block(ref block) => { + validate_block(block)?; + Ok(()) + } + crate::Statement::If { + condition, + ref accept, + ref reject, + } => { + validate_expr(condition)?; + validate_block(accept)?; + validate_block(reject)?; + Ok(()) + } + crate::Statement::Switch { + selector, + ref cases, + } => { + validate_expr(selector)?; + for &crate::SwitchCase { + value: _, + ref body, + fall_through: _, + } in cases + { + validate_block(body)?; + } + Ok(()) + } + crate::Statement::Loop { + ref body, + ref continuing, + break_if, + } => { + validate_block(body)?; + validate_block(continuing)?; + validate_expr_opt(break_if)?; + Ok(()) + } + crate::Statement::Return { value } => validate_expr_opt(value), + crate::Statement::Store { pointer, value } => { + validate_expr(pointer)?; + validate_expr(value)?; + Ok(()) + } + crate::Statement::ImageStore { + image, + coordinate, + array_index, + value, + } => { + validate_expr(image)?; + validate_expr(coordinate)?; + validate_expr_opt(array_index)?; + validate_expr(value)?; + Ok(()) + } + crate::Statement::Atomic { + pointer, + fun, + value, + result, + } => { + validate_expr(pointer)?; + match fun { + crate::AtomicFunction::Add + | crate::AtomicFunction::Subtract + | crate::AtomicFunction::And + | crate::AtomicFunction::ExclusiveOr + | crate::AtomicFunction::InclusiveOr + | crate::AtomicFunction::Min + | crate::AtomicFunction::Max => (), + crate::AtomicFunction::Exchange { compare } => validate_expr_opt(compare)?, + }; + validate_expr(value)?; + validate_expr(result)?; + Ok(()) + } + crate::Statement::WorkGroupUniformLoad { pointer, result } => { + validate_expr(pointer)?; + validate_expr(result)?; + Ok(()) + } + crate::Statement::Call { + function, + ref arguments, + result, + } => { + Self::validate_function_handle(function, functions)?; + for arg in arguments.iter().copied() { + validate_expr(arg)?; + } + validate_expr_opt(result)?; + Ok(()) + } + crate::Statement::RayQuery { query, ref fun } => { + validate_expr(query)?; + match *fun { + crate::RayQueryFunction::Initialize { + acceleration_structure, + descriptor, + } => { + validate_expr(acceleration_structure)?; + validate_expr(descriptor)?; + } + crate::RayQueryFunction::Proceed { result } => { + validate_expr(result)?; + } + crate::RayQueryFunction::Terminate => {} + } + Ok(()) + } + crate::Statement::Break + | crate::Statement::Continue + | crate::Statement::Kill + | crate::Statement::Barrier(_) => Ok(()), + }) + } +} + +impl From for ValidationError { + fn from(source: BadHandle) -> Self { + Self::InvalidHandle(source.into()) + } +} + +impl From for ValidationError { + fn from(source: FwdDepError) -> Self { + Self::InvalidHandle(source.into()) + } +} + +impl From for ValidationError { + fn from(source: BadRangeError) -> Self { + Self::InvalidHandle(source.into()) + } +} + +#[derive(Clone, Debug, thiserror::Error)] +pub enum InvalidHandleError { + #[error(transparent)] + BadHandle(#[from] BadHandle), + #[error(transparent)] + ForwardDependency(#[from] FwdDepError), + #[error(transparent)] + BadRange(#[from] BadRangeError), +} + +#[derive(Clone, Debug, thiserror::Error)] +#[error( + "{subject:?} of kind {subject_kind:?} depends on {depends_on:?} of kind {depends_on_kind}, \ + which has not been processed yet" +)] +pub struct FwdDepError { + // This error is used for many `Handle` types, but there's no point in making this generic, so + // we just flatten them all to `Handle<()>` here. + subject: Handle<()>, + subject_kind: &'static str, + depends_on: Handle<()>, + depends_on_kind: &'static str, +} + +impl Handle { + /// Check that `self` is valid within `arena` using [`Arena::check_contains_handle`]. + pub(self) fn check_valid_for(self, arena: &Arena) -> Result<(), InvalidHandleError> { + arena.check_contains_handle(self)?; + Ok(()) + } + + /// Check that `self` is valid within `arena` using [`UniqueArena::check_contains_handle`]. + pub(self) fn check_valid_for_uniq( + self, + arena: &UniqueArena, + ) -> Result<(), InvalidHandleError> + where + T: Eq + Hash, + { + arena.check_contains_handle(self)?; + Ok(()) + } + + /// Check that `depends_on` was constructed before `self` by comparing handle indices. + /// + /// If `self` is a valid handle (i.e., it has been validated using [`Self::check_valid_for`]) + /// and this function returns [`Ok`], then it may be assumed that `depends_on` is also valid. + /// In [`naga`](crate)'s current arena-based implementation, this is useful for validating + /// recursive definitions of arena-based values in linear time. + /// + /// # Errors + /// + /// If `depends_on`'s handle is from the same [`Arena`] as `self'`s, but not constructed earlier + /// than `self`'s, this function returns an error. + pub(self) fn check_dep(self, depends_on: Self) -> Result { + if depends_on < self { + Ok(self) + } else { + let erase_handle_type = |handle: Handle<_>| { + Handle::new(NonZeroU32::new((handle.index() + 1).try_into().unwrap()).unwrap()) + }; + Err(FwdDepError { + subject: erase_handle_type(self), + subject_kind: std::any::type_name::(), + depends_on: erase_handle_type(depends_on), + depends_on_kind: std::any::type_name::(), + }) + } + } + + /// Like [`Self::check_dep`], except for [`Option`]al handle values. + pub(self) fn check_dep_opt(self, depends_on: Option) -> Result { + self.check_dep_iter(depends_on.into_iter()) + } + + /// Like [`Self::check_dep`], except for [`Iterator`]s over handle values. + pub(self) fn check_dep_iter( + self, + depends_on: impl Iterator, + ) -> Result { + for handle in depends_on { + self.check_dep(handle)?; + } + Ok(self) + } +} + +impl crate::arena::Range { + pub(self) fn check_valid_for(&self, arena: &Arena) -> Result<(), BadRangeError> { + arena.check_contains_range(self) + } +} + +#[test] +fn constant_deps() { + use crate::{Constant, Expression, Literal, Span, Type, TypeInner}; + + let nowhere = Span::default(); + + let mut types = UniqueArena::new(); + let mut const_exprs = Arena::new(); + let mut fun_exprs = Arena::new(); + let mut constants = Arena::new(); + + let i32_handle = types.insert( + Type { + name: None, + inner: TypeInner::Scalar(crate::Scalar::I32), + }, + nowhere, + ); + + // Construct a self-referential constant by misusing a handle to + // fun_exprs as a constant initializer. + let fun_expr = fun_exprs.append(Expression::Literal(Literal::I32(42)), nowhere); + let self_referential_const = constants.append( + Constant { + name: None, + r#override: crate::Override::None, + ty: i32_handle, + init: fun_expr, + }, + nowhere, + ); + let _self_referential_expr = + const_exprs.append(Expression::Constant(self_referential_const), nowhere); + + for handle_and_expr in const_exprs.iter() { + assert!(super::Validator::validate_const_expression_handles( + handle_and_expr, + &constants, + &types, + ) + .is_err()); + } +} diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs new file mode 100644 index 0000000000..57863048e5 --- /dev/null +++ b/naga/src/valid/interface.rs @@ -0,0 +1,709 @@ +use super::{ + analyzer::{FunctionInfo, GlobalUse}, + Capabilities, Disalignment, FunctionError, ModuleInfo, +}; +use crate::arena::{Handle, UniqueArena}; + +use crate::span::{AddSpan as _, MapErrWithSpan as _, SpanProvider as _, WithSpan}; +use bit_set::BitSet; + +const MAX_WORKGROUP_SIZE: u32 = 0x4000; + +#[derive(Clone, Debug, thiserror::Error)] +pub enum GlobalVariableError { + #[error("Usage isn't compatible with address space {0:?}")] + InvalidUsage(crate::AddressSpace), + #[error("Type isn't compatible with address space {0:?}")] + InvalidType(crate::AddressSpace), + #[error("Type flags {seen:?} do not meet the required {required:?}")] + MissingTypeFlags { + required: super::TypeFlags, + seen: super::TypeFlags, + }, + #[error("Capability {0:?} is not supported")] + UnsupportedCapability(Capabilities), + #[error("Binding decoration is missing or not applicable")] + InvalidBinding, + #[error("Alignment requirements for address space {0:?} are not met by {1:?}")] + Alignment( + crate::AddressSpace, + Handle, + #[source] Disalignment, + ), + #[error("Initializer doesn't match the variable type")] + InitializerType, + #[error("Initializer can't be used with address space {0:?}")] + InitializerNotAllowed(crate::AddressSpace), + #[error("Storage address space doesn't support write-only access")] + StorageAddressSpaceWriteOnlyNotSupported, +} + +#[derive(Clone, Debug, thiserror::Error)] +pub enum VaryingError { + #[error("The type {0:?} does not match the varying")] + InvalidType(Handle), + #[error("The type {0:?} cannot be used for user-defined entry point inputs or outputs")] + NotIOShareableType(Handle), + #[error("Interpolation is not valid")] + InvalidInterpolation, + #[error("Interpolation must be specified on vertex shader outputs and fragment shader inputs")] + MissingInterpolation, + #[error("Built-in {0:?} is not available at this stage")] + InvalidBuiltInStage(crate::BuiltIn), + #[error("Built-in type for {0:?} is invalid")] + InvalidBuiltInType(crate::BuiltIn), + #[error("Entry point arguments and return values must all have bindings")] + MissingBinding, + #[error("Struct member {0} is missing a binding")] + MemberMissingBinding(u32), + #[error("Multiple bindings at location {location} are present")] + BindingCollision { location: u32 }, + #[error("Built-in {0:?} is present more than once")] + DuplicateBuiltIn(crate::BuiltIn), + #[error("Capability {0:?} is not supported")] + UnsupportedCapability(Capabilities), + #[error("The attribute {0:?} is only valid as an output for stage {1:?}")] + InvalidInputAttributeInStage(&'static str, crate::ShaderStage), + #[error("The attribute {0:?} is not valid for stage {1:?}")] + InvalidAttributeInStage(&'static str, crate::ShaderStage), + #[error( + "The location index {location} cannot be used together with the attribute {attribute:?}" + )] + InvalidLocationAttributeCombination { + location: u32, + attribute: &'static str, + }, +} + +#[derive(Clone, Debug, thiserror::Error)] +pub enum EntryPointError { + #[error("Multiple conflicting entry points")] + Conflict, + #[error("Vertex shaders must return a `@builtin(position)` output value")] + MissingVertexOutputPosition, + #[error("Early depth test is not applicable")] + UnexpectedEarlyDepthTest, + #[error("Workgroup size is not applicable")] + UnexpectedWorkgroupSize, + #[error("Workgroup size is out of range")] + OutOfRangeWorkgroupSize, + #[error("Uses operations forbidden at this stage")] + ForbiddenStageOperations, + #[error("Global variable {0:?} is used incorrectly as {1:?}")] + InvalidGlobalUsage(Handle, GlobalUse), + #[error("More than 1 push constant variable is used")] + MoreThanOnePushConstantUsed, + #[error("Bindings for {0:?} conflict with other resource")] + BindingCollision(Handle), + #[error("Argument {0} varying error")] + Argument(u32, #[source] VaryingError), + #[error(transparent)] + Result(#[from] VaryingError), + #[error("Location {location} interpolation of an integer has to be flat")] + InvalidIntegerInterpolation { location: u32 }, + #[error(transparent)] + Function(#[from] FunctionError), + #[error( + "Invalid locations {location_mask:?} are set while dual source blending. Only location 0 may be set." + )] + InvalidLocationsWhileDualSourceBlending { location_mask: BitSet }, +} + +fn storage_usage(access: crate::StorageAccess) -> GlobalUse { + let mut storage_usage = GlobalUse::QUERY; + if access.contains(crate::StorageAccess::LOAD) { + storage_usage |= GlobalUse::READ; + } + if access.contains(crate::StorageAccess::STORE) { + storage_usage |= GlobalUse::WRITE; + } + storage_usage +} + +struct VaryingContext<'a> { + stage: crate::ShaderStage, + output: bool, + second_blend_source: bool, + types: &'a UniqueArena, + type_info: &'a Vec, + location_mask: &'a mut BitSet, + built_ins: &'a mut crate::FastHashSet, + capabilities: Capabilities, + flags: super::ValidationFlags, +} + +impl VaryingContext<'_> { + fn validate_impl( + &mut self, + ty: Handle, + binding: &crate::Binding, + ) -> Result<(), VaryingError> { + use crate::{BuiltIn as Bi, ShaderStage as St, TypeInner as Ti, VectorSize as Vs}; + + let ty_inner = &self.types[ty].inner; + match *binding { + crate::Binding::BuiltIn(built_in) => { + // Ignore the `invariant` field for the sake of duplicate checks, + // but use the original in error messages. + let canonical = if let crate::BuiltIn::Position { .. } = built_in { + crate::BuiltIn::Position { invariant: false } + } else { + built_in + }; + + if self.built_ins.contains(&canonical) { + return Err(VaryingError::DuplicateBuiltIn(built_in)); + } + self.built_ins.insert(canonical); + + let required = match built_in { + Bi::ClipDistance => Capabilities::CLIP_DISTANCE, + Bi::CullDistance => Capabilities::CULL_DISTANCE, + Bi::PrimitiveIndex => Capabilities::PRIMITIVE_INDEX, + Bi::ViewIndex => Capabilities::MULTIVIEW, + Bi::SampleIndex => Capabilities::MULTISAMPLED_SHADING, + _ => Capabilities::empty(), + }; + if !self.capabilities.contains(required) { + return Err(VaryingError::UnsupportedCapability(required)); + } + + let (visible, type_good) = match built_in { + Bi::BaseInstance | Bi::BaseVertex | Bi::InstanceIndex | Bi::VertexIndex => ( + self.stage == St::Vertex && !self.output, + *ty_inner == Ti::Scalar(crate::Scalar::U32), + ), + Bi::ClipDistance | Bi::CullDistance => ( + self.stage == St::Vertex && self.output, + match *ty_inner { + Ti::Array { base, .. } => { + self.types[base].inner == Ti::Scalar(crate::Scalar::F32) + } + _ => false, + }, + ), + Bi::PointSize => ( + self.stage == St::Vertex && self.output, + *ty_inner == Ti::Scalar(crate::Scalar::F32), + ), + Bi::PointCoord => ( + self.stage == St::Fragment && !self.output, + *ty_inner + == Ti::Vector { + size: Vs::Bi, + scalar: crate::Scalar::F32, + }, + ), + Bi::Position { .. } => ( + match self.stage { + St::Vertex => self.output, + St::Fragment => !self.output, + St::Compute => false, + }, + *ty_inner + == Ti::Vector { + size: Vs::Quad, + scalar: crate::Scalar::F32, + }, + ), + Bi::ViewIndex => ( + match self.stage { + St::Vertex | St::Fragment => !self.output, + St::Compute => false, + }, + *ty_inner == Ti::Scalar(crate::Scalar::I32), + ), + Bi::FragDepth => ( + self.stage == St::Fragment && self.output, + *ty_inner == Ti::Scalar(crate::Scalar::F32), + ), + Bi::FrontFacing => ( + self.stage == St::Fragment && !self.output, + *ty_inner == Ti::Scalar(crate::Scalar::BOOL), + ), + Bi::PrimitiveIndex => ( + self.stage == St::Fragment && !self.output, + *ty_inner == Ti::Scalar(crate::Scalar::U32), + ), + Bi::SampleIndex => ( + self.stage == St::Fragment && !self.output, + *ty_inner == Ti::Scalar(crate::Scalar::U32), + ), + Bi::SampleMask => ( + self.stage == St::Fragment, + *ty_inner == Ti::Scalar(crate::Scalar::U32), + ), + Bi::LocalInvocationIndex => ( + self.stage == St::Compute && !self.output, + *ty_inner == Ti::Scalar(crate::Scalar::U32), + ), + Bi::GlobalInvocationId + | Bi::LocalInvocationId + | Bi::WorkGroupId + | Bi::WorkGroupSize + | Bi::NumWorkGroups => ( + self.stage == St::Compute && !self.output, + *ty_inner + == Ti::Vector { + size: Vs::Tri, + scalar: crate::Scalar::U32, + }, + ), + }; + + if !visible { + return Err(VaryingError::InvalidBuiltInStage(built_in)); + } + if !type_good { + log::warn!("Wrong builtin type: {:?}", ty_inner); + return Err(VaryingError::InvalidBuiltInType(built_in)); + } + } + crate::Binding::Location { + location, + interpolation, + sampling, + second_blend_source, + } => { + // Only IO-shareable types may be stored in locations. + if !self.type_info[ty.index()] + .flags + .contains(super::TypeFlags::IO_SHAREABLE) + { + return Err(VaryingError::NotIOShareableType(ty)); + } + + if second_blend_source { + if !self + .capabilities + .contains(Capabilities::DUAL_SOURCE_BLENDING) + { + return Err(VaryingError::UnsupportedCapability( + Capabilities::DUAL_SOURCE_BLENDING, + )); + } + if self.stage != crate::ShaderStage::Fragment { + return Err(VaryingError::InvalidAttributeInStage( + "second_blend_source", + self.stage, + )); + } + if !self.output { + return Err(VaryingError::InvalidInputAttributeInStage( + "second_blend_source", + self.stage, + )); + } + if location != 0 { + return Err(VaryingError::InvalidLocationAttributeCombination { + location, + attribute: "second_blend_source", + }); + } + + self.second_blend_source = true; + } else if !self.location_mask.insert(location as usize) { + if self.flags.contains(super::ValidationFlags::BINDINGS) { + return Err(VaryingError::BindingCollision { location }); + } + } + + let needs_interpolation = match self.stage { + crate::ShaderStage::Vertex => self.output, + crate::ShaderStage::Fragment => !self.output, + crate::ShaderStage::Compute => false, + }; + + // It doesn't make sense to specify a sampling when `interpolation` is `Flat`, but + // SPIR-V and GLSL both explicitly tolerate such combinations of decorators / + // qualifiers, so we won't complain about that here. + let _ = sampling; + + let required = match sampling { + Some(crate::Sampling::Sample) => Capabilities::MULTISAMPLED_SHADING, + _ => Capabilities::empty(), + }; + if !self.capabilities.contains(required) { + return Err(VaryingError::UnsupportedCapability(required)); + } + + match ty_inner.scalar_kind() { + Some(crate::ScalarKind::Float) => { + if needs_interpolation && interpolation.is_none() { + return Err(VaryingError::MissingInterpolation); + } + } + Some(_) => { + if needs_interpolation && interpolation != Some(crate::Interpolation::Flat) + { + return Err(VaryingError::InvalidInterpolation); + } + } + None => return Err(VaryingError::InvalidType(ty)), + } + } + } + + Ok(()) + } + + fn validate( + &mut self, + ty: Handle, + binding: Option<&crate::Binding>, + ) -> Result<(), WithSpan> { + let span_context = self.types.get_span_context(ty); + match binding { + Some(binding) => self + .validate_impl(ty, binding) + .map_err(|e| e.with_span_context(span_context)), + None => { + match self.types[ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + for (index, member) in members.iter().enumerate() { + let span_context = self.types.get_span_context(ty); + match member.binding { + None => { + if self.flags.contains(super::ValidationFlags::BINDINGS) { + return Err(VaryingError::MemberMissingBinding( + index as u32, + ) + .with_span_context(span_context)); + } + } + Some(ref binding) => self + .validate_impl(member.ty, binding) + .map_err(|e| e.with_span_context(span_context))?, + } + } + } + _ => { + if self.flags.contains(super::ValidationFlags::BINDINGS) { + return Err(VaryingError::MissingBinding.with_span()); + } + } + } + Ok(()) + } + } + } +} + +impl super::Validator { + pub(super) fn validate_global_var( + &self, + var: &crate::GlobalVariable, + gctx: crate::proc::GlobalCtx, + mod_info: &ModuleInfo, + ) -> Result<(), GlobalVariableError> { + use super::TypeFlags; + + log::debug!("var {:?}", var); + let inner_ty = match gctx.types[var.ty].inner { + // A binding array is (mostly) supposed to behave the same as a + // series of individually bound resources, so we can (mostly) + // validate a `binding_array` as if it were just a plain `T`. + crate::TypeInner::BindingArray { base, .. } => match var.space { + crate::AddressSpace::Storage { .. } + | crate::AddressSpace::Uniform + | crate::AddressSpace::Handle => base, + _ => return Err(GlobalVariableError::InvalidUsage(var.space)), + }, + _ => var.ty, + }; + let type_info = &self.types[inner_ty.index()]; + + let (required_type_flags, is_resource) = match var.space { + crate::AddressSpace::Function => { + return Err(GlobalVariableError::InvalidUsage(var.space)) + } + crate::AddressSpace::Storage { access } => { + if let Err((ty_handle, disalignment)) = type_info.storage_layout { + if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) { + return Err(GlobalVariableError::Alignment( + var.space, + ty_handle, + disalignment, + )); + } + } + if access == crate::StorageAccess::STORE { + return Err(GlobalVariableError::StorageAddressSpaceWriteOnlyNotSupported); + } + (TypeFlags::DATA | TypeFlags::HOST_SHAREABLE, true) + } + crate::AddressSpace::Uniform => { + if let Err((ty_handle, disalignment)) = type_info.uniform_layout { + if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) { + return Err(GlobalVariableError::Alignment( + var.space, + ty_handle, + disalignment, + )); + } + } + ( + TypeFlags::DATA + | TypeFlags::COPY + | TypeFlags::SIZED + | TypeFlags::HOST_SHAREABLE, + true, + ) + } + crate::AddressSpace::Handle => { + match gctx.types[inner_ty].inner { + crate::TypeInner::Image { class, .. } => match class { + crate::ImageClass::Storage { + format: + crate::StorageFormat::R16Unorm + | crate::StorageFormat::R16Snorm + | crate::StorageFormat::Rg16Unorm + | crate::StorageFormat::Rg16Snorm + | crate::StorageFormat::Rgba16Unorm + | crate::StorageFormat::Rgba16Snorm, + .. + } => { + if !self + .capabilities + .contains(Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS) + { + return Err(GlobalVariableError::UnsupportedCapability( + Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS, + )); + } + } + _ => {} + }, + crate::TypeInner::Sampler { .. } + | crate::TypeInner::AccelerationStructure + | crate::TypeInner::RayQuery => {} + _ => { + return Err(GlobalVariableError::InvalidType(var.space)); + } + } + + (TypeFlags::empty(), true) + } + crate::AddressSpace::Private => (TypeFlags::CONSTRUCTIBLE, false), + crate::AddressSpace::WorkGroup => (TypeFlags::DATA | TypeFlags::SIZED, false), + crate::AddressSpace::PushConstant => { + if !self.capabilities.contains(Capabilities::PUSH_CONSTANT) { + return Err(GlobalVariableError::UnsupportedCapability( + Capabilities::PUSH_CONSTANT, + )); + } + ( + TypeFlags::DATA + | TypeFlags::COPY + | TypeFlags::HOST_SHAREABLE + | TypeFlags::SIZED, + false, + ) + } + }; + + if !type_info.flags.contains(required_type_flags) { + return Err(GlobalVariableError::MissingTypeFlags { + seen: type_info.flags, + required: required_type_flags, + }); + } + + if is_resource != var.binding.is_some() { + if self.flags.contains(super::ValidationFlags::BINDINGS) { + return Err(GlobalVariableError::InvalidBinding); + } + } + + if let Some(init) = var.init { + match var.space { + crate::AddressSpace::Private | crate::AddressSpace::Function => {} + _ => { + return Err(GlobalVariableError::InitializerNotAllowed(var.space)); + } + } + + let decl_ty = &gctx.types[var.ty].inner; + let init_ty = mod_info[init].inner_with(gctx.types); + if !decl_ty.equivalent(init_ty, gctx.types) { + return Err(GlobalVariableError::InitializerType); + } + } + + Ok(()) + } + + pub(super) fn validate_entry_point( + &mut self, + ep: &crate::EntryPoint, + module: &crate::Module, + mod_info: &ModuleInfo, + ) -> Result> { + if ep.early_depth_test.is_some() { + let required = Capabilities::EARLY_DEPTH_TEST; + if !self.capabilities.contains(required) { + return Err( + EntryPointError::Result(VaryingError::UnsupportedCapability(required)) + .with_span(), + ); + } + + if ep.stage != crate::ShaderStage::Fragment { + return Err(EntryPointError::UnexpectedEarlyDepthTest.with_span()); + } + } + + if ep.stage == crate::ShaderStage::Compute { + if ep + .workgroup_size + .iter() + .any(|&s| s == 0 || s > MAX_WORKGROUP_SIZE) + { + return Err(EntryPointError::OutOfRangeWorkgroupSize.with_span()); + } + } else if ep.workgroup_size != [0; 3] { + return Err(EntryPointError::UnexpectedWorkgroupSize.with_span()); + } + + let mut info = self + .validate_function(&ep.function, module, mod_info, true) + .map_err(WithSpan::into_other)?; + + { + use super::ShaderStages; + + let stage_bit = match ep.stage { + crate::ShaderStage::Vertex => ShaderStages::VERTEX, + crate::ShaderStage::Fragment => ShaderStages::FRAGMENT, + crate::ShaderStage::Compute => ShaderStages::COMPUTE, + }; + + if !info.available_stages.contains(stage_bit) { + return Err(EntryPointError::ForbiddenStageOperations.with_span()); + } + } + + self.location_mask.clear(); + let mut argument_built_ins = crate::FastHashSet::default(); + // TODO: add span info to function arguments + for (index, fa) in ep.function.arguments.iter().enumerate() { + let mut ctx = VaryingContext { + stage: ep.stage, + output: false, + second_blend_source: false, + types: &module.types, + type_info: &self.types, + location_mask: &mut self.location_mask, + built_ins: &mut argument_built_ins, + capabilities: self.capabilities, + flags: self.flags, + }; + ctx.validate(fa.ty, fa.binding.as_ref()) + .map_err_inner(|e| EntryPointError::Argument(index as u32, e).with_span())?; + } + + self.location_mask.clear(); + if let Some(ref fr) = ep.function.result { + let mut result_built_ins = crate::FastHashSet::default(); + let mut ctx = VaryingContext { + stage: ep.stage, + output: true, + second_blend_source: false, + types: &module.types, + type_info: &self.types, + location_mask: &mut self.location_mask, + built_ins: &mut result_built_ins, + capabilities: self.capabilities, + flags: self.flags, + }; + ctx.validate(fr.ty, fr.binding.as_ref()) + .map_err_inner(|e| EntryPointError::Result(e).with_span())?; + if ctx.second_blend_source { + // Only the first location may be used whhen dual source blending + if ctx.location_mask.len() == 1 && ctx.location_mask.contains(0) { + info.dual_source_blending = true; + } else { + return Err(EntryPointError::InvalidLocationsWhileDualSourceBlending { + location_mask: self.location_mask.clone(), + } + .with_span()); + } + } + + if ep.stage == crate::ShaderStage::Vertex + && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false }) + { + return Err(EntryPointError::MissingVertexOutputPosition.with_span()); + } + } else if ep.stage == crate::ShaderStage::Vertex { + return Err(EntryPointError::MissingVertexOutputPosition.with_span()); + } + + { + let used_push_constants = module + .global_variables + .iter() + .filter(|&(_, var)| var.space == crate::AddressSpace::PushConstant) + .map(|(handle, _)| handle) + .filter(|&handle| !info[handle].is_empty()); + // Check if there is more than one push constant, and error if so. + // Use a loop for when returning multiple errors is supported. + #[allow(clippy::never_loop)] + for handle in used_push_constants.skip(1) { + return Err(EntryPointError::MoreThanOnePushConstantUsed + .with_span_handle(handle, &module.global_variables)); + } + } + + self.ep_resource_bindings.clear(); + for (var_handle, var) in module.global_variables.iter() { + let usage = info[var_handle]; + if usage.is_empty() { + continue; + } + + let allowed_usage = match var.space { + crate::AddressSpace::Function => unreachable!(), + crate::AddressSpace::Uniform => GlobalUse::READ | GlobalUse::QUERY, + crate::AddressSpace::Storage { access } => storage_usage(access), + crate::AddressSpace::Handle => match module.types[var.ty].inner { + crate::TypeInner::BindingArray { base, .. } => match module.types[base].inner { + crate::TypeInner::Image { + class: crate::ImageClass::Storage { access, .. }, + .. + } => storage_usage(access), + _ => GlobalUse::READ | GlobalUse::QUERY, + }, + crate::TypeInner::Image { + class: crate::ImageClass::Storage { access, .. }, + .. + } => storage_usage(access), + _ => GlobalUse::READ | GlobalUse::QUERY, + }, + crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => GlobalUse::all(), + crate::AddressSpace::PushConstant => GlobalUse::READ, + }; + if !allowed_usage.contains(usage) { + log::warn!("\tUsage error for: {:?}", var); + log::warn!( + "\tAllowed usage: {:?}, requested: {:?}", + allowed_usage, + usage + ); + return Err(EntryPointError::InvalidGlobalUsage(var_handle, usage) + .with_span_handle(var_handle, &module.global_variables)); + } + + if let Some(ref bind) = var.binding { + if !self.ep_resource_bindings.insert(bind.clone()) { + if self.flags.contains(super::ValidationFlags::BINDINGS) { + return Err(EntryPointError::BindingCollision(var_handle) + .with_span_handle(var_handle, &module.global_variables)); + } + } + } + } + + Ok(info) + } +} diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs new file mode 100644 index 0000000000..70a4d39d2a --- /dev/null +++ b/naga/src/valid/mod.rs @@ -0,0 +1,477 @@ +/*! +Shader validator. +*/ + +mod analyzer; +mod compose; +mod expression; +mod function; +mod handles; +mod interface; +mod r#type; + +use crate::{ + arena::Handle, + proc::{LayoutError, Layouter, TypeResolution}, + FastHashSet, +}; +use bit_set::BitSet; +use std::ops; + +//TODO: analyze the model at the same time as we validate it, +// merge the corresponding matches over expressions and statements. + +use crate::span::{AddSpan as _, WithSpan}; +pub use analyzer::{ExpressionInfo, FunctionInfo, GlobalUse, Uniformity, UniformityRequirements}; +pub use compose::ComposeError; +pub use expression::{check_literal_value, LiteralError}; +pub use expression::{ConstExpressionError, ExpressionError}; +pub use function::{CallError, FunctionError, LocalVariableError}; +pub use interface::{EntryPointError, GlobalVariableError, VaryingError}; +pub use r#type::{Disalignment, TypeError, TypeFlags}; + +use self::handles::InvalidHandleError; + +bitflags::bitflags! { + /// Validation flags. + /// + /// If you are working with trusted shaders, then you may be able + /// to save some time by skipping validation. + /// + /// If you do not perform full validation, invalid shaders may + /// cause Naga to panic. If you do perform full validation and + /// [`Validator::validate`] returns `Ok`, then Naga promises that + /// code generation will either succeed or return an error; it + /// should never panic. + /// + /// The default value for `ValidationFlags` is + /// `ValidationFlags::all()`. + #[cfg_attr(feature = "serialize", derive(serde::Serialize))] + #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct ValidationFlags: u8 { + /// Expressions. + const EXPRESSIONS = 0x1; + /// Statements and blocks of them. + const BLOCKS = 0x2; + /// Uniformity of control flow for operations that require it. + const CONTROL_FLOW_UNIFORMITY = 0x4; + /// Host-shareable structure layouts. + const STRUCT_LAYOUTS = 0x8; + /// Constants. + const CONSTANTS = 0x10; + /// Group, binding, and location attributes. + const BINDINGS = 0x20; + } +} + +impl Default for ValidationFlags { + fn default() -> Self { + Self::all() + } +} + +bitflags::bitflags! { + /// Allowed IR capabilities. + #[must_use] + #[cfg_attr(feature = "serialize", derive(serde::Serialize))] + #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct Capabilities: u16 { + /// Support for [`AddressSpace:PushConstant`]. + const PUSH_CONSTANT = 0x1; + /// Float values with width = 8. + const FLOAT64 = 0x2; + /// Support for [`Builtin:PrimitiveIndex`]. + const PRIMITIVE_INDEX = 0x4; + /// Support for non-uniform indexing of sampled textures and storage buffer arrays. + const SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING = 0x8; + /// Support for non-uniform indexing of uniform buffers and storage texture arrays. + const UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING = 0x10; + /// Support for non-uniform indexing of samplers. + const SAMPLER_NON_UNIFORM_INDEXING = 0x20; + /// Support for [`Builtin::ClipDistance`]. + const CLIP_DISTANCE = 0x40; + /// Support for [`Builtin::CullDistance`]. + const CULL_DISTANCE = 0x80; + /// Support for 16-bit normalized storage texture formats. + const STORAGE_TEXTURE_16BIT_NORM_FORMATS = 0x100; + /// Support for [`BuiltIn::ViewIndex`]. + const MULTIVIEW = 0x200; + /// Support for `early_depth_test`. + const EARLY_DEPTH_TEST = 0x400; + /// Support for [`Builtin::SampleIndex`] and [`Sampling::Sample`]. + const MULTISAMPLED_SHADING = 0x800; + /// Support for ray queries and acceleration structures. + const RAY_QUERY = 0x1000; + /// Support for generating two sources for blending from fragement shaders. + const DUAL_SOURCE_BLENDING = 0x2000; + /// Support for arrayed cube textures. + const CUBE_ARRAY_TEXTURES = 0x4000; + } +} + +impl Default for Capabilities { + fn default() -> Self { + Self::MULTISAMPLED_SHADING | Self::CUBE_ARRAY_TEXTURES + } +} + +bitflags::bitflags! { + /// Validation flags. + #[cfg_attr(feature = "serialize", derive(serde::Serialize))] + #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct ShaderStages: u8 { + const VERTEX = 0x1; + const FRAGMENT = 0x2; + const COMPUTE = 0x4; + } +} + +#[derive(Debug)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct ModuleInfo { + type_flags: Vec, + functions: Vec, + entry_points: Vec, + const_expression_types: Box<[TypeResolution]>, +} + +impl ops::Index> for ModuleInfo { + type Output = TypeFlags; + fn index(&self, handle: Handle) -> &Self::Output { + &self.type_flags[handle.index()] + } +} + +impl ops::Index> for ModuleInfo { + type Output = FunctionInfo; + fn index(&self, handle: Handle) -> &Self::Output { + &self.functions[handle.index()] + } +} + +impl ops::Index> for ModuleInfo { + type Output = TypeResolution; + fn index(&self, handle: Handle) -> &Self::Output { + &self.const_expression_types[handle.index()] + } +} + +#[derive(Debug)] +pub struct Validator { + flags: ValidationFlags, + capabilities: Capabilities, + types: Vec, + layouter: Layouter, + location_mask: BitSet, + ep_resource_bindings: FastHashSet, + #[allow(dead_code)] + switch_values: FastHashSet, + valid_expression_list: Vec>, + valid_expression_set: BitSet, +} + +#[derive(Clone, Debug, thiserror::Error)] +pub enum ConstantError { + #[error("The type doesn't match the constant")] + InvalidType, + #[error("The type is not constructible")] + NonConstructibleType, +} + +#[derive(Clone, Debug, thiserror::Error)] +pub enum ValidationError { + #[error(transparent)] + InvalidHandle(#[from] InvalidHandleError), + #[error(transparent)] + Layouter(#[from] LayoutError), + #[error("Type {handle:?} '{name}' is invalid")] + Type { + handle: Handle, + name: String, + source: TypeError, + }, + #[error("Constant expression {handle:?} is invalid")] + ConstExpression { + handle: Handle, + source: ConstExpressionError, + }, + #[error("Constant {handle:?} '{name}' is invalid")] + Constant { + handle: Handle, + name: String, + source: ConstantError, + }, + #[error("Global variable {handle:?} '{name}' is invalid")] + GlobalVariable { + handle: Handle, + name: String, + source: GlobalVariableError, + }, + #[error("Function {handle:?} '{name}' is invalid")] + Function { + handle: Handle, + name: String, + source: FunctionError, + }, + #[error("Entry point {name} at {stage:?} is invalid")] + EntryPoint { + stage: crate::ShaderStage, + name: String, + source: EntryPointError, + }, + #[error("Module is corrupted")] + Corrupted, +} + +impl crate::TypeInner { + const fn is_sized(&self) -> bool { + match *self { + Self::Scalar { .. } + | Self::Vector { .. } + | Self::Matrix { .. } + | Self::Array { + size: crate::ArraySize::Constant(_), + .. + } + | Self::Atomic { .. } + | Self::Pointer { .. } + | Self::ValuePointer { .. } + | Self::Struct { .. } => true, + Self::Array { .. } + | Self::Image { .. } + | Self::Sampler { .. } + | Self::AccelerationStructure + | Self::RayQuery + | Self::BindingArray { .. } => false, + } + } + + /// Return the `ImageDimension` for which `self` is an appropriate coordinate. + const fn image_storage_coordinates(&self) -> Option { + match *self { + Self::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + .. + }) => Some(crate::ImageDimension::D1), + Self::Vector { + size: crate::VectorSize::Bi, + scalar: + crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + .. + }, + } => Some(crate::ImageDimension::D2), + Self::Vector { + size: crate::VectorSize::Tri, + scalar: + crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + .. + }, + } => Some(crate::ImageDimension::D3), + _ => None, + } + } +} + +impl Validator { + /// Construct a new validator instance. + pub fn new(flags: ValidationFlags, capabilities: Capabilities) -> Self { + Validator { + flags, + capabilities, + types: Vec::new(), + layouter: Layouter::default(), + location_mask: BitSet::new(), + ep_resource_bindings: FastHashSet::default(), + switch_values: FastHashSet::default(), + valid_expression_list: Vec::new(), + valid_expression_set: BitSet::new(), + } + } + + /// Reset the validator internals + pub fn reset(&mut self) { + self.types.clear(); + self.layouter.clear(); + self.location_mask.clear(); + self.ep_resource_bindings.clear(); + self.switch_values.clear(); + self.valid_expression_list.clear(); + self.valid_expression_set.clear(); + } + + fn validate_constant( + &self, + handle: Handle, + gctx: crate::proc::GlobalCtx, + mod_info: &ModuleInfo, + ) -> Result<(), ConstantError> { + let con = &gctx.constants[handle]; + + let type_info = &self.types[con.ty.index()]; + if !type_info.flags.contains(TypeFlags::CONSTRUCTIBLE) { + return Err(ConstantError::NonConstructibleType); + } + + let decl_ty = &gctx.types[con.ty].inner; + let init_ty = mod_info[con.init].inner_with(gctx.types); + if !decl_ty.equivalent(init_ty, gctx.types) { + return Err(ConstantError::InvalidType); + } + + Ok(()) + } + + /// Check the given module to be valid. + pub fn validate( + &mut self, + module: &crate::Module, + ) -> Result> { + self.reset(); + self.reset_types(module.types.len()); + + Self::validate_module_handles(module).map_err(|e| e.with_span())?; + + self.layouter.update(module.to_ctx()).map_err(|e| { + let handle = e.ty; + ValidationError::from(e).with_span_handle(handle, &module.types) + })?; + + // These should all get overwritten. + let placeholder = TypeResolution::Value(crate::TypeInner::Scalar(crate::Scalar { + kind: crate::ScalarKind::Bool, + width: 0, + })); + + let mut mod_info = ModuleInfo { + type_flags: Vec::with_capacity(module.types.len()), + functions: Vec::with_capacity(module.functions.len()), + entry_points: Vec::with_capacity(module.entry_points.len()), + const_expression_types: vec![placeholder; module.const_expressions.len()] + .into_boxed_slice(), + }; + + for (handle, ty) in module.types.iter() { + let ty_info = self + .validate_type(handle, module.to_ctx()) + .map_err(|source| { + ValidationError::Type { + handle, + name: ty.name.clone().unwrap_or_default(), + source, + } + .with_span_handle(handle, &module.types) + })?; + mod_info.type_flags.push(ty_info.flags); + self.types[handle.index()] = ty_info; + } + + { + let t = crate::Arena::new(); + let resolve_context = crate::proc::ResolveContext::with_locals(module, &t, &[]); + for (handle, _) in module.const_expressions.iter() { + mod_info + .process_const_expression(handle, &resolve_context, module.to_ctx()) + .map_err(|source| { + ValidationError::ConstExpression { handle, source } + .with_span_handle(handle, &module.const_expressions) + })? + } + } + + if self.flags.contains(ValidationFlags::CONSTANTS) { + for (handle, _) in module.const_expressions.iter() { + self.validate_const_expression(handle, module.to_ctx(), &mod_info) + .map_err(|source| { + ValidationError::ConstExpression { handle, source } + .with_span_handle(handle, &module.const_expressions) + })? + } + + for (handle, constant) in module.constants.iter() { + self.validate_constant(handle, module.to_ctx(), &mod_info) + .map_err(|source| { + ValidationError::Constant { + handle, + name: constant.name.clone().unwrap_or_default(), + source, + } + .with_span_handle(handle, &module.constants) + })? + } + } + + for (var_handle, var) in module.global_variables.iter() { + self.validate_global_var(var, module.to_ctx(), &mod_info) + .map_err(|source| { + ValidationError::GlobalVariable { + handle: var_handle, + name: var.name.clone().unwrap_or_default(), + source, + } + .with_span_handle(var_handle, &module.global_variables) + })?; + } + + for (handle, fun) in module.functions.iter() { + match self.validate_function(fun, module, &mod_info, false) { + Ok(info) => mod_info.functions.push(info), + Err(error) => { + return Err(error.and_then(|source| { + ValidationError::Function { + handle, + name: fun.name.clone().unwrap_or_default(), + source, + } + .with_span_handle(handle, &module.functions) + })) + } + } + } + + let mut ep_map = FastHashSet::default(); + for ep in module.entry_points.iter() { + if !ep_map.insert((ep.stage, &ep.name)) { + return Err(ValidationError::EntryPoint { + stage: ep.stage, + name: ep.name.clone(), + source: EntryPointError::Conflict, + } + .with_span()); // TODO: keep some EP span information? + } + + match self.validate_entry_point(ep, module, &mod_info) { + Ok(info) => mod_info.entry_points.push(info), + Err(error) => { + return Err(error.and_then(|source| { + ValidationError::EntryPoint { + stage: ep.stage, + name: ep.name.clone(), + source, + } + .with_span() + })); + } + } + } + + Ok(mod_info) + } +} + +fn validate_atomic_compare_exchange_struct( + types: &crate::UniqueArena, + members: &[crate::StructMember], + scalar_predicate: impl FnOnce(&crate::TypeInner) -> bool, +) -> bool { + members.len() == 2 + && members[0].name.as_deref() == Some("old_value") + && scalar_predicate(&types[members[0].ty].inner) + && members[1].name.as_deref() == Some("exchanged") + && types[members[1].ty].inner == crate::TypeInner::Scalar(crate::Scalar::BOOL) +} diff --git a/naga/src/valid/type.rs b/naga/src/valid/type.rs new file mode 100644 index 0000000000..1e3e03fe19 --- /dev/null +++ b/naga/src/valid/type.rs @@ -0,0 +1,643 @@ +use super::Capabilities; +use crate::{arena::Handle, proc::Alignment}; + +bitflags::bitflags! { + /// Flags associated with [`Type`]s by [`Validator`]. + /// + /// [`Type`]: crate::Type + /// [`Validator`]: crate::valid::Validator + #[cfg_attr(feature = "serialize", derive(serde::Serialize))] + #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] + #[repr(transparent)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct TypeFlags: u8 { + /// Can be used for data variables. + /// + /// This flag is required on types of local variables, function + /// arguments, array elements, and struct members. + /// + /// This includes all types except `Image`, `Sampler`, + /// and some `Pointer` types. + const DATA = 0x1; + + /// The data type has a size known by pipeline creation time. + /// + /// Unsized types are quite restricted. The only unsized types permitted + /// by Naga, other than the non-[`DATA`] types like [`Image`] and + /// [`Sampler`], are dynamically-sized [`Array`s], and [`Struct`s] whose + /// last members are such arrays. See the documentation for those types + /// for details. + /// + /// [`DATA`]: TypeFlags::DATA + /// [`Image`]: crate::Type::Image + /// [`Sampler`]: crate::Type::Sampler + /// [`Array`]: crate::Type::Array + /// [`Struct`]: crate::Type::struct + const SIZED = 0x2; + + /// The data can be copied around. + const COPY = 0x4; + + /// Can be be used for user-defined IO between pipeline stages. + /// + /// This covers anything that can be in [`Location`] binding: + /// non-bool scalars and vectors, matrices, and structs and + /// arrays containing only interface types. + const IO_SHAREABLE = 0x8; + + /// Can be used for host-shareable structures. + const HOST_SHAREABLE = 0x10; + + /// This type can be passed as a function argument. + const ARGUMENT = 0x40; + + /// A WGSL [constructible] type. + /// + /// The constructible types are scalars, vectors, matrices, fixed-size + /// arrays of constructible types, and structs whose members are all + /// constructible. + /// + /// [constructible]: https://gpuweb.github.io/gpuweb/wgsl/#constructible + const CONSTRUCTIBLE = 0x80; + } +} + +#[derive(Clone, Copy, Debug, thiserror::Error)] +pub enum Disalignment { + #[error("The array stride {stride} is not a multiple of the required alignment {alignment}")] + ArrayStride { stride: u32, alignment: Alignment }, + #[error("The struct span {span}, is not a multiple of the required alignment {alignment}")] + StructSpan { span: u32, alignment: Alignment }, + #[error("The struct member[{index}] offset {offset} is not a multiple of the required alignment {alignment}")] + MemberOffset { + index: u32, + offset: u32, + alignment: Alignment, + }, + #[error("The struct member[{index}] offset {offset} must be at least {expected}")] + MemberOffsetAfterStruct { + index: u32, + offset: u32, + expected: u32, + }, + #[error("The struct member[{index}] is not statically sized")] + UnsizedMember { index: u32 }, + #[error("The type is not host-shareable")] + NonHostShareable, +} + +#[derive(Clone, Debug, thiserror::Error)] +pub enum TypeError { + #[error("Capability {0:?} is required")] + MissingCapability(Capabilities), + #[error("The {0:?} scalar width {1} is not supported for an atomic")] + InvalidAtomicWidth(crate::ScalarKind, crate::Bytes), + #[error("Invalid type for pointer target {0:?}")] + InvalidPointerBase(Handle), + #[error("Unsized types like {base:?} must be in the `Storage` address space, not `{space:?}`")] + InvalidPointerToUnsized { + base: Handle, + space: crate::AddressSpace, + }, + #[error("Expected data type, found {0:?}")] + InvalidData(Handle), + #[error("Base type {0:?} for the array is invalid")] + InvalidArrayBaseType(Handle), + #[error("Matrix elements must always be floating-point types")] + MatrixElementNotFloat, + #[error("The constant {0:?} is specialized, and cannot be used as an array size")] + UnsupportedSpecializedArrayLength(Handle), + #[error("Array stride {stride} does not match the expected {expected}")] + InvalidArrayStride { stride: u32, expected: u32 }, + #[error("Field '{0}' can't be dynamically-sized, has type {1:?}")] + InvalidDynamicArray(String, Handle), + #[error("The base handle {0:?} has to be a struct")] + BindingArrayBaseTypeNotStruct(Handle), + #[error("Structure member[{index}] at {offset} overlaps the previous member")] + MemberOverlap { index: u32, offset: u32 }, + #[error( + "Structure member[{index}] at {offset} and size {size} crosses the structure boundary of size {span}" + )] + MemberOutOfBounds { + index: u32, + offset: u32, + size: u32, + span: u32, + }, + #[error("Structure types must have at least one member")] + EmptyStruct, + #[error(transparent)] + WidthError(#[from] WidthError), +} + +#[derive(Clone, Debug, thiserror::Error)] +#[cfg_attr(test, derive(PartialEq))] +pub enum WidthError { + #[error("The {0:?} scalar width {1} is not supported")] + Invalid(crate::ScalarKind, crate::Bytes), + #[error("Using `{name}` values requires the `naga::valid::Capabilities::{flag}` flag")] + MissingCapability { + name: &'static str, + flag: &'static str, + }, + + #[error("64-bit integers are not yet supported")] + Unsupported64Bit, + + #[error("Abstract types may only appear in constant expressions")] + Abstract, +} + +// Only makes sense if `flags.contains(HOST_SHAREABLE)` +type LayoutCompatibility = Result, Disalignment)>; + +fn check_member_layout( + accum: &mut LayoutCompatibility, + member: &crate::StructMember, + member_index: u32, + member_layout: LayoutCompatibility, + parent_handle: Handle, +) { + *accum = match (*accum, member_layout) { + (Ok(cur_alignment), Ok(alignment)) => { + if alignment.is_aligned(member.offset) { + Ok(cur_alignment.max(alignment)) + } else { + Err(( + parent_handle, + Disalignment::MemberOffset { + index: member_index, + offset: member.offset, + alignment, + }, + )) + } + } + (Err(e), _) | (_, Err(e)) => Err(e), + }; +} + +/// Determine whether a pointer in `space` can be passed as an argument. +/// +/// If a pointer in `space` is permitted to be passed as an argument to a +/// user-defined function, return `TypeFlags::ARGUMENT`. Otherwise, return +/// `TypeFlags::empty()`. +/// +/// Pointers passed as arguments to user-defined functions must be in the +/// `Function` or `Private` address space. +const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags { + use crate::AddressSpace as As; + match space { + As::Function | As::Private => TypeFlags::ARGUMENT, + As::Uniform | As::Storage { .. } | As::Handle | As::PushConstant | As::WorkGroup => { + TypeFlags::empty() + } + } +} + +#[derive(Clone, Debug)] +pub(super) struct TypeInfo { + pub flags: TypeFlags, + pub uniform_layout: LayoutCompatibility, + pub storage_layout: LayoutCompatibility, +} + +impl TypeInfo { + const fn dummy() -> Self { + TypeInfo { + flags: TypeFlags::empty(), + uniform_layout: Ok(Alignment::ONE), + storage_layout: Ok(Alignment::ONE), + } + } + + const fn new(flags: TypeFlags, alignment: Alignment) -> Self { + TypeInfo { + flags, + uniform_layout: Ok(alignment), + storage_layout: Ok(alignment), + } + } +} + +impl super::Validator { + const fn require_type_capability(&self, capability: Capabilities) -> Result<(), TypeError> { + if self.capabilities.contains(capability) { + Ok(()) + } else { + Err(TypeError::MissingCapability(capability)) + } + } + + pub(super) const fn check_width(&self, scalar: crate::Scalar) -> Result<(), WidthError> { + let good = match scalar.kind { + crate::ScalarKind::Bool => scalar.width == crate::BOOL_WIDTH, + crate::ScalarKind::Float => { + if scalar.width == 8 { + if !self.capabilities.contains(Capabilities::FLOAT64) { + return Err(WidthError::MissingCapability { + name: "f64", + flag: "FLOAT64", + }); + } + true + } else { + scalar.width == 4 + } + } + crate::ScalarKind::Sint | crate::ScalarKind::Uint => { + if scalar.width == 8 { + return Err(WidthError::Unsupported64Bit); + } + scalar.width == 4 + } + crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => { + return Err(WidthError::Abstract); + } + }; + if good { + Ok(()) + } else { + Err(WidthError::Invalid(scalar.kind, scalar.width)) + } + } + + pub(super) fn reset_types(&mut self, size: usize) { + self.types.clear(); + self.types.resize(size, TypeInfo::dummy()); + self.layouter.clear(); + } + + pub(super) fn validate_type( + &self, + handle: Handle, + gctx: crate::proc::GlobalCtx, + ) -> Result { + use crate::TypeInner as Ti; + Ok(match gctx.types[handle].inner { + Ti::Scalar(scalar) => { + self.check_width(scalar)?; + let shareable = if scalar.kind.is_numeric() { + TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE + } else { + TypeFlags::empty() + }; + TypeInfo::new( + TypeFlags::DATA + | TypeFlags::SIZED + | TypeFlags::COPY + | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE + | shareable, + Alignment::from_width(scalar.width), + ) + } + Ti::Vector { size, scalar } => { + self.check_width(scalar)?; + let shareable = if scalar.kind.is_numeric() { + TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE + } else { + TypeFlags::empty() + }; + TypeInfo::new( + TypeFlags::DATA + | TypeFlags::SIZED + | TypeFlags::COPY + | TypeFlags::HOST_SHAREABLE + | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE + | shareable, + Alignment::from(size) * Alignment::from_width(scalar.width), + ) + } + Ti::Matrix { + columns: _, + rows, + scalar, + } => { + if scalar.kind != crate::ScalarKind::Float { + return Err(TypeError::MatrixElementNotFloat); + } + self.check_width(scalar)?; + TypeInfo::new( + TypeFlags::DATA + | TypeFlags::SIZED + | TypeFlags::COPY + | TypeFlags::HOST_SHAREABLE + | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE, + Alignment::from(rows) * Alignment::from_width(scalar.width), + ) + } + Ti::Atomic(crate::Scalar { kind, width }) => { + let good = match kind { + crate::ScalarKind::Bool + | crate::ScalarKind::Float + | crate::ScalarKind::AbstractInt + | crate::ScalarKind::AbstractFloat => false, + crate::ScalarKind::Sint | crate::ScalarKind::Uint => width == 4, + }; + if !good { + return Err(TypeError::InvalidAtomicWidth(kind, width)); + } + TypeInfo::new( + TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE, + Alignment::from_width(width), + ) + } + Ti::Pointer { base, space } => { + use crate::AddressSpace as As; + + let base_info = &self.types[base.index()]; + if !base_info.flags.contains(TypeFlags::DATA) { + return Err(TypeError::InvalidPointerBase(base)); + } + + // Runtime-sized values can only live in the `Storage` address + // space, so it's useless to have a pointer to such a type in + // any other space. + // + // Detecting this problem here prevents the definition of + // functions like: + // + // fn f(p: ptr) -> ... { ... } + // + // which would otherwise be permitted, but uncallable. (They + // may also present difficulties in code generation). + if !base_info.flags.contains(TypeFlags::SIZED) { + match space { + As::Storage { .. } => {} + _ => { + return Err(TypeError::InvalidPointerToUnsized { base, space }); + } + } + } + + // `Validator::validate_function` actually checks the address + // space of pointer arguments explicitly before checking the + // `ARGUMENT` flag, to give better error messages. But it seems + // best to set `ARGUMENT` accurately anyway. + let argument_flag = ptr_space_argument_flag(space); + + // Pointers cannot be stored in variables, structure members, or + // array elements, so we do not mark them as `DATA`. + TypeInfo::new( + argument_flag | TypeFlags::SIZED | TypeFlags::COPY, + Alignment::ONE, + ) + } + Ti::ValuePointer { + size: _, + scalar, + space, + } => { + // ValuePointer should be treated the same way as the equivalent + // Pointer / Scalar / Vector combination, so each step in those + // variants' match arms should have a counterpart here. + // + // However, some cases are trivial: All our implicit base types + // are DATA and SIZED, so we can never return + // `InvalidPointerBase` or `InvalidPointerToUnsized`. + self.check_width(scalar)?; + + // `Validator::validate_function` actually checks the address + // space of pointer arguments explicitly before checking the + // `ARGUMENT` flag, to give better error messages. But it seems + // best to set `ARGUMENT` accurately anyway. + let argument_flag = ptr_space_argument_flag(space); + + // Pointers cannot be stored in variables, structure members, or + // array elements, so we do not mark them as `DATA`. + TypeInfo::new( + argument_flag | TypeFlags::SIZED | TypeFlags::COPY, + Alignment::ONE, + ) + } + Ti::Array { base, size, stride } => { + let base_info = &self.types[base.index()]; + if !base_info.flags.contains(TypeFlags::DATA | TypeFlags::SIZED) { + return Err(TypeError::InvalidArrayBaseType(base)); + } + + let base_layout = self.layouter[base]; + let general_alignment = base_layout.alignment; + let uniform_layout = match base_info.uniform_layout { + Ok(base_alignment) => { + let alignment = base_alignment + .max(general_alignment) + .max(Alignment::MIN_UNIFORM); + if alignment.is_aligned(stride) { + Ok(alignment) + } else { + Err((handle, Disalignment::ArrayStride { stride, alignment })) + } + } + Err(e) => Err(e), + }; + let storage_layout = match base_info.storage_layout { + Ok(base_alignment) => { + let alignment = base_alignment.max(general_alignment); + if alignment.is_aligned(stride) { + Ok(alignment) + } else { + Err((handle, Disalignment::ArrayStride { stride, alignment })) + } + } + Err(e) => Err(e), + }; + + let type_info_mask = match size { + crate::ArraySize::Constant(_) => { + TypeFlags::DATA + | TypeFlags::SIZED + | TypeFlags::COPY + | TypeFlags::HOST_SHAREABLE + | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE + } + crate::ArraySize::Dynamic => { + // Non-SIZED types may only appear as the last element of a structure. + // This is enforced by checks for SIZED-ness for all compound types, + // and a special case for structs. + TypeFlags::DATA | TypeFlags::COPY | TypeFlags::HOST_SHAREABLE + } + }; + + TypeInfo { + flags: base_info.flags & type_info_mask, + uniform_layout, + storage_layout, + } + } + Ti::Struct { ref members, span } => { + if members.is_empty() { + return Err(TypeError::EmptyStruct); + } + + let mut ti = TypeInfo::new( + TypeFlags::DATA + | TypeFlags::SIZED + | TypeFlags::COPY + | TypeFlags::HOST_SHAREABLE + | TypeFlags::IO_SHAREABLE + | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE, + Alignment::ONE, + ); + ti.uniform_layout = Ok(Alignment::MIN_UNIFORM); + + let mut min_offset = 0; + + let mut prev_struct_data: Option<(u32, u32)> = None; + + for (i, member) in members.iter().enumerate() { + let base_info = &self.types[member.ty.index()]; + if !base_info.flags.contains(TypeFlags::DATA) { + return Err(TypeError::InvalidData(member.ty)); + } + if !base_info.flags.contains(TypeFlags::HOST_SHAREABLE) { + if ti.uniform_layout.is_ok() { + ti.uniform_layout = Err((member.ty, Disalignment::NonHostShareable)); + } + if ti.storage_layout.is_ok() { + ti.storage_layout = Err((member.ty, Disalignment::NonHostShareable)); + } + } + ti.flags &= base_info.flags; + + if member.offset < min_offset { + // HACK: this could be nicer. We want to allow some structures + // to not bother with offsets/alignments if they are never + // used for host sharing. + if member.offset == 0 { + ti.flags.set(TypeFlags::HOST_SHAREABLE, false); + } else { + return Err(TypeError::MemberOverlap { + index: i as u32, + offset: member.offset, + }); + } + } + + let base_size = gctx.types[member.ty].inner.size(gctx); + min_offset = member.offset + base_size; + if min_offset > span { + return Err(TypeError::MemberOutOfBounds { + index: i as u32, + offset: member.offset, + size: base_size, + span, + }); + } + + check_member_layout( + &mut ti.uniform_layout, + member, + i as u32, + base_info.uniform_layout, + handle, + ); + check_member_layout( + &mut ti.storage_layout, + member, + i as u32, + base_info.storage_layout, + handle, + ); + + // Validate rule: If a structure member itself has a structure type S, + // then the number of bytes between the start of that member and + // the start of any following member must be at least roundUp(16, SizeOf(S)). + if let Some((span, offset)) = prev_struct_data { + let diff = member.offset - offset; + let min = Alignment::MIN_UNIFORM.round_up(span); + if diff < min { + ti.uniform_layout = Err(( + handle, + Disalignment::MemberOffsetAfterStruct { + index: i as u32, + offset: member.offset, + expected: offset + min, + }, + )); + } + }; + + prev_struct_data = match gctx.types[member.ty].inner { + crate::TypeInner::Struct { span, .. } => Some((span, member.offset)), + _ => None, + }; + + // The last field may be an unsized array. + if !base_info.flags.contains(TypeFlags::SIZED) { + let is_array = match gctx.types[member.ty].inner { + crate::TypeInner::Array { .. } => true, + _ => false, + }; + if !is_array || i + 1 != members.len() { + let name = member.name.clone().unwrap_or_default(); + return Err(TypeError::InvalidDynamicArray(name, member.ty)); + } + if ti.uniform_layout.is_ok() { + ti.uniform_layout = + Err((handle, Disalignment::UnsizedMember { index: i as u32 })); + } + } + } + + let alignment = self.layouter[handle].alignment; + if !alignment.is_aligned(span) { + ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment })); + ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment })); + } + + ti + } + Ti::Image { + dim, + arrayed, + class: _, + } => { + if arrayed && matches!(dim, crate::ImageDimension::Cube) { + self.require_type_capability(Capabilities::CUBE_ARRAY_TEXTURES)?; + } + TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE) + } + Ti::Sampler { .. } => TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE), + Ti::AccelerationStructure => { + self.require_type_capability(Capabilities::RAY_QUERY)?; + TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE) + } + Ti::RayQuery => { + self.require_type_capability(Capabilities::RAY_QUERY)?; + TypeInfo::new( + TypeFlags::DATA | TypeFlags::CONSTRUCTIBLE | TypeFlags::SIZED, + Alignment::ONE, + ) + } + Ti::BindingArray { base, size } => { + if base >= handle { + return Err(TypeError::InvalidArrayBaseType(base)); + } + let type_info_mask = match size { + crate::ArraySize::Constant(_) => TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE, + crate::ArraySize::Dynamic => { + // Final type is non-sized + TypeFlags::HOST_SHAREABLE + } + }; + let base_info = &self.types[base.index()]; + + if base_info.flags.contains(TypeFlags::DATA) { + // Currently Naga only supports binding arrays of structs for non-handle types. + match gctx.types[base].inner { + crate::TypeInner::Struct { .. } => {} + _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)), + }; + } + + TypeInfo::new(base_info.flags & type_info_mask, Alignment::ONE) + } + }) + } +} diff --git a/tests/tests/example_wgsl.rs b/naga/tests/example_wgsl.rs similarity index 96% rename from tests/tests/example_wgsl.rs rename to naga/tests/example_wgsl.rs index d645dd4ed3..115d8769a7 100644 --- a/tests/tests/example_wgsl.rs +++ b/naga/tests/example_wgsl.rs @@ -1,9 +1,11 @@ +#![cfg(feature = "wgsl-in")] + use naga::{front::wgsl, valid::Validator}; use std::{fs, path::PathBuf}; /// Runs through all example shaders and ensures they are valid wgsl. #[test] -fn parse_example_wgsl() { +pub fn parse_example_wgsl() { let read_dir = match PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("examples") .read_dir() diff --git a/naga/tests/in/abstract-types-const.wgsl b/naga/tests/in/abstract-types-const.wgsl new file mode 100644 index 0000000000..0cf20b5d26 --- /dev/null +++ b/naga/tests/in/abstract-types-const.wgsl @@ -0,0 +1,81 @@ +// i/x: type inferred / explicit +// vX/mX/aX: vector / matrix / array of X +// where X: u/i/f: u32 / i32 / f32 +// s: vector splat +// r: vector spread (vector arg to vector constructor) +// p: "partial" constructor (type parameter inferred) +// u/i/f/ai/af: u32 / i32 / f32 / abstract float / abstract integer as parameter +// _: just for alignment + +// Ensure that: +// - the inferred type is correct. +// - all parameters' types are considered. +// - all parameters are converted to the consensus type. + +const xvupaiai: vec2 = vec2(42, 43); +const xvfpaiai: vec2 = vec2(44, 45); + +const xvupuai: vec2 = vec2(42u, 43); +const xvupaiu: vec2 = vec2(42, 43u); + +const xvuuai: vec2 = vec2(42u, 43); +const xvuaiu: vec2 = vec2(42, 43u); + +const xmfpaiaiaiai: mat2x2 = mat2x2(1, 2, 3, 4); +const xmfpafaiaiai: mat2x2 = mat2x2(1.0, 2, 3, 4); +const xmfpaiafaiai: mat2x2 = mat2x2(1, 2.0, 3, 4); +const xmfpaiaiafai: mat2x2 = mat2x2(1, 2, 3.0, 4); +const xmfpaiaiaiaf: mat2x2 = mat2x2(1, 2, 3, 4.0); + +const imfpaiaiaiai = mat2x2(1, 2, 3, 4); +const imfpafaiaiai = mat2x2(1.0, 2, 3, 4); +const imfpafafafaf = mat2x2(1.0, 2.0, 3.0, 4.0); + +const ivispai = vec2(1); +const ivfspaf = vec2(1.0); +const ivis_ai = vec2(1); +const ivus_ai = vec2(1); +const ivfs_ai = vec2(1); +const ivfs_af = vec2(1.0); + +const iafafaf = array(1.0, 2.0); +const iafaiai = array(1, 2); + +const iafpafaf = array(1.0, 2.0); +const iafpaiaf = array(1, 2.0); +const iafpafai = array(1.0, 2); +const xafpafaf: array = array(1.0, 2.0); + +struct S { + f: f32, + i: i32, + u: u32, +} + +const s_f_i_u: S = S(1.0f, 1i, 1u); +const s_f_iai: S = S(1.0f, 1i, 1); +const s_fai_u: S = S(1.0f, 1, 1u); +const s_faiai: S = S(1.0f, 1, 1); +const saf_i_u: S = S(1.0, 1i, 1u); +const saf_iai: S = S(1.0, 1i, 1); +const safai_u: S = S(1.0, 1, 1u); +const safaiai: S = S(1.0, 1, 1); + +// Vector construction with spreads +const ivfr_f__f = vec3(vec2(1.0f, 2.0f), 3.0f); +const ivfr_f_af = vec3(vec2(1.0f, 2.0f), 3.0 ); +const ivfraf__f = vec3(vec2 (1.0 , 2.0 ), 3.0f); +const ivfraf_af = vec3(vec2 (1.0 , 2.0 ), 3.0 ); + +const ivf__fr_f = vec3(1.0f, vec2(2.0f, 3.0f)); +const ivf__fraf = vec3(1.0f, vec2 (2.0 , 3.0 )); +const ivf_afr_f = vec3(1.0 , vec2(2.0f, 3.0f)); +const ivf_afraf = vec3(1.0 , vec2 (2.0 , 3.0 )); + +const ivfr_f_ai = vec3(vec2(1.0f, 2.0f), 3 ); +const ivfrai__f = vec3(vec2 (1 , 2 ), 3.0f); +const ivfrai_ai = vec3(vec2 (1 , 2 ), 3 ); + +const ivf__frai = vec3(1.0f, vec2 (2 , 3 )); +const ivf_air_f = vec3(1 , vec2(2.0f, 3.0f)); +const ivf_airai = vec3(1 , vec2 (2 , 3 )); diff --git a/naga/tests/in/abstract-types-operators.wgsl b/naga/tests/in/abstract-types-operators.wgsl new file mode 100644 index 0000000000..25f9236426 --- /dev/null +++ b/naga/tests/in/abstract-types-operators.wgsl @@ -0,0 +1,69 @@ +const plus_fafaf: f32 = 1.0 + 2.0; +const plus_fafai: f32 = 1.0 + 2; +const plus_faf_f: f32 = 1.0 + 2f; +const plus_faiaf: f32 = 1 + 2.0; +const plus_faiai: f32 = 1 + 2; +const plus_fai_f: f32 = 1 + 2f; +const plus_f_faf: f32 = 1f + 2.0; +const plus_f_fai: f32 = 1f + 2; +const plus_f_f_f: f32 = 1f + 2f; + +const plus_iaiai: i32 = 1 + 2; +const plus_iai_i: i32 = 1 + 2i; +const plus_i_iai: i32 = 1i + 2; +const plus_i_i_i: i32 = 1i + 2i; + +const plus_uaiai: u32 = 1 + 2; +const plus_uai_u: u32 = 1 + 2u; +const plus_u_uai: u32 = 1u + 2; +const plus_u_u_u: u32 = 1u + 2u; + +const bitflip_u_u: u32 = ~0xffffffffu; +const bitflip_uai: u32 = ~0xffffffff & (0x100000000 - 1); + +const least_i32: i32 = -2147483648; +const least_f32: f32 = -3.40282347e+38; + +fn runtime_values() { + var f: f32 = 42; + var i: i32 = 43; + var u: u32 = 44; + + var plus_fafaf: f32 = 1.0 + 2.0; + var plus_fafai: f32 = 1.0 + 2; + var plus_faf_f: f32 = 1.0 + f; + var plus_faiaf: f32 = 1 + 2.0; + var plus_faiai: f32 = 1 + 2; + var plus_fai_f: f32 = 1 + f; + var plus_f_faf: f32 = f + 2.0; + var plus_f_fai: f32 = f + 2; + var plus_f_f_f: f32 = f + f; + + var plus_iaiai: i32 = 1 + 2; + var plus_iai_i: i32 = 1 + i; + var plus_i_iai: i32 = i + 2; + var plus_i_i_i: i32 = i + i; + + var plus_uaiai: u32 = 1 + 2; + var plus_uai_u: u32 = 1 + u; + var plus_u_uai: u32 = u + 2; + var plus_u_u_u: u32 = u + u; +} + +fn wgpu_4445() { + // This ok: + let a = (3.0*2.0-(1.0)) * 1.0; + let b = (3.0*2.0+1.0) * 1.0; + // This fails: + let c = (3.0*2.0-1.0) * 1.0; +} + +const wgpu_4492 = i32(-0x80000000); +const wgpu_4492_2 = -2147483648; + +var a: array; + +fn wgpu_4435() { + let x = 1; + let y = a[x-1]; +} diff --git a/naga/tests/in/abstract-types-var.wgsl b/naga/tests/in/abstract-types-var.wgsl new file mode 100644 index 0000000000..a733888530 --- /dev/null +++ b/naga/tests/in/abstract-types-var.wgsl @@ -0,0 +1,120 @@ +// i/x: type inferred / explicit +// vX/mX/aX: vector / matrix / array of X +// where X: u/i/f: u32 / i32 / f32 +// s: vector splat +// r: vector spread (vector arg to vector constructor) +// p: "partial" constructor (type parameter inferred) +// u/i/f/ai/af: u32 / i32 / f32 / abstract float / abstract integer as parameter +// _: just for alignment + +// Ensure that: +// - the inferred type is correct. +// - all parameters' types are considered. +// - all parameters are converted to the consensus type. + +var xvipaiai: vec2 = vec2(42, 43); +var xvupaiai: vec2 = vec2(44, 45); +var xvfpaiai: vec2 = vec2(46, 47); + +var xvupuai: vec2 = vec2(42u, 43); +var xvupaiu: vec2 = vec2(42, 43u); + +var xvuuai: vec2 = vec2(42u, 43); +var xvuaiu: vec2 = vec2(42, 43u); + +var xmfpaiaiaiai: mat2x2 = mat2x2(1, 2, 3, 4); +var xmfpafaiaiai: mat2x2 = mat2x2(1.0, 2, 3, 4); +var xmfpaiafaiai: mat2x2 = mat2x2(1, 2.0, 3, 4); +var xmfpaiaiafai: mat2x2 = mat2x2(1, 2, 3.0, 4); +var xmfpaiaiaiaf: mat2x2 = mat2x2(1, 2, 3, 4.0); + +var xvispai: vec2 = vec2(1); +var xvfspaf: vec2 = vec2(1.0); +var xvis_ai: vec2 = vec2(1); +var xvus_ai: vec2 = vec2(1); +var xvfs_ai: vec2 = vec2(1); +var xvfs_af: vec2 = vec2(1.0); + +var xafafaf: array = array(1.0, 2.0); +var xafaiai: array = array(1, 2); + +var xafpaiai: array = array(1, 2); +var xafpaiaf: array = array(1, 2.0); +var xafpafai: array = array(1.0, 2); +var xafpafaf: array = array(1.0, 2.0); + +fn all_constant_arguments() { + var xvipaiai: vec2 = vec2(42, 43); + var xvupaiai: vec2 = vec2(44, 45); + var xvfpaiai: vec2 = vec2(46, 47); + + var xvupuai: vec2 = vec2(42u, 43); + var xvupaiu: vec2 = vec2(42, 43u); + + var xvuuai: vec2 = vec2(42u, 43); + var xvuaiu: vec2 = vec2(42, 43u); + + var xmfpaiaiaiai: mat2x2 = mat2x2(1, 2, 3, 4); + var xmfpafaiaiai: mat2x2 = mat2x2(1.0, 2, 3, 4); + var xmfpaiafaiai: mat2x2 = mat2x2(1, 2.0, 3, 4); + var xmfpaiaiafai: mat2x2 = mat2x2(1, 2, 3.0, 4); + var xmfpaiaiaiaf: mat2x2 = mat2x2(1, 2, 3, 4.0); + + var xmfp_faiaiai: mat2x2 = mat2x2(1.0f, 2, 3, 4); + var xmfpai_faiai: mat2x2 = mat2x2(1, 2.0f, 3, 4); + var xmfpaiai_fai: mat2x2 = mat2x2(1, 2, 3.0f, 4); + var xmfpaiaiai_f: mat2x2 = mat2x2(1, 2, 3, 4.0f); + + var xvispai: vec2 = vec2(1); + var xvfspaf: vec2 = vec2(1.0); + var xvis_ai: vec2 = vec2(1); + var xvus_ai: vec2 = vec2(1); + var xvfs_ai: vec2 = vec2(1); + var xvfs_af: vec2 = vec2(1.0); + + var xafafaf: array = array(1.0, 2.0); + var xaf_faf: array = array(1.0f, 2.0); + var xafaf_f: array = array(1.0, 2.0f); + var xafaiai: array = array(1, 2); + var xai_iai: array = array(1i, 2); + var xaiai_i: array = array(1, 2i); + + // Ideally these would infer the var type from the initializer, + // but we don't support that yet. + var xaipaiai: array = array(1, 2); + var xafpaiai: array = array(1, 2); + var xafpaiaf: array = array(1, 2.0); + var xafpafai: array = array(1.0, 2); + var xafpafaf: array = array(1.0, 2.0); +} + +fn mixed_constant_and_runtime_arguments() { + var u: u32; + var i: i32; + var f: f32; + + var xvupuai: vec2 = vec2(u, 43); + var xvupaiu: vec2 = vec2(42, u); + + var xvuuai: vec2 = vec2(u, 43); + var xvuaiu: vec2 = vec2(42, u); + + var xmfp_faiaiai: mat2x2 = mat2x2(f, 2, 3, 4); + var xmfpai_faiai: mat2x2 = mat2x2(1, f, 3, 4); + var xmfpaiai_fai: mat2x2 = mat2x2(1, 2, f, 4); + var xmfpaiaiai_f: mat2x2 = mat2x2(1, 2, 3, f); + + var xaf_faf: array = array(f, 2.0); + var xafaf_f: array = array(1.0, f); + var xaf_fai: array = array(f, 2); + var xafai_f: array = array(1, f); + var xai_iai: array = array(i, 2); + var xaiai_i: array = array(1, i); + + var xafp_faf: array = array(f, 2.0); + var xafpaf_f: array = array(1.0, f); + var xafp_fai: array = array(f, 2); + var xafpai_f: array = array(1, f); + var xaip_iai: array = array(i, 2); + var xaipai_i: array = array(1, i); +} diff --git a/naga/tests/in/access.param.ron b/naga/tests/in/access.param.ron new file mode 100644 index 0000000000..e67f90cf2f --- /dev/null +++ b/naga/tests/in/access.param.ron @@ -0,0 +1,38 @@ +( + spv: ( + version: (1, 1), + debug: true, + adjust_coordinate_space: false, + ), + msl: ( + lang_version: (1, 2), + per_entry_point_map: { + "foo_vert": ( + resources: { + (group: 0, binding: 0): (buffer: Some(0), mutable: false), + (group: 0, binding: 1): (buffer: Some(1), mutable: false), + (group: 0, binding: 2): (buffer: Some(2), mutable: false), + (group: 0, binding: 3): (buffer: Some(3), mutable: false), + }, + sizes_buffer: Some(24), + ), + "foo_frag": ( + resources: { + (group: 0, binding: 0): (buffer: Some(0), mutable: true), + (group: 0, binding: 2): (buffer: Some(2), mutable: true), + }, + sizes_buffer: Some(24), + ), + "atomics": ( + resources: { + (group: 0, binding: 0): (buffer: Some(0), mutable: true), + }, + sizes_buffer: Some(24), + ), + }, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/access.wgsl b/naga/tests/in/access.wgsl new file mode 100644 index 0000000000..956a694aaa --- /dev/null +++ b/naga/tests/in/access.wgsl @@ -0,0 +1,169 @@ +// This snapshot tests accessing various containers, dereferencing pointers. + +struct GlobalConst { + a: u32, + b: vec3, + c: i32, +} +// tests msl padding insertion for global constants +var global_const: GlobalConst = GlobalConst(0u, vec3(0u, 0u, 0u), 0); + +struct AlignedWrapper { + @align(8) value: i32 +} + +struct Bar { + _matrix: mat4x3, + matrix_array: array, 2>, + atom: atomic, + atom_arr: array, 10>, + arr: array, 2>, + data: array, +} + +@group(0) @binding(0) +var bar: Bar; + +struct Baz { + m: mat3x2, +} + +@group(0) @binding(1) +var baz: Baz; + +@group(0) @binding(2) +var qux: vec2; + +fn test_matrix_within_struct_accesses() { + var idx = 1; + + idx--; + + // loads + let l0 = baz.m; + let l1 = baz.m[0]; + let l2 = baz.m[idx]; + let l3 = baz.m[0][1]; + let l4 = baz.m[0][idx]; + let l5 = baz.m[idx][1]; + let l6 = baz.m[idx][idx]; + + var t = Baz(mat3x2(vec2(1.0), vec2(2.0), vec2(3.0))); + + idx++; + + // stores + t.m = mat3x2(vec2(6.0), vec2(5.0), vec2(4.0)); + t.m[0] = vec2(9.0); + t.m[idx] = vec2(90.0); + t.m[0][1] = 10.0; + t.m[0][idx] = 20.0; + t.m[idx][1] = 30.0; + t.m[idx][idx] = 40.0; +} + +struct MatCx2InArray { + am: array, 2>, +} + +@group(0) @binding(3) +var nested_mat_cx2: MatCx2InArray; + +fn test_matrix_within_array_within_struct_accesses() { + var idx = 1; + + idx--; + + // loads + let l0 = nested_mat_cx2.am; + let l1 = nested_mat_cx2.am[0]; + let l2 = nested_mat_cx2.am[0][0]; + let l3 = nested_mat_cx2.am[0][idx]; + let l4 = nested_mat_cx2.am[0][0][1]; + let l5 = nested_mat_cx2.am[0][0][idx]; + let l6 = nested_mat_cx2.am[0][idx][1]; + let l7 = nested_mat_cx2.am[0][idx][idx]; + + var t = MatCx2InArray(array, 2>()); + + idx++; + + // stores + t.am = array, 2>(); + t.am[0] = mat4x2(vec2(8.0), vec2(7.0), vec2(6.0), vec2(5.0)); + t.am[0][0] = vec2(9.0); + t.am[0][idx] = vec2(90.0); + t.am[0][0][1] = 10.0; + t.am[0][0][idx] = 20.0; + t.am[0][idx][1] = 30.0; + t.am[0][idx][idx] = 40.0; +} + +fn read_from_private(foo: ptr) -> f32 { + return *foo; +} + +fn test_arr_as_arg(a: array, 5>) -> f32 { + return a[4][9]; +} + +@vertex +fn foo_vert(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4 { + var foo: f32 = 0.0; + // We should check that backed doesn't skip this expression + let baz: f32 = foo; + foo = 1.0; + + test_matrix_within_struct_accesses(); + test_matrix_within_array_within_struct_accesses(); + + // test storage loads + let _matrix = bar._matrix; + let arr = bar.arr; + let index = 3u; + let b = bar._matrix[index].x; + let a = bar.data[arrayLength(&bar.data) - 2u].value; + let c = qux; + + // test pointer types + let data_pointer: ptr = &bar.data[0].value; + let foo_value = read_from_private(&foo); + + // test array indexing + var c2 = array(a, i32(b), 3, 4, 5); + c2[vi + 1u] = 42; + let value = c2[vi]; + + test_arr_as_arg(array, 5>()); + + return vec4(_matrix * vec4(vec4(value)), 2.0); +} + +@fragment +fn foo_frag() -> @location(0) vec4 { + // test storage stores + bar._matrix[1].z = 1.0; + bar._matrix = mat4x3(vec3(0.0), vec3(1.0), vec3(2.0), vec3(3.0)); + bar.arr = array, 2>(vec2(0u), vec2(1u)); + bar.data[1].value = 1; + qux = vec2(); + + return vec4(0.0); +} + +fn assign_through_ptr_fn(p: ptr) { + *p = 42u; +} + +fn assign_array_through_ptr_fn(foo: ptr, 2>>) { + *foo = array, 2>(vec4(1.0), vec4(2.0)); +} + +@compute @workgroup_size(1) +fn assign_through_ptr() { + var val = 33u; + assign_through_ptr_fn(&val); + + var arr = array, 2>(vec4(6.0), vec4(7.0)); + assign_array_through_ptr_fn(&arr); +} diff --git a/naga/tests/in/array-in-ctor.param.ron b/naga/tests/in/array-in-ctor.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/array-in-ctor.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/array-in-ctor.wgsl b/naga/tests/in/array-in-ctor.wgsl new file mode 100644 index 0000000000..9607826fdf --- /dev/null +++ b/naga/tests/in/array-in-ctor.wgsl @@ -0,0 +1,10 @@ +struct Ah { + inner: array, +}; +@group(0) @binding(0) +var ah: Ah; + +@compute @workgroup_size(1) +fn cs_main() { + let ah = ah; +} diff --git a/naga/tests/in/array-in-function-return-type.param.ron b/naga/tests/in/array-in-function-return-type.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/array-in-function-return-type.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/array-in-function-return-type.wgsl b/naga/tests/in/array-in-function-return-type.wgsl new file mode 100644 index 0000000000..21e2012e78 --- /dev/null +++ b/naga/tests/in/array-in-function-return-type.wgsl @@ -0,0 +1,9 @@ +fn ret_array() -> array { + return array(1.0, 2.0); +} + +@fragment +fn main() -> @location(0) vec4 { + let a = ret_array(); + return vec4(a[0], a[1], 0.0, 1.0); +} diff --git a/naga/tests/in/atomicCompareExchange.wgsl b/naga/tests/in/atomicCompareExchange.wgsl new file mode 100644 index 0000000000..4b6144b12d --- /dev/null +++ b/naga/tests/in/atomicCompareExchange.wgsl @@ -0,0 +1,34 @@ +const SIZE: u32 = 128u; + +@group(0) @binding(0) +var arr_i32: array, SIZE>; +@group(0) @binding(1) +var arr_u32: array, SIZE>; + +@compute @workgroup_size(1) +fn test_atomic_compare_exchange_i32() { + for(var i = 0u; i < SIZE; i++) { + var old = atomicLoad(&arr_i32[i]); + var exchanged = false; + while(!exchanged) { + let new_ = bitcast(bitcast(old) + 1.0); + let result = atomicCompareExchangeWeak(&arr_i32[i], old, new_); + old = result.old_value; + exchanged = result.exchanged; + } + } +} + +@compute @workgroup_size(1) +fn test_atomic_compare_exchange_u32() { + for(var i = 0u; i < SIZE; i++) { + var old = atomicLoad(&arr_u32[i]); + var exchanged = false; + while(!exchanged) { + let new_ = bitcast(bitcast(old) + 1.0); + let result = atomicCompareExchangeWeak(&arr_u32[i], old, new_); + old = result.old_value; + exchanged = result.exchanged; + } + } +} diff --git a/naga/tests/in/atomicOps.wgsl b/naga/tests/in/atomicOps.wgsl new file mode 100644 index 0000000000..c1dd6b6326 --- /dev/null +++ b/naga/tests/in/atomicOps.wgsl @@ -0,0 +1,141 @@ +// This test covers the cross product of: +// +// * All atomic operations. +// * On all applicable scopes (storage read-write, workgroup). +// * For all shapes of modeling atomic data. + +struct Struct { + atomic_scalar: atomic, + atomic_arr: array, 2>, +} + +@group(0) @binding(0) +var storage_atomic_scalar: atomic; +@group(0) @binding(1) +var storage_atomic_arr: array, 2>; +@group(0) @binding(2) +var storage_struct: Struct; + +var workgroup_atomic_scalar: atomic; +var workgroup_atomic_arr: array, 2>; +var workgroup_struct: Struct; + +@compute +@workgroup_size(2) +fn cs_main(@builtin(local_invocation_id) id: vec3) { + atomicStore(&storage_atomic_scalar, 1u); + atomicStore(&storage_atomic_arr[1], 1i); + atomicStore(&storage_struct.atomic_scalar, 1u); + atomicStore(&storage_struct.atomic_arr[1], 1i); + atomicStore(&workgroup_atomic_scalar, 1u); + atomicStore(&workgroup_atomic_arr[1], 1i); + atomicStore(&workgroup_struct.atomic_scalar, 1u); + atomicStore(&workgroup_struct.atomic_arr[1], 1i); + + workgroupBarrier(); + + let l0 = atomicLoad(&storage_atomic_scalar); + let l1 = atomicLoad(&storage_atomic_arr[1]); + let l2 = atomicLoad(&storage_struct.atomic_scalar); + let l3 = atomicLoad(&storage_struct.atomic_arr[1]); + let l4 = atomicLoad(&workgroup_atomic_scalar); + let l5 = atomicLoad(&workgroup_atomic_arr[1]); + let l6 = atomicLoad(&workgroup_struct.atomic_scalar); + let l7 = atomicLoad(&workgroup_struct.atomic_arr[1]); + + workgroupBarrier(); + + atomicAdd(&storage_atomic_scalar, 1u); + atomicAdd(&storage_atomic_arr[1], 1i); + atomicAdd(&storage_struct.atomic_scalar, 1u); + atomicAdd(&storage_struct.atomic_arr[1], 1i); + atomicAdd(&workgroup_atomic_scalar, 1u); + atomicAdd(&workgroup_atomic_arr[1], 1i); + atomicAdd(&workgroup_struct.atomic_scalar, 1u); + atomicAdd(&workgroup_struct.atomic_arr[1], 1i); + + workgroupBarrier(); + + atomicSub(&storage_atomic_scalar, 1u); + atomicSub(&storage_atomic_arr[1], 1i); + atomicSub(&storage_struct.atomic_scalar, 1u); + atomicSub(&storage_struct.atomic_arr[1], 1i); + atomicSub(&workgroup_atomic_scalar, 1u); + atomicSub(&workgroup_atomic_arr[1], 1i); + atomicSub(&workgroup_struct.atomic_scalar, 1u); + atomicSub(&workgroup_struct.atomic_arr[1], 1i); + + workgroupBarrier(); + + atomicMax(&storage_atomic_scalar, 1u); + atomicMax(&storage_atomic_arr[1], 1i); + atomicMax(&storage_struct.atomic_scalar, 1u); + atomicMax(&storage_struct.atomic_arr[1], 1i); + atomicMax(&workgroup_atomic_scalar, 1u); + atomicMax(&workgroup_atomic_arr[1], 1i); + atomicMax(&workgroup_struct.atomic_scalar, 1u); + atomicMax(&workgroup_struct.atomic_arr[1], 1i); + + workgroupBarrier(); + + atomicMin(&storage_atomic_scalar, 1u); + atomicMin(&storage_atomic_arr[1], 1i); + atomicMin(&storage_struct.atomic_scalar, 1u); + atomicMin(&storage_struct.atomic_arr[1], 1i); + atomicMin(&workgroup_atomic_scalar, 1u); + atomicMin(&workgroup_atomic_arr[1], 1i); + atomicMin(&workgroup_struct.atomic_scalar, 1u); + atomicMin(&workgroup_struct.atomic_arr[1], 1i); + + workgroupBarrier(); + + atomicAnd(&storage_atomic_scalar, 1u); + atomicAnd(&storage_atomic_arr[1], 1i); + atomicAnd(&storage_struct.atomic_scalar, 1u); + atomicAnd(&storage_struct.atomic_arr[1], 1i); + atomicAnd(&workgroup_atomic_scalar, 1u); + atomicAnd(&workgroup_atomic_arr[1], 1i); + atomicAnd(&workgroup_struct.atomic_scalar, 1u); + atomicAnd(&workgroup_struct.atomic_arr[1], 1i); + + workgroupBarrier(); + + atomicOr(&storage_atomic_scalar, 1u); + atomicOr(&storage_atomic_arr[1], 1i); + atomicOr(&storage_struct.atomic_scalar, 1u); + atomicOr(&storage_struct.atomic_arr[1], 1i); + atomicOr(&workgroup_atomic_scalar, 1u); + atomicOr(&workgroup_atomic_arr[1], 1i); + atomicOr(&workgroup_struct.atomic_scalar, 1u); + atomicOr(&workgroup_struct.atomic_arr[1], 1i); + + workgroupBarrier(); + + atomicXor(&storage_atomic_scalar, 1u); + atomicXor(&storage_atomic_arr[1], 1i); + atomicXor(&storage_struct.atomic_scalar, 1u); + atomicXor(&storage_struct.atomic_arr[1], 1i); + atomicXor(&workgroup_atomic_scalar, 1u); + atomicXor(&workgroup_atomic_arr[1], 1i); + atomicXor(&workgroup_struct.atomic_scalar, 1u); + atomicXor(&workgroup_struct.atomic_arr[1], 1i); + + atomicExchange(&storage_atomic_scalar, 1u); + atomicExchange(&storage_atomic_arr[1], 1i); + atomicExchange(&storage_struct.atomic_scalar, 1u); + atomicExchange(&storage_struct.atomic_arr[1], 1i); + atomicExchange(&workgroup_atomic_scalar, 1u); + atomicExchange(&workgroup_atomic_arr[1], 1i); + atomicExchange(&workgroup_struct.atomic_scalar, 1u); + atomicExchange(&workgroup_struct.atomic_arr[1], 1i); + + // // TODO: https://github.com/gpuweb/gpuweb/issues/2021 + // atomicCompareExchangeWeak(&storage_atomic_scalar, 1u); + // atomicCompareExchangeWeak(&storage_atomic_arr[1], 1i); + // atomicCompareExchangeWeak(&storage_struct.atomic_scalar, 1u); + // atomicCompareExchangeWeak(&storage_struct.atomic_arr[1], 1i); + // atomicCompareExchangeWeak(&workgroup_atomic_scalar, 1u); + // atomicCompareExchangeWeak(&workgroup_atomic_arr[1], 1i); + // atomicCompareExchangeWeak(&workgroup_struct.atomic_scalar, 1u); + // atomicCompareExchangeWeak(&workgroup_struct.atomic_arr[1], 1i); +} diff --git a/naga/tests/in/binding-arrays.param.ron b/naga/tests/in/binding-arrays.param.ron new file mode 100644 index 0000000000..39d6c03664 --- /dev/null +++ b/naga/tests/in/binding-arrays.param.ron @@ -0,0 +1,47 @@ +( + god_mode: true, + hlsl: ( + shader_model: V5_1, + binding_map: { + (group: 0, binding: 0): (space: 0, register: 0, binding_array_size: Some(10)), + (group: 0, binding: 1): (space: 1, register: 0), + (group: 0, binding: 2): (space: 2, register: 0), + (group: 0, binding: 3): (space: 3, register: 0), + (group: 0, binding: 4): (space: 4, register: 0), + (group: 0, binding: 5): (space: 5, register: 0), + (group: 0, binding: 6): (space: 6, register: 0), + (group: 0, binding: 7): (space: 7, register: 0), + (group: 0, binding: 8): (space: 8, register: 0), + }, + fake_missing_bindings: true, + special_constants_binding: None, + zero_initialize_workgroup_memory: true, + ), + msl: ( + lang_version: (2, 0), + per_entry_point_map: { + "main": ( + resources: { + (group: 0, binding: 0): (texture: Some(0), binding_array_size: Some(10), mutable: false), + }, + sizes_buffer: None, + ) + }, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: true, + zero_initialize_workgroup_memory: true, + ), + spv: ( + version: (1, 1), + binding_map: { + (group: 0, binding: 0): (binding_array_size: Some(10)), + }, + ), + bounds_check_policies: ( + index: ReadZeroSkipWrite, + buffer: ReadZeroSkipWrite, + image_load: ReadZeroSkipWrite, + image_store: ReadZeroSkipWrite, + ) +) diff --git a/naga/tests/in/binding-arrays.wgsl b/naga/tests/in/binding-arrays.wgsl new file mode 100644 index 0000000000..d1bf1210d7 --- /dev/null +++ b/naga/tests/in/binding-arrays.wgsl @@ -0,0 +1,108 @@ +struct UniformIndex { + index: u32 +}; + +@group(0) @binding(0) +var texture_array_unbounded: binding_array>; +@group(0) @binding(1) +var texture_array_bounded: binding_array, 5>; +@group(0) @binding(2) +var texture_array_2darray: binding_array, 5>; +@group(0) @binding(3) +var texture_array_multisampled: binding_array, 5>; +@group(0) @binding(4) +var texture_array_depth: binding_array; +@group(0) @binding(5) +var texture_array_storage: binding_array, 5>; +@group(0) @binding(6) +var samp: binding_array; +@group(0) @binding(7) +var samp_comp: binding_array; +@group(0) @binding(8) +var uni: UniformIndex; + +struct FragmentIn { + @location(0) index: u32, +}; + +@fragment +fn main(fragment_in: FragmentIn) -> @location(0) vec4 { + let uniform_index = uni.index; + let non_uniform_index = fragment_in.index; + + var u1 = 0u; + var u2 = vec2(0u); + var v1 = 0.0; + var v4 = vec4(0.0); + + // This example is arranged in the order of the texture definitions in the wgsl spec + // + // The first function uses texture_array_unbounded, the rest use texture_array_bounded to make sure + // they both show up in the output. Functions that need depth use texture_array_2darray. + // + // We only test 2D f32 textures here as the machinery for binding indexing doesn't care about + // texture format or texture dimension. + + let uv = vec2(0.0); + let pix = vec2(0); + + u2 += textureDimensions(texture_array_unbounded[0]); + u2 += textureDimensions(texture_array_unbounded[uniform_index]); + u2 += textureDimensions(texture_array_unbounded[non_uniform_index]); + + v4 += textureGather(0, texture_array_bounded[0], samp[0], uv); + v4 += textureGather(0, texture_array_bounded[uniform_index], samp[uniform_index], uv); + v4 += textureGather(0, texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv); + + v4 += textureGatherCompare(texture_array_depth[0], samp_comp[0], uv, 0.0); + v4 += textureGatherCompare(texture_array_depth[uniform_index], samp_comp[uniform_index], uv, 0.0); + v4 += textureGatherCompare(texture_array_depth[non_uniform_index], samp_comp[non_uniform_index], uv, 0.0); + + v4 += textureLoad(texture_array_unbounded[0], pix, 0); + v4 += textureLoad(texture_array_unbounded[uniform_index], pix, 0); + v4 += textureLoad(texture_array_unbounded[non_uniform_index], pix, 0); + + u1 += textureNumLayers(texture_array_2darray[0]); + u1 += textureNumLayers(texture_array_2darray[uniform_index]); + u1 += textureNumLayers(texture_array_2darray[non_uniform_index]); + + u1 += textureNumLevels(texture_array_bounded[0]); + u1 += textureNumLevels(texture_array_bounded[uniform_index]); + u1 += textureNumLevels(texture_array_bounded[non_uniform_index]); + + u1 += textureNumSamples(texture_array_multisampled[0]); + u1 += textureNumSamples(texture_array_multisampled[uniform_index]); + u1 += textureNumSamples(texture_array_multisampled[non_uniform_index]); + + v4 += textureSample(texture_array_bounded[0], samp[0], uv); + v4 += textureSample(texture_array_bounded[uniform_index], samp[uniform_index], uv); + v4 += textureSample(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv); + + v4 += textureSampleBias(texture_array_bounded[0], samp[0], uv, 0.0); + v4 += textureSampleBias(texture_array_bounded[uniform_index], samp[uniform_index], uv, 0.0); + v4 += textureSampleBias(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv, 0.0); + + v1 += textureSampleCompare(texture_array_depth[0], samp_comp[0], uv, 0.0); + v1 += textureSampleCompare(texture_array_depth[uniform_index], samp_comp[uniform_index], uv, 0.0); + v1 += textureSampleCompare(texture_array_depth[non_uniform_index], samp_comp[non_uniform_index], uv, 0.0); + + v1 += textureSampleCompareLevel(texture_array_depth[0], samp_comp[0], uv, 0.0); + v1 += textureSampleCompareLevel(texture_array_depth[uniform_index], samp_comp[uniform_index], uv, 0.0); + v1 += textureSampleCompareLevel(texture_array_depth[non_uniform_index], samp_comp[non_uniform_index], uv, 0.0); + + v4 += textureSampleGrad(texture_array_bounded[0], samp[0], uv, uv, uv); + v4 += textureSampleGrad(texture_array_bounded[uniform_index], samp[uniform_index], uv, uv, uv); + v4 += textureSampleGrad(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv, uv, uv); + + v4 += textureSampleLevel(texture_array_bounded[0], samp[0], uv, 0.0); + v4 += textureSampleLevel(texture_array_bounded[uniform_index], samp[uniform_index], uv, 0.0); + v4 += textureSampleLevel(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv, 0.0); + + textureStore(texture_array_storage[0], pix, v4); + textureStore(texture_array_storage[uniform_index], pix, v4); + textureStore(texture_array_storage[non_uniform_index], pix, v4); + + let v2 = vec2(u2 + vec2(u1)); + + return v4 + vec4(v2.x, v2.y, v2.x, v2.y) + v1; +} diff --git a/naga/tests/in/binding-buffer-arrays.param.ron b/naga/tests/in/binding-buffer-arrays.param.ron new file mode 100644 index 0000000000..4f653bb21b --- /dev/null +++ b/naga/tests/in/binding-buffer-arrays.param.ron @@ -0,0 +1,14 @@ +( + god_mode: false, + spv: ( + version: (1, 1), + binding_map: { + (group: 0, binding: 0): (binding_array_size: Some(10)), + }, + ), + bounds_check_policies: ( + index: ReadZeroSkipWrite, + buffer: ReadZeroSkipWrite, + image: ReadZeroSkipWrite, + ) +) diff --git a/naga/tests/in/binding-buffer-arrays.wgsl b/naga/tests/in/binding-buffer-arrays.wgsl new file mode 100644 index 0000000000..a76d52c200 --- /dev/null +++ b/naga/tests/in/binding-buffer-arrays.wgsl @@ -0,0 +1,27 @@ +struct UniformIndex { + index: u32 +} + +struct Foo { x: u32 } +@group(0) @binding(0) +var storage_array: binding_array; +@group(0) @binding(10) +var uni: UniformIndex; + +struct FragmentIn { + @location(0) index: u32, +} + +@fragment +fn main(fragment_in: FragmentIn) -> @location(0) u32 { + let uniform_index = uni.index; + let non_uniform_index = fragment_in.index; + + var u1 = 0u; + + u1 += storage_array[0].x; + u1 += storage_array[uniform_index].x; + u1 += storage_array[non_uniform_index].x; + + return u1; +} diff --git a/naga/tests/in/bitcast.params.ron b/naga/tests/in/bitcast.params.ron new file mode 100644 index 0000000000..febd505f73 --- /dev/null +++ b/naga/tests/in/bitcast.params.ron @@ -0,0 +1,16 @@ +( + msl: ( + lang_version: (1, 0), + per_entry_point_map: { + "main": ( + resources: { + }, + sizes_buffer: Some(0), + ) + }, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/bitcast.wgsl b/naga/tests/in/bitcast.wgsl new file mode 100644 index 0000000000..c698f1bdff --- /dev/null +++ b/naga/tests/in/bitcast.wgsl @@ -0,0 +1,26 @@ +@compute @workgroup_size(1) +fn main() { + var i2 = vec2(0); + var i3 = vec3(0); + var i4 = vec4(0); + + var u2 = vec2(0u); + var u3 = vec3(0u); + var u4 = vec4(0u); + + var f2 = vec2(0.0); + var f3 = vec3(0.0); + var f4 = vec4(0.0); + + u2 = bitcast>(i2); + u3 = bitcast>(i3); + u4 = bitcast>(i4); + + i2 = bitcast>(u2); + i3 = bitcast>(u3); + i4 = bitcast>(u4); + + f2 = bitcast>(i2); + f3 = bitcast>(i3); + f4 = bitcast>(i4); +} diff --git a/naga/tests/in/bits.param.ron b/naga/tests/in/bits.param.ron new file mode 100644 index 0000000000..b40cf9fa08 --- /dev/null +++ b/naga/tests/in/bits.param.ron @@ -0,0 +1,16 @@ +( + msl: ( + lang_version: (1, 2), + per_entry_point_map: { + "main": ( + resources: { + }, + sizes_buffer: Some(0), + ) + }, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/bits.wgsl b/naga/tests/in/bits.wgsl new file mode 100644 index 0000000000..549ff08ec7 --- /dev/null +++ b/naga/tests/in/bits.wgsl @@ -0,0 +1,61 @@ +@compute @workgroup_size(1) +fn main() { + var i = 0; + var i2 = vec2(0); + var i3 = vec3(0); + var i4 = vec4(0); + var u = 0u; + var u2 = vec2(0u); + var u3 = vec3(0u); + var u4 = vec4(0u); + var f2 = vec2(0.0); + var f4 = vec4(0.0); + u = pack4x8snorm(f4); + u = pack4x8unorm(f4); + u = pack2x16snorm(f2); + u = pack2x16unorm(f2); + u = pack2x16float(f2); + f4 = unpack4x8snorm(u); + f4 = unpack4x8unorm(u); + f2 = unpack2x16snorm(u); + f2 = unpack2x16unorm(u); + f2 = unpack2x16float(u); + i = insertBits(i, i, 5u, 10u); + i2 = insertBits(i2, i2, 5u, 10u); + i3 = insertBits(i3, i3, 5u, 10u); + i4 = insertBits(i4, i4, 5u, 10u); + u = insertBits(u, u, 5u, 10u); + u2 = insertBits(u2, u2, 5u, 10u); + u3 = insertBits(u3, u3, 5u, 10u); + u4 = insertBits(u4, u4, 5u, 10u); + i = extractBits(i, 5u, 10u); + i2 = extractBits(i2, 5u, 10u); + i3 = extractBits(i3, 5u, 10u); + i4 = extractBits(i4, 5u, 10u); + u = extractBits(u, 5u, 10u); + u2 = extractBits(u2, 5u, 10u); + u3 = extractBits(u3, 5u, 10u); + u4 = extractBits(u4, 5u, 10u); + i = firstTrailingBit(i); + u2 = firstTrailingBit(u2); + i3 = firstLeadingBit(i3); + u3 = firstLeadingBit(u3); + i = firstLeadingBit(i); + u = firstLeadingBit(u); + i = countOneBits(i); + i2 = countOneBits(i2); + i3 = countOneBits(i3); + i4 = countOneBits(i4); + u = countOneBits(u); + u2 = countOneBits(u2); + u3 = countOneBits(u3); + u4 = countOneBits(u4); + i = reverseBits(i); + i2 = reverseBits(i2); + i3 = reverseBits(i3); + i4 = reverseBits(i4); + u = reverseBits(u); + u2 = reverseBits(u2); + u3 = reverseBits(u3); + u4 = reverseBits(u4); +} diff --git a/naga/tests/in/boids.param.ron b/naga/tests/in/boids.param.ron new file mode 100644 index 0000000000..25f81b8afd --- /dev/null +++ b/naga/tests/in/boids.param.ron @@ -0,0 +1,24 @@ +( + spv: ( + version: (1, 0), + debug: true, + adjust_coordinate_space: false, + ), + msl: ( + lang_version: (1, 0), + per_entry_point_map: { + "main": ( + resources: { + (group: 0, binding: 0): (buffer: Some(0), mutable: false), + (group: 0, binding: 1): (buffer: Some(1), mutable: true), + (group: 0, binding: 2): (buffer: Some(2), mutable: true), + }, + sizes_buffer: Some(3), + ) + }, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/boids.wgsl b/naga/tests/in/boids.wgsl new file mode 100644 index 0000000000..caa67df77d --- /dev/null +++ b/naga/tests/in/boids.wgsl @@ -0,0 +1,107 @@ +const NUM_PARTICLES: u32 = 1500u; + +struct Particle { + pos : vec2, + vel : vec2, +} + +struct SimParams { + deltaT : f32, + rule1Distance : f32, + rule2Distance : f32, + rule3Distance : f32, + rule1Scale : f32, + rule2Scale : f32, + rule3Scale : f32, +} + +struct Particles { + particles : array +} + +@group(0) @binding(0) var params : SimParams; +@group(0) @binding(1) var particlesSrc : Particles; +@group(0) @binding(2) var particlesDst : Particles; + +// https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp +@compute @workgroup_size(64) +fn main(@builtin(global_invocation_id) global_invocation_id : vec3) { + let index : u32 = global_invocation_id.x; + if index >= NUM_PARTICLES { + return; + } + + var vPos = particlesSrc.particles[index].pos; + var vVel = particlesSrc.particles[index].vel; + + var cMass = vec2(0.0, 0.0); + var cVel = vec2(0.0, 0.0); + var colVel = vec2(0.0, 0.0); + var cMassCount : i32 = 0; + var cVelCount : i32 = 0; + + var pos : vec2; + var vel : vec2; + var i : u32 = 0u; + loop { + if i >= NUM_PARTICLES { + break; + } + if i == index { + continue; + } + + pos = particlesSrc.particles[i].pos; + vel = particlesSrc.particles[i].vel; + + if distance(pos, vPos) < params.rule1Distance { + cMass = cMass + pos; + cMassCount = cMassCount + 1; + } + if distance(pos, vPos) < params.rule2Distance { + colVel = colVel - (pos - vPos); + } + if distance(pos, vPos) < params.rule3Distance { + cVel = cVel + vel; + cVelCount = cVelCount + 1; + } + + continuing { + i = i + 1u; + } + } + if cMassCount > 0 { + cMass = cMass / f32(cMassCount) - vPos; + } + if cVelCount > 0 { + cVel = cVel / f32(cVelCount); + } + + vVel = vVel + (cMass * params.rule1Scale) + + (colVel * params.rule2Scale) + + (cVel * params.rule3Scale); + + // clamp velocity for a more pleasing simulation + vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1); + + // kinematic update + vPos = vPos + (vVel * params.deltaT); + + // Wrap around boundary + if vPos.x < -1.0 { + vPos.x = 1.0; + } + if vPos.x > 1.0 { + vPos.x = -1.0; + } + if vPos.y < -1.0 { + vPos.y = 1.0; + } + if vPos.y > 1.0 { + vPos.y = -1.0; + } + + // Write back + particlesDst.particles[index].pos = vPos; + particlesDst.particles[index].vel = vVel; +} diff --git a/naga/tests/in/bounds-check-image-restrict.param.ron b/naga/tests/in/bounds-check-image-restrict.param.ron new file mode 100644 index 0000000000..d7ff0f006b --- /dev/null +++ b/naga/tests/in/bounds-check-image-restrict.param.ron @@ -0,0 +1,24 @@ +( + bounds_check_policies: ( + image_load: Restrict, + image_store: Restrict, + ), + spv: ( + version: (1, 1), + debug: true, + ), + glsl: ( + version: Desktop(430), + writer_flags: (""), + binding_map: { }, + zero_initialize_workgroup_memory: true, + ), + msl: ( + lang_version: (1, 2), + per_entry_point_map: {}, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: true, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/bounds-check-image-restrict.wgsl b/naga/tests/in/bounds-check-image-restrict.wgsl new file mode 100644 index 0000000000..4bd8d117c8 --- /dev/null +++ b/naga/tests/in/bounds-check-image-restrict.wgsl @@ -0,0 +1,119 @@ +@group(0) @binding(0) +var image_1d: texture_1d; + +fn test_textureLoad_1d(coords: i32, level: i32) -> vec4 { + return textureLoad(image_1d, coords, level); +} + +@group(0) @binding(1) +var image_2d: texture_2d; + +fn test_textureLoad_2d(coords: vec2, level: i32) -> vec4 { + return textureLoad(image_2d, coords, level); +} + +@group(0) @binding(2) +var image_2d_array: texture_2d_array; + +fn test_textureLoad_2d_array_u(coords: vec2, index: u32, level: i32) -> vec4 { + return textureLoad(image_2d_array, coords, index, level); +} + +fn test_textureLoad_2d_array_s(coords: vec2, index: i32, level: i32) -> vec4 { + return textureLoad(image_2d_array, coords, index, level); +} + +@group(0) @binding(3) +var image_3d: texture_3d; + +fn test_textureLoad_3d(coords: vec3, level: i32) -> vec4 { + return textureLoad(image_3d, coords, level); +} + +@group(0) @binding(4) +var image_multisampled_2d: texture_multisampled_2d; + +fn test_textureLoad_multisampled_2d(coords: vec2, _sample: i32) -> vec4 { + return textureLoad(image_multisampled_2d, coords, _sample); +} + +@group(0) @binding(5) +var image_depth_2d: texture_depth_2d; + +fn test_textureLoad_depth_2d(coords: vec2, level: i32) -> f32 { + return textureLoad(image_depth_2d, coords, level); +} + +@group(0) @binding(6) +var image_depth_2d_array: texture_depth_2d_array; + +fn test_textureLoad_depth_2d_array_u(coords: vec2, index: u32, level: i32) -> f32 { + return textureLoad(image_depth_2d_array, coords, index, level); +} + +fn test_textureLoad_depth_2d_array_s(coords: vec2, index: i32, level: i32) -> f32 { + return textureLoad(image_depth_2d_array, coords, index, level); +} + +@group(0) @binding(7) +var image_depth_multisampled_2d: texture_depth_multisampled_2d; + +fn test_textureLoad_depth_multisampled_2d(coords: vec2, _sample: i32) -> f32 { + return textureLoad(image_depth_multisampled_2d, coords, _sample); +} + +@group(0) @binding(8) +var image_storage_1d: texture_storage_1d; + +fn test_textureStore_1d(coords: i32, value: vec4) { + textureStore(image_storage_1d, coords, value); +} + +@group(0) @binding(9) +var image_storage_2d: texture_storage_2d; + +fn test_textureStore_2d(coords: vec2, value: vec4) { + textureStore(image_storage_2d, coords, value); +} + +@group(0) @binding(10) +var image_storage_2d_array: texture_storage_2d_array; + +fn test_textureStore_2d_array_u(coords: vec2, array_index: u32, value: vec4) { + textureStore(image_storage_2d_array, coords, array_index, value); +} + +fn test_textureStore_2d_array_s(coords: vec2, array_index: i32, value: vec4) { + textureStore(image_storage_2d_array, coords, array_index, value); +} + +@group(0) @binding(11) +var image_storage_3d: texture_storage_3d; + +fn test_textureStore_3d(coords: vec3, value: vec4) { + textureStore(image_storage_3d, coords, value); +} + +// GLSL output requires that we identify an entry point, so +// that it can tell what "in" and "out" globals to write. +@fragment +fn fragment_shader() -> @location(0) vec4 { + test_textureLoad_1d(0, 0); + test_textureLoad_2d(vec2(), 0); + test_textureLoad_2d_array_u(vec2(), 0u, 0); + test_textureLoad_2d_array_s(vec2(), 0, 0); + test_textureLoad_3d(vec3(), 0); + test_textureLoad_multisampled_2d(vec2(), 0); + // Not yet implemented for GLSL: + // test_textureLoad_depth_2d(vec2(), 0); + // test_textureLoad_depth_2d_array_u(vec2(), 0u, 0); + // test_textureLoad_depth_2d_array_s(vec2(), 0, 0); + // test_textureLoad_depth_multisampled_2d(vec2(), 0); + test_textureStore_1d(0, vec4()); + test_textureStore_2d(vec2(), vec4()); + test_textureStore_2d_array_u(vec2(), 0u, vec4()); + test_textureStore_2d_array_s(vec2(), 0, vec4()); + test_textureStore_3d(vec3(), vec4()); + + return vec4(0.,0.,0.,0.); +} diff --git a/naga/tests/in/bounds-check-image-rzsw.param.ron b/naga/tests/in/bounds-check-image-rzsw.param.ron new file mode 100644 index 0000000000..b256790e15 --- /dev/null +++ b/naga/tests/in/bounds-check-image-rzsw.param.ron @@ -0,0 +1,24 @@ +( + bounds_check_policies: ( + image_load: ReadZeroSkipWrite, + image_store: ReadZeroSkipWrite, + ), + spv: ( + version: (1, 1), + debug: true, + ), + glsl: ( + version: Desktop(430), + writer_flags: (""), + binding_map: { }, + zero_initialize_workgroup_memory: true, + ), + msl: ( + lang_version: (1, 2), + per_entry_point_map: {}, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: true, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/bounds-check-image-rzsw.wgsl b/naga/tests/in/bounds-check-image-rzsw.wgsl new file mode 100644 index 0000000000..4bd8d117c8 --- /dev/null +++ b/naga/tests/in/bounds-check-image-rzsw.wgsl @@ -0,0 +1,119 @@ +@group(0) @binding(0) +var image_1d: texture_1d; + +fn test_textureLoad_1d(coords: i32, level: i32) -> vec4 { + return textureLoad(image_1d, coords, level); +} + +@group(0) @binding(1) +var image_2d: texture_2d; + +fn test_textureLoad_2d(coords: vec2, level: i32) -> vec4 { + return textureLoad(image_2d, coords, level); +} + +@group(0) @binding(2) +var image_2d_array: texture_2d_array; + +fn test_textureLoad_2d_array_u(coords: vec2, index: u32, level: i32) -> vec4 { + return textureLoad(image_2d_array, coords, index, level); +} + +fn test_textureLoad_2d_array_s(coords: vec2, index: i32, level: i32) -> vec4 { + return textureLoad(image_2d_array, coords, index, level); +} + +@group(0) @binding(3) +var image_3d: texture_3d; + +fn test_textureLoad_3d(coords: vec3, level: i32) -> vec4 { + return textureLoad(image_3d, coords, level); +} + +@group(0) @binding(4) +var image_multisampled_2d: texture_multisampled_2d; + +fn test_textureLoad_multisampled_2d(coords: vec2, _sample: i32) -> vec4 { + return textureLoad(image_multisampled_2d, coords, _sample); +} + +@group(0) @binding(5) +var image_depth_2d: texture_depth_2d; + +fn test_textureLoad_depth_2d(coords: vec2, level: i32) -> f32 { + return textureLoad(image_depth_2d, coords, level); +} + +@group(0) @binding(6) +var image_depth_2d_array: texture_depth_2d_array; + +fn test_textureLoad_depth_2d_array_u(coords: vec2, index: u32, level: i32) -> f32 { + return textureLoad(image_depth_2d_array, coords, index, level); +} + +fn test_textureLoad_depth_2d_array_s(coords: vec2, index: i32, level: i32) -> f32 { + return textureLoad(image_depth_2d_array, coords, index, level); +} + +@group(0) @binding(7) +var image_depth_multisampled_2d: texture_depth_multisampled_2d; + +fn test_textureLoad_depth_multisampled_2d(coords: vec2, _sample: i32) -> f32 { + return textureLoad(image_depth_multisampled_2d, coords, _sample); +} + +@group(0) @binding(8) +var image_storage_1d: texture_storage_1d; + +fn test_textureStore_1d(coords: i32, value: vec4) { + textureStore(image_storage_1d, coords, value); +} + +@group(0) @binding(9) +var image_storage_2d: texture_storage_2d; + +fn test_textureStore_2d(coords: vec2, value: vec4) { + textureStore(image_storage_2d, coords, value); +} + +@group(0) @binding(10) +var image_storage_2d_array: texture_storage_2d_array; + +fn test_textureStore_2d_array_u(coords: vec2, array_index: u32, value: vec4) { + textureStore(image_storage_2d_array, coords, array_index, value); +} + +fn test_textureStore_2d_array_s(coords: vec2, array_index: i32, value: vec4) { + textureStore(image_storage_2d_array, coords, array_index, value); +} + +@group(0) @binding(11) +var image_storage_3d: texture_storage_3d; + +fn test_textureStore_3d(coords: vec3, value: vec4) { + textureStore(image_storage_3d, coords, value); +} + +// GLSL output requires that we identify an entry point, so +// that it can tell what "in" and "out" globals to write. +@fragment +fn fragment_shader() -> @location(0) vec4 { + test_textureLoad_1d(0, 0); + test_textureLoad_2d(vec2(), 0); + test_textureLoad_2d_array_u(vec2(), 0u, 0); + test_textureLoad_2d_array_s(vec2(), 0, 0); + test_textureLoad_3d(vec3(), 0); + test_textureLoad_multisampled_2d(vec2(), 0); + // Not yet implemented for GLSL: + // test_textureLoad_depth_2d(vec2(), 0); + // test_textureLoad_depth_2d_array_u(vec2(), 0u, 0); + // test_textureLoad_depth_2d_array_s(vec2(), 0, 0); + // test_textureLoad_depth_multisampled_2d(vec2(), 0); + test_textureStore_1d(0, vec4()); + test_textureStore_2d(vec2(), vec4()); + test_textureStore_2d_array_u(vec2(), 0u, vec4()); + test_textureStore_2d_array_s(vec2(), 0, vec4()); + test_textureStore_3d(vec3(), vec4()); + + return vec4(0.,0.,0.,0.); +} diff --git a/naga/tests/in/bounds-check-restrict.param.ron b/naga/tests/in/bounds-check-restrict.param.ron new file mode 100644 index 0000000000..93c734c146 --- /dev/null +++ b/naga/tests/in/bounds-check-restrict.param.ron @@ -0,0 +1,6 @@ +( + bounds_check_policies: ( + index: Restrict, + buffer: Restrict, + ), +) diff --git a/naga/tests/in/bounds-check-restrict.wgsl b/naga/tests/in/bounds-check-restrict.wgsl new file mode 100644 index 0000000000..2b7208355c --- /dev/null +++ b/naga/tests/in/bounds-check-restrict.wgsl @@ -0,0 +1,72 @@ +// Tests for `naga::back::BoundsCheckPolicy::Restrict`. + +struct Globals { + a: array, + v: vec4, + m: mat3x4, + d: array, +} + +@group(0) @binding(0) var globals: Globals; + +fn index_array(i: i32) -> f32 { + return globals.a[i]; +} + +fn index_dynamic_array(i: i32) -> f32 { + return globals.d[i]; +} + +fn index_vector(i: i32) -> f32 { + return globals.v[i]; +} + +fn index_vector_by_value(v: vec4, i: i32) -> f32 { + return v[i]; +} + +fn index_matrix(i: i32) -> vec4 { + return globals.m[i]; +} + +fn index_twice(i: i32, j: i32) -> f32 { + return globals.m[i][j]; +} + +fn index_expensive(i: i32) -> f32 { + return globals.a[i32(sin(f32(i) / 100.0) * 100.0)]; +} + +fn index_in_bounds() -> f32 { + return globals.a[9] + globals.v[3] + globals.m[2][3]; +} + +fn set_array(i: i32, v: f32) { + globals.a[i] = v; +} + +fn set_dynamic_array(i: i32, v: f32) { + globals.d[i] = v; +} + +fn set_vector(i: i32, v: f32) { + globals.v[i] = v; +} + +fn set_matrix(i: i32, v: vec4) { + globals.m[i] = v; +} + +fn set_index_twice(i: i32, j: i32, v: f32) { + globals.m[i][j] = v; +} + +fn set_expensive(i: i32, v: f32) { + globals.a[i32(sin(f32(i) / 100.0) * 100.0)] = v; +} + +fn set_in_bounds(v: f32) { + globals.a[9] = v; + globals.v[3] = v; + globals.m[2][3] = v; +} diff --git a/naga/tests/in/bounds-check-zero-atomic.param.ron b/naga/tests/in/bounds-check-zero-atomic.param.ron new file mode 100644 index 0000000000..3ca0053af1 --- /dev/null +++ b/naga/tests/in/bounds-check-zero-atomic.param.ron @@ -0,0 +1,6 @@ +( + bounds_check_policies: ( + index: ReadZeroSkipWrite, + buffer: ReadZeroSkipWrite, + ), +) diff --git a/naga/tests/in/bounds-check-zero-atomic.wgsl b/naga/tests/in/bounds-check-zero-atomic.wgsl new file mode 100644 index 0000000000..004f08a0a5 --- /dev/null +++ b/naga/tests/in/bounds-check-zero-atomic.wgsl @@ -0,0 +1,38 @@ +// Tests for `naga::back::BoundsCheckPolicy::ReadZeroSkipWrite` for atomic types. + +// These are separate from `bounds-check-zero.wgsl because SPIR-V does not yet +// support `ReadZeroSkipWrite` for atomics. Once it does, the test files could +// be combined. + +struct Globals { + a: atomic, + b: array, 10>, + c: array>, +} + +@group(0) @binding(0) var globals: Globals; + +fn fetch_add_atomic() -> u32 { + return atomicAdd(&globals.a, 1u); +} + +fn fetch_add_atomic_static_sized_array(i: i32) -> u32 { + return atomicAdd(&globals.b[i], 1u); +} + +fn fetch_add_atomic_dynamic_sized_array(i: i32) -> u32 { + return atomicAdd(&globals.c[i], 1u); +} + +fn exchange_atomic() -> u32 { + return atomicExchange(&globals.a, 1u); +} + +fn exchange_atomic_static_sized_array(i: i32) -> u32 { + return atomicExchange(&globals.b[i], 1u); +} + +fn exchange_atomic_dynamic_sized_array(i: i32) -> u32 { + return atomicExchange(&globals.c[i], 1u); +} + diff --git a/naga/tests/in/bounds-check-zero.param.ron b/naga/tests/in/bounds-check-zero.param.ron new file mode 100644 index 0000000000..3ca0053af1 --- /dev/null +++ b/naga/tests/in/bounds-check-zero.param.ron @@ -0,0 +1,6 @@ +( + bounds_check_policies: ( + index: ReadZeroSkipWrite, + buffer: ReadZeroSkipWrite, + ), +) diff --git a/naga/tests/in/bounds-check-zero.wgsl b/naga/tests/in/bounds-check-zero.wgsl new file mode 100644 index 0000000000..010f46ec3b --- /dev/null +++ b/naga/tests/in/bounds-check-zero.wgsl @@ -0,0 +1,72 @@ +// Tests for `naga::back::BoundsCheckPolicy::ReadZeroSkipWrite`. + +struct Globals { + a: array, + v: vec4, + m: mat3x4, + d: array, +} + +@group(0) @binding(0) var globals: Globals; + +fn index_array(i: i32) -> f32 { + return globals.a[i]; +} + +fn index_dynamic_array(i: i32) -> f32 { + return globals.d[i]; +} + +fn index_vector(i: i32) -> f32 { + return globals.v[i]; +} + +fn index_vector_by_value(v: vec4, i: i32) -> f32 { + return v[i]; +} + +fn index_matrix(i: i32) -> vec4 { + return globals.m[i]; +} + +fn index_twice(i: i32, j: i32) -> f32 { + return globals.m[i][j]; +} + +fn index_expensive(i: i32) -> f32 { + return globals.a[i32(sin(f32(i) / 100.0) * 100.0)]; +} + +fn index_in_bounds() -> f32 { + return globals.a[9] + globals.v[3] + globals.m[2][3]; +} + +fn set_array(i: i32, v: f32) { + globals.a[i] = v; +} + +fn set_dynamic_array(i: i32, v: f32) { + globals.d[i] = v; +} + +fn set_vector(i: i32, v: f32) { + globals.v[i] = v; +} + +fn set_matrix(i: i32, v: vec4) { + globals.m[i] = v; +} + +fn set_index_twice(i: i32, j: i32, v: f32) { + globals.m[i][j] = v; +} + +fn set_expensive(i: i32, v: f32) { + globals.a[i32(sin(f32(i) / 100.0) * 100.0)] = v; +} + +fn set_in_bounds(v: f32) { + globals.a[9] = v; + globals.v[3] = v; + globals.m[2][3] = v; +} diff --git a/naga/tests/in/break-if.wgsl b/naga/tests/in/break-if.wgsl new file mode 100644 index 0000000000..27536edb37 --- /dev/null +++ b/naga/tests/in/break-if.wgsl @@ -0,0 +1,44 @@ +@compute @workgroup_size(1) +fn main() {} + +fn breakIfEmpty() { + loop { + continuing { + break if true; + } + } +} + +fn breakIfEmptyBody(a: bool) { + loop { + continuing { + var b = a; + var c = a != b; + + break if a == c; + } + } +} + +fn breakIf(a: bool) { + loop { + var d = a; + var e = a != d; + + continuing { + break if a == e; + } + } +} + +fn breakIfSeparateVariable() { + var counter = 0u; + + loop { + counter += 1u; + + continuing { + break if counter == 5u; + } + } +} diff --git a/naga/tests/in/collatz.param.ron b/naga/tests/in/collatz.param.ron new file mode 100644 index 0000000000..5b157849bc --- /dev/null +++ b/naga/tests/in/collatz.param.ron @@ -0,0 +1,6 @@ +( + spv: ( + version: (1, 0), + debug: true, + ), +) diff --git a/naga/tests/in/collatz.wgsl b/naga/tests/in/collatz.wgsl new file mode 100644 index 0000000000..ebfc41b6f5 --- /dev/null +++ b/naga/tests/in/collatz.wgsl @@ -0,0 +1,32 @@ +struct PrimeIndices { + data: array +} // this is used as both input and output for convenience + +@group(0) @binding(0) +var v_indices: PrimeIndices; + +// The Collatz Conjecture states that for any integer n: +// If n is even, n = n/2 +// If n is odd, n = 3n+1 +// And repeat this process for each new n, you will always eventually reach 1. +// Though the conjecture has not been proven, no counterexample has ever been found. +// This function returns how many times this recurrence needs to be applied to reach 1. +fn collatz_iterations(n_base: u32) -> u32 { + var n = n_base; + var i: u32 = 0u; + while n > 1u { + if n % 2u == 0u { + n = n / 2u; + } + else { + n = 3u * n + 1u; + } + i = i + 1u; + } + return i; +} + +@compute @workgroup_size(1) +fn main(@builtin(global_invocation_id) global_id: vec3) { + v_indices.data[global_id.x] = collatz_iterations(v_indices.data[global_id.x]); +} diff --git a/naga/tests/in/const-exprs.wgsl b/naga/tests/in/const-exprs.wgsl new file mode 100644 index 0000000000..ee9304ce45 --- /dev/null +++ b/naga/tests/in/const-exprs.wgsl @@ -0,0 +1,89 @@ +const TWO: u32 = 2u; +const THREE: i32 = 3i; + +@compute @workgroup_size(TWO, THREE, TWO - 1u) +fn main() { + swizzle_of_compose(); + index_of_compose(); + compose_three_deep(); + non_constant_initializers(); + splat_of_constant(); + compose_of_constant(); + compose_of_splat(); +} + +// Swizzle the value of nested Compose expressions. +fn swizzle_of_compose() { + var out = vec4(vec2(1, 2), vec2(3, 4)).wzyx; // should assign vec4(4, 3, 2, 1); +} + +// Index the value of nested Compose expressions. +fn index_of_compose() { + var out = vec4(vec2(1, 2), vec2(3, 4))[1]; // should assign 2 +} + +// Index the value of Compose expressions nested three deep +fn compose_three_deep() { + var out = vec4(vec3(vec2(6, 7), 8), 9)[0]; // should assign 6 +} + +// While WGSL allows local variables to be declared anywhere in the function, +// Naga treats them all as appearing at the top of the function. To ensure that +// WGSL initializer expressions are evaluated at the right time, in the general +// case they need to be turned into Naga `Store` statements executed at the +// point of the WGSL declaration. +// +// When a variable's initializer is a constant expression, however, it can be +// evaluated at any time. The WGSL front end thus renders locals with +// initializers that are constants as Naga locals with initializers. This test +// checks that Naga local variable initializers are only used when safe. +fn non_constant_initializers() { + var w = 10 + 20; + var x = w; + var y = x; + var z = 30 + 40; + + var out = vec4(w, x, y, z); +} + +// Constant evaluation should be able to see through constants to +// their values. +const FOUR: i32 = 4; + +const FOUR_ALIAS: i32 = FOUR; + +const TEST_CONSTANT_ADDITION: i32 = FOUR + FOUR; +const TEST_CONSTANT_ALIAS_ADDITION: i32 = FOUR_ALIAS + FOUR_ALIAS; + +fn splat_of_constant() { + var out = -vec4(FOUR); +} + +fn compose_of_constant() { + var out = -vec4(FOUR, FOUR, FOUR, FOUR); +} + +const PI: f32 = 3.141; +const phi_sun: f32 = PI * 2.0; + +const DIV: vec4f = vec4(4.0 / 9.0, 0.0, 0.0, 0.0); + +const TEXTURE_KIND_REGULAR: i32 = 0; +const TEXTURE_KIND_WARP: i32 = 1; +const TEXTURE_KIND_SKY: i32 = 2; + +fn map_texture_kind(texture_kind: i32) -> u32 { + switch (texture_kind) { + case TEXTURE_KIND_REGULAR: { return 10u; } + case TEXTURE_KIND_WARP: { return 20u; } + case TEXTURE_KIND_SKY: { return 30u; } + default: { return 0u; } + } +} + +fn compose_of_splat() { + var x = vec4f(vec3f(1.0), 2.0).wzyx; +} + +const add_vec = vec2(1.0f) + vec2(3.0f, 4.0f); +const compare_vec = vec2(3.0f) == vec2(3.0f, 4.0f); diff --git a/naga/tests/in/constructors.param.ron b/naga/tests/in/constructors.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/constructors.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/constructors.wgsl b/naga/tests/in/constructors.wgsl new file mode 100644 index 0000000000..d221dc80de --- /dev/null +++ b/naga/tests/in/constructors.wgsl @@ -0,0 +1,67 @@ +struct Foo { + a: vec4, + b: i32, +} + +// const const1 = vec3(0.0); // TODO: this is now a splat and we need to const eval it +const const2 = vec3(0.0, 1.0, 2.0); +const const3 = mat2x2(0.0, 1.0, 2.0, 3.0); +const const4 = array, 1>(mat2x2(0.0, 1.0, 2.0, 3.0)); + +// zero value constructors +const cz0 = bool(); +const cz1 = i32(); +const cz2 = u32(); +const cz3 = f32(); +const cz4 = vec2(); +const cz5 = mat2x2(); +const cz6 = array(); +const cz7 = Foo(); + +// constructors that infer their type from their parameters +// TODO: these also contain splats +// const cp1 = vec2(0u); +// const cp2 = mat2x2(vec2(0.), vec2(0.)); +const cp3 = array(0, 1, 2, 3); + +@compute @workgroup_size(1) +fn main() { + var foo: Foo; + foo = Foo(vec4(1.0), 1); + + let m0 = mat2x2( + 1.0, 0.0, + 0.0, 1.0, + ); + let m1 = mat4x4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + ); + + // zero value constructors + let zvc0 = bool(); + let zvc1 = i32(); + let zvc2 = u32(); + let zvc3 = f32(); + let zvc4 = vec2(); + let zvc5 = mat2x2(); + let zvc6 = array(); + let zvc7 = Foo(); + + // constructors that infer their type from their parameters + let cit0 = vec2(0u); + let cit1 = mat2x2(vec2(0.), vec2(0.)); + let cit2 = array(0, 1, 2, 3); + + // identity constructors + let ic0 = bool(bool()); + let ic1 = i32(i32()); + let ic2 = u32(u32()); + let ic3 = f32(f32()); + let ic4 = vec2(vec2()); + let ic5 = mat2x3(mat2x3()); + let ic6 = vec2(vec2()); + let ic7 = mat2x3(mat2x3()); +} diff --git a/naga/tests/in/control-flow.param.ron b/naga/tests/in/control-flow.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/control-flow.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/control-flow.wgsl b/naga/tests/in/control-flow.wgsl new file mode 100644 index 0000000000..5a0ef1cbbf --- /dev/null +++ b/naga/tests/in/control-flow.wgsl @@ -0,0 +1,90 @@ +@compute @workgroup_size(1) +fn main(@builtin(global_invocation_id) global_id: vec3) { + //TODO: execution-only barrier? + storageBarrier(); + workgroupBarrier(); + + var pos: i32; + // switch without cases + switch 1 { + default: { + pos = 1; + } + } + + // non-empty switch *not* in last-statement-in-function position + // (return statements might be inserted into the switch cases otherwise) + switch pos { + case 1: { + pos = 0; + break; + } + case 2: { + pos = 1; + } + case 3, 4: { + pos = 2; + } + case 5: { + pos = 3; + } + case default, 6: { + pos = 4; + } + } + + // switch with unsigned integer selectors + switch(0u) { + case 0u: { + } + default: { + } + } + + // non-empty switch in last-statement-in-function position + switch pos { + case 1: { + pos = 0; + break; + } + case 2: { + pos = 1; + } + case 3: { + pos = 2; + } + case 4: {} + default: { + pos = 3; + } + } +} + +fn switch_default_break(i: i32) { + switch i { + default: { + break; + } + } +} + +fn switch_case_break() { + switch(0) { + case 0: { + break; + } + default: {} + } + return; +} + +fn loop_switch_continue(x: i32) { + loop { + switch x { + case 1: { + continue; + } + default: {} + } + } +} diff --git a/naga/tests/in/cubeArrayShadow.wgsl b/naga/tests/in/cubeArrayShadow.wgsl new file mode 100644 index 0000000000..78b8845582 --- /dev/null +++ b/naga/tests/in/cubeArrayShadow.wgsl @@ -0,0 +1,12 @@ +@group(0) @binding(4) +var point_shadow_textures: texture_depth_cube_array; +@group(0) @binding(5) +var point_shadow_textures_sampler: sampler_comparison; + +@fragment +fn fragment() -> @location(0) vec4 { + let frag_ls = vec4(1., 1., 2., 1.).xyz; + let a = textureSampleCompare(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(1), 1.); + + return vec4(a, 1., 1., 1.); +} diff --git a/naga/tests/in/debug-symbol-simple.param.ron b/naga/tests/in/debug-symbol-simple.param.ron new file mode 100644 index 0000000000..2869084cd0 --- /dev/null +++ b/naga/tests/in/debug-symbol-simple.param.ron @@ -0,0 +1,7 @@ +( + spv: ( + version: (1, 1), + debug: true, + adjust_coordinate_space: false, + ), +) \ No newline at end of file diff --git a/naga/tests/in/debug-symbol-simple.wgsl b/naga/tests/in/debug-symbol-simple.wgsl new file mode 100644 index 0000000000..86505a1592 --- /dev/null +++ b/naga/tests/in/debug-symbol-simple.wgsl @@ -0,0 +1,33 @@ +struct VertexInput { + @location(0) position: vec3, + @location(1) color: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) color: vec3, +}; + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.color = model.color; + out.clip_position = vec4(model.position, 1.0); + return out; +} + +// Fragment shader + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + var color = in.color; + for (var i = 0; i < 10; i += 1) { + var ii = f32(i); + color.x += ii*0.001; + color.y += ii*0.002; + } + + return vec4(color, 1.0); +} \ No newline at end of file diff --git a/naga/tests/in/debug-symbol-terrain.param.ron b/naga/tests/in/debug-symbol-terrain.param.ron new file mode 100644 index 0000000000..2869084cd0 --- /dev/null +++ b/naga/tests/in/debug-symbol-terrain.param.ron @@ -0,0 +1,7 @@ +( + spv: ( + version: (1, 1), + debug: true, + adjust_coordinate_space: false, + ), +) \ No newline at end of file diff --git a/naga/tests/in/debug-symbol-terrain.wgsl b/naga/tests/in/debug-symbol-terrain.wgsl new file mode 100644 index 0000000000..3f3dd58dd3 --- /dev/null +++ b/naga/tests/in/debug-symbol-terrain.wgsl @@ -0,0 +1,297 @@ +// Taken from https://github.com/sotrh/learn-wgpu/blob/11820796f5e1dbce42fb1119f04ddeb4b167d2a0/code/intermediate/tutorial13-terrain/src/terrain.wgsl +// ============================ +// Terrain Generation +// ============================ + +// https://gist.github.com/munrocket/236ed5ba7e409b8bdf1ff6eca5dcdc39 +// MIT License. © Ian McEwan, Stefan Gustavson, Munrocket +// - Less condensed glsl implementation with comments can be found at https://weber.itn.liu.se/~stegu/jgt2012/article.pdf + +fn permute3(x: vec3) -> vec3 { return (((x * 34.) + 1.) * x) % vec3(289.); } + +fn snoise2(v: vec2) -> f32 { + let C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); + var i: vec2 = floor(v + dot(v, C.yy)); + let x0 = v - i + dot(i, C.xx); + // I flipped the condition here from > to < as it fixed some artifacting I was observing + var i1: vec2 = select(vec2(1., 0.), vec2(0., 1.), (x0.x < x0.y)); + var x12: vec4 = x0.xyxy + C.xxzz - vec4(i1, 0., 0.); + i = i % vec2(289.); + let p = permute3(permute3(i.y + vec3(0., i1.y, 1.)) + i.x + vec3(0., i1.x, 1.)); + var m: vec3 = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), vec3(0.)); + m = m * m; + m = m * m; + let x = 2. * fract(p * C.www) - 1.; + let h = abs(x) - 0.5; + let ox = floor(x + 0.5); + let a0 = x - ox; + m = m * (1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h)); + let g = vec3(a0.x * x0.x + h.x * x0.y, a0.yz * x12.xz + h.yz * x12.yw); + return 130. * dot(m, g); +} + + +fn fbm(p: vec2) -> f32 { + let NUM_OCTAVES: u32 = 5u; + var x = p * 0.01; + var v = 0.0; + var a = 0.5; + let shift = vec2(100.0); + let cs = vec2(cos(0.5), sin(0.5)); + let rot = mat2x2(cs.x, cs.y, -cs.y, cs.x); + + for (var i = 0u; i < NUM_OCTAVES; i = i + 1u) { + v = v + a * snoise2(x); + x = rot * x * 2.0 + shift; + a = a * 0.5; + } + + return v; +} + +struct ChunkData { + chunk_size: vec2, + chunk_corner: vec2, + min_max_height: vec2, +} + +struct Vertex { + @location(0) position: vec3, + @location(1) normal: vec3, +} + +struct VertexBuffer { + data: array, // stride: 32 +} + +struct IndexBuffer { + data: array, +} + +@group(0) @binding(0) var chunk_data: ChunkData; +@group(0) @binding(1) var vertices: VertexBuffer; +@group(0) @binding(2) var indices: IndexBuffer; + +fn terrain_point(p: vec2, min_max_height: vec2) -> vec3 { + return vec3( + p.x, + mix(min_max_height.x, min_max_height.y, fbm(p)), + p.y, + ); +} + +fn terrain_vertex(p: vec2, min_max_height: vec2) -> Vertex { + let v = terrain_point(p, min_max_height); + + let tpx = terrain_point(p + vec2(0.1, 0.0), min_max_height) - v; + let tpz = terrain_point(p + vec2(0.0, 0.1), min_max_height) - v; + let tnx = terrain_point(p + vec2(-0.1, 0.0), min_max_height) - v; + let tnz = terrain_point(p + vec2(0.0, -0.1), min_max_height) - v; + + let pn = normalize(cross(tpz, tpx)); + let nn = normalize(cross(tnz, tnx)); + + let n = (pn + nn) * 0.5; + + return Vertex(v, n); +} + +fn index_to_p(vert_index: u32, chunk_size: vec2, chunk_corner: vec2) -> vec2 { + return vec2( + f32(vert_index) % f32(chunk_size.x + 1u), + f32(vert_index / (chunk_size.x + 1u)), + ) + vec2(chunk_corner); +} + +@compute @workgroup_size(64) +fn gen_terrain_compute( + @builtin(global_invocation_id) gid: vec3 +) { + // Create vert_component + let vert_index = gid.x; + + let p = index_to_p(vert_index, chunk_data.chunk_size, chunk_data.chunk_corner); + + vertices.data[vert_index] = terrain_vertex(p, chunk_data.min_max_height); + + // Create indices + let start_index = gid.x * 6u; // using TriangleList + + if (start_index >= (chunk_data.chunk_size.x * chunk_data.chunk_size.y * 6u)) { return; } + + let v00 = vert_index + gid.x / chunk_data.chunk_size.x; + let v10 = v00 + 1u; + let v01 = v00 + chunk_data.chunk_size.x + 1u; + let v11 = v01 + 1u; + + indices.data[start_index] = v00; + indices.data[start_index + 1u] = v01; + indices.data[start_index + 2u] = v11; + indices.data[start_index + 3u] = v00; + indices.data[start_index + 4u] = v11; + indices.data[start_index + 5u] = v10; +} + +// ============================ +// Terrain Gen (Fragment Shader) +// ============================ + +struct GenData { + chunk_size: vec2, + chunk_corner: vec2, + min_max_height: vec2, + texture_size: u32, + start_index: u32, +} +@group(0) +@binding(0) +var gen_data: GenData; + +struct GenVertexOutput { + @location(0) + index: u32, + @builtin(position) + position: vec4, + @location(1) + uv: vec2, +}; + +@vertex +fn gen_terrain_vertex(@builtin(vertex_index) vindex: u32) -> GenVertexOutput { + let u = f32(((vindex + 2u) / 3u) % 2u); + let v = f32(((vindex + 1u) / 3u) % 2u); + let uv = vec2(u, v); + + let position = vec4(-1.0 + uv * 2.0, 0.0, 1.0); + + // TODO: maybe replace this with u32(dot(uv, vec2(f32(gen_data.texture_dim.x)))) + let index = u32(uv.x * f32(gen_data.texture_size) + uv.y * f32(gen_data.texture_size)) + gen_data.start_index; + + return GenVertexOutput(index, position, uv); +} + + +struct GenFragmentOutput { + @location(0) vert_component: u32, + @location(1) index: u32, +} + +@fragment +fn gen_terrain_fragment(in: GenVertexOutput) -> GenFragmentOutput { + let i = u32(in.uv.x * f32(gen_data.texture_size) + in.uv.y * f32(gen_data.texture_size * gen_data.texture_size)) + gen_data.start_index; + let vert_index = u32(floor(f32(i) / 6.)); + let comp_index = i % 6u; + + let p = index_to_p(vert_index, gen_data.chunk_size, gen_data.chunk_corner); + let v = terrain_vertex(p, gen_data.min_max_height); + + var vert_component: f32 = 0.; + + switch comp_index { + case 0u: { vert_component = v.position.x; } + case 1u: { vert_component = v.position.y; } + case 2u: { vert_component = v.position.z; } + case 3u: { vert_component = v.normal.x; } + case 4u: { vert_component = v.normal.y; } + case 5u: { vert_component = v.normal.z; } + default: {} + } + + let v00 = vert_index + vert_index / gen_data.chunk_size.x; + let v10 = v00 + 1u; + let v01 = v00 + gen_data.chunk_size.x + 1u; + let v11 = v01 + 1u; + + var index = 0u; + switch comp_index { + case 0u, 3u: { index = v00; } + case 2u, 4u: { index = v11; } + case 1u: { index = v01; } + case 5u: { index = v10; } + default: {} + } + index = in.index; + // index = gen_data.start_index; + // indices.data[start_index] = v00; + // indices.data[start_index + 1u] = v01; + // indices.data[start_index + 2u] = v11; + // indices.data[start_index + 3u] = v00; + // indices.data[start_index + 4u] = v11; + // indices.data[start_index + 5u] = v10; + + let ivert_component = bitcast(vert_component); + return GenFragmentOutput(ivert_component, index); +} + +// ============================ +// Terrain Rendering +// ============================ + +struct Camera { + view_pos: vec4, + view_proj: mat4x4, +} +@group(0) @binding(0) +var camera: Camera; + +struct Light { + position: vec3, + color: vec3, +} +@group(1) @binding(0) +var light: Light; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) normal: vec3, + @location(1) world_pos: vec3, +} + +@vertex +fn vs_main( + vertex: Vertex, +) -> VertexOutput { + let clip_position = camera.view_proj * vec4(vertex.position, 1.); + let normal = vertex.normal; + return VertexOutput(clip_position, normal, vertex.position); +} + +@group(2) @binding(0) +var t_diffuse: texture_2d; +@group(2) @binding(1) +var s_diffuse: sampler; +@group(2) @binding(2) +var t_normal: texture_2d; +@group(2) @binding(3) +var s_normal: sampler; + +fn color23(p: vec2) -> vec3 { + return vec3( + snoise2(p) * 0.5 + 0.5, + snoise2(p + vec2(23., 32.)) * 0.5 + 0.5, + snoise2(p + vec2(-43., 3.)) * 0.5 + 0.5, + ); +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + var color = smoothstep(vec3(0.0), vec3(0.1), fract(in.world_pos)); + color = mix(vec3(0.5, 0.1, 0.7), vec3(0.2, 0.2, 0.2), vec3(color.x * color.y * color.z)); + + let ambient_strength = 0.1; + let ambient_color = light.color * ambient_strength; + + let light_dir = normalize(light.position - in.world_pos); + let view_dir = normalize(camera.view_pos.xyz - in.world_pos); + let half_dir = normalize(view_dir + light_dir); + + let diffuse_strength = max(dot(in.normal, light_dir), 0.0); + let diffuse_color = diffuse_strength * light.color; + + let specular_strength = pow(max(dot(in.normal, half_dir), 0.0), 32.0); + let specular_color = specular_strength * light.color; + + let result = (ambient_color + diffuse_color + specular_color) * color; + + return vec4(result, 1.0); +} \ No newline at end of file diff --git a/naga/tests/in/dualsource.param.ron b/naga/tests/in/dualsource.param.ron new file mode 100644 index 0000000000..9ab5ee4146 --- /dev/null +++ b/naga/tests/in/dualsource.param.ron @@ -0,0 +1,11 @@ +( + god_mode: true, + msl: ( + lang_version: (1, 2), + per_entry_point_map: {}, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/dualsource.wgsl b/naga/tests/in/dualsource.wgsl new file mode 100644 index 0000000000..e7b658ef7d --- /dev/null +++ b/naga/tests/in/dualsource.wgsl @@ -0,0 +1,11 @@ +/* Simple test for multiple output sources from fragment shaders */ +struct FragmentOutput{ + @location(0) color: vec4, + @location(0) @second_blend_source mask: vec4, +} +@fragment +fn main(@builtin(position) position: vec4) -> FragmentOutput { + var color = vec4(0.4,0.3,0.2,0.1); + var mask = vec4(0.9,0.8,0.7,0.6); + return FragmentOutput(color, mask); +} diff --git a/naga/tests/in/empty.param.ron b/naga/tests/in/empty.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/empty.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/empty.wgsl b/naga/tests/in/empty.wgsl new file mode 100644 index 0000000000..b4af5cb416 --- /dev/null +++ b/naga/tests/in/empty.wgsl @@ -0,0 +1,2 @@ +@compute @workgroup_size(1) +fn main() {} diff --git a/naga/tests/in/extra.param.ron b/naga/tests/in/extra.param.ron new file mode 100644 index 0000000000..581c6d6bdb --- /dev/null +++ b/naga/tests/in/extra.param.ron @@ -0,0 +1,18 @@ +( + god_mode: true, + spv: ( + version: (1, 2), + ), + msl: ( + lang_version: (2, 2), + per_entry_point_map: { + "main": ( + push_constant_buffer: Some(1), + ), + }, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/extra.wgsl b/naga/tests/in/extra.wgsl new file mode 100644 index 0000000000..ef68f4aa80 --- /dev/null +++ b/naga/tests/in/extra.wgsl @@ -0,0 +1,19 @@ +struct PushConstants { + index: u32, + double: vec2, +} +var pc: PushConstants; + +struct FragmentIn { + @location(0) color: vec4, + @builtin(primitive_index) primitive_index: u32, +} + +@fragment +fn main(in: FragmentIn) -> @location(0) vec4 { + if in.primitive_index == pc.index { + return in.color; + } else { + return vec4(vec3(1.0) - in.color.rgb, in.color.a); + } +} diff --git a/naga/tests/in/f64.param.ron b/naga/tests/in/f64.param.ron new file mode 100644 index 0000000000..f1f5359da6 --- /dev/null +++ b/naga/tests/in/f64.param.ron @@ -0,0 +1,12 @@ +( + god_mode: true, + spv: ( + version: (1, 0), + ), + glsl: ( + version: Desktop(420), + writer_flags: (""), + binding_map: { }, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/f64.wgsl b/naga/tests/in/f64.wgsl new file mode 100644 index 0000000000..268a6184a6 --- /dev/null +++ b/naga/tests/in/f64.wgsl @@ -0,0 +1,13 @@ +var v: f64 = 1lf; +const k: f64 = 2.0lf; + +fn f(x: f64) -> f64 { + let y: f64 = 3e1lf + 4.0e2lf; + var z = y + f64(5); + return x + y + k + 5.0lf; +} + +@compute @workgroup_size(1) +fn main() { + f(6.0lf); +} diff --git a/naga/tests/in/force_point_size_vertex_shader_webgl.param.ron b/naga/tests/in/force_point_size_vertex_shader_webgl.param.ron new file mode 100644 index 0000000000..c0b7a4d457 --- /dev/null +++ b/naga/tests/in/force_point_size_vertex_shader_webgl.param.ron @@ -0,0 +1,11 @@ +( + glsl: ( + version: Embedded ( + version: 300, + is_webgl: true + ), + writer_flags: ("FORCE_POINT_SIZE"), + binding_map: {}, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/force_point_size_vertex_shader_webgl.wgsl b/naga/tests/in/force_point_size_vertex_shader_webgl.wgsl new file mode 100644 index 0000000000..001468bd50 --- /dev/null +++ b/naga/tests/in/force_point_size_vertex_shader_webgl.wgsl @@ -0,0 +1,14 @@ +// AUTHOR: REASY +// ISSUE: https://github.com/gfx-rs/wgpu/issues/3179 +// FIX: https://github.com/gfx-rs/wgpu/pull/3440 +@vertex +fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + let x = f32(i32(in_vertex_index) - 1); + let y = f32(i32(in_vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); +} + +@fragment +fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/naga/tests/in/fragment-output.wgsl b/naga/tests/in/fragment-output.wgsl new file mode 100644 index 0000000000..d2d75e7898 --- /dev/null +++ b/naga/tests/in/fragment-output.wgsl @@ -0,0 +1,41 @@ +// Split up because some output languages limit number of locations to 8. +struct FragmentOutputVec4Vec3 { + @location(0) vec4f: vec4, + @location(1) vec4i: vec4, + @location(2) vec4u: vec4, + @location(3) vec3f: vec3, + @location(4) vec3i: vec3, + @location(5) vec3u: vec3, +} +@fragment +fn main_vec4vec3() -> FragmentOutputVec4Vec3 { + var output: FragmentOutputVec4Vec3; + output.vec4f = vec4(0.0); + output.vec4i = vec4(0); + output.vec4u = vec4(0u); + output.vec3f = vec3(0.0); + output.vec3i = vec3(0); + output.vec3u = vec3(0u); + return output; +} + +struct FragmentOutputVec2Scalar { + @location(0) vec2f: vec2, + @location(1) vec2i: vec2, + @location(2) vec2u: vec2, + @location(3) scalarf: f32, + @location(4) scalari: i32, + @location(5) scalaru: u32, +} + +@fragment +fn main_vec2scalar() -> FragmentOutputVec2Scalar { + var output: FragmentOutputVec2Scalar; + output.vec2f = vec2(0.0); + output.vec2i = vec2(0); + output.vec2u = vec2(0u); + output.scalarf = 0.0; + output.scalari = 0; + output.scalaru = 0u; + return output; +} diff --git a/naga/tests/in/functions-webgl.param.ron b/naga/tests/in/functions-webgl.param.ron new file mode 100644 index 0000000000..ddb54c093c --- /dev/null +++ b/naga/tests/in/functions-webgl.param.ron @@ -0,0 +1,11 @@ +( + glsl: ( + version: Embedded( + version: 320, + is_webgl: false + ), + writer_flags: (""), + binding_map: {}, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/functions-webgl.wgsl b/naga/tests/in/functions-webgl.wgsl new file mode 100644 index 0000000000..2355aa2c99 --- /dev/null +++ b/naga/tests/in/functions-webgl.wgsl @@ -0,0 +1,13 @@ +fn test_fma() -> vec2 { + let a = vec2(2.0, 2.0); + let b = vec2(0.5, 0.5); + let c = vec2(0.5, 0.5); + + return fma(a, b, c); +} + + +@fragment +fn main() { + let a = test_fma(); +} diff --git a/naga/tests/in/functions.param.ron b/naga/tests/in/functions.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/functions.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/functions.wgsl b/naga/tests/in/functions.wgsl new file mode 100644 index 0000000000..23e4b0400e --- /dev/null +++ b/naga/tests/in/functions.wgsl @@ -0,0 +1,29 @@ +fn test_fma() -> vec2 { + let a = vec2(2.0, 2.0); + let b = vec2(0.5, 0.5); + let c = vec2(0.5, 0.5); + + // Hazard: HLSL needs a different intrinsic function for f32 and f64 + // See: https://github.com/gfx-rs/naga/issues/1579 + return fma(a, b, c); +} + +fn test_integer_dot_product() -> i32 { + let a_2 = vec2(1); + let b_2 = vec2(1); + let c_2: i32 = dot(a_2, b_2); + + let a_3 = vec3(1u); + let b_3 = vec3(1u); + let c_3: u32 = dot(a_3, b_3); + + // test baking of arguments + let c_4: i32 = dot(vec4(4), vec4(2)); + return c_4; +} + +@compute @workgroup_size(1) +fn main() { + let a = test_fma(); + let b = test_integer_dot_product(); +} diff --git a/naga/tests/in/globals.param.ron b/naga/tests/in/globals.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/globals.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/globals.wgsl b/naga/tests/in/globals.wgsl new file mode 100644 index 0000000000..859ad369ed --- /dev/null +++ b/naga/tests/in/globals.wgsl @@ -0,0 +1,78 @@ +// Global variable & constant declarations + +const Foo: bool = true; + +var wg : array; +var at: atomic; + +struct FooStruct { + v3: vec3, + // test packed vec3 + v1: f32, +} +@group(0) @binding(1) +var alignment: FooStruct; + +@group(0) @binding(2) +var dummy: array>; + +@group(0) @binding(3) +var float_vecs: array, 20>; + +@group(0) @binding(4) +var global_vec: vec3; + +@group(0) @binding(5) +var global_mat: mat3x2; + +@group(0) @binding(6) +var global_nested_arrays_of_matrices_2x4: array, 2>, 2>; + +@group(0) @binding(7) +var global_nested_arrays_of_matrices_4x2: array, 2>, 2>; + +fn test_msl_packed_vec3_as_arg(arg: vec3) {} + +fn test_msl_packed_vec3() { + // stores + alignment.v3 = vec3(1.0); + var idx = 1; + alignment.v3.x = 1.0; + alignment.v3[0] = 2.0; + alignment.v3[idx] = 3.0; + + // force load to happen here + let data = alignment; + + // loads + let l0 = data.v3; + let l1 = data.v3.zx; + test_msl_packed_vec3_as_arg(data.v3); + + // matrix vector multiplication + let mvm0 = data.v3 * mat3x3(); + let mvm1 = mat3x3() * data.v3; + + // scalar vector multiplication + let svm0 = data.v3 * 2.0; + let svm1 = 2.0 * data.v3; +} + +@compute @workgroup_size(1) +fn main() { + test_msl_packed_vec3(); + + wg[7] = (global_nested_arrays_of_matrices_4x2[0][0] * global_nested_arrays_of_matrices_2x4[0][0][0]).x; + wg[6] = (global_mat * global_vec).x; + wg[5] = dummy[1].y; + wg[4] = float_vecs[0].w; + wg[3] = alignment.v1; + wg[2] = alignment.v3.x; + alignment.v1 = 4.0; + wg[1] = f32(arrayLength(&dummy)); + atomicStore(&at, 2u); + + // Valid, Foo and at is in function scope + var Foo: f32 = 1.0; + var at: bool = true; +} diff --git a/naga/tests/in/glsl/210-bevy-2d-shader.frag b/naga/tests/in/glsl/210-bevy-2d-shader.frag new file mode 100644 index 0000000000..93119ef259 --- /dev/null +++ b/naga/tests/in/glsl/210-bevy-2d-shader.frag @@ -0,0 +1,27 @@ +// AUTHOR: mrk-its +// ISSUE: #210 +// FIX: #898 +#version 450 + +layout(location = 0) in vec2 v_Uv; + +layout(location = 0) out vec4 o_Target; + +layout(set = 1, binding = 0) uniform ColorMaterial_color { + vec4 Color; +}; + +# ifdef COLORMATERIAL_TEXTURE +layout(set = 1, binding = 1) uniform texture2D ColorMaterial_texture; +layout(set = 1, binding = 2) uniform sampler ColorMaterial_texture_sampler; +# endif + +void main() { + vec4 color = Color; +# ifdef COLORMATERIAL_TEXTURE + color *= texture( + sampler2D(ColorMaterial_texture, ColorMaterial_texture_sampler), + v_Uv); +# endif + o_Target = color; +} diff --git a/naga/tests/in/glsl/210-bevy-2d-shader.vert b/naga/tests/in/glsl/210-bevy-2d-shader.vert new file mode 100644 index 0000000000..1d99e1b177 --- /dev/null +++ b/naga/tests/in/glsl/210-bevy-2d-shader.vert @@ -0,0 +1,27 @@ +// AUTHOR: mrk-its +// ISSUE: #210 +// FIX: #898 +#version 450 + +layout(location = 0) in vec3 Vertex_Position; +layout(location = 1) in vec3 Vertex_Normal; +layout(location = 2) in vec2 Vertex_Uv; + +layout(location = 0) out vec2 v_Uv; + +layout(set = 0, binding = 0) uniform Camera { + mat4 ViewProj; +}; + +layout(set = 2, binding = 0) uniform Transform { + mat4 Model; +}; +layout(set = 2, binding = 1) uniform Sprite_size { + vec2 size; +}; + +void main() { + v_Uv = Vertex_Uv; + vec3 position = Vertex_Position * vec3(size, 1.0); + gl_Position = ViewProj * Model * vec4(position, 1.0); +} diff --git a/naga/tests/in/glsl/210-bevy-shader.vert b/naga/tests/in/glsl/210-bevy-shader.vert new file mode 100644 index 0000000000..64cdca2ce2 --- /dev/null +++ b/naga/tests/in/glsl/210-bevy-shader.vert @@ -0,0 +1,28 @@ +// AUTHOR: enfipy +// ISSUE: #210 +// FIX: #898 +#version 450 + +layout(location = 0) in vec3 Vertex_Position; +layout(location = 1) in vec3 Vertex_Normal; +layout(location = 2) in vec2 Vertex_Uv; + +layout(location = 0) out vec3 v_Position; +layout(location = 1) out vec3 v_Normal; +layout(location = 2) out vec2 v_Uv; + +layout(set = 0, binding = 0) uniform Camera { + mat4 ViewProj; +}; + +layout(set = 2, binding = 0) uniform Transform { + mat4 Model; +}; + +void main() { + v_Normal = (Model * vec4(Vertex_Normal, 1.0)).xyz; + v_Normal = mat3(Model) * Vertex_Normal; + v_Position = (Model * vec4(Vertex_Position, 1.0)).xyz; + v_Uv = Vertex_Uv; + gl_Position = ViewProj * vec4(v_Position, 1.0); +} diff --git a/naga/tests/in/glsl/246-collatz.comp b/naga/tests/in/glsl/246-collatz.comp new file mode 100644 index 0000000000..274cdca1c3 --- /dev/null +++ b/naga/tests/in/glsl/246-collatz.comp @@ -0,0 +1,34 @@ +// AUTHOR: Unknown +// ISSUE: #246 +// NOTE: Taken from the wgpu repo +#version 450 +layout(local_size_x = 1) in; + +layout(set = 0, binding = 0) buffer PrimeIndices { + uint[] indices; +}; // this is used as both input and output for convenience + +// The Collatz Conjecture states that for any integer n: +// If n is even, n = n/2 +// If n is odd, n = 3n+1 +// And repeat this process for each new n, you will always eventually reach 1. +// Though the conjecture has not been proven, no counterexample has ever been found. +// This function returns how many times this recurrence needs to be applied to reach 1. +uint collatz_iterations(uint n) { + uint i = 0; + while(n != 1) { + if (mod(n, 2) == 0) { + n = n / 2; + } + else { + n = (3 * n) + 1; + } + i++; + } + return i; +} + +void main() { + uint index = gl_GlobalInvocationID.x; + indices[index] = collatz_iterations(indices[index]); +} diff --git a/naga/tests/in/glsl/277-casting.frag b/naga/tests/in/glsl/277-casting.frag new file mode 100644 index 0000000000..939be006b7 --- /dev/null +++ b/naga/tests/in/glsl/277-casting.frag @@ -0,0 +1,8 @@ +// AUTHOR: Napokue +// ISSUE: #277 +// FIX: #278 +#version 450 + +void main() { + float a = float(1); +} diff --git a/naga/tests/in/glsl/280-matrix-cast.frag b/naga/tests/in/glsl/280-matrix-cast.frag new file mode 100644 index 0000000000..560a442608 --- /dev/null +++ b/naga/tests/in/glsl/280-matrix-cast.frag @@ -0,0 +1,8 @@ +// AUTHOR: pjoe +// ISSUE: #280 +// FIX: #898 +#version 450 + +void main() { + mat4 a = mat4(1); +} diff --git a/naga/tests/in/glsl/484-preprocessor-if.frag b/naga/tests/in/glsl/484-preprocessor-if.frag new file mode 100644 index 0000000000..d2c92030e8 --- /dev/null +++ b/naga/tests/in/glsl/484-preprocessor-if.frag @@ -0,0 +1,10 @@ +// AUTHOR: fintelia +// ISSUE: #484 +// FIX: https://github.com/Kangz/glslpp-rs/pull/30 +// NOTE: Shader altered to use correct syntax +#version 450 core + +#if 0 +#endif + +void main() { } diff --git a/naga/tests/in/glsl/800-out-of-bounds-panic.vert b/naga/tests/in/glsl/800-out-of-bounds-panic.vert new file mode 100644 index 0000000000..e689d26767 --- /dev/null +++ b/naga/tests/in/glsl/800-out-of-bounds-panic.vert @@ -0,0 +1,25 @@ +// AUTHOR: Herschel +// ISSUE: #800 +// FIX: #901 +#version 450 + +// Set 0: globals +layout(set = 0, binding = 0) uniform Globals { + mat4 view_matrix; +}; + +// Push constants: matrix + color +layout(push_constant) uniform VertexPushConstants { + mat4 world_matrix; +}; + +layout(location = 0) in vec2 position; +layout(location = 1) in vec4 color; + +layout(location = 0) out vec4 frag_color; + +void main() { + frag_color = color; + gl_Position = view_matrix * world_matrix * vec4(position, 0.0, 1.0); + gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0; +} diff --git a/naga/tests/in/glsl/896-push-constant.frag b/naga/tests/in/glsl/896-push-constant.frag new file mode 100644 index 0000000000..59ea1e4c4e --- /dev/null +++ b/naga/tests/in/glsl/896-push-constant.frag @@ -0,0 +1,10 @@ +// AUTHOR: Foltik +// ISSUE: #896 +// FIX: #897 +#version 450 + +layout(push_constant) uniform PushConstants { + float example; +} c; + +void main() {} diff --git a/naga/tests/in/glsl/900-implicit-conversions.frag b/naga/tests/in/glsl/900-implicit-conversions.frag new file mode 100644 index 0000000000..c534cfad51 --- /dev/null +++ b/naga/tests/in/glsl/900-implicit-conversions.frag @@ -0,0 +1,22 @@ +// ISSUE: #900 +#version 450 + +// Signature match call the second overload +void exact(float a) {} +void exact(int a) {} + +// No signature match but one overload satisfies the cast rules +void implicit(float a) {} +void implicit(int a) {} + +// All satisfy the kind condition but they have different dimensions +void implicit_dims(float v) { } +void implicit_dims(vec2 v) { } +void implicit_dims(vec3 v) { } +void implicit_dims(vec4 v) { } + +void main() { + exact(1); + implicit(1u); + implicit_dims(ivec3(1)); +} diff --git a/naga/tests/in/glsl/901-lhs-field-select.frag b/naga/tests/in/glsl/901-lhs-field-select.frag new file mode 100644 index 0000000000..a9b7348fd9 --- /dev/null +++ b/naga/tests/in/glsl/901-lhs-field-select.frag @@ -0,0 +1,9 @@ +// AUTHOR: JCapucho +// ISSUE: #901 +// FIX: #948 +#version 450 + +void main() { + vec4 a = vec4(1.0); + a.x = 2.0; +} diff --git a/naga/tests/in/glsl/931-constant-emitting.frag b/naga/tests/in/glsl/931-constant-emitting.frag new file mode 100644 index 0000000000..1a3300bc81 --- /dev/null +++ b/naga/tests/in/glsl/931-constant-emitting.frag @@ -0,0 +1,12 @@ +// AUTHOR: jakobhellermann +// ISSUE: #931 +// FIX: #933 +#version 450 + +const int constant = 10; + +float function() { + return 0.0; +} + +void main() {} diff --git a/naga/tests/in/glsl/932-for-loop-if.frag b/naga/tests/in/glsl/932-for-loop-if.frag new file mode 100644 index 0000000000..07dbdd751c --- /dev/null +++ b/naga/tests/in/glsl/932-for-loop-if.frag @@ -0,0 +1,8 @@ +// AUTHOR: jakobhellermann +// ISSUE: #932 +// FIX: #935 +#version 450 + +void main() { + for (int i = 0; i < 1; i += 1) {} +} diff --git a/naga/tests/in/glsl/bevy-pbr.frag b/naga/tests/in/glsl/bevy-pbr.frag new file mode 100644 index 0000000000..976ff91f8f --- /dev/null +++ b/naga/tests/in/glsl/bevy-pbr.frag @@ -0,0 +1,390 @@ +// MIT License +// +// Copyright (c) 2020 Carter Anderson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// NOTE: Taken from the bevy repo +#version 450 + +const int MAX_POINT_LIGHTS = 10; +const int MAX_DIRECTIONAL_LIGHTS = 1; + +struct PointLight { + vec4 pos; + vec4 color; + vec4 lightParams; +}; + +struct DirectionalLight { + vec4 direction; + vec4 color; +}; + +layout(location = 0) in vec3 v_WorldPosition; +layout(location = 1) in vec3 v_WorldNormal; +layout(location = 2) in vec2 v_Uv; +layout(location = 3) in vec4 v_WorldTangent; + +layout(location = 0) out vec4 o_Target; + +layout(set = 0, binding = 0) uniform CameraViewProj { + mat4 ViewProj; +}; +layout(std140, set = 0, binding = 1) uniform CameraPosition { + vec4 CameraPos; +}; + +layout(std140, set = 1, binding = 0) uniform Lights { + vec4 AmbientColor; + uvec4 NumLights; // x = point lights, y = directional lights + PointLight PointLights[MAX_POINT_LIGHTS]; + DirectionalLight DirectionalLights[MAX_DIRECTIONAL_LIGHTS]; +}; + +layout(set = 3, binding = 0) uniform StandardMaterial_base_color { + vec4 base_color; +}; + +layout(set = 3, binding = 1) uniform texture2D StandardMaterial_base_color_texture; +layout(set = 3, + binding = 2) uniform sampler StandardMaterial_base_color_texture_sampler; + +layout(set = 3, binding = 3) uniform StandardMaterial_roughness { + float perceptual_roughness; +}; + +layout(set = 3, binding = 4) uniform StandardMaterial_metallic { + float metallic; +}; + +layout(set = 3, binding = 5) uniform texture2D StandardMaterial_metallic_roughness_texture; +layout(set = 3, + binding = 6) uniform sampler StandardMaterial_metallic_roughness_texture_sampler; + +layout(set = 3, binding = 7) uniform StandardMaterial_reflectance { + float reflectance; +}; + +layout(set = 3, binding = 8) uniform texture2D StandardMaterial_normal_map; +layout(set = 3, + binding = 9) uniform sampler StandardMaterial_normal_map_sampler; + +layout(set = 3, binding = 10) uniform texture2D StandardMaterial_occlusion_texture; +layout(set = 3, + binding = 11) uniform sampler StandardMaterial_occlusion_texture_sampler; + +layout(set = 3, binding = 12) uniform StandardMaterial_emissive { + vec4 emissive; +}; + +layout(set = 3, binding = 13) uniform texture2D StandardMaterial_emissive_texture; +layout(set = 3, + binding = 14) uniform sampler StandardMaterial_emissive_texture_sampler; + +# define saturate(x) clamp(x, 0.0, 1.0) +const float PI = 3.141592653589793; + +float pow5(float x) { + float x2 = x * x; + return x2 * x2 * x; +} + +// distanceAttenuation is simply the square falloff of light intensity +// combined with a smooth attenuation at the edge of the light radius +// +// light radius is a non-physical construct for efficiency purposes, +// because otherwise every light affects every fragment in the scene +float getDistanceAttenuation(float distanceSquare, float inverseRangeSquared) { + float factor = distanceSquare * inverseRangeSquared; + float smoothFactor = saturate(1.0 - factor * factor); + float attenuation = smoothFactor * smoothFactor; + return attenuation * 1.0 / max(distanceSquare, 1e-3); +} + +// Normal distribution function (specular D) +// Based on https://google.github.io/filament/Filament.html#citation-walter07 + +// D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 } + +// Simple implementation, has precision problems when using fp16 instead of fp32 +// see https://google.github.io/filament/Filament.html#listing_speculardfp16 +float D_GGX(float roughness, float NoH, const vec3 h) { + float oneMinusNoHSquared = 1.0 - NoH * NoH; + float a = NoH * roughness; + float k = roughness / (oneMinusNoHSquared + a * a); + float d = k * k * (1.0 / PI); + return d; +} + +// Visibility function (Specular G) +// V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) } +// such that f_r becomes +// f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0) +// where +// V(v,l,α) = 0.5 / { n⋅l sqrt((n⋅v)^2 (1−α2) + α2) + n⋅v sqrt((n⋅l)^2 (1−α2) + α2) } +// Note the two sqrt's, that may be slow on mobile, see https://google.github.io/filament/Filament.html#listing_approximatedspecularv +float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) { + float a2 = roughness * roughness; + float lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2); + float lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2); + float v = 0.5 / (lambdaV + lambdaL); + return v; +} + +// Fresnel function +// see https://google.github.io/filament/Filament.html#citation-schlick94 +// F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5 +vec3 F_Schlick(const vec3 f0, float f90, float VoH) { + // not using mix to keep the vec3 and float versions identical + return f0 + (vec3(f90) - f0) * pow5(1.0 - VoH); +} + +float F_Schlick(float f0, float f90, float VoH) { + // not using mix to keep the vec3 and float versions identical + return f0 + (f90 - f0) * pow5(1.0 - VoH); +} + +vec3 fresnel(vec3 f0, float LoH) { + // f_90 suitable for ambient occlusion + // see https://google.github.io/filament/Filament.html#lighting/occlusion + float f90 = saturate(dot(f0, vec3(50.0 * 0.33))); + return F_Schlick(f0, f90, LoH); +} + +// Specular BRDF +// https://google.github.io/filament/Filament.html#materialsystem/specularbrdf + +// Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m +// f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) } +vec3 specular(vec3 f0, float roughness, const vec3 h, float NoV, float NoL, + float NoH, float LoH, float specularIntensity) { + float D = D_GGX(roughness, NoH, h); + float V = V_SmithGGXCorrelated(roughness, NoV, NoL); + vec3 F = fresnel(f0, LoH); + + return (specularIntensity * D * V) * F; +} + +// Diffuse BRDF +// https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf +// fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm + +// simplest approximation +// float Fd_Lambert() { +// return 1.0 / PI; +// } +// +// vec3 Fd = diffuseColor * Fd_Lambert(); + +// Disney approximation +// See https://google.github.io/filament/Filament.html#citation-burley12 +// minimal quality difference +float Fd_Burley(float roughness, float NoV, float NoL, float LoH) { + float f90 = 0.5 + 2.0 * roughness * LoH * LoH; + float lightScatter = F_Schlick(1.0, f90, NoL); + float viewScatter = F_Schlick(1.0, f90, NoV); + return lightScatter * viewScatter * (1.0 / PI); +} + +// From https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile +vec3 EnvBRDFApprox(vec3 f0, float perceptual_roughness, float NoV) { + const vec4 c0 = { -1.0, -0.0275, -0.572, 0.022 }; + const vec4 c1 = { 1.0, 0.0425, 1.04, -0.04 }; + vec4 r = vec4(perceptual_roughness) * c0 + c1; + float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; + vec2 AB = vec2(-1.04, 1.04) * vec2(a004) + r.zw; + return f0 * vec3(AB.x) + vec3(AB.y); +} + +float perceptualRoughnessToRoughness(float perceptualRoughness) { + // clamp perceptual roughness to prevent precision problems + // According to Filament design 0.089 is recommended for mobile + // Filament uses 0.045 for non-mobile + float clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0); + return clampedPerceptualRoughness * clampedPerceptualRoughness; +} + +// from https://64.github.io/tonemapping/ +// reinhard on RGB oversaturates colors +vec3 reinhard(vec3 color) { + return color / (vec3(1.0) + color); +} + +vec3 reinhard_extended(vec3 color, float max_white) { + vec3 numerator = color * (vec3(1.0) + (color / vec3(max_white * max_white))); + return numerator / (vec3(1.0) + color); +} + +// luminance coefficients from Rec. 709. +// https://en.wikipedia.org/wiki/Rec._709 +float luminance(vec3 v) { + return dot(v, vec3(0.2126, 0.7152, 0.0722)); +} + +vec3 change_luminance(vec3 c_in, float l_out) { + float l_in = luminance(c_in); + return c_in * (l_out / l_in); +} + +vec3 reinhard_luminance(vec3 color) { + float l_old = luminance(color); + float l_new = l_old / (1.0f + l_old); + return change_luminance(color, l_new); +} + +vec3 reinhard_extended_luminance(vec3 color, float max_white_l) { + float l_old = luminance(color); + float numerator = l_old * (1.0f + (l_old / (max_white_l * max_white_l))); + float l_new = numerator / (1.0f + l_old); + return change_luminance(color, l_new); +} + +vec3 point_light(PointLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) { + vec3 light_to_frag = light.pos.xyz - v_WorldPosition.xyz; + float distance_square = dot(light_to_frag, light_to_frag); + float rangeAttenuation = + getDistanceAttenuation(distance_square, light.lightParams.r); + + // Specular. + // Representative Point Area Lights. + // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 + float a = roughness; + float radius = light.lightParams.g; + vec3 centerToRay = dot(light_to_frag, R) * R - light_to_frag; + vec3 closestPoint = light_to_frag + centerToRay * saturate(radius * inversesqrt(dot(centerToRay, centerToRay))); + float LspecLengthInverse = inversesqrt(dot(closestPoint, closestPoint)); + float normalizationFactor = a / saturate(a + (radius * 0.5 * LspecLengthInverse)); + float specularIntensity = normalizationFactor * normalizationFactor; + + vec3 L = closestPoint * LspecLengthInverse; // normalize() equivalent? + vec3 H = normalize(L + V); + float NoL = saturate(dot(N, L)); + float NoH = saturate(dot(N, H)); + float LoH = saturate(dot(L, H)); + + vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); + + // Diffuse. + // Comes after specular since its NoL is used in the lighting equation. + L = normalize(light_to_frag); + H = normalize(L + V); + NoL = saturate(dot(N, L)); + NoH = saturate(dot(N, H)); + LoH = saturate(dot(L, H)); + + vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); + + // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩ + // where + // f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color + // Φ is light intensity + + // our rangeAttentuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius + // It's not 100% clear where the 1/4π goes in the derivation, but we follow the filament shader and leave it out + + // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation + // TODO compensate for energy loss https://google.github.io/filament/Filament.html#materialsystem/improvingthebrdfs/energylossinspecularreflectance + // light.color.rgb is premultiplied with light.intensity on the CPU + return ((diffuse + specular) * light.color.rgb) * (rangeAttenuation * NoL); +} + +vec3 dir_light(DirectionalLight light, float roughness, float NdotV, vec3 normal, vec3 view, vec3 R, vec3 F0, vec3 diffuseColor) { + vec3 incident_light = light.direction.xyz; + + vec3 half_vector = normalize(incident_light + view); + float NoL = saturate(dot(normal, incident_light)); + float NoH = saturate(dot(normal, half_vector)); + float LoH = saturate(dot(incident_light, half_vector)); + + vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); + float specularIntensity = 1.0; + vec3 specular = specular(F0, roughness, half_vector, NdotV, NoL, NoH, LoH, specularIntensity); + + return (specular + diffuse) * light.color.rgb * NoL; +} + +void main() { + vec4 output_color = base_color; + output_color *= texture(sampler2D(StandardMaterial_base_color_texture, + StandardMaterial_base_color_texture_sampler), + v_Uv); + + // calculate non-linear roughness from linear perceptualRoughness + vec4 metallic_roughness = texture(sampler2D(StandardMaterial_metallic_roughness_texture, StandardMaterial_metallic_roughness_texture_sampler), v_Uv); + // Sampling from GLTF standard channels for now + float metallic = metallic * metallic_roughness.b; + float perceptual_roughness = perceptual_roughness * metallic_roughness.g; + + float roughness = perceptualRoughnessToRoughness(perceptual_roughness); + + vec3 N = normalize(v_WorldNormal); + + vec3 T = normalize(v_WorldTangent.xyz); + vec3 B = cross(N, T) * v_WorldTangent.w; + + N = gl_FrontFacing ? N : -N; + T = gl_FrontFacing ? T : -T; + B = gl_FrontFacing ? B : -B; + + mat3 TBN = mat3(T, B, N); + N = TBN * normalize(texture(sampler2D(StandardMaterial_normal_map, StandardMaterial_normal_map_sampler), v_Uv).rgb * 2.0 - vec3(1.0)); + + float occlusion = texture(sampler2D(StandardMaterial_occlusion_texture, StandardMaterial_occlusion_texture_sampler), v_Uv).r; + + vec4 emissive = emissive; + // TODO use .a for exposure compensation in HDR + emissive.rgb *= texture(sampler2D(StandardMaterial_emissive_texture, StandardMaterial_emissive_texture_sampler), v_Uv).rgb; + vec3 V = normalize(CameraPos.xyz - v_WorldPosition.xyz); + // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" + float NdotV = max(dot(N, V), 1e-3); + + // Remapping [0,1] reflectance to F0 + // See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping + vec3 F0 = vec3(0.16 * reflectance * reflectance * (1.0 - metallic)) + output_color.rgb * vec3(metallic); + + // Diffuse strength inversely related to metallicity + vec3 diffuseColor = output_color.rgb * vec3(1.0 - metallic); + + vec3 R = reflect(-V, N); + + // accumulate color + vec3 light_accum = vec3(0.0); + for (int i = 0; i < int(NumLights.x) && i < MAX_POINT_LIGHTS; ++i) { + light_accum += point_light(PointLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); + } + for (int i = 0; i < int(NumLights.y) && i < MAX_DIRECTIONAL_LIGHTS; ++i) { + light_accum += dir_light(DirectionalLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); + } + + vec3 diffuse_ambient = EnvBRDFApprox(diffuseColor, 1.0, NdotV); + vec3 specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV); + + output_color.rgb = light_accum; + output_color.rgb += (diffuse_ambient + specular_ambient) * AmbientColor.xyz * occlusion; + output_color.rgb += emissive.rgb * output_color.a; + + // tone_mapping + output_color.rgb = reinhard_luminance(output_color.rgb); + // Gamma correction. + // Not needed with sRGB buffer + // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); + + o_Target = output_color; +} diff --git a/naga/tests/in/glsl/bevy-pbr.vert b/naga/tests/in/glsl/bevy-pbr.vert new file mode 100644 index 0000000000..0afd3f64c1 --- /dev/null +++ b/naga/tests/in/glsl/bevy-pbr.vert @@ -0,0 +1,53 @@ +// MIT License +// +// Copyright (c) 2020 Carter Anderson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// NOTE: Taken from the bevy repo +#version 450 + +layout(location = 0) in vec3 Vertex_Position; +layout(location = 1) in vec3 Vertex_Normal; +layout(location = 2) in vec2 Vertex_Uv; + +layout(location = 3) in vec4 Vertex_Tangent; + +layout(location = 0) out vec3 v_WorldPosition; +layout(location = 1) out vec3 v_WorldNormal; +layout(location = 2) out vec2 v_Uv; + +layout(set = 0, binding = 0) uniform CameraViewProj { + mat4 ViewProj; +}; + +layout(location = 3) out vec4 v_WorldTangent; + +layout(set = 2, binding = 0) uniform Transform { + mat4 Model; +}; + +void main() { + vec4 world_position = Model * vec4(Vertex_Position, 1.0); + v_WorldPosition = world_position.xyz; + v_WorldNormal = mat3(Model) * Vertex_Normal; + v_Uv = Vertex_Uv; + v_WorldTangent = vec4(mat3(Model) * Vertex_Tangent.xyz, Vertex_Tangent.w); + gl_Position = ViewProj * world_position; +} diff --git a/naga/tests/in/glsl/bits_glsl.frag b/naga/tests/in/glsl/bits_glsl.frag new file mode 100644 index 0000000000..807ca32160 --- /dev/null +++ b/naga/tests/in/glsl/bits_glsl.frag @@ -0,0 +1,56 @@ +#version 450 + +void main() { + int i = 0; + ivec2 i2 = ivec2(0); + ivec3 i3 = ivec3(0); + ivec4 i4 = ivec4(0); + uint u = 0; + uvec2 u2 = uvec2(0); + uvec3 u3 = uvec3(0); + uvec4 u4 = uvec4(0); + vec2 f2 = vec2(0.0); + vec4 f4 = vec4(0.0); + u = packSnorm4x8(f4); + u = packUnorm4x8(f4); + u = packSnorm2x16(f2); + u = packUnorm2x16(f2); + u = packHalf2x16(f2); + f4 = unpackSnorm4x8(u); + f4 = unpackUnorm4x8(u); + f2 = unpackSnorm2x16(u); + f2 = unpackUnorm2x16(u); + f2 = unpackHalf2x16(u); + i = bitfieldInsert(i, i, 5, 10); + i2 = bitfieldInsert(i2, i2, 5, 10); + i3 = bitfieldInsert(i3, i3, 5, 10); + i4 = bitfieldInsert(i4, i4, 5, 10); + u = bitfieldInsert(u, u, 5, 10); + u2 = bitfieldInsert(u2, u2, 5, 10); + u3 = bitfieldInsert(u3, u3, 5, 10); + u4 = bitfieldInsert(u4, u4, 5, 10); + i = bitfieldExtract(i, 5, 10); + i2 = bitfieldExtract(i2, 5, 10); + i3 = bitfieldExtract(i3, 5, 10); + i4 = bitfieldExtract(i4, 5, 10); + u = bitfieldExtract(u, 5, 10); + u2 = bitfieldExtract(u2, 5, 10); + u3 = bitfieldExtract(u3, 5, 10); + u4 = bitfieldExtract(u4, 5, 10); + i = findLSB(i); + i2 = findLSB(i2); + i3 = findLSB(i3); + i4 = findLSB(i4); + i = findLSB(u); + i2 = findLSB(u2); + i3 = findLSB(u3); + i4 = findLSB(u4); + i = findMSB(i); + i2 = findMSB(i2); + i3 = findMSB(i3); + i4 = findMSB(i4); + i = findMSB(u); + i2 = findMSB(u2); + i3 = findMSB(u3); + i4 = findMSB(u4); +} \ No newline at end of file diff --git a/naga/tests/in/glsl/bool-select.frag b/naga/tests/in/glsl/bool-select.frag new file mode 100644 index 0000000000..96cd7d4ae8 --- /dev/null +++ b/naga/tests/in/glsl/bool-select.frag @@ -0,0 +1,17 @@ +#version 440 core +precision highp float; + +layout(location = 0) out vec4 o_color; + +float TevPerCompGT(float a, float b) { + return float(a > b); +} + +vec3 TevPerCompGT(vec3 a, vec3 b) { + return vec3(greaterThan(a, b)); +} + +void main() { + o_color.rgb = TevPerCompGT(vec3(3.0), vec3(5.0)); + o_color.a = TevPerCompGT(3.0, 5.0); +} diff --git a/naga/tests/in/glsl/buffer.frag b/naga/tests/in/glsl/buffer.frag new file mode 100644 index 0000000000..e57380f46e --- /dev/null +++ b/naga/tests/in/glsl/buffer.frag @@ -0,0 +1,16 @@ +#version 450 + +layout(set = 0, binding = 0) buffer testBufferBlock { + uint[] data; +} testBuffer; + +layout(set = 0, binding = 2) readonly buffer testBufferReadOnlyBlock { + uint[] data; +} testBufferReadOnly; + +void main() { + uint a = testBuffer.data[0]; + testBuffer.data[1] = 2; + + uint b = testBufferReadOnly.data[0]; +} diff --git a/naga/tests/in/glsl/clamp-splat.vert b/naga/tests/in/glsl/clamp-splat.vert new file mode 100644 index 0000000000..70ade60980 --- /dev/null +++ b/naga/tests/in/glsl/clamp-splat.vert @@ -0,0 +1,6 @@ +#version 450 +layout(location = 0) in vec2 a_pos; + +void main() { + gl_Position = vec4(clamp(a_pos, 0.0, 1.0), 0.0, 1.0); +} diff --git a/naga/tests/in/glsl/const-global-swizzle.frag b/naga/tests/in/glsl/const-global-swizzle.frag new file mode 100644 index 0000000000..956f33560d --- /dev/null +++ b/naga/tests/in/glsl/const-global-swizzle.frag @@ -0,0 +1,15 @@ +// ISSUE: #4773 +#version 450 + +#define MIX2(c) c.xy + +layout(location = 0) in vec2 v_Uv; + +layout(location = 0) out vec4 o_Target; + +const vec2 blank = MIX2(vec2(0.0, 1.0)); + +void main() { + vec2 col = MIX2(v_Uv) * blank; + o_Target = vec4(col, 0.0, 1.0); +} \ No newline at end of file diff --git a/naga/tests/in/glsl/constant-array-size.frag b/naga/tests/in/glsl/constant-array-size.frag new file mode 100644 index 0000000000..9d0f580b51 --- /dev/null +++ b/naga/tests/in/glsl/constant-array-size.frag @@ -0,0 +1,16 @@ +#version 450 + +const int NUM_VECS = 42; +layout(std140, set = 1, binding = 0) uniform Data { + vec4 vecs[NUM_VECS]; +}; + +vec4 function() { + vec4 sum = vec4(0); + for (int i = 0; i < NUM_VECS; i++) { + sum += vecs[i]; + } + return sum; +} + +void main() {} diff --git a/naga/tests/in/glsl/declarations.frag b/naga/tests/in/glsl/declarations.frag new file mode 100644 index 0000000000..7ef8b56a7c --- /dev/null +++ b/naga/tests/in/glsl/declarations.frag @@ -0,0 +1,39 @@ +#version 450 + +layout(location = 0) in VertexData { + vec2 position; + vec2 a; +} vert; + +layout(location = 0) out FragmentData { + vec2 position; + vec2 a; +} frag; + +layout(location = 2) in vec4 in_array[2]; +layout(location = 2) out vec4 out_array[2]; + +struct TestStruct { + float a; + float b; +}; + +float array_2d[2][2]; +float array_toomanyd[2][2][2][2][2][2][2]; + +struct LightScatteringParams { + float BetaRay, BetaMie[3], HGg, DistanceMul[4], BlendCoeff; + vec3 SunDirection, SunColor; +}; + +void main() { + const vec3 positions[2] = vec3[2]( + vec3(-1.0, 1.0, 0.0), + vec3(-1.0, -1.0, 0.0) + ); + const TestStruct strct = TestStruct( 1, 2 ); + const vec4 from_input_array = in_array[1]; + const float a = array_2d[0][0]; + const float b = array_toomanyd[0][0][0][0][0][0][0]; + out_array[0] = vec4(2.0); +} diff --git a/naga/tests/in/glsl/double-math-functions.frag b/naga/tests/in/glsl/double-math-functions.frag new file mode 100644 index 0000000000..78f53f9ef6 --- /dev/null +++ b/naga/tests/in/glsl/double-math-functions.frag @@ -0,0 +1,34 @@ +#version 450 + +void main() { + dvec4 a = dvec4(1.0); + dvec4 b = dvec4(2.0); + dmat4 m = dmat4(a, b, a, b); + int i = 5; + + dvec4 ceilOut = ceil(a); + dvec4 roundOut = round(a); + dvec4 floorOut = floor(a); + dvec4 fractOut = fract(a); + dvec4 truncOut = trunc(a); + dvec4 absOut = abs(a); + dvec4 sqrtOut = sqrt(a); + dvec4 inversesqrtOut = inversesqrt(a); + dvec4 signOut = sign(a); + dmat4 transposeOut = transpose(m); + dvec4 normalizeOut = normalize(a); + double lengthOut = length(a); + double determinantOut = determinant(m); + double modOut = mod(a.x, b.x); + double dotOut = dot(a, b); + dvec4 maxOut = max(a, b); + dvec4 minOut = min(a, b); + dvec4 reflectOut = reflect(a, b); + dvec3 crossOut = cross(a.xyz, b.xyz); + double distanceOut = distance(a, b); + dvec4 stepOut = step(a, b); + double ldexpOut = ldexp(a.x, i); + double smoothStepScalar = smoothstep(0.0, 1.0, 0.5); + dvec4 smoothStepVector = smoothstep(dvec4(0.0), dvec4(1.0), dvec4(0.5)); + dvec4 smoothStepMixed = smoothstep(0.0, 1.0, dvec4(0.5)); +} diff --git a/naga/tests/in/glsl/expressions.frag b/naga/tests/in/glsl/expressions.frag new file mode 100644 index 0000000000..e1d9fe191b --- /dev/null +++ b/naga/tests/in/glsl/expressions.frag @@ -0,0 +1,164 @@ +#version 440 core + +void testBinOpVecFloat(vec4 a, float b) { + vec4 v; + v = a * 2.0; + v = a / 2.0; + v = a + 2.0; + v = a - 2.0; +} + +void testBinOpFloatVec(vec4 a, float b) { + vec4 v; + v = a * b; + v = a / b; + v = a + b; + v = a - b; +} + +void testBinOpIVecInt(ivec4 a, int b) { + ivec4 v; + v = a * b; + v = a / b; + v = a + b; + v = a - b; + v = a & b; + v = a | b; + v = a ^ b; + v = a >> b; + v = a << b; +} + +void testBinOpIntIVec(int a, ivec4 b) { + ivec4 v; + v = a * b; + v = a + b; + v = a - b; + v = a & b; + v = a | b; + v = a ^ b; +} + +void testBinOpUVecUint(uvec4 a, uint b) { + uvec4 v; + v = a * b; + v = a / b; + v = a + b; + v = a - b; + v = a & b; + v = a | b; + v = a ^ b; + v = a >> b; + v = a << b; +} + +void testBinOpUintUVec(uint a, uvec4 b) { + uvec4 v; + v = a * b; + v = a + b; + v = a - b; + v = a & b; + v = a | b; + v = a ^ b; +} + +void testBinOpMatMat(mat3 a, mat3 b) { + mat3 v; + bool c; + v = a / b; + v = a * b; + v = a + b; + v = a - b; + c = a == b; + c = a != b; +} + +void testBinOpMatFloat(float a, mat3 b) { + mat3 v; + v = a / b; + v = a * b; + v = a + b; + v = a - b; + + v = b / a; + v = b * a; + v = b + a; + v = b - a; +} + +void testUnaryOpMat(mat3 a) { + mat3 v; + v = -a; + v = --a; + v = a--; +} + +void testStructConstructor() { + struct BST { + int data; + }; + + BST tree = BST(1); +} + +void testNonScalarToScalarConstructor() { + float f = float(mat2(1.0)); +} + +void testArrayConstructor() { + float tree[1] = float[1](0.0); +} + +void testFreestandingConstructor() { + vec4(1.0); +} + +void testNonImplicitCastVectorCast() { + uint a = 1; + ivec4 b = ivec4(a); +} + +float global; +void privatePointer(inout float a) {} + +void ternary(bool a) { + uint b = a ? 0 : 1u; + uint c = a ? 0u : 1; + + uint nested = a ? (a ? (a ? 2u : 3) : 4u) : 5; +} + +void testMatrixMultiplication(mat4x3 a, mat4x4 b) { + mat4x3 c = a * b; +} + +layout(std430, binding = 0) buffer a_buf { + float a[]; +}; + +void testLength() { + int len = a.length(); +} + +void testConstantLength(float a[4u]) { + int len = a.length(); +} + +struct TestStruct { uvec4 array[2]; }; +const TestStruct strct = { { uvec4(0), uvec4(1) } }; + +void indexConstantNonConstantIndex(int i) { + const uvec4 a = strct.array[i]; +} + +void testSwizzleWrites(vec3 a) { + a.zxy.xy = vec2(3.0, 4.0); + a.rg *= 5.0; + a.zy++; +} + +out vec4 o_color; +void main() { + privatePointer(global); + o_color.rgba = vec4(1.0); +} diff --git a/naga/tests/in/glsl/fma.frag b/naga/tests/in/glsl/fma.frag new file mode 100644 index 0000000000..2e3fb9b74a --- /dev/null +++ b/naga/tests/in/glsl/fma.frag @@ -0,0 +1,9 @@ +#version 440 core + +struct Mat4x3 { vec4 mx; vec4 my; vec4 mz; }; +void Fma(inout Mat4x3 d, Mat4x3 m, float s) { d.mx += m.mx * s; d.my += m.my * s; d.mz += m.mz * s; } + +out vec4 o_color; +void main() { + o_color.rgba = vec4(1.0); +} diff --git a/naga/tests/in/glsl/functions_call.frag b/naga/tests/in/glsl/functions_call.frag new file mode 100644 index 0000000000..74d9c2673d --- /dev/null +++ b/naga/tests/in/glsl/functions_call.frag @@ -0,0 +1,21 @@ +#version 450 + +void swizzleCallee(inout vec2 a) {} + +void swizzleCaller(vec3 a) { + swizzleCallee(a.xz); +} + +void outImplicitCastCallee(out uint a) {} + +void outImplicitCastCaller(float a) { + outImplicitCastCallee(a); +} + +void swizzleImplicitCastCallee(out uvec2 a) {} + +void swizzleImplicitCastCaller(vec3 a) { + swizzleImplicitCastCallee(a.xz); +} + +void main() {} diff --git a/naga/tests/in/glsl/global-constant-array.frag b/naga/tests/in/glsl/global-constant-array.frag new file mode 100644 index 0000000000..de4f3c7080 --- /dev/null +++ b/naga/tests/in/glsl/global-constant-array.frag @@ -0,0 +1,6 @@ +#version 450 core + +uint i; +const float[2] array = { 1.0, 2.0 }; + +void main() { array[i]; } diff --git a/naga/tests/in/glsl/images.frag b/naga/tests/in/glsl/images.frag new file mode 100644 index 0000000000..b8d7622bc8 --- /dev/null +++ b/naga/tests/in/glsl/images.frag @@ -0,0 +1,73 @@ +#version 460 core + +layout(rgba8, binding = 0) uniform image1D img1D; +layout(rgba8, binding = 1) uniform image2D img2D; +layout(rgba8, binding = 2) uniform image3D img3D; +// layout(rgba8, binding = 3) uniform imageCube imgCube; +layout(rgba8, binding = 4) uniform image1DArray img1DArray; +layout(rgba8, binding = 5) uniform image2DArray img2DArray; +// layout(rgba8, binding = 6) uniform imageCubeArray imgCubeArray; + +layout(rgba8, binding = 7) readonly uniform image2D imgReadOnly; +layout(rgba8, binding = 8) writeonly uniform image2D imgWriteOnly; +layout(rgba8, binding = 9) writeonly readonly uniform image2D imgWriteReadOnly; + +void testImg1D(in int coord) { + int size = imageSize(img1D); + imageStore(img1D, coord, vec4(2)); + vec4 c = imageLoad(img1D, coord); +} + +void testImg1DArray(in ivec2 coord) { + vec2 size = imageSize(img1DArray); + vec4 c = imageLoad(img1DArray, coord); + imageStore(img1DArray, coord, vec4(2)); +} + +void testImg2D(in ivec2 coord) { + vec2 size = imageSize(img2D); + vec4 c = imageLoad(img2D, coord); + imageStore(img2D, coord, vec4(2)); +} + +void testImg2DArray(in ivec3 coord) { + vec3 size = imageSize(img2DArray); + vec4 c = imageLoad(img2DArray, coord); + imageStore(img2DArray, coord, vec4(2)); +} + +void testImg3D(in ivec3 coord) { + vec3 size = imageSize(img3D); + vec4 c = imageLoad(img3D, coord); + imageStore(img3D, coord, vec4(2)); +} + +// Naga doesn't support cube images and it's usefulness +// is questionable, so they won't be supported for now +// void testImgCube(in ivec3 coord) { +// vec2 size = imageSize(imgCube); +// vec4 c = imageLoad(imgCube, coord); +// imageStore(imgCube, coord, vec4(2)); +// } +// +// void testImgCubeArray(in ivec3 coord) { +// vec3 size = imageSize(imgCubeArray); +// vec4 c = imageLoad(imgCubeArray, coord); +// imageStore(imgCubeArray, coord, vec4(2)); +// } + +void testImgReadOnly(in ivec2 coord) { + vec2 size = imageSize(img2D); + vec4 c = imageLoad(imgReadOnly, coord); +} + +void testImgWriteOnly(in ivec2 coord) { + vec2 size = imageSize(img2D); + imageStore(imgWriteOnly, coord, vec4(2)); +} + +void testImgWriteReadOnly(in ivec2 coord) { + vec2 size = imageSize(imgWriteReadOnly); +} + +void main() {} diff --git a/naga/tests/in/glsl/local-var-init-in-loop.comp b/naga/tests/in/glsl/local-var-init-in-loop.comp new file mode 100644 index 0000000000..e8b83ec40f --- /dev/null +++ b/naga/tests/in/glsl/local-var-init-in-loop.comp @@ -0,0 +1,7 @@ +void main() { + vec4 sum = vec4(0); + for (int i = 0; i < 4; i++) { + vec4 a = vec4(1); + sum += a; + } +} \ No newline at end of file diff --git a/naga/tests/in/glsl/long-form-matrix.frag b/naga/tests/in/glsl/long-form-matrix.frag new file mode 100644 index 0000000000..ab40baa841 --- /dev/null +++ b/naga/tests/in/glsl/long-form-matrix.frag @@ -0,0 +1,25 @@ +// ISSUE: #1064 +#version 450 + +void main() { + // Sane ways to build a matrix + mat2 splat = mat2(1); + mat2 normal = mat2(vec2(1), vec2(2)); + mat2x4 from_matrix = mat2x4(mat3(1.0)); + + // This is a little bit weirder but still makes some sense + // Since this matrix has 2 rows we take two numbers to make a column + // and we do this twice because we 2 columns. + // Final result in wgsl should be: + // mat2x2(vec2(1.0, 2.0), vec2(3.0, 4.0)) + mat2 a = mat2(1, 2, 3, 4); + + // ??? + // Glsl has decided that for it's matrix constructor arguments it doesn't + // take them as is but instead flattens them so the `b` matrix is + // equivalent to the `a` matrix but in value and semantics + mat2 b = mat2(1, vec2(2, 3), 4); + mat3 c = mat3(1, 2, 3, vec3(1), vec3(1)); + mat3 d = mat3(vec2(2), 1, vec3(1), vec3(1)); + mat4 e = mat4(vec2(2), vec4(1), vec2(2), vec4(1), vec4(1)); +} diff --git a/naga/tests/in/glsl/math-functions.frag b/naga/tests/in/glsl/math-functions.frag new file mode 100644 index 0000000000..1006bda838 --- /dev/null +++ b/naga/tests/in/glsl/math-functions.frag @@ -0,0 +1,60 @@ +#version 450 + +void main() { + vec4 a = vec4(1.0); + vec4 b = vec4(2.0); + mat4 m = mat4(a, b, a, b); + int i = 5; + + vec4 ceilOut = ceil(a); + vec4 roundOut = round(a); + vec4 floorOut = floor(a); + vec4 fractOut = fract(a); + vec4 truncOut = trunc(a); + vec4 sinOut = sin(a); + vec4 absOut = abs(a); + vec4 sqrtOut = sqrt(a); + vec4 inversesqrtOut = inversesqrt(a); + vec4 expOut = exp(a); + vec4 exp2Out = exp2(a); + vec4 signOut = sign(a); + mat4 transposeOut = transpose(m); + // TODO: support inverse function in wgsl output + // mat4 inverseOut = inverse(m); + vec4 normalizeOut = normalize(a); + vec4 sinhOut = sinh(a); + vec4 cosOut = cos(a); + vec4 coshOut = cosh(a); + vec4 tanOut = tan(a); + vec4 tanhOut = tanh(a); + vec4 acosOut = acos(a); + vec4 asinOut = asin(a); + vec4 logOut = log(a); + vec4 log2Out = log2(a); + float lengthOut = length(a); + float determinantOut = determinant(m); + int bitCountOut = bitCount(i); + int bitfieldReverseOut = bitfieldReverse(i); + float atanOut = atan(a.x); + float atan2Out = atan(a.x, a.y); + float modOut = mod(a.x, b.x); + vec4 powOut = pow(a, b); + float dotOut = dot(a, b); + vec4 maxOut = max(a, b); + vec4 minOut = min(a, b); + vec4 reflectOut = reflect(a, b); + vec3 crossOut = cross(a.xyz, b.xyz); + // TODO: support outerProduct function in wgsl output + // mat4 outerProductOut = outerProduct(a, b); + float distanceOut = distance(a, b); + vec4 stepOut = step(a, b); + // TODO: support out params in wgsl output + // vec4 modfOut = modf(a, b); + // vec4 frexpOut = frexp(a, b); + float ldexpOut = ldexp(a.x, i); + vec4 rad = radians(a); + float deg = degrees(a.x); + float smoothStepScalar = smoothstep(0.0, 1.0, 0.5); + vec4 smoothStepVector = smoothstep(vec4(0.0), vec4(1.0), vec4(0.5)); + vec4 smoothStepMixed = smoothstep(0.0, 1.0, vec4(0.5)); +} diff --git a/naga/tests/in/glsl/prepostfix.frag b/naga/tests/in/glsl/prepostfix.frag new file mode 100644 index 0000000000..1b59428927 --- /dev/null +++ b/naga/tests/in/glsl/prepostfix.frag @@ -0,0 +1,18 @@ +#version 450 core + +void main() { + int scalar_target; + int scalar = 1; + scalar_target = scalar++; + scalar_target = --scalar; + + uvec2 vec_target; + uvec2 vec = uvec2(1); + vec_target = vec--; + vec_target = ++vec; + + mat4x3 mat_target; + mat4x3 mat = mat4x3(1); + mat_target = mat++; + mat_target = --mat; +} diff --git a/naga/tests/in/glsl/quad_glsl.frag b/naga/tests/in/glsl/quad_glsl.frag new file mode 100644 index 0000000000..7c0ebf6737 --- /dev/null +++ b/naga/tests/in/glsl/quad_glsl.frag @@ -0,0 +1,15 @@ +#version 450 +layout(location = 0) in vec2 v_uv; +#ifdef TEXTURE +layout(set = 0, binding = 0) uniform texture2D u_texture; +layout(set = 0, binding = 1) uniform sampler u_sampler; +#endif +layout(location = 0) out vec4 o_color; + +void main() { +#ifdef TEXTURE + o_color = texture(sampler2D(u_texture, u_sampler), v_uv); +#else + o_color = vec4(1.0, 1.0, 1.0, 1.0); +#endif +} diff --git a/naga/tests/in/glsl/quad_glsl.vert b/naga/tests/in/glsl/quad_glsl.vert new file mode 100644 index 0000000000..aa72a833a3 --- /dev/null +++ b/naga/tests/in/glsl/quad_glsl.vert @@ -0,0 +1,11 @@ +#version 450 +const float c_scale = 1.2; + +layout(location = 0) in vec2 a_pos; +layout(location = 1) in vec2 a_uv; +layout(location = 0) out vec2 v_uv; + +void main() { + v_uv = a_uv; + gl_Position = vec4(c_scale * a_pos, 0.0, 1.0); +} diff --git a/naga/tests/in/glsl/sampler-functions.frag b/naga/tests/in/glsl/sampler-functions.frag new file mode 100644 index 0000000000..0d868780bf --- /dev/null +++ b/naga/tests/in/glsl/sampler-functions.frag @@ -0,0 +1,16 @@ +#version 440 +precision mediump float; + +float CalcShadowPCF1(texture2D T_P_t_TextureDepth, samplerShadow S_P_t_TextureDepth, in vec3 t_ProjCoord) { + float t_Res = 0.0f; + t_Res += texture(sampler2DShadow(T_P_t_TextureDepth, S_P_t_TextureDepth), t_ProjCoord.xyz) * (1.0 / 5.0); + return t_Res; +} + +float CalcShadowPCF(texture2D T_P_t_TextureDepth, samplerShadow S_P_t_TextureDepth, in vec3 t_ProjCoord, in float t_Bias) { + t_ProjCoord.z += t_Bias; + return CalcShadowPCF1(T_P_t_TextureDepth, S_P_t_TextureDepth, t_ProjCoord.xyz); +} + +void main() { +} diff --git a/naga/tests/in/glsl/samplers.frag b/naga/tests/in/glsl/samplers.frag new file mode 100644 index 0000000000..a1a438db9a --- /dev/null +++ b/naga/tests/in/glsl/samplers.frag @@ -0,0 +1,265 @@ +#version 440 core +precision mediump float; + +layout(set = 1, binding = 0) uniform texture1D tex1D; +layout(set = 1, binding = 1) uniform texture1DArray tex1DArray; +layout(set = 1, binding = 2) uniform texture2D tex2D; +layout(set = 1, binding = 3) uniform texture2DArray tex2DArray; +layout(set = 1, binding = 4) uniform textureCube texCube; +layout(set = 1, binding = 5) uniform textureCubeArray texCubeArray; +layout(set = 1, binding = 6) uniform texture3D tex3D; +layout(set = 1, binding = 7) uniform sampler samp; + +// WGSL doesn't have 1D depth samplers. +#define HAS_1D_DEPTH_TEXTURES 0 + +#if HAS_1D_DEPTH_TEXTURES +layout(set = 1, binding = 10) uniform texture1D tex1DShadow; +layout(set = 1, binding = 11) uniform texture1DArray tex1DArrayShadow; +#endif + +layout(set = 1, binding = 12) uniform texture2D tex2DShadow; +layout(set = 1, binding = 13) uniform texture2DArray tex2DArrayShadow; +layout(set = 1, binding = 14) uniform textureCube texCubeShadow; +layout(set = 1, binding = 15) uniform textureCubeArray texCubeArrayShadow; +layout(set = 1, binding = 16) uniform texture3D tex3DShadow; +layout(set = 1, binding = 17) uniform samplerShadow sampShadow; + +layout(binding = 18) uniform texture2DMS tex2DMS; +layout(binding = 19) uniform texture2DMSArray tex2DMSArray; + +// Conventions for readability: +// 1.0 = Shadow Ref +// 2.0 = LOD Bias +// 3.0 = Explicit LOD +// 4.0 = Grad Derivatives +// 5 = Offset +// 6.0 = Proj W + +void testTex1D(in float coord) { + int size1D = textureSize(sampler1D(tex1D, samp), 0); + vec4 c; + c = texture(sampler1D(tex1D, samp), coord); + c = texture(sampler1D(tex1D, samp), coord, 2.0); + c = textureGrad(sampler1D(tex1D, samp), coord, 4.0, 4.0); + c = textureGradOffset(sampler1D(tex1D, samp), coord, 4.0, 4.0, 5); + c = textureLod(sampler1D(tex1D, samp), coord, 3.0); + c = textureLodOffset(sampler1D(tex1D, samp), coord, 3.0, 5); + c = textureOffset(sampler1D(tex1D, samp), coord, 5); + c = textureOffset(sampler1D(tex1D, samp), coord, 5, 2.0); + c = textureProj(sampler1D(tex1D, samp), vec2(coord, 6.0)); + c = textureProj(sampler1D(tex1D, samp), vec4(coord, 0.0, 0.0, 6.0)); + c = textureProj(sampler1D(tex1D, samp), vec2(coord, 6.0), 2.0); + c = textureProj(sampler1D(tex1D, samp), vec4(coord, 0.0, 0.0, 6.0), 2.0); + c = textureProjGrad(sampler1D(tex1D, samp), vec2(coord, 6.0), 4.0, 4.0); + c = textureProjGrad(sampler1D(tex1D, samp), vec4(coord, 0.0, 0.0, 6.0), 4.0, 4.0); + c = textureProjGradOffset(sampler1D(tex1D, samp), vec2(coord, 6.0), 4.0, 4.0, 5); + c = textureProjGradOffset(sampler1D(tex1D, samp), vec4(coord, 0.0, 0.0, 6.0), 4.0, 4.0, 5); + c = textureProjLod(sampler1D(tex1D, samp), vec2(coord, 6.0), 3.0); + c = textureProjLod(sampler1D(tex1D, samp), vec4(coord, 0.0, 0.0, 6.0), 3.0); + c = textureProjLodOffset(sampler1D(tex1D, samp), vec2(coord, 6.0), 3.0, 5); + c = textureProjLodOffset(sampler1D(tex1D, samp), vec4(coord, 0.0, 0.0, 6.0), 3.0, 5); + c = textureProjOffset(sampler1D(tex1D, samp), vec2(coord, 6.0), 5); + c = textureProjOffset(sampler1D(tex1D, samp), vec4(coord, 0.0, 0.0, 6.0), 5); + c = textureProjOffset(sampler1D(tex1D, samp), vec2(coord, 6.0), 5, 2.0); + c = textureProjOffset(sampler1D(tex1D, samp), vec4(coord, 0.0, 0.0, 6.0), 5, 2.0); + c = texelFetch(sampler1D(tex1D, samp), int(coord), 3); + c = texelFetchOffset(sampler1D(tex1D, samp), int(coord), 3, 5); +} + +#if HAS_1D_DEPTH_TEXTURES +void testTex1DShadow(float coord) { + int size1DShadow = textureSize(sampler1DShadow(tex1DShadow, sampShadow), 0); + float d; + d = texture(sampler1DShadow(tex1DShadow, sampShadow), vec3(coord, 1.0, 1.0)); + // d = texture(sampler1DShadow(tex1DShadow, sampShadow), vec3(coord, 1.0, 1.0), 2.0); + d = textureGrad(sampler1DShadow(tex1DShadow, sampShadow), vec3(coord, 1.0, 1.0), 4.0, 4.0); + d = textureGradOffset(sampler1DShadow(tex1DShadow, sampShadow), vec3(coord, 1.0, 1.0), 4.0, 4.0, 5); + d = textureLod(sampler1DShadow(tex1DShadow, sampShadow), vec3(coord, 1.0, 1.0), 3.0); + d = textureLodOffset(sampler1DShadow(tex1DShadow, sampShadow), vec3(coord, 1.0, 1.0), 3.0, 5); + d = textureOffset(sampler1DShadow(tex1DShadow, sampShadow), vec3(coord, 1.0, 1.0), 5); + // d = textureOffset(sampler1DShadow(tex1DShadow, sampShadow), vec3(coord, 1.0, 1.0), 5, 2.0); + d = textureProj(sampler1DShadow(tex1DShadow, sampShadow), vec4(coord, 0.0, 1.0, 6.0)); + // d = textureProj(sampler1DShadow(tex1DShadow, sampShadow), vec4(coord, 0.0, 1.0, 6.0), 2.0); + d = textureProjGrad(sampler1DShadow(tex1DShadow, sampShadow), vec4(coord, 0.0, 1.0, 6.0), 4.0, 4.0); + d = textureProjGradOffset(sampler1DShadow(tex1DShadow, sampShadow), vec4(coord, 0.0, 1.0, 6.0), 4.0, 4.0, 5); + d = textureProjLod(sampler1DShadow(tex1DShadow, sampShadow), vec4(coord, 0.0, 1.0, 6.0), 3.0); + d = textureProjLodOffset(sampler1DShadow(tex1DShadow, sampShadow), vec4(coord, 0.0, 1.0, 6.0), 3.0, 5); + d = textureProjOffset(sampler1DShadow(tex1DShadow, sampShadow), vec4(coord, 0.0, 1.0, 6.0), 5); + // d = textureProjOffset(sampler1DShadow(tex1DShadow, sampShadow), vec4(coord, 0.0, 1.0, 6.0), 5, 2.0); +} +#endif + +void testTex1DArray(in vec2 coord) { + ivec2 size1DArray = textureSize(sampler1DArray(tex1DArray, samp), 0); + vec4 c; + c = texture(sampler1DArray(tex1DArray, samp), coord); + c = texture(sampler1DArray(tex1DArray, samp), coord, 2.0); + c = textureGrad(sampler1DArray(tex1DArray, samp), coord, 4.0, 4.0); + c = textureGradOffset(sampler1DArray(tex1DArray, samp), coord, 4.0, 4.0, 5); + c = textureLod(sampler1DArray(tex1DArray, samp), coord, 3.0); + c = textureLodOffset(sampler1DArray(tex1DArray, samp), coord, 3.0, 5); + c = textureOffset(sampler1DArray(tex1DArray, samp), coord, 5); + c = textureOffset(sampler1DArray(tex1DArray, samp), coord, 5, 2.0); + c = texelFetch(sampler1DArray(tex1DArray, samp), ivec2(coord), 3); + c = texelFetchOffset(sampler1DArray(tex1DArray, samp), ivec2(coord), 3, 5); +} + +#if HAS_1D_DEPTH_TEXTURES +void testTex1DArrayShadow(in vec2 coord) { + ivec2 size1DArrayShadow = textureSize(sampler1DArrayShadow(tex1DArrayShadow, sampShadow), 0); + float d; + d = texture(sampler1DArrayShadow(tex1DArrayShadow, sampShadow), vec3(coord, 1.0)); + d = textureGrad(sampler1DArrayShadow(tex1DArrayShadow, sampShadow), vec3(coord, 1.0), 4.0, 4.0); + d = textureGradOffset(sampler1DArrayShadow(tex1DArrayShadow, sampShadow), vec3(coord, 1.0), 4.0, 4.0, 5); + d = textureLod(sampler1DArrayShadow(tex1DArrayShadow, sampShadow), vec3(coord, 1.0), 3.0); + d = textureLodOffset(sampler1DArrayShadow(tex1DArrayShadow, sampShadow), vec3(coord, 1.0), 3.0, 5); + d = textureOffset(sampler1DArrayShadow(tex1DArrayShadow, sampShadow), vec3(coord, 1.0), 5); + // d = textureOffset(sampler1DArrayShadow(tex1DArrayShadow, sampShadow), vec3(coord, 1.0), 5, 2.0); +} +#endif + +void testTex2D(in vec2 coord) { + ivec2 size2D = textureSize(sampler2D(tex2D, samp), 0); + vec4 c; + c = texture(sampler2D(tex2D, samp), coord); + c = texture(sampler2D(tex2D, samp), coord, 2.0); + c = textureGrad(sampler2D(tex2D, samp), coord, vec2(4.0), vec2(4.0)); + c = textureGradOffset(sampler2D(tex2D, samp), coord, vec2(4.0), vec2(4.0), ivec2(5)); + c = textureLod(sampler2D(tex2D, samp), coord, 3.0); + c = textureLodOffset(sampler2D(tex2D, samp), coord, 3.0, ivec2(5)); + c = textureOffset(sampler2D(tex2D, samp), coord, ivec2(5)); + c = textureOffset(sampler2D(tex2D, samp), coord, ivec2(5), 2.0); + c = textureProj(sampler2D(tex2D, samp), vec3(coord, 6.0)); + c = textureProj(sampler2D(tex2D, samp), vec4(coord, 0.0, 6.0)); + c = textureProj(sampler2D(tex2D, samp), vec3(coord, 6.0), 2.0); + c = textureProj(sampler2D(tex2D, samp), vec4(coord, 0.0, 6.0), 2.0); + c = textureProjGrad(sampler2D(tex2D, samp), vec3(coord, 6.0), vec2(4.0), vec2(4.0)); + c = textureProjGrad(sampler2D(tex2D, samp), vec4(coord, 0.0, 6.0), vec2(4.0), vec2(4.0)); + c = textureProjGradOffset(sampler2D(tex2D, samp), vec3(coord, 6.0), vec2(4.0), vec2(4.0), ivec2(5)); + c = textureProjGradOffset(sampler2D(tex2D, samp), vec4(coord, 0.0, 6.0), vec2(4.0), vec2(4.0), ivec2(5)); + c = textureProjLod(sampler2D(tex2D, samp), vec3(coord, 6.0), 3.0); + c = textureProjLod(sampler2D(tex2D, samp), vec4(coord, 0.0, 6.0), 3.0); + c = textureProjLodOffset(sampler2D(tex2D, samp), vec3(coord, 6.0), 3.0, ivec2(5)); + c = textureProjLodOffset(sampler2D(tex2D, samp), vec4(coord, 0.0, 6.0), 3.0, ivec2(5)); + c = textureProjOffset(sampler2D(tex2D, samp), vec3(coord, 6.0), ivec2(5)); + c = textureProjOffset(sampler2D(tex2D, samp), vec4(coord, 0.0, 6.0), ivec2(5)); + c = textureProjOffset(sampler2D(tex2D, samp), vec3(coord, 6.0), ivec2(5), 2.0); + c = textureProjOffset(sampler2D(tex2D, samp), vec4(coord, 0.0, 6.0), ivec2(5), 2.0); + c = texelFetch(sampler2D(tex2D, samp), ivec2(coord), 3); + c = texelFetchOffset(sampler2D(tex2D, samp), ivec2(coord), 3, ivec2(5)); +} + +void testTex2DShadow(vec2 coord) { + ivec2 size2DShadow = textureSize(sampler2DShadow(tex2DShadow, sampShadow), 0); + float d; + d = texture(sampler2DShadow(tex2DShadow, sampShadow), vec3(coord, 1.0)); + // d = texture(sampler2DShadow(tex2DShadow, sampShadow), vec3(coord, 1.0), 2.0); + d = textureGrad(sampler2DShadow(tex2DShadow, sampShadow), vec3(coord, 1.0), vec2(4.0), vec2(4.0)); + d = textureGradOffset(sampler2DShadow(tex2DShadow, sampShadow), vec3(coord, 1.0), vec2(4.0), vec2(4.0), ivec2(5)); + d = textureLod(sampler2DShadow(tex2DShadow, sampShadow), vec3(coord, 1.0), 3.0); + d = textureLodOffset(sampler2DShadow(tex2DShadow, sampShadow), vec3(coord, 1.0), 3.0, ivec2(5)); + d = textureOffset(sampler2DShadow(tex2DShadow, sampShadow), vec3(coord, 1.0), ivec2(5)); + // d = textureOffset(sampler2DShadow(tex2DShadow, sampShadow), vec3(coord, 1.0), ivec2(5), 2.0); + d = textureProj(sampler2DShadow(tex2DShadow, sampShadow), vec4(coord, 1.0, 6.0)); + // d = textureProj(sampler2DShadow(tex2DShadow, sampShadow), vec4(coord, 1.0, 6.0), 2.0); + d = textureProjGrad(sampler2DShadow(tex2DShadow, sampShadow), vec4(coord, 1.0, 6.0), vec2(4.0), vec2(4.0)); + d = textureProjGradOffset(sampler2DShadow(tex2DShadow, sampShadow), vec4(coord, 1.0, 6.0), vec2(4.0), vec2(4.0), ivec2(5)); + d = textureProjLod(sampler2DShadow(tex2DShadow, sampShadow), vec4(coord, 1.0, 6.0), 3.0); + d = textureProjLodOffset(sampler2DShadow(tex2DShadow, sampShadow), vec4(coord, 1.0, 6.0), 3.0, ivec2(5)); + d = textureProjOffset(sampler2DShadow(tex2DShadow, sampShadow), vec4(coord, 1.0, 6.0), ivec2(5)); + // d = textureProjOffset(sampler2DShadow(tex2DShadow, sampShadow), vec4(coord, 1.0, 6.0), ivec2(5), 2.0); +} + +void testTex2DArray(in vec3 coord) { + ivec3 size2DArray = textureSize(sampler2DArray(tex2DArray, samp), 0); + vec4 c; + c = texture(sampler2DArray(tex2DArray, samp), coord); + c = texture(sampler2DArray(tex2DArray, samp), coord, 2.0); + c = textureGrad(sampler2DArray(tex2DArray, samp), coord, vec2(4.0), vec2(4.0)); + c = textureGradOffset(sampler2DArray(tex2DArray, samp), coord, vec2(4.0), vec2(4.0), ivec2(5)); + c = textureLod(sampler2DArray(tex2DArray, samp), coord, 3.0); + c = textureLodOffset(sampler2DArray(tex2DArray, samp), coord, 3.0, ivec2(5)); + c = textureOffset(sampler2DArray(tex2DArray, samp), coord, ivec2(5)); + c = textureOffset(sampler2DArray(tex2DArray, samp), coord, ivec2(5), 2.0); + c = texelFetch(sampler2DArray(tex2DArray, samp), ivec3(coord), 3); + c = texelFetchOffset(sampler2DArray(tex2DArray, samp), ivec3(coord), 3, ivec2(5)); +} + +void testTex2DArrayShadow(in vec3 coord) { + ivec3 size2DArrayShadow = textureSize(sampler2DArrayShadow(tex2DArrayShadow, sampShadow), 0); + float d; + d = texture(sampler2DArrayShadow(tex2DArrayShadow, sampShadow), vec4(coord, 1.0)); + d = textureGrad(sampler2DArrayShadow(tex2DArrayShadow, sampShadow), vec4(coord, 1.0), vec2(4.0), vec2(4.0)); + d = textureGradOffset(sampler2DArrayShadow(tex2DArrayShadow, sampShadow), vec4(coord, 1.0), vec2(4.0), vec2(4.0), ivec2(5)); + d = textureOffset(sampler2DArrayShadow(tex2DArrayShadow, sampShadow), vec4(coord, 1.0), ivec2(5)); +} + +void testTexCube(in vec3 coord) { + ivec2 sizeCube = textureSize(samplerCube(texCube, samp), 0); + vec4 c; + c = texture(samplerCube(texCube, samp), coord); + c = texture(samplerCube(texCube, samp), coord, 2.0); + c = textureGrad(samplerCube(texCube, samp), coord, vec3(4.0), vec3(4.0)); + c = textureLod(samplerCube(texCube, samp), coord, 3.0); +} + +void testTexCubeShadow(in vec3 coord) { + ivec2 sizeCubeShadow = textureSize(samplerCubeShadow(texCubeShadow, sampShadow), 0); + float d; + d = texture(samplerCubeShadow(texCubeShadow, sampShadow), vec4(coord, 1.0)); + d = textureGrad(samplerCubeShadow(texCubeShadow, sampShadow), vec4(coord, 1.0), vec3(4.0), vec3(4.0)); +} + +void testTexCubeArray(in vec4 coord) { + ivec3 sizeCubeArray = textureSize(samplerCubeArray(texCubeArray, samp), 0); + vec4 c; + c = texture(samplerCubeArray(texCubeArray, samp), coord); + c = texture(samplerCubeArray(texCubeArray, samp), coord, 2.0); + c = textureGrad(samplerCubeArray(texCubeArray, samp), coord, vec3(4.0), vec3(4.0)); + c = textureLod(samplerCubeArray(texCubeArray, samp), coord, 3.0); +} + +void testTexCubeArrayShadow(in vec4 coord) { + ivec3 sizeCubeArrayShadow = textureSize(samplerCubeArrayShadow(texCubeArrayShadow, sampShadow), 0); + float d; + d = texture(samplerCubeArrayShadow(texCubeArrayShadow, sampShadow), coord, 1.0); + // The rest of the variants aren't defined by GLSL. +} + +void testTex3D(in vec3 coord) { + ivec3 size3D = textureSize(sampler3D(tex3D, samp), 0); + vec4 c; + c = texture(sampler3D(tex3D, samp), coord); + c = texture(sampler3D(tex3D, samp), coord, 2.0); + c = textureProj(sampler3D(tex3D, samp), vec4(coord, 6.0)); + c = textureProj(sampler3D(tex3D, samp), vec4(coord, 6.0), 2.0); + c = textureProjOffset(sampler3D(tex3D, samp), vec4(coord, 6.0), ivec3(5)); + c = textureProjOffset(sampler3D(tex3D, samp), vec4(coord, 6.0), ivec3(5), 2.0); + c = textureProjLod(sampler3D(tex3D, samp), vec4(coord, 6.0), 3.0); + c = textureProjLodOffset(sampler3D(tex3D, samp), vec4(coord, 6.0), 3.0, ivec3(5)); + c = textureProjGrad(sampler3D(tex3D, samp), vec4(coord, 6.0), vec3(4.0), vec3(4.0)); + c = textureProjGradOffset(sampler3D(tex3D, samp), vec4(coord, 6.0), vec3(4.0), vec3(4.0), ivec3(5)); + c = textureGrad(sampler3D(tex3D, samp), coord, vec3(4.0), vec3(4.0)); + c = textureGradOffset(sampler3D(tex3D, samp), coord, vec3(4.0), vec3(4.0), ivec3(5)); + c = textureLod(sampler3D(tex3D, samp), coord, 3.0); + c = textureLodOffset(sampler3D(tex3D, samp), coord, 3.0, ivec3(5)); + c = textureOffset(sampler3D(tex3D, samp), coord, ivec3(5)); + c = textureOffset(sampler3D(tex3D, samp), coord, ivec3(5), 2.0); + c = texelFetch(sampler3D(tex3D, samp), ivec3(coord), 3); + c = texelFetchOffset(sampler3D(tex3D, samp), ivec3(coord), 3, ivec3(5)); +} + +void testTex2DMS(in vec2 coord) { + ivec2 size2DMS = textureSize(sampler2DMS(tex2DMS, samp)); + vec4 c; + c = texelFetch(sampler2DMS(tex2DMS, samp), ivec2(coord), 3); +} + +void testTex2DMSArray(in vec3 coord) { + ivec3 size2DMSArray = textureSize(sampler2DMSArray(tex2DMSArray, samp)); + vec4 c; + c = texelFetch(sampler2DMSArray(tex2DMSArray, samp), ivec3(coord), 3); +} + +void main() {} diff --git a/naga/tests/in/glsl/statements.frag b/naga/tests/in/glsl/statements.frag new file mode 100644 index 0000000000..3423e73b80 --- /dev/null +++ b/naga/tests/in/glsl/statements.frag @@ -0,0 +1,36 @@ +#version 460 core + +void switchEmpty(int a) { + switch (a) {} + + return; +} + +void switchNoDefault(int a) { + switch (a) { + case 0: + break; + } + + return; +} + +void switchCaseImplConv(uint a) { + switch (a) { + case 0: + break; + } + + return; +} + +void switchNoLastBreak(int a) { + switch (a) { + default: + int b = a; + } + + return; +} + +void main() {} diff --git a/naga/tests/in/glsl/vector-functions.frag b/naga/tests/in/glsl/vector-functions.frag new file mode 100644 index 0000000000..bb212cdc93 --- /dev/null +++ b/naga/tests/in/glsl/vector-functions.frag @@ -0,0 +1,47 @@ +#version 450 + +void ftest(vec4 a, vec4 b) { + bvec4 c = lessThan(a, b); + bvec4 d = lessThanEqual(a, b); + bvec4 e = greaterThan(a, b); + bvec4 f = greaterThanEqual(a, b); + bvec4 g = equal(a, b); + bvec4 h = notEqual(a, b); +} + +void dtest(dvec4 a, dvec4 b) { + bvec4 c = lessThan(a, b); + bvec4 d = lessThanEqual(a, b); + bvec4 e = greaterThan(a, b); + bvec4 f = greaterThanEqual(a, b); + bvec4 g = equal(a, b); + bvec4 h = notEqual(a, b); +} + +void itest(ivec4 a, ivec4 b) { + bvec4 c = lessThan(a, b); + bvec4 d = lessThanEqual(a, b); + bvec4 e = greaterThan(a, b); + bvec4 f = greaterThanEqual(a, b); + bvec4 g = equal(a, b); + bvec4 h = notEqual(a, b); +} + +void utest(uvec4 a, uvec4 b) { + bvec4 c = lessThan(a, b); + bvec4 d = lessThanEqual(a, b); + bvec4 e = greaterThan(a, b); + bvec4 f = greaterThanEqual(a, b); + bvec4 g = equal(a, b); + bvec4 h = notEqual(a, b); +} + +void btest(bvec4 a, bvec4 b) { + bvec4 c = equal(a, b); + bvec4 d = notEqual(a, b); + bool e = any(a); + bool f = all(a); + bvec4 g = not(a); +} + +void main() {} diff --git a/naga/tests/in/hlsl-keyword.wgsl b/naga/tests/in/hlsl-keyword.wgsl new file mode 100644 index 0000000000..e2e4e722d2 --- /dev/null +++ b/naga/tests/in/hlsl-keyword.wgsl @@ -0,0 +1,6 @@ +@fragment +fn fs_main() -> @location(0) vec4f { + // Make sure case-insensitive keywords are escaped in HLSL. + var Pass = vec4(1.0,1.0,1.0,1.0); + return Pass; +} \ No newline at end of file diff --git a/naga/tests/in/image.param.ron b/naga/tests/in/image.param.ron new file mode 100644 index 0000000000..5b6d71defa --- /dev/null +++ b/naga/tests/in/image.param.ron @@ -0,0 +1,7 @@ +( + spv: ( + version: (1, 1), + debug: true, + ), + glsl_exclude_list: ["depth_load", "depth_no_comparison", "levels_queries"] +) diff --git a/naga/tests/in/image.wgsl b/naga/tests/in/image.wgsl new file mode 100644 index 0000000000..2bae8f9d80 --- /dev/null +++ b/naga/tests/in/image.wgsl @@ -0,0 +1,190 @@ +@group(0) @binding(0) +var image_mipmapped_src: texture_2d; +@group(0) @binding(3) +var image_multisampled_src: texture_multisampled_2d; +@group(0) @binding(4) +var image_depth_multisampled_src: texture_depth_multisampled_2d; +@group(0) @binding(1) +var image_storage_src: texture_storage_2d; +@group(0) @binding(5) +var image_array_src: texture_2d_array; +@group(0) @binding(6) +var image_dup_src: texture_storage_1d; // for #1307 +@group(0) @binding(7) +var image_1d_src: texture_1d; +@group(0) @binding(2) +var image_dst: texture_storage_1d; + +@compute @workgroup_size(16) +fn main(@builtin(local_invocation_id) local_id: vec3) { + let dim = textureDimensions(image_storage_src); + let itc = vec2(dim * local_id.xy) % vec2(10, 20); + // loads with ivec2 coords. + let value1 = textureLoad(image_mipmapped_src, itc, i32(local_id.z)); + let value2 = textureLoad(image_multisampled_src, itc, i32(local_id.z)); + let value4 = textureLoad(image_storage_src, itc); + let value5 = textureLoad(image_array_src, itc, local_id.z, i32(local_id.z) + 1); + let value6 = textureLoad(image_array_src, itc, i32(local_id.z), i32(local_id.z) + 1); + let value7 = textureLoad(image_1d_src, i32(local_id.x), i32(local_id.z)); + // loads with uvec2 coords. + let value1u = textureLoad(image_mipmapped_src, vec2(itc), i32(local_id.z)); + let value2u = textureLoad(image_multisampled_src, vec2(itc), i32(local_id.z)); + let value4u = textureLoad(image_storage_src, vec2(itc)); + let value5u = textureLoad(image_array_src, vec2(itc), local_id.z, i32(local_id.z) + 1); + let value6u = textureLoad(image_array_src, vec2(itc), i32(local_id.z), i32(local_id.z) + 1); + let value7u = textureLoad(image_1d_src, u32(local_id.x), i32(local_id.z)); + // store with ivec2 coords. + textureStore(image_dst, itc.x, value1 + value2 + value4 + value5 + value6); + // store with uvec2 coords. + textureStore(image_dst, u32(itc.x), value1u + value2u + value4u + value5u + value6u); +} + +@compute @workgroup_size(16, 1, 1) +fn depth_load(@builtin(local_invocation_id) local_id: vec3) { + let dim: vec2 = textureDimensions(image_storage_src); + let itc: vec2 = (vec2(dim * local_id.xy) % vec2(10, 20)); + let val: f32 = textureLoad(image_depth_multisampled_src, itc, i32(local_id.z)); + textureStore(image_dst, itc.x, vec4(u32(val))); + return; +} + +@group(0) @binding(0) +var image_1d: texture_1d; +@group(0) @binding(1) +var image_2d: texture_2d; +@group(0) @binding(2) +var image_2d_u32: texture_2d; +@group(0) @binding(3) +var image_2d_i32: texture_2d; +@group(0) @binding(4) +var image_2d_array: texture_2d_array; +@group(0) @binding(5) +var image_cube: texture_cube; +@group(0) @binding(6) +var image_cube_array: texture_cube_array; +@group(0) @binding(7) +var image_3d: texture_3d; +@group(0) @binding(8) +var image_aa: texture_multisampled_2d; + +@vertex +fn queries() -> @builtin(position) vec4 { + let dim_1d = textureDimensions(image_1d); + let dim_1d_lod = textureDimensions(image_1d, i32(dim_1d)); + let dim_2d = textureDimensions(image_2d); + let dim_2d_lod = textureDimensions(image_2d, 1); + let dim_2d_array = textureDimensions(image_2d_array); + let dim_2d_array_lod = textureDimensions(image_2d_array, 1); + let dim_cube = textureDimensions(image_cube); + let dim_cube_lod = textureDimensions(image_cube, 1); + let dim_cube_array = textureDimensions(image_cube_array); + let dim_cube_array_lod = textureDimensions(image_cube_array, 1); + let dim_3d = textureDimensions(image_3d); + let dim_3d_lod = textureDimensions(image_3d, 1); + let dim_2s_ms = textureDimensions(image_aa); + + let sum = dim_1d + dim_2d.y + dim_2d_lod.y + dim_2d_array.y + dim_2d_array_lod.y + + dim_cube.y + dim_cube_lod.y + dim_cube_array.y + dim_cube_array_lod.y + + dim_3d.z + dim_3d_lod.z; + return vec4(f32(sum)); +} + +@vertex +fn levels_queries() -> @builtin(position) vec4 { + let num_levels_2d = textureNumLevels(image_2d); + let num_levels_2d_array = textureNumLevels(image_2d_array); + let num_layers_2d = textureNumLayers(image_2d_array); + let num_levels_cube = textureNumLevels(image_cube); + let num_levels_cube_array = textureNumLevels(image_cube_array); + let num_layers_cube = textureNumLayers(image_cube_array); + let num_levels_3d = textureNumLevels(image_3d); + let num_samples_aa = textureNumSamples(image_aa); + + let sum = num_layers_2d + num_layers_cube + num_samples_aa + + num_levels_2d + num_levels_2d_array + num_levels_3d + num_levels_cube + num_levels_cube_array; + return vec4(f32(sum)); +} + +@group(1) @binding(0) +var sampler_reg: sampler; + +@fragment +fn texture_sample() -> @location(0) vec4 { + let tc = vec2(0.5); + let tc3 = vec3(0.5); + let level = 2.3; + var a: vec4; + a += textureSample(image_1d, sampler_reg, tc.x); + a += textureSample(image_2d, sampler_reg, tc); + a += textureSample(image_2d, sampler_reg, tc, vec2(3, 1)); + a += textureSampleLevel(image_2d, sampler_reg, tc, level); + a += textureSampleLevel(image_2d, sampler_reg, tc, level, vec2(3, 1)); + a += textureSampleBias(image_2d, sampler_reg, tc, 2.0, vec2(3, 1)); + a += textureSample(image_2d_array, sampler_reg, tc, 0u); + a += textureSample(image_2d_array, sampler_reg, tc, 0u, vec2(3, 1)); + a += textureSampleLevel(image_2d_array, sampler_reg, tc, 0u, level); + a += textureSampleLevel(image_2d_array, sampler_reg, tc, 0u, level, vec2(3, 1)); + a += textureSampleBias(image_2d_array, sampler_reg, tc, 0u, 2.0, vec2(3, 1)); + a += textureSample(image_2d_array, sampler_reg, tc, 0); + a += textureSample(image_2d_array, sampler_reg, tc, 0, vec2(3, 1)); + a += textureSampleLevel(image_2d_array, sampler_reg, tc, 0, level); + a += textureSampleLevel(image_2d_array, sampler_reg, tc, 0, level, vec2(3, 1)); + a += textureSampleBias(image_2d_array, sampler_reg, tc, 0, 2.0, vec2(3, 1)); + a += textureSample(image_cube_array, sampler_reg, tc3, 0u); + a += textureSampleLevel(image_cube_array, sampler_reg, tc3, 0u, level); + a += textureSampleBias(image_cube_array, sampler_reg, tc3, 0u, 2.0); + a += textureSample(image_cube_array, sampler_reg, tc3, 0); + a += textureSampleLevel(image_cube_array, sampler_reg, tc3, 0, level); + a += textureSampleBias(image_cube_array, sampler_reg, tc3, 0, 2.0); + return a; +} + +@group(1) @binding(1) +var sampler_cmp: sampler_comparison; +@group(1) @binding(2) +var image_2d_depth: texture_depth_2d; +@group(1) @binding(3) +var image_2d_array_depth: texture_depth_2d_array; +@group(1) @binding(4) +var image_cube_depth: texture_depth_cube; + +@fragment +fn texture_sample_comparison() -> @location(0) f32 { + let tc = vec2(0.5); + let tc3 = vec3(0.5); + let dref = 0.5; + var a: f32; + a += textureSampleCompare(image_2d_depth, sampler_cmp, tc, dref); + a += textureSampleCompare(image_2d_array_depth, sampler_cmp, tc, 0u, dref); + a += textureSampleCompare(image_2d_array_depth, sampler_cmp, tc, 0, dref); + a += textureSampleCompare(image_cube_depth, sampler_cmp, tc3, dref); + a += textureSampleCompareLevel(image_2d_depth, sampler_cmp, tc, dref); + a += textureSampleCompareLevel(image_2d_array_depth, sampler_cmp, tc, 0u, dref); + a += textureSampleCompareLevel(image_2d_array_depth, sampler_cmp, tc, 0, dref); + a += textureSampleCompareLevel(image_cube_depth, sampler_cmp, tc3, dref); + return a; +} + +@fragment +fn gather() -> @location(0) vec4 { + let tc = vec2(0.5); + let dref = 0.5; + let s2d = textureGather(1, image_2d, sampler_reg, tc); + let s2d_offset = textureGather(3, image_2d, sampler_reg, tc, vec2(3, 1)); + let s2d_depth = textureGatherCompare(image_2d_depth, sampler_cmp, tc, dref); + let s2d_depth_offset = textureGatherCompare(image_2d_depth, sampler_cmp, tc, dref, vec2(3, 1)); + + let u = textureGather(0, image_2d_u32, sampler_reg, tc); + let i = textureGather(0, image_2d_i32, sampler_reg, tc); + let f = vec4(u) + vec4(i); + + return s2d + s2d_offset + s2d_depth + s2d_depth_offset + f; +} + +@fragment +fn depth_no_comparison() -> @location(0) vec4 { + let tc = vec2(0.5); + let s2d = textureSample(image_2d_depth, sampler_reg, tc); + let s2d_gather = textureGather(image_2d_depth, sampler_reg, tc); + return s2d + s2d_gather; +} diff --git a/naga/tests/in/interface.param.ron b/naga/tests/in/interface.param.ron new file mode 100644 index 0000000000..4d85661767 --- /dev/null +++ b/naga/tests/in/interface.param.ron @@ -0,0 +1,31 @@ +( + spv: ( + version: (1, 0), + capabilities: [ Shader, SampleRateShading ], + adjust_coordinate_space: false, + force_point_size: true, + clamp_frag_depth: true, + separate_entry_points: true, + ), + hlsl: ( + shader_model: V5_1, + binding_map: {}, + fake_missing_bindings: false, + special_constants_binding: Some((space: 1, register: 0)), + zero_initialize_workgroup_memory: true, + ), + wgsl: ( + explicit_types: true, + ), + msl: ( + lang_version: (2, 1), + per_entry_point_map: {}, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), + msl_pipeline: ( + allow_and_force_point_size: true, + ), +) diff --git a/naga/tests/in/interface.wgsl b/naga/tests/in/interface.wgsl new file mode 100644 index 0000000000..0f64f95644 --- /dev/null +++ b/naga/tests/in/interface.wgsl @@ -0,0 +1,61 @@ +// Testing various parts of the pipeline interface: locations, built-ins, and entry points + +struct VertexOutput { + @builtin(position) @invariant position: vec4, + @location(1) _varying: f32, +} + +@vertex +fn vertex( + @builtin(vertex_index) vertex_index: u32, + @builtin(instance_index) instance_index: u32, + @location(10) color: u32, +) -> VertexOutput { + let tmp = vertex_index + instance_index + color; + return VertexOutput(vec4(1.0), f32(tmp)); +} + +struct FragmentOutput { + @builtin(frag_depth) depth: f32, + @builtin(sample_mask) sample_mask: u32, + @location(0) color: f32, +} + +@fragment +fn fragment( + in: VertexOutput, + @builtin(front_facing) front_facing: bool, + @builtin(sample_index) sample_index: u32, + @builtin(sample_mask) sample_mask: u32, +) -> FragmentOutput { + let mask = sample_mask & (1u << sample_index); + let color = select(0.0, 1.0, front_facing); + return FragmentOutput(in._varying, mask, color); +} + +var output: array; + +@compute @workgroup_size(1) +fn compute( + @builtin(global_invocation_id) global_id: vec3, + @builtin(local_invocation_id) local_id: vec3, + @builtin(local_invocation_index) local_index: u32, + @builtin(workgroup_id) wg_id: vec3, + @builtin(num_workgroups) num_wgs: vec3, +) { + output[0] = global_id.x + local_id.x + local_index + wg_id.x + num_wgs.x; +} + +struct Input1 { + @builtin(vertex_index) index: u32, +} + +struct Input2 { + @builtin(instance_index) index: u32, +} + +@vertex +fn vertex_two_structs(in1: Input1, in2: Input2) -> @builtin(position) @invariant vec4 { + var index = 2u; + return vec4(f32(in1.index), f32(in2.index), f32(index), 0.0); +} diff --git a/naga/tests/in/interpolate.param.ron b/naga/tests/in/interpolate.param.ron new file mode 100644 index 0000000000..b6d629c4ea --- /dev/null +++ b/naga/tests/in/interpolate.param.ron @@ -0,0 +1,15 @@ +( + spv: ( + version: (1, 0), + capabilities: [ Shader, SampleRateShading ], + debug: true, + force_point_size: true, + adjust_coordinate_space: true, + ), + glsl: ( + version: Desktop(400), + writer_flags: (""), + binding_map: {}, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/interpolate.wgsl b/naga/tests/in/interpolate.wgsl new file mode 100644 index 0000000000..2f6967b3e7 --- /dev/null +++ b/naga/tests/in/interpolate.wgsl @@ -0,0 +1,31 @@ +//TODO: merge with "interface"? + +struct FragmentInput { + @builtin(position) position: vec4, + @location(0) @interpolate(flat) _flat : u32, + @location(1) @interpolate(linear) _linear : f32, + @location(2) @interpolate(linear, centroid) linear_centroid : vec2, + @location(3) @interpolate(linear, sample) linear_sample : vec3, + @location(4) @interpolate(perspective) perspective : vec4, + @location(5) @interpolate(perspective, centroid) perspective_centroid : f32, + @location(6) @interpolate(perspective, sample) perspective_sample : f32, +} + +@vertex +fn vert_main() -> FragmentInput { + var out: FragmentInput; + + out.position = vec4(2.0, 4.0, 5.0, 6.0); + out._flat = 8u; + out._linear = 27.0; + out.linear_centroid = vec2(64.0, 125.0); + out.linear_sample = vec3(216.0, 343.0, 512.0); + out.perspective = vec4(729.0, 1000.0, 1331.0, 1728.0); + out.perspective_centroid = 2197.0; + out.perspective_sample = 2744.0; + + return out; +} + +@fragment +fn frag_main(val : FragmentInput) { } diff --git a/naga/tests/in/invariant.param.ron b/naga/tests/in/invariant.param.ron new file mode 100644 index 0000000000..b622806ad0 --- /dev/null +++ b/naga/tests/in/invariant.param.ron @@ -0,0 +1,11 @@ +( + glsl: ( + version: Embedded ( + version: 300, + is_webgl: true + ), + writer_flags: (""), + binding_map: {}, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/invariant.wgsl b/naga/tests/in/invariant.wgsl new file mode 100644 index 0000000000..ac6bc09f1e --- /dev/null +++ b/naga/tests/in/invariant.wgsl @@ -0,0 +1,7 @@ +@vertex +fn vs() -> @builtin(position) @invariant vec4 { + return vec4(0.0); +} + +@fragment +fn fs(@builtin(position) @invariant position: vec4) { } diff --git a/naga/tests/in/lexical-scopes.wgsl b/naga/tests/in/lexical-scopes.wgsl new file mode 100644 index 0000000000..9aee919d36 --- /dev/null +++ b/naga/tests/in/lexical-scopes.wgsl @@ -0,0 +1,54 @@ +fn blockLexicalScope(a: bool) { + { + let a = 2; + { + let a = 2.0; + } + let test: i32 = a; + } + let test: bool = a; +} + +fn ifLexicalScope(a: bool) { + if (a) { + let a = 2.0; + } + let test: bool = a; +} + + +fn loopLexicalScope(a: bool) { + loop { + let a = 2.0; + } + let test: bool = a; +} + +fn forLexicalScope(a: f32) { + for (var a = 0; a < 1; a++) { + let a = true; + } + let test: f32 = a; +} + +fn whileLexicalScope(a: i32) { + while (a > 2) { + let a = false; + } + let test: i32 = a; +} + +fn switchLexicalScope(a: i32) { + switch (a) { + case 0 { + let a = false; + } + case 1 { + let a = 2.0; + } + default { + let a = true; + } + } + let test = a == 2; +} diff --git a/naga/tests/in/math-functions.param.ron b/naga/tests/in/math-functions.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/math-functions.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/math-functions.wgsl b/naga/tests/in/math-functions.wgsl new file mode 100644 index 0000000000..d08e76e4f2 --- /dev/null +++ b/naga/tests/in/math-functions.wgsl @@ -0,0 +1,48 @@ +@fragment +fn main() { + let f = 1.0; + let v = vec4(0.0); + let a = degrees(f); + let b = radians(f); + let c = degrees(v); + let d = radians(v); + let e = saturate(v); + let g = refract(v, v, f); + let sign_a = sign(-1); + let sign_b = sign(vec4(-1)); + let sign_c = sign(-1.0); + let sign_d = sign(vec4(-1.0)); + let const_dot = dot(vec2(), vec2()); + let first_leading_bit_abs = firstLeadingBit(abs(0u)); + let flb_a = firstLeadingBit(-1); + let flb_b = firstLeadingBit(vec2(-1)); + let flb_c = firstLeadingBit(vec2(1u)); + let ftb_a = firstTrailingBit(-1); + let ftb_b = firstTrailingBit(1u); + let ftb_c = firstTrailingBit(vec2(-1)); + let ftb_d = firstTrailingBit(vec2(1u)); + let ctz_a = countTrailingZeros(0u); + let ctz_b = countTrailingZeros(0); + let ctz_c = countTrailingZeros(0xFFFFFFFFu); + let ctz_d = countTrailingZeros(-1); + let ctz_e = countTrailingZeros(vec2(0u)); + let ctz_f = countTrailingZeros(vec2(0)); + let ctz_g = countTrailingZeros(vec2(1u)); + let ctz_h = countTrailingZeros(vec2(1)); + let clz_a = countLeadingZeros(-1); + let clz_b = countLeadingZeros(1u); + let clz_c = countLeadingZeros(vec2(-1)); + let clz_d = countLeadingZeros(vec2(1u)); + let lde_a = ldexp(1.0, 2); + let lde_b = ldexp(vec2(1.0, 2.0), vec2(3, 4)); + let modf_a = modf(1.5); + let modf_b = modf(1.5).fract; + let modf_c = modf(1.5).whole; + let modf_d = modf(vec2(1.5, 1.5)); + let modf_e = modf(vec4(1.5, 1.5, 1.5, 1.5)).whole.x; + let modf_f: f32 = modf(vec2(1.5, 1.5)).fract.y; + let frexp_a = frexp(1.5); + let frexp_b = frexp(1.5).fract; + let frexp_c: i32 = frexp(1.5).exp; + let frexp_d: i32 = frexp(vec4(1.5, 1.5, 1.5, 1.5)).exp.x; +} diff --git a/naga/tests/in/module-scope.wgsl b/naga/tests/in/module-scope.wgsl new file mode 100644 index 0000000000..6d1e3c95ea --- /dev/null +++ b/naga/tests/in/module-scope.wgsl @@ -0,0 +1,26 @@ +fn call() { + statement(); + let x: S = returns(); + let vf = f32(Value); + let s = textureSample(Texture, Sampler, Vec2(vf)); +} + +fn statement() {} + +fn returns() -> S { + return S(Value); +} + +struct S { + x: i32, +} + +const Value: i32 = 1; + +@group(0) @binding(0) +var Texture: texture_2d; + +@group(0) @binding(1) +var Sampler: sampler; + +alias Vec2 = vec2; diff --git a/naga/tests/in/msl-varyings.wgsl b/naga/tests/in/msl-varyings.wgsl new file mode 100644 index 0000000000..21c6184bf4 --- /dev/null +++ b/naga/tests/in/msl-varyings.wgsl @@ -0,0 +1,23 @@ +struct Vertex { + @location(0) position: vec2f +} + +struct NoteInstance { + @location(1) position: vec2f +} + +struct VertexOutput { + @builtin(position) position: vec4f +} + +@vertex +fn vs_main(vertex: Vertex, note: NoteInstance) -> VertexOutput { + var out: VertexOutput; + return out; +} + +@fragment +fn fs_main(in: VertexOutput, note: NoteInstance) -> @location(0) vec4f { + let position = vec3(1f); + return in.position; +} diff --git a/naga/tests/in/multiview.param.ron b/naga/tests/in/multiview.param.ron new file mode 100644 index 0000000000..69390f9fd8 --- /dev/null +++ b/naga/tests/in/multiview.param.ron @@ -0,0 +1,4 @@ +( + god_mode: true, + glsl_multiview: Some(2), +) diff --git a/naga/tests/in/multiview.wgsl b/naga/tests/in/multiview.wgsl new file mode 100644 index 0000000000..0eedd08786 --- /dev/null +++ b/naga/tests/in/multiview.wgsl @@ -0,0 +1,2 @@ +@fragment +fn main(@builtin(view_index) view_index: i32) {} diff --git a/naga/tests/in/multiview_webgl.param.ron b/naga/tests/in/multiview_webgl.param.ron new file mode 100644 index 0000000000..bea71aa412 --- /dev/null +++ b/naga/tests/in/multiview_webgl.param.ron @@ -0,0 +1,13 @@ +( + god_mode: true, + glsl: ( + version: Embedded ( + version: 300, + is_webgl: true + ), + writer_flags: (""), + binding_map: {}, + zero_initialize_workgroup_memory: true, + ), + glsl_multiview: Some(2), +) diff --git a/naga/tests/in/multiview_webgl.wgsl b/naga/tests/in/multiview_webgl.wgsl new file mode 100644 index 0000000000..0eedd08786 --- /dev/null +++ b/naga/tests/in/multiview_webgl.wgsl @@ -0,0 +1,2 @@ +@fragment +fn main(@builtin(view_index) view_index: i32) {} diff --git a/naga/tests/in/operators.param.ron b/naga/tests/in/operators.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/operators.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/operators.wgsl b/naga/tests/in/operators.wgsl new file mode 100644 index 0000000000..293b84ab11 --- /dev/null +++ b/naga/tests/in/operators.wgsl @@ -0,0 +1,305 @@ +const v_f32_one = vec4(1.0, 1.0, 1.0, 1.0); +const v_f32_zero = vec4(0.0, 0.0, 0.0, 0.0); +const v_f32_half = vec4(0.5, 0.5, 0.5, 0.5); +const v_i32_one = vec4(1, 1, 1, 1); + +fn builtins() -> vec4 { + // select() + let condition = true; + let s1 = select(0, 1, condition); + let s2 = select(v_f32_zero, v_f32_one, condition); + let s3 = select(v_f32_one, v_f32_zero, vec4(false, false, false, false)); + // mix() + let m1 = mix(v_f32_zero, v_f32_one, v_f32_half); + let m2 = mix(v_f32_zero, v_f32_one, 0.1); + // bitcast() + let b1 = bitcast(v_i32_one.x); + let b2 = bitcast>(v_i32_one); + // convert + let v_i32_zero = vec4(v_f32_zero); + // done + return vec4(vec4(s1) + v_i32_zero) + s2 + m1 + m2 + b1 + b2; +} + +fn splat(m: f32, n: i32) -> vec4 { + let a = (2.0 + vec2(m) - 4.0) / 8.0; + let b = vec4(n) % 2; + return a.xyxy + vec4(b); +} + +fn splat_assignment() -> vec2 { + var a = vec2(2.0); + a += 1.0; + a -= 3.0; + a /= 4.0; + return a; +} + +fn bool_cast(x: vec3) -> vec3 { + let y = vec3(x); + return vec3(y); +} + +fn logical() { + let t = true; + let f = false; + + // unary + let neg0 = !t; + let neg1 = !vec2(t); + + // binary + let or = t || f; + let and = t && f; + let bitwise_or0 = t | f; + let bitwise_or1 = vec3(t) | vec3(f); + let bitwise_and0 = t & f; + let bitwise_and1 = vec4(t) & vec4(f); +} + +fn arithmetic() { + let one_i = 1i; + let one_u = 1u; + let one_f = 1.0; + let two_i = 2i; + let two_u = 2u; + let two_f = 2.0; + + // unary + let neg0 = -one_f; + let neg1 = -vec2(one_i); + let neg2 = -vec2(one_f); + + // binary + // Addition + let add0 = two_i + one_i; + let add1 = two_u + one_u; + let add2 = two_f + one_f; + let add3 = vec2(two_i) + vec2(one_i); + let add4 = vec3(two_u) + vec3(one_u); + let add5 = vec4(two_f) + vec4(one_f); + + // Subtraction + let sub0 = two_i - one_i; + let sub1 = two_u - one_u; + let sub2 = two_f - one_f; + let sub3 = vec2(two_i) - vec2(one_i); + let sub4 = vec3(two_u) - vec3(one_u); + let sub5 = vec4(two_f) - vec4(one_f); + + // Multiplication + let mul0 = two_i * one_i; + let mul1 = two_u * one_u; + let mul2 = two_f * one_f; + let mul3 = vec2(two_i) * vec2(one_i); + let mul4 = vec3(two_u) * vec3(one_u); + let mul5 = vec4(two_f) * vec4(one_f); + + // Division + let div0 = two_i / one_i; + let div1 = two_u / one_u; + let div2 = two_f / one_f; + let div3 = vec2(two_i) / vec2(one_i); + let div4 = vec3(two_u) / vec3(one_u); + let div5 = vec4(two_f) / vec4(one_f); + + // Remainder + let rem0 = two_i % one_i; + let rem1 = two_u % one_u; + let rem2 = two_f % one_f; + let rem3 = vec2(two_i) % vec2(one_i); + let rem4 = vec3(two_u) % vec3(one_u); + let rem5 = vec4(two_f) % vec4(one_f); + + // Binary arithmetic expressions with mixed scalar and vector operands + { + let add0 = vec2(two_i) + one_i; + let add1 = two_i + vec2(one_i); + let add2 = vec2(two_u) + one_u; + let add3 = two_u + vec2(one_u); + let add4 = vec2(two_f) + one_f; + let add5 = two_f + vec2(one_f); + + let sub0 = vec2(two_i) - one_i; + let sub1 = two_i - vec2(one_i); + let sub2 = vec2(two_u) - one_u; + let sub3 = two_u - vec2(one_u); + let sub4 = vec2(two_f) - one_f; + let sub5 = two_f - vec2(one_f); + + let mul0 = vec2(two_i) * one_i; + let mul1 = two_i * vec2(one_i); + let mul2 = vec2(two_u) * one_u; + let mul3 = two_u * vec2(one_u); + let mul4 = vec2(two_f) * one_f; + let mul5 = two_f * vec2(one_f); + + let div0 = vec2(two_i) / one_i; + let div1 = two_i / vec2(one_i); + let div2 = vec2(two_u) / one_u; + let div3 = two_u / vec2(one_u); + let div4 = vec2(two_f) / one_f; + let div5 = two_f / vec2(one_f); + + let rem0 = vec2(two_i) % one_i; + let rem1 = two_i % vec2(one_i); + let rem2 = vec2(two_u) % one_u; + let rem3 = two_u % vec2(one_u); + let rem4 = vec2(two_f) % one_f; + let rem5 = two_f % vec2(one_f); + } + + // Matrix arithmetic + let add = mat3x3() + mat3x3(); + let sub = mat3x3() - mat3x3(); + + let mul_scalar0 = mat3x3() * one_f; + let mul_scalar1 = two_f * mat3x3(); + + let mul_vector0 = mat4x3() * vec4(one_f); + let mul_vector1 = vec3f(two_f) * mat4x3f(); + + let mul = mat4x3() * mat3x4(); +} + +fn bit() { + let one_i = 1i; + let one_u = 1u; + let two_i = 2i; + let two_u = 2u; + + // unary + let flip0 = ~one_i; + let flip1 = ~one_u; + let flip2 = ~vec2(one_i); + let flip3 = ~vec3(one_u); + + // binary + let or0 = two_i | one_i; + let or1 = two_u | one_u; + let or2 = vec2(two_i) | vec2(one_i); + let or3 = vec3(two_u) | vec3(one_u); + + let and0 = two_i & one_i; + let and1 = two_u & one_u; + let and2 = vec2(two_i) & vec2(one_i); + let and3 = vec3(two_u) & vec3(one_u); + + let xor0 = two_i ^ one_i; + let xor1 = two_u ^ one_u; + let xor2 = vec2(two_i) ^ vec2(one_i); + let xor3 = vec3(two_u) ^ vec3(one_u); + + let shl0 = two_i << one_u; + let shl1 = two_u << one_u; + let shl2 = vec2(two_i) << vec2(one_u); + let shl3 = vec3(two_u) << vec3(one_u); + + let shr0 = two_i >> one_u; + let shr1 = two_u >> one_u; + let shr2 = vec2(two_i) >> vec2(one_u); + let shr3 = vec3(two_u) >> vec3(one_u); +} + +fn comparison() { + let one_i = 1i; + let one_u = 1u; + let one_f = 1.0; + let two_i = 2i; + let two_u = 2u; + let two_f = 2.0; + + let eq0 = two_i == one_i; + let eq1 = two_u == one_u; + let eq2 = two_f == one_f; + let eq3 = vec2(two_i) == vec2(one_i); + let eq4 = vec3(two_u) == vec3(one_u); + let eq5 = vec4(two_f) == vec4(one_f); + + let neq0 = two_i != one_i; + let neq1 = two_u != one_u; + let neq2 = two_f != one_f; + let neq3 = vec2(two_i) != vec2(one_i); + let neq4 = vec3(two_u) != vec3(one_u); + let neq5 = vec4(two_f) != vec4(one_f); + + let lt0 = two_i < one_i; + let lt1 = two_u < one_u; + let lt2 = two_f < one_f; + let lt3 = vec2(two_i) < vec2(one_i); + let lt4 = vec3(two_u) < vec3(one_u); + let lt5 = vec4(two_f) < vec4(one_f); + + let lte0 = two_i <= one_i; + let lte1 = two_u <= one_u; + let lte2 = two_f <= one_f; + let lte3 = vec2(two_i) <= vec2(one_i); + let lte4 = vec3(two_u) <= vec3(one_u); + let lte5 = vec4(two_f) <= vec4(one_f); + + let gt0 = two_i > one_i; + let gt1 = two_u > one_u; + let gt2 = two_f > one_f; + let gt3 = vec2(two_i) > vec2(one_i); + let gt4 = vec3(two_u) > vec3(one_u); + let gt5 = vec4(two_f) > vec4(one_f); + + let gte0 = two_i >= one_i; + let gte1 = two_u >= one_u; + let gte2 = two_f >= one_f; + let gte3 = vec2(two_i) >= vec2(one_i); + let gte4 = vec3(two_u) >= vec3(one_u); + let gte5 = vec4(two_f) >= vec4(one_f); +} + +fn assignment() { + let zero_i = 0i; + let one_i = 1i; + let one_u = 1u; + let two_u = 2u; + + var a = one_i; + + a += one_i; + a -= one_i; + a *= a; + a /= a; + a %= one_i; + a &= zero_i; + a |= zero_i; + a ^= zero_i; + a <<= two_u; + a >>= one_u; + + a++; + a--; + + var vec0: vec3 = vec3(); + vec0[one_i]++; + vec0[one_i]--; +} + +@compute @workgroup_size(1) +fn main(@builtin(workgroup_id) id: vec3) { + builtins(); + splat(f32(id.x), i32(id.y)); + bool_cast(v_f32_one.xyz); + + logical(); + arithmetic(); + bit(); + comparison(); + assignment(); +} + +fn negation_avoids_prefix_decrement() { + let x = 1; + let p0 = -x; + let p1 = - -x; + let p2 = -(-x); + let p3 = -(- x); + let p4 = - - -x; + let p5 = - - - - x; + let p6 = - - -(- -x); + let p7 = (- - - - -x); +} diff --git a/naga/tests/in/padding.param.ron b/naga/tests/in/padding.param.ron new file mode 100644 index 0000000000..1a735a201e --- /dev/null +++ b/naga/tests/in/padding.param.ron @@ -0,0 +1,23 @@ +( + spv: ( + version: (1, 1), + debug: true, + adjust_coordinate_space: false, + ), + msl: ( + lang_version: (1, 0), + per_entry_point_map: { + "vertex": ( + resources: { + (group: 0, binding: 0): (buffer: Some(0), mutable: false), + (group: 0, binding: 1): (buffer: Some(1), mutable: false), + (group: 0, binding: 2): (buffer: Some(2), mutable: false), + }, + ), + }, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/padding.wgsl b/naga/tests/in/padding.wgsl new file mode 100644 index 0000000000..24c764c173 --- /dev/null +++ b/naga/tests/in/padding.wgsl @@ -0,0 +1,33 @@ +struct S { + a: vec3, +} + +struct Test { + a: S, + b: f32, // offset: 16 +} + +struct Test2 { + a: array, 2>, + b: f32, // offset: 32 +} + +struct Test3 { + a: mat4x3, + b: f32, // offset: 64 +} + +@group(0) @binding(0) +var input1: Test; + +@group(0) @binding(1) +var input2: Test2; + +@group(0) @binding(2) +var input3: Test3; + + +@vertex +fn vertex() -> @builtin(position) vec4 { + return vec4(1.0) * input1.b * input2.b * input3.b; +} diff --git a/naga/tests/in/pointers.param.ron b/naga/tests/in/pointers.param.ron new file mode 100644 index 0000000000..fc40272838 --- /dev/null +++ b/naga/tests/in/pointers.param.ron @@ -0,0 +1,11 @@ +( + bounds_check_policies: ( + image_load: ReadZeroSkipWrite, + image_store: ReadZeroSkipWrite, + ), + spv: ( + version: (1, 2), + debug: true, + adjust_coordinate_space: false, + ), +) diff --git a/naga/tests/in/pointers.wgsl b/naga/tests/in/pointers.wgsl new file mode 100644 index 0000000000..bfd88c9467 --- /dev/null +++ b/naga/tests/in/pointers.wgsl @@ -0,0 +1,26 @@ +fn f() { + var v: vec2; + let px = &v.x; + *px = 10; +} + +struct DynamicArray { + arr: array +} + +@group(0) @binding(0) +var dynamic_array: DynamicArray; + +fn index_unsized(i: i32, v: u32) { + let p: ptr = &dynamic_array; + + let val = (*p).arr[i]; + (*p).arr[i] = val + v; +} + +fn index_dynamic_array(i: i32, v: u32) { + let p: ptr, read_write> = &dynamic_array.arr; + + let val = (*p)[i]; + (*p)[i] = val + v; +} diff --git a/naga/tests/in/policy-mix.param.ron b/naga/tests/in/policy-mix.param.ron new file mode 100644 index 0000000000..e5469157ed --- /dev/null +++ b/naga/tests/in/policy-mix.param.ron @@ -0,0 +1,12 @@ +( + bounds_check_policies: ( + index: Restrict, + buffer: Unchecked, + image_load: ReadZeroSkipWrite, + image_store: ReadZeroSkipWrite, + ), + spv: ( + version: (1, 1), + debug: true, + ), +) diff --git a/naga/tests/in/policy-mix.wgsl b/naga/tests/in/policy-mix.wgsl new file mode 100644 index 0000000000..f1fb723892 --- /dev/null +++ b/naga/tests/in/policy-mix.wgsl @@ -0,0 +1,33 @@ +// Tests that the index, buffer, and texture bounds checks policies are +// implemented separately. + +// Storage and Uniform storage classes +struct InStorage { + a: array, 10> +} +@group(0) @binding(0) var in_storage: InStorage; + +struct InUniform { + a: array, 20> +} +@group(0) @binding(1) var in_uniform: InUniform; + +// Textures automatically land in the `handle` storage class. +@group(0) @binding(2) var image_2d_array: texture_2d_array; + +// None of the above. +var in_workgroup: array; +var in_private: array; + +fn mock_function(c: vec2, i: i32, l: i32) -> vec4 { + var in_function: array, 2> = + array, 2>(vec4(0.707, 0.0, 0.0, 1.0), + vec4(0.0, 0.707, 0.0, 1.0)); + + return (in_storage.a[i] + + in_uniform.a[i] + + textureLoad(image_2d_array, c, i, l) + + in_workgroup[i] + + in_private[i] + + in_function[i]); +} diff --git a/naga/tests/in/push-constants.param.ron b/naga/tests/in/push-constants.param.ron new file mode 100644 index 0000000000..083d028bbf --- /dev/null +++ b/naga/tests/in/push-constants.param.ron @@ -0,0 +1,20 @@ +( + god_mode: true, + glsl: ( + version: Embedded( + version: 320, + is_webgl: false + ), + writer_flags: (""), + binding_map: {}, + zero_initialize_workgroup_memory: true, + ), + hlsl: ( + shader_model: V5_1, + binding_map: {}, + fake_missing_bindings: true, + special_constants_binding: Some((space: 1, register: 0)), + push_constants_target: Some((space: 0, register: 0)), + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/push-constants.wgsl b/naga/tests/in/push-constants.wgsl new file mode 100644 index 0000000000..dbe81a0295 --- /dev/null +++ b/naga/tests/in/push-constants.wgsl @@ -0,0 +1,22 @@ +struct PushConstants { + multiplier: f32 +} +var pc: PushConstants; + +struct FragmentIn { + @location(0) color: vec4 +} + +@vertex +fn vert_main( + @location(0) pos : vec2, + @builtin(instance_index) ii: u32, + @builtin(vertex_index) vi: u32, +) -> @builtin(position) vec4 { + return vec4(f32(ii) * f32(vi) * pc.multiplier * pos, 0.0, 1.0); +} + +@fragment +fn main(in: FragmentIn) -> @location(0) vec4 { + return in.color * pc.multiplier; +} diff --git a/naga/tests/in/quad.param.ron b/naga/tests/in/quad.param.ron new file mode 100644 index 0000000000..7e3f5504db --- /dev/null +++ b/naga/tests/in/quad.param.ron @@ -0,0 +1,16 @@ +( + spv: ( + version: (1, 0), + debug: true, + adjust_coordinate_space: true, + ), + glsl: ( + version: Embedded( + version: 300, + is_webgl: false + ), + writer_flags: (""), + binding_map: {}, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/quad.wgsl b/naga/tests/in/quad.wgsl new file mode 100644 index 0000000000..b51e1a91d9 --- /dev/null +++ b/naga/tests/in/quad.wgsl @@ -0,0 +1,38 @@ +// vertex +const c_scale: f32 = 1.2; + +struct VertexOutput { + @location(0) uv : vec2, + @builtin(position) position : vec4, +} + +@vertex +fn vert_main( + @location(0) pos : vec2, + @location(1) uv : vec2, +) -> VertexOutput { + return VertexOutput(uv, vec4(c_scale * pos, 0.0, 1.0)); +} + +// fragment +@group(0) @binding(0) var u_texture : texture_2d; +@group(0) @binding(1) var u_sampler : sampler; + +@fragment +fn frag_main(@location(0) uv : vec2) -> @location(0) vec4 { + let color = textureSample(u_texture, u_sampler, uv); + if color.a == 0.0 { + discard; + } + // forcing the expression here to be emitted in order to check the + // uniformity of the control flow a bit more strongly. + let premultiplied = color.a * color; + return premultiplied; +} + + +// We need to make sure that backends are successfully handling multiple entry points for the same shader stage. +@fragment +fn fs_extra() -> @location(0) vec4 { + return vec4(0.0, 0.5, 0.0, 0.5); +} diff --git a/naga/tests/in/ray-query.param.ron b/naga/tests/in/ray-query.param.ron new file mode 100644 index 0000000000..c400db8c64 --- /dev/null +++ b/naga/tests/in/ray-query.param.ron @@ -0,0 +1,14 @@ +( + god_mode: true, + spv: ( + version: (1, 4), + ), + msl: ( + lang_version: (2, 4), + spirv_cross_compatibility: false, + fake_missing_bindings: true, + zero_initialize_workgroup_memory: false, + per_entry_point_map: {}, + inline_samplers: [], + ), +) diff --git a/naga/tests/in/ray-query.wgsl b/naga/tests/in/ray-query.wgsl new file mode 100644 index 0000000000..4826547ded --- /dev/null +++ b/naga/tests/in/ray-query.wgsl @@ -0,0 +1,73 @@ +@group(0) @binding(0) +var acc_struct: acceleration_structure; + +/* +let RAY_FLAG_NONE = 0x00u; +let RAY_FLAG_OPAQUE = 0x01u; +let RAY_FLAG_NO_OPAQUE = 0x02u; +let RAY_FLAG_TERMINATE_ON_FIRST_HIT = 0x04u; +let RAY_FLAG_SKIP_CLOSEST_HIT_SHADER = 0x08u; +let RAY_FLAG_CULL_BACK_FACING = 0x10u; +let RAY_FLAG_CULL_FRONT_FACING = 0x20u; +let RAY_FLAG_CULL_OPAQUE = 0x40u; +let RAY_FLAG_CULL_NO_OPAQUE = 0x80u; +let RAY_FLAG_SKIP_TRIANGLES = 0x100u; +let RAY_FLAG_SKIP_AABBS = 0x200u; + +let RAY_QUERY_INTERSECTION_NONE = 0u; +let RAY_QUERY_INTERSECTION_TRIANGLE = 1u; +let RAY_QUERY_INTERSECTION_GENERATED = 2u; +let RAY_QUERY_INTERSECTION_AABB = 4u; + +struct RayDesc { + flags: u32, + cull_mask: u32, + t_min: f32, + t_max: f32, + origin: vec3, + dir: vec3, +} + +struct RayIntersection { + kind: u32, + t: f32, + instance_custom_index: u32, + instance_id: u32, + sbt_record_offset: u32, + geometry_index: u32, + primitive_index: u32, + barycentrics: vec2, + front_face: bool, + object_to_world: mat4x3, + world_to_object: mat4x3, +} +*/ + +struct Output { + visible: u32, + normal: vec3, +} + +@group(0) @binding(1) +var output: Output; + +fn get_torus_normal(world_point: vec3, intersection: RayIntersection) -> vec3 { + let local_point = intersection.world_to_object * vec4(world_point, 1.0); + let point_on_guiding_line = normalize(local_point.xy) * 2.4; + let world_point_on_guiding_line = intersection.object_to_world * vec4(point_on_guiding_line, 0.0, 1.0); + return normalize(world_point - world_point_on_guiding_line); +} + +@compute @workgroup_size(1) +fn main() { + var rq: ray_query; + + let dir = vec3(0.0, 1.0, 0.0); + rayQueryInitialize(&rq, acc_struct, RayDesc(RAY_FLAG_TERMINATE_ON_FIRST_HIT, 0xFFu, 0.1, 100.0, vec3(0.0), dir)); + + while (rayQueryProceed(&rq)) {} + + let intersection = rayQueryGetCommittedIntersection(&rq); + output.visible = u32(intersection.kind == RAY_QUERY_INTERSECTION_NONE); + output.normal = get_torus_normal(dir * intersection.t, intersection); +} diff --git a/naga/tests/in/resource-binding-map.param.ron b/naga/tests/in/resource-binding-map.param.ron new file mode 100644 index 0000000000..25e7b054b0 --- /dev/null +++ b/naga/tests/in/resource-binding-map.param.ron @@ -0,0 +1,54 @@ +( + god_mode: true, + msl: ( + lang_version: (1, 0), + per_entry_point_map: { + "entry_point_one": ( + resources: { + (group: 0, binding: 0): (texture: Some(0)), + (group: 0, binding: 2): (sampler: Some(Inline(0))), + (group: 0, binding: 4): (buffer: Some(0)), + } + ), + "entry_point_two": ( + resources: { + (group: 0, binding: 0): (texture: Some(0)), + (group: 0, binding: 2): (sampler: Some(Resource(0))), + (group: 0, binding: 4): (buffer: Some(0)), + } + ), + "entry_point_three": ( + resources: { + (group: 0, binding: 0): (texture: Some(0)), + (group: 0, binding: 1): (texture: Some(1)), + (group: 0, binding: 2): (sampler: Some(Inline(0))), + (group: 0, binding: 3): (sampler: Some(Resource(1))), + (group: 0, binding: 4): (buffer: Some(0)), + (group: 1, binding: 0): (buffer: Some(1)), + } + ) + }, + inline_samplers: [ + ( + coord: Normalized, + address: (ClampToEdge, ClampToEdge, ClampToEdge), + mag_filter: Linear, + min_filter: Linear, + mip_filter: None, + border_color: TransparentBlack, + compare_func: Never, + lod_clamp: Some((start: 0.5, end: 10.0)), + max_anisotropy: Some(8), + ), + ], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), + bounds_check_policies: ( + index: ReadZeroSkipWrite, + buffer: ReadZeroSkipWrite, + image_load: ReadZeroSkipWrite, + image_store: ReadZeroSkipWrite, + ) +) diff --git a/naga/tests/in/resource-binding-map.wgsl b/naga/tests/in/resource-binding-map.wgsl new file mode 100644 index 0000000000..fa5fce4ee1 --- /dev/null +++ b/naga/tests/in/resource-binding-map.wgsl @@ -0,0 +1,23 @@ +@group(0) @binding(0) var t1: texture_2d; +@group(0) @binding(1) var t2: texture_2d; +@group(0) @binding(2) var s1: sampler; +@group(0) @binding(3) var s2: sampler; + +@group(0) @binding(4) var uniformOne: vec2; +@group(1) @binding(0) var uniformTwo: vec2; + +@fragment +fn entry_point_one(@builtin(position) pos: vec4) -> @location(0) vec4 { + return textureSample(t1, s1, pos.xy); +} + +@fragment +fn entry_point_two() -> @location(0) vec4 { + return textureSample(t1, s1, uniformOne); +} + +@fragment +fn entry_point_three() -> @location(0) vec4 { + return textureSample(t1, s1, uniformTwo + uniformOne) + + textureSample(t2, s2, uniformOne); +} diff --git a/naga/tests/in/runtime-array-in-unused-struct.wgsl b/naga/tests/in/runtime-array-in-unused-struct.wgsl new file mode 100644 index 0000000000..bcee56d9b0 --- /dev/null +++ b/naga/tests/in/runtime-array-in-unused-struct.wgsl @@ -0,0 +1,12 @@ +struct DataStruct { + data: f32, + data_vec: vec4, +} + +struct Struct { + data: array, +}; + +struct PrimitiveStruct { + data: array, +}; diff --git a/naga/tests/in/separate-entry-points.param.ron b/naga/tests/in/separate-entry-points.param.ron new file mode 100644 index 0000000000..af0931c111 --- /dev/null +++ b/naga/tests/in/separate-entry-points.param.ron @@ -0,0 +1,6 @@ +( + spv: ( + version: (1, 0), + separate_entry_points: true, + ), +) diff --git a/naga/tests/in/separate-entry-points.wgsl b/naga/tests/in/separate-entry-points.wgsl new file mode 100644 index 0000000000..a7ec3b083a --- /dev/null +++ b/naga/tests/in/separate-entry-points.wgsl @@ -0,0 +1,23 @@ +// only available in the fragment stage +fn derivatives() { + let x = dpdx(0.0); + let y = dpdy(0.0); + let width = fwidth(0.0); +} + +// only available in the compute stage +fn barriers() { + storageBarrier(); + workgroupBarrier(); +} + +@fragment +fn fragment() -> @location(0) vec4 { + derivatives(); + return vec4(); +} + +@compute @workgroup_size(1) +fn compute() { + barriers(); +} \ No newline at end of file diff --git a/naga/tests/in/shadow.param.ron b/naga/tests/in/shadow.param.ron new file mode 100644 index 0000000000..e37f9108ae --- /dev/null +++ b/naga/tests/in/shadow.param.ron @@ -0,0 +1,7 @@ +( + spv: ( + version: (1, 2), + debug: true, + adjust_coordinate_space: true, + ), +) diff --git a/naga/tests/in/shadow.wgsl b/naga/tests/in/shadow.wgsl new file mode 100644 index 0000000000..b02cf68775 --- /dev/null +++ b/naga/tests/in/shadow.wgsl @@ -0,0 +1,117 @@ +struct Globals { + view_proj: mat4x4, + num_lights: vec4, +} + +@group(0) +@binding(0) +var u_globals: Globals; + +struct Entity { + world: mat4x4, + color: vec4, +} + +@group(1) +@binding(0) +var u_entity: Entity; + +/* Not useful for testing +@vertex +fn vs_bake(@location(0) position: vec4) -> @builtin(position) vec4 { + return u_globals.view_proj * u_entity.world * vec4(position); +} +*/ + +struct VertexOutput { + @builtin(position) proj_position: vec4, + @location(0) world_normal: vec3, + @location(1) world_position: vec4, +} + +@vertex +fn vs_main( + @location(0) position: vec4, + @location(1) normal: vec4, +) -> VertexOutput { + let w = u_entity.world; + let world_pos = u_entity.world * vec4(position); + var out: VertexOutput; + out.world_normal = mat3x3(w.x.xyz, w.y.xyz, w.z.xyz) * vec3(normal.xyz); + out.world_position = world_pos; + out.proj_position = u_globals.view_proj * world_pos; + return out; +} + +// fragment shader + +struct Light { + proj: mat4x4, + pos: vec4, + color: vec4, +} + +@group(0) +@binding(1) +var s_lights: array; +@group(0) +@binding(1) +var u_lights: array; // Used when storage types are not supported +@group(0) +@binding(2) +var t_shadow: texture_depth_2d_array; +@group(0) +@binding(3) +var sampler_shadow: sampler_comparison; + +fn fetch_shadow(light_id: u32, homogeneous_coords: vec4) -> f32 { + if (homogeneous_coords.w <= 0.0) { + return 1.0; + } + // compensate for the Y-flip difference between the NDC and texture coordinates + let flip_correction = vec2(0.5, -0.5); + // compute texture coordinates for shadow lookup + let proj_correction = 1.0 / homogeneous_coords.w; + let light_local = homogeneous_coords.xy * flip_correction * proj_correction + vec2(0.5, 0.5); + // do the lookup, using HW PCF and comparison + return textureSampleCompareLevel(t_shadow, sampler_shadow, light_local, i32(light_id), homogeneous_coords.z * proj_correction); +} + +const c_ambient: vec3 = vec3(0.05, 0.05, 0.05); +const c_max_lights: u32 = 10u; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let normal = normalize(in.world_normal); + // accumulate color + var color: vec3 = c_ambient; + for(var i = 0u; i < min(u_globals.num_lights.x, c_max_lights); i++) { + let light = s_lights[i]; + // project into the light space + let shadow = fetch_shadow(i, light.proj * in.world_position); + // compute Lambertian diffuse term + let light_dir = normalize(light.pos.xyz - in.world_position.xyz); + let diffuse = max(0.0, dot(normal, light_dir)); + // add light contribution + color += shadow * diffuse * light.color.xyz; + } + // multiply the light by material color + return vec4(color, 1.0) * u_entity.color; +} + +// The fragment entrypoint used when storage buffers are not available for the lights +@fragment +fn fs_main_without_storage(in: VertexOutput) -> @location(0) vec4 { + let normal = normalize(in.world_normal); + var color: vec3 = c_ambient; + for(var i = 0u; i < min(u_globals.num_lights.x, c_max_lights); i++) { + // This line is the only difference from the entrypoint above. It uses the lights + // uniform instead of the lights storage buffer + let light = u_lights[i]; + let shadow = fetch_shadow(i, light.proj * in.world_position); + let light_dir = normalize(light.pos.xyz - in.world_position.xyz); + let diffuse = max(0.0, dot(normal, light_dir)); + color += shadow * diffuse * light.color.xyz; + } + return vec4(color, 1.0) * u_entity.color; +} diff --git a/naga/tests/in/skybox.param.ron b/naga/tests/in/skybox.param.ron new file mode 100644 index 0000000000..4d7fdf7347 --- /dev/null +++ b/naga/tests/in/skybox.param.ron @@ -0,0 +1,63 @@ +( + spv_flow_dump_prefix: "", + spv: ( + version: (1, 0), + debug: false, + adjust_coordinate_space: false, + ), + msl: ( + lang_version: (2, 1), + per_entry_point_map: { + "vs_main": ( + resources: { + (group: 0, binding: 0): (buffer: Some(0)), + }, + ), + "fs_main": ( + resources: { + (group: 0, binding: 1): (texture: Some(0)), + (group: 0, binding: 2): (sampler: Some(Inline(0))), + }, + ), + }, + inline_samplers: [ + ( + coord: Normalized, + address: (ClampToEdge, ClampToEdge, ClampToEdge), + mag_filter: Linear, + min_filter: Linear, + mip_filter: None, + border_color: TransparentBlack, + compare_func: Never, + lod_clamp: Some((start: 0.5, end: 10.0)), + max_anisotropy: Some(8), + ), + ], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), + glsl: ( + version: Embedded( + version: 320, + is_webgl: false + ), + writer_flags: (""), + binding_map: { + (group: 0, binding: 0): 0, + (group: 0, binding: 1): 0, + }, + zero_initialize_workgroup_memory: true, + ), + hlsl: ( + shader_model: V5_1, + binding_map: { + (group: 0, binding: 0): (space: 0, register: 0), + (group: 0, binding: 1): (space: 0, register: 0), + (group: 0, binding: 2): (space: 1, register: 0), + }, + fake_missing_bindings: false, + special_constants_binding: Some((space: 0, register: 1)), + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/skybox.wgsl b/naga/tests/in/skybox.wgsl new file mode 100644 index 0000000000..f4cc37a44b --- /dev/null +++ b/naga/tests/in/skybox.wgsl @@ -0,0 +1,38 @@ +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) uv: vec3, +} + +struct Data { + proj_inv: mat4x4, + view: mat4x4, +} +@group(0) @binding(0) +var r_data: Data; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { + // hacky way to draw a large triangle + var tmp1 = i32(vertex_index) / 2; + var tmp2 = i32(vertex_index) & 1; + let pos = vec4( + f32(tmp1) * 4.0 - 1.0, + f32(tmp2) * 4.0 - 1.0, + 0.0, + 1.0, + ); + + let inv_model_view = transpose(mat3x3(r_data.view.x.xyz, r_data.view.y.xyz, r_data.view.z.xyz)); + let unprojected = r_data.proj_inv * pos; + return VertexOutput(pos, inv_model_view * unprojected.xyz); +} + +@group(0) @binding(1) +var r_texture: texture_cube; +@group(0) @binding(2) +var r_sampler: sampler; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return textureSample(r_texture, r_sampler, in.uv); +} diff --git a/naga/tests/in/sprite.param.ron b/naga/tests/in/sprite.param.ron new file mode 100644 index 0000000000..08598faeef --- /dev/null +++ b/naga/tests/in/sprite.param.ron @@ -0,0 +1,5 @@ +( + spv: ( + version: (1, 4), + ), +) diff --git a/naga/tests/in/sprite.wgsl b/naga/tests/in/sprite.wgsl new file mode 100644 index 0000000000..6828b29465 --- /dev/null +++ b/naga/tests/in/sprite.wgsl @@ -0,0 +1,7 @@ +@group(0) @binding(0) var u_texture : texture_2d; +@group(0) @binding(1) var u_sampler : sampler; + +@fragment +fn main(@location(0) uv : vec2) -> @location(0) vec4 { + return textureSample(u_texture, u_sampler, uv); +} diff --git a/naga/tests/in/spv/binding-arrays.dynamic.spv b/naga/tests/in/spv/binding-arrays.dynamic.spv new file mode 100644 index 0000000000..12bf2e00ed Binary files /dev/null and b/naga/tests/in/spv/binding-arrays.dynamic.spv differ diff --git a/naga/tests/in/spv/binding-arrays.dynamic.spvasm b/naga/tests/in/spv/binding-arrays.dynamic.spvasm new file mode 100644 index 0000000000..d1c38a8324 --- /dev/null +++ b/naga/tests/in/spv/binding-arrays.dynamic.spvasm @@ -0,0 +1,75 @@ +;; Make sure that we promote `OpTypeRuntimeArray` of textures and samplers into +;; `TypeInner::BindingArray` and support indexing it through `OpAccessChain` +;; and `OpInBoundsAccessChain`. +;; +;; Code in here corresponds to, more or less: +;; +;; ```rust +;; #[spirv(fragment)] +;; pub fn main( +;; #[spirv(descriptor_set = 0, binding = 0)] +;; images: &RuntimeArray, +;; #[spirv(descriptor_set = 0, binding = 1)] +;; samplers: &RuntimeArray, +;; out: &mut Vec4, +;; ) { +;; let image = images[1]; +;; let sampler = samplers[1]; +;; +;; *out = image.sample_by_lod(sampler, vec2(0.5, 0.5), 0.0); +;; } +;; ``` + + OpCapability Shader + OpMemoryModel Logical Simple + OpEntryPoint Fragment %main "main" %fn_param_images %fn_param_samplers %fn_param_out + OpExecutionMode %main OriginUpperLeft + OpDecorate %images ArrayStride 4 + OpDecorate %samplers ArrayStride 4 + OpDecorate %fn_param_images DescriptorSet 0 + OpDecorate %fn_param_images Binding 0 + OpDecorate %fn_param_samplers DescriptorSet 0 + OpDecorate %fn_param_samplers Binding 1 + OpDecorate %fn_param_out Location 0 + + %void = OpTypeVoid + + %float = OpTypeFloat 32 + %v2float = OpTypeVector %float 2 + %v4float = OpTypeVector %float 4 + %v4float_ptr = OpTypePointer Output %v4float + %float_0_5 = OpConstant %float 0.5 + %float_0_5_0_5 = OpConstantComposite %v2float %float_0_5 %float_0_5 + %float_0 = OpConstant %float 0 + + %int = OpTypeInt 32 1 + %int_1 = OpConstant %int 1 + + %image = OpTypeImage %float 2D 2 0 0 1 Unknown + %image_ptr = OpTypePointer UniformConstant %image + %images = OpTypeRuntimeArray %image + %images_ptr = OpTypePointer UniformConstant %images + + %sampler = OpTypeSampler + %sampler_ptr = OpTypePointer UniformConstant %sampler + %samplers = OpTypeRuntimeArray %sampler + %samplers_ptr = OpTypePointer UniformConstant %samplers + + %sampled_image = OpTypeSampledImage %image + + %fn_void = OpTypeFunction %void +%fn_param_images = OpVariable %images_ptr UniformConstant +%fn_param_samplers = OpVariable %samplers_ptr UniformConstant + %fn_param_out = OpVariable %v4float_ptr Output + + %main = OpFunction %void None %fn_void + %main_prelude = OpLabel + %1 = OpAccessChain %image_ptr %fn_param_images %int_1 + %2 = OpInBoundsAccessChain %sampler_ptr %fn_param_samplers %int_1 + %3 = OpLoad %sampler %2 + %4 = OpLoad %image %1 + %5 = OpSampledImage %sampled_image %4 %3 + %6 = OpImageSampleExplicitLod %v4float %5 %float_0_5_0_5 Lod %float_0 + OpStore %fn_param_out %6 + OpReturn + OpFunctionEnd diff --git a/naga/tests/in/spv/binding-arrays.static.spv b/naga/tests/in/spv/binding-arrays.static.spv new file mode 100644 index 0000000000..0d3c304af4 Binary files /dev/null and b/naga/tests/in/spv/binding-arrays.static.spv differ diff --git a/naga/tests/in/spv/binding-arrays.static.spvasm b/naga/tests/in/spv/binding-arrays.static.spvasm new file mode 100644 index 0000000000..d08fe72061 --- /dev/null +++ b/naga/tests/in/spv/binding-arrays.static.spvasm @@ -0,0 +1,78 @@ +;; Make sure that we promote `OpTypeArray` of textures and samplers into +;; `TypeInner::BindingArray` and support indexing it through `OpAccessChain` +;; and `OpInBoundsAccessChain`. +;; +;; Code in here corresponds to, more or less: +;; +;; ```rust +;; #[spirv(fragment)] +;; pub fn main( +;; #[spirv(descriptor_set = 0, binding = 0)] +;; images: &[Image!(2D, type=f32, sampled); 256], +;; #[spirv(descriptor_set = 0, binding = 1)] +;; samplers: &[Sampler; 256], +;; out: &mut Vec4, +;; ) { +;; let image = images[1]; +;; let sampler = samplers[1]; +;; +;; *out = image.sample_by_lod(sampler, vec2(0.5, 0.5), 0.0); +;; } +;; ``` + + OpCapability Shader + OpMemoryModel Logical Simple + OpEntryPoint Fragment %main "main" %fn_param_images %fn_param_samplers %fn_param_out + OpExecutionMode %main OriginUpperLeft + OpDecorate %images ArrayStride 4 + OpDecorate %samplers ArrayStride 4 + OpDecorate %fn_param_images DescriptorSet 0 + OpDecorate %fn_param_images Binding 0 + OpDecorate %fn_param_samplers DescriptorSet 0 + OpDecorate %fn_param_samplers Binding 1 + OpDecorate %fn_param_out Location 0 + + %void = OpTypeVoid + + %float = OpTypeFloat 32 + %v2float = OpTypeVector %float 2 + %v4float = OpTypeVector %float 4 + %v4float_ptr = OpTypePointer Output %v4float + %float_0_5 = OpConstant %float 0.5 + %float_0_5_0_5 = OpConstantComposite %v2float %float_0_5 %float_0_5 + %float_0 = OpConstant %float 0 + + %int = OpTypeInt 32 1 + %int_1 = OpConstant %int 1 + + %uint = OpTypeInt 32 0 + %uint_256 = OpConstant %uint 256 + + %image = OpTypeImage %float 2D 2 0 0 1 Unknown + %image_ptr = OpTypePointer UniformConstant %image + %images = OpTypeArray %image %uint_256 + %images_ptr = OpTypePointer UniformConstant %images + + %sampler = OpTypeSampler + %sampler_ptr = OpTypePointer UniformConstant %sampler + %samplers = OpTypeArray %sampler %uint_256 + %samplers_ptr = OpTypePointer UniformConstant %samplers + + %sampled_image = OpTypeSampledImage %image + + %fn_void = OpTypeFunction %void +%fn_param_images = OpVariable %images_ptr UniformConstant +%fn_param_samplers = OpVariable %samplers_ptr UniformConstant + %fn_param_out = OpVariable %v4float_ptr Output + + %main = OpFunction %void None %fn_void + %main_prelude = OpLabel + %1 = OpAccessChain %image_ptr %fn_param_images %int_1 + %2 = OpInBoundsAccessChain %sampler_ptr %fn_param_samplers %int_1 + %3 = OpLoad %sampler %2 + %4 = OpLoad %image %1 + %5 = OpSampledImage %sampled_image %4 %3 + %6 = OpImageSampleExplicitLod %v4float %5 %float_0_5_0_5 Lod %float_0 + OpStore %fn_param_out %6 + OpReturn + OpFunctionEnd diff --git a/naga/tests/in/spv/degrees.spv b/naga/tests/in/spv/degrees.spv new file mode 100644 index 0000000000..b7aa393c07 Binary files /dev/null and b/naga/tests/in/spv/degrees.spv differ diff --git a/naga/tests/in/spv/degrees.spvasm b/naga/tests/in/spv/degrees.spvasm new file mode 100644 index 0000000000..de2605a517 --- /dev/null +++ b/naga/tests/in/spv/degrees.spvasm @@ -0,0 +1,47 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 10 +; Bound: 27 +; Schema: 0 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %colour + OpSource GLSL 450 + OpName %main "main" + OpName %deg "deg" + OpName %rad "rad" + OpName %deg_again "deg_again" + OpName %colour "colour" + OpDecorate %colour Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float + %float_15 = OpConstant %float 15 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %colour = OpVariable %_ptr_Output_v4float Output + %v3float = OpTypeVector %float 3 + %float_1 = OpConstant %float 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %deg = OpVariable %_ptr_Function_float Function + %rad = OpVariable %_ptr_Function_float Function + %deg_again = OpVariable %_ptr_Function_float Function + OpStore %deg %float_15 + %11 = OpLoad %float %deg + %12 = OpExtInst %float %1 Radians %11 + OpStore %rad %12 + %14 = OpLoad %float %rad + %15 = OpExtInst %float %1 Degrees %14 + OpStore %deg_again %15 + %19 = OpLoad %float %deg_again + %21 = OpCompositeConstruct %v3float %19 %19 %19 + %23 = OpCompositeExtract %float %21 0 + %24 = OpCompositeExtract %float %21 1 + %25 = OpCompositeExtract %float %21 2 + %26 = OpCompositeConstruct %v4float %23 %24 %25 %float_1 + OpStore %colour %26 + OpReturn + OpFunctionEnd diff --git a/naga/tests/in/spv/do-while.spv b/naga/tests/in/spv/do-while.spv new file mode 100644 index 0000000000..23d45958b9 Binary files /dev/null and b/naga/tests/in/spv/do-while.spv differ diff --git a/naga/tests/in/spv/do-while.spvasm b/naga/tests/in/spv/do-while.spvasm new file mode 100644 index 0000000000..fa27c3544f --- /dev/null +++ b/naga/tests/in/spv/do-while.spvasm @@ -0,0 +1,64 @@ +;; Ensure that `do`-`while`-style loops, with conditional backedges, are properly +;; supported, via `break if` (as `continuing { ... if c { break; } }` is illegal). +;; +;; The SPIR-V below was compiled from this GLSL fragment shader: +;; ```glsl +;; #version 450 +;; +;; void f(bool cond) { +;; do {} while(cond); +;; } +;; +;; void main() { +;; f(false); +;; } +;; ``` + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %f_b1_ "f(b1;" + OpName %cond "cond" + OpName %param "param" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %bool = OpTypeBool +%_ptr_Function_bool = OpTypePointer Function %bool + %8 = OpTypeFunction %void %_ptr_Function_bool + %false = OpConstantFalse %bool + + %main = OpFunction %void None %3 + %5 = OpLabel + %param = OpVariable %_ptr_Function_bool Function + OpStore %param %false + %19 = OpFunctionCall %void %f_b1_ %param + OpReturn + OpFunctionEnd + + %f_b1_ = OpFunction %void None %8 + %cond = OpFunctionParameter %_ptr_Function_bool + + %11 = OpLabel + OpBranch %12 + + %12 = OpLabel + OpLoopMerge %14 %15 None + OpBranch %13 + + %13 = OpLabel + OpBranch %15 + +;; This is the "continuing" block, and it contains a conditional branch between +;; the backedge (back to the loop header) and the loop merge ("break") target. + %15 = OpLabel + %16 = OpLoad %bool %cond + OpBranchConditional %16 %12 %14 + + %14 = OpLabel + OpReturn + + OpFunctionEnd diff --git a/naga/tests/in/spv/empty-global-name.spv b/naga/tests/in/spv/empty-global-name.spv new file mode 100644 index 0000000000..ebbd7a1aab Binary files /dev/null and b/naga/tests/in/spv/empty-global-name.spv differ diff --git a/naga/tests/in/spv/empty-global-name.spvasm b/naga/tests/in/spv/empty-global-name.spvasm new file mode 100644 index 0000000000..1aa1073918 --- /dev/null +++ b/naga/tests/in/spv/empty-global-name.spvasm @@ -0,0 +1,44 @@ +;; Make sure we handle globals whose assigned name is "". +;; +;; In MSL, the anonymous global sometimes ends up looking like +;; +;; struct Blah { int member; } ; +;; +;; where the null name just becomes an empty string before that last semicolon. +;; This is, unfortunately, valid MSL, simply declaring the type Blah, so it will +;; pass validation. However, an attempt to *use* the global will generate a +;; garbage expression like ".member", so we include a function that returns the +;; member's value. + + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %global + OpExecutionMode %main LocalSize 1 1 1 + + OpName %global "" + OpDecorate %block Block + OpMemberDecorate %block 0 Offset 0 + OpDecorate %global DescriptorSet 0 + OpDecorate %global Binding 0 + + %void = OpTypeVoid + %int = OpTypeInt 32 1 + %block = OpTypeStruct %int + %ptr_int = OpTypePointer StorageBuffer %int + %ptr_block = OpTypePointer StorageBuffer %block + %fn_void = OpTypeFunction %void + %fn_int = OpTypeFunction %int + %zero = OpConstant %int 0 + %one = OpConstant %int 1 + +;; This global is said to have a name of "". + %global = OpVariable %ptr_block StorageBuffer + + %main = OpFunction %void None %fn_void + %main_prelude = OpLabel + %member_ptr = OpAccessChain %ptr_int %global %zero + %member_val = OpLoad %int %member_ptr + %plus_one = OpIAdd %int %member_val %one + OpStore %member_ptr %plus_one + OpReturn + OpFunctionEnd diff --git a/naga/tests/in/spv/inv-hyperbolic-trig-functions.spv b/naga/tests/in/spv/inv-hyperbolic-trig-functions.spv new file mode 100644 index 0000000000..da365355e8 Binary files /dev/null and b/naga/tests/in/spv/inv-hyperbolic-trig-functions.spv differ diff --git a/naga/tests/in/spv/inv-hyperbolic-trig-functions.spvasm b/naga/tests/in/spv/inv-hyperbolic-trig-functions.spvasm new file mode 100644 index 0000000000..efa9620893 --- /dev/null +++ b/naga/tests/in/spv/inv-hyperbolic-trig-functions.spvasm @@ -0,0 +1,37 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 10 +; Bound: 19 +; Schema: 0 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpSource GLSL 450 + OpName %main "main" + OpName %b "b" + OpName %a "a" + OpName %c "c" + OpName %d "d" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Private_float = OpTypePointer Private %float + %a = OpVariable %_ptr_Private_float Private + %main = OpFunction %void None %3 + %5 = OpLabel + %b = OpVariable %_ptr_Function_float Function + %c = OpVariable %_ptr_Function_float Function + %d = OpVariable %_ptr_Function_float Function + %11 = OpLoad %float %a + %12 = OpExtInst %float %1 Asinh %11 + OpStore %b %12 + %14 = OpLoad %float %a + %15 = OpExtInst %float %1 Acosh %14 + OpStore %c %15 + %17 = OpLoad %float %a + %18 = OpExtInst %float %1 Atanh %17 + OpStore %d %18 + OpReturn + OpFunctionEnd diff --git a/naga/tests/in/spv/quad-vert.spv b/naga/tests/in/spv/quad-vert.spv new file mode 100644 index 0000000000..eaf03d2fbe Binary files /dev/null and b/naga/tests/in/spv/quad-vert.spv differ diff --git a/naga/tests/in/spv/quad-vert.spvasm b/naga/tests/in/spv/quad-vert.spvasm new file mode 100644 index 0000000000..7633c94c59 --- /dev/null +++ b/naga/tests/in/spv/quad-vert.spvasm @@ -0,0 +1,61 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 10 +; Bound: 31 +; Schema: 0 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %v_uv %a_uv %_ %a_pos + OpSource GLSL 460 + OpName %main "main" + OpName %v_uv "v_uv" + OpName %a_uv "a_uv" + OpName %gl_PerVertex "gl_PerVertex" + OpMemberName %gl_PerVertex 0 "gl_Position" + OpMemberName %gl_PerVertex 1 "gl_PointSize" + OpMemberName %gl_PerVertex 2 "gl_ClipDistance" + OpMemberName %gl_PerVertex 3 "gl_CullDistance" + OpName %_ "" + OpName %a_pos "a_pos" + OpDecorate %v_uv Location 0 + OpDecorate %a_uv Location 1 + OpMemberDecorate %gl_PerVertex 0 BuiltIn Position + OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize + OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance + OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance + OpDecorate %gl_PerVertex Block + OpDecorate %a_pos Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v2float = OpTypeVector %float 2 +%_ptr_Output_v2float = OpTypePointer Output %v2float + %v_uv = OpVariable %_ptr_Output_v2float Output +%_ptr_Input_v2float = OpTypePointer Input %v2float + %a_uv = OpVariable %_ptr_Input_v2float Input + %v4float = OpTypeVector %float 4 + %uint = OpTypeInt 32 0 + %uint_1 = OpConstant %uint 1 +%_arr_float_uint_1 = OpTypeArray %float %uint_1 +%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1 +%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex + %_ = OpVariable %_ptr_Output_gl_PerVertex Output + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %a_pos = OpVariable %_ptr_Input_v2float Input + %float_0 = OpConstant %float 0 + %float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %main = OpFunction %void None %3 + %5 = OpLabel + %12 = OpLoad %v2float %a_uv + OpStore %v_uv %12 + %23 = OpLoad %v2float %a_pos + %26 = OpCompositeExtract %float %23 0 + %27 = OpCompositeExtract %float %23 1 + %28 = OpCompositeConstruct %v4float %26 %27 %float_0 %float_1 + %30 = OpAccessChain %_ptr_Output_v4float %_ %int_0 + OpStore %30 %28 + OpReturn + OpFunctionEnd diff --git a/naga/tests/in/spv/shadow.spv b/naga/tests/in/spv/shadow.spv new file mode 100644 index 0000000000..b4dff9df6d Binary files /dev/null and b/naga/tests/in/spv/shadow.spv differ diff --git a/naga/tests/in/spv/shadow.spvasm b/naga/tests/in/spv/shadow.spvasm new file mode 100644 index 0000000000..e928b71c40 --- /dev/null +++ b/naga/tests/in/spv/shadow.spvasm @@ -0,0 +1,291 @@ +; SPIR-V +; Version: 1.0 +; Generator: Khronos SPIR-V Tools Assembler; 0 +; Bound: 221 +; Schema: 0 + OpCapability Shader + OpExtension "SPV_KHR_storage_buffer_storage_class" + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %fs_main "fs_main" %in_normal_fs %in_position_fs %out_color_fs + OpExecutionMode %fs_main OriginUpperLeft + OpSource GLSL 450 + OpName %t_shadow "t_shadow" + OpName %sampler_shadow "sampler_shadow" + OpName %color "color" + OpName %i "i" + OpName %Globals "Globals" + OpMemberName %Globals 0 "num_lights" + OpName %u_globals "u_globals" + OpName %Light "Light" + OpMemberName %Light 0 "proj" + OpMemberName %Light 1 "pos" + OpMemberName %Light 2 "color" + OpName %Lights "Lights" + OpMemberName %Lights 0 "data" + OpName %s_lights "s_lights" + OpName %in_position_fs "in_position_fs" + OpName %in_normal_fs "in_normal_fs" + OpName %out_color_fs "out_color_fs" + OpName %fs_main "fs_main" + OpDecorate %t_shadow DescriptorSet 0 + OpDecorate %t_shadow Binding 2 + OpDecorate %sampler_shadow DescriptorSet 0 + OpDecorate %sampler_shadow Binding 3 + OpDecorate %Globals Block + OpMemberDecorate %Globals 0 Offset 0 + OpDecorate %u_globals DescriptorSet 0 + OpDecorate %u_globals Binding 0 + OpMemberDecorate %Light 0 Offset 0 + OpMemberDecorate %Light 0 ColMajor + OpMemberDecorate %Light 0 MatrixStride 16 + OpMemberDecorate %Light 1 Offset 64 + OpMemberDecorate %Light 2 Offset 80 + OpDecorate %_runtimearr_Light ArrayStride 96 + OpDecorate %Lights BufferBlock + OpMemberDecorate %Lights 0 Offset 0 + OpMemberDecorate %Lights 0 NonWritable + OpDecorate %s_lights DescriptorSet 0 + OpDecorate %s_lights Binding 1 + OpDecorate %in_position_fs Location 1 + OpDecorate %in_normal_fs Location 0 + OpDecorate %out_color_fs Location 0 + %void = OpTypeVoid + %float = OpTypeFloat 32 + %float_0 = OpConstant %float 0 + %float_1 = OpConstant %float 1 + %float_0_5 = OpConstant %float 0.5 + %float_n0_5 = OpConstant %float -0.5 +%float_0_0500000007 = OpConstant %float 0.0500000007 + %v3float = OpTypeVector %float 3 + %9 = OpConstantComposite %v3float %float_0_0500000007 %float_0_0500000007 %float_0_0500000007 + %uint = OpTypeInt 32 0 + %uint_10 = OpConstant %uint 10 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %v4float = OpTypeVector %float 4 + %19 = OpTypeFunction %float %uint %v4float + %bool = OpTypeBool + %27 = OpTypeImage %float 2D 1 1 0 1 Unknown +%_ptr_UniformConstant_27 = OpTypePointer UniformConstant %27 + %t_shadow = OpVariable %_ptr_UniformConstant_27 UniformConstant + %31 = OpTypeSampledImage %27 + %32 = OpTypeSampler +%_ptr_UniformConstant_32 = OpTypePointer UniformConstant %32 +%sampler_shadow = OpVariable %_ptr_UniformConstant_32 UniformConstant + %v2float = OpTypeVector %float 2 + %int = OpTypeInt 32 1 + %float_0_0 = OpConstant %float 0 +%_ptr_Function_v3float = OpTypePointer Function %v3float +%_ptr_Function_uint = OpTypePointer Function %uint + %65 = OpTypeFunction %void + %v4uint = OpTypeVector %uint 4 + %Globals = OpTypeStruct %v4uint +%_ptr_Uniform_Globals = OpTypePointer Uniform %Globals + %u_globals = OpVariable %_ptr_Uniform_Globals Uniform +%_ptr_Uniform_v4uint = OpTypePointer Uniform %v4uint + %int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint + %int_0_0 = OpConstant %int 0 +%mat4v4float = OpTypeMatrix %v4float 4 + %Light = OpTypeStruct %mat4v4float %v4float %v4float +%_runtimearr_Light = OpTypeRuntimeArray %Light + %Lights = OpTypeStruct %_runtimearr_Light +%_ptr_StorageBuffer_Lights = OpTypePointer StorageBuffer %Lights + %s_lights = OpVariable %_ptr_StorageBuffer_Lights StorageBuffer +%_ptr_StorageBuffer__runtimearr_Light = OpTypePointer StorageBuffer %_runtimearr_Light + %int_0_1 = OpConstant %int 0 +%_ptr_StorageBuffer_Light = OpTypePointer StorageBuffer %Light +%_ptr_StorageBuffer_mat4v4float = OpTypePointer StorageBuffer %mat4v4float + %int_0_2 = OpConstant %int 0 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%in_position_fs = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_v3float = OpTypePointer Input %v3float +%in_normal_fs = OpVariable %_ptr_Input_v3float Input +%_ptr_StorageBuffer__runtimearr_Light_0 = OpTypePointer StorageBuffer %_runtimearr_Light + %int_0_3 = OpConstant %int 0 +%_ptr_StorageBuffer_Light_0 = OpTypePointer StorageBuffer %Light +%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float + %int_1 = OpConstant %int 1 +%_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float + %int_0_4 = OpConstant %int 0 +%_ptr_StorageBuffer__runtimearr_Light_1 = OpTypePointer StorageBuffer %_runtimearr_Light + %int_0_5 = OpConstant %int 0 +%_ptr_StorageBuffer_Light_1 = OpTypePointer StorageBuffer %Light +%_ptr_StorageBuffer_v4float_0 = OpTypePointer StorageBuffer %v4float + %int_1_0 = OpConstant %int 1 +%_ptr_StorageBuffer_float_0 = OpTypePointer StorageBuffer %float + %int_1_1 = OpConstant %int 1 +%_ptr_StorageBuffer__runtimearr_Light_2 = OpTypePointer StorageBuffer %_runtimearr_Light + %int_0_6 = OpConstant %int 0 +%_ptr_StorageBuffer_Light_2 = OpTypePointer StorageBuffer %Light +%_ptr_StorageBuffer_v4float_1 = OpTypePointer StorageBuffer %v4float + %int_1_2 = OpConstant %int 1 +%_ptr_StorageBuffer_float_1 = OpTypePointer StorageBuffer %float + %int_2 = OpConstant %int 2 +%_ptr_Input_float = OpTypePointer Input %float + %int_0_7 = OpConstant %int 0 +%_ptr_Input_float_0 = OpTypePointer Input %float + %int_1_3 = OpConstant %int 1 +%_ptr_Input_float_1 = OpTypePointer Input %float + %int_2_0 = OpConstant %int 2 +%_ptr_StorageBuffer__runtimearr_Light_3 = OpTypePointer StorageBuffer %_runtimearr_Light + %int_0_8 = OpConstant %int 0 +%_ptr_StorageBuffer_Light_3 = OpTypePointer StorageBuffer %Light +%_ptr_StorageBuffer_v4float_2 = OpTypePointer StorageBuffer %v4float + %int_2_1 = OpConstant %int 2 +%_ptr_StorageBuffer_float_2 = OpTypePointer StorageBuffer %float + %int_0_9 = OpConstant %int 0 +%_ptr_StorageBuffer__runtimearr_Light_4 = OpTypePointer StorageBuffer %_runtimearr_Light + %int_0_10 = OpConstant %int 0 +%_ptr_StorageBuffer_Light_4 = OpTypePointer StorageBuffer %Light +%_ptr_StorageBuffer_v4float_3 = OpTypePointer StorageBuffer %v4float + %int_2_2 = OpConstant %int 2 +%_ptr_StorageBuffer_float_3 = OpTypePointer StorageBuffer %float + %int_1_4 = OpConstant %int 1 +%_ptr_StorageBuffer__runtimearr_Light_5 = OpTypePointer StorageBuffer %_runtimearr_Light + %int_0_11 = OpConstant %int 0 +%_ptr_StorageBuffer_Light_5 = OpTypePointer StorageBuffer %Light +%_ptr_StorageBuffer_v4float_4 = OpTypePointer StorageBuffer %v4float + %int_2_3 = OpConstant %int 2 +%_ptr_StorageBuffer_float_4 = OpTypePointer StorageBuffer %float + %int_2_4 = OpConstant %int 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%out_color_fs = OpVariable %_ptr_Output_v4float Output + %18 = OpFunction %float None %19 + %15 = OpFunctionParameter %uint + %16 = OpFunctionParameter %v4float + %20 = OpLabel + %23 = OpCompositeExtract %float %16 3 + %22 = OpFOrdLessThanEqual %bool %23 %float_0 + OpSelectionMerge %24 None + OpBranchConditional %22 %25 %26 + %25 = OpLabel + OpReturnValue %float_1 + %26 = OpLabel + OpBranch %24 + %24 = OpLabel + %30 = OpLoad %27 %t_shadow + %35 = OpLoad %32 %sampler_shadow + %40 = OpCompositeExtract %float %16 0 + %41 = OpCompositeExtract %float %16 1 + %42 = OpCompositeConstruct %v2float %40 %41 + %43 = OpCompositeConstruct %v2float %float_0_5 %float_n0_5 + %39 = OpFMul %v2float %42 %43 + %45 = OpCompositeExtract %float %16 3 + %44 = OpFDiv %float %float_1 %45 + %38 = OpVectorTimesScalar %v2float %39 %44 + %46 = OpCompositeConstruct %v2float %float_0_5 %float_0_5 + %37 = OpFAdd %v2float %38 %46 + %47 = OpCompositeExtract %float %37 0 + %48 = OpCompositeExtract %float %37 1 + %51 = OpBitcast %int %15 + %49 = OpConvertUToF %float %51 + %52 = OpCompositeConstruct %v3float %47 %48 %49 + %53 = OpSampledImage %31 %30 %35 + %56 = OpCompositeExtract %float %16 2 + %58 = OpCompositeExtract %float %16 3 + %57 = OpFDiv %float %float_1 %58 + %55 = OpFMul %float %56 %57 + %54 = OpImageSampleDrefExplicitLod %float %53 %52 %55 Lod %float_0_0 + OpReturnValue %54 + OpFunctionEnd + %fs_main = OpFunction %void None %65 + %66 = OpLabel + %color = OpVariable %_ptr_Function_v3float Function %9 + %i = OpVariable %_ptr_Function_uint Function %uint_0 + OpBranch %67 + %67 = OpLabel + OpLoopMerge %68 %70 None + OpBranch %69 + %69 = OpLabel + %72 = OpLoad %uint %i + %75 = OpAccessChain %_ptr_Uniform_v4uint %u_globals %int_0 + %73 = OpAccessChain %_ptr_Uniform_uint %75 %int_0_0 + %83 = OpLoad %uint %73 + %84 = OpExtInst %uint %1 UMin %83 %uint_10 + %71 = OpUGreaterThanEqual %bool %72 %84 + OpSelectionMerge %85 None + OpBranchConditional %71 %86 %87 + %86 = OpLabel + OpBranch %68 + %87 = OpLabel + OpBranch %85 + %85 = OpLabel + %89 = OpLoad %v3float %color + %93 = OpLoad %uint %i + %100 = OpAccessChain %_ptr_StorageBuffer__runtimearr_Light %s_lights %int_0_1 + %106 = OpLoad %uint %i + %98 = OpAccessChain %_ptr_StorageBuffer_Light %100 %106 + %96 = OpAccessChain %_ptr_StorageBuffer_mat4v4float %98 %int_0_2 + %110 = OpLoad %mat4v4float %96 + %113 = OpLoad %v4float %in_position_fs + %94 = OpMatrixTimesVector %v4float %110 %113 + %92 = OpFunctionCall %float %18 %93 %94 + %116 = OpLoad %v3float %in_normal_fs + %117 = OpExtInst %v3float %1 Normalize %116 + %122 = OpAccessChain %_ptr_StorageBuffer__runtimearr_Light_0 %s_lights %int_0_3 + %125 = OpLoad %uint %i + %121 = OpAccessChain %_ptr_StorageBuffer_Light_0 %122 %125 + %120 = OpAccessChain %_ptr_StorageBuffer_v4float %121 %int_1 + %119 = OpAccessChain %_ptr_StorageBuffer_float %120 %int_0_4 + %131 = OpLoad %float %119 + %135 = OpAccessChain %_ptr_StorageBuffer__runtimearr_Light_1 %s_lights %int_0_5 + %138 = OpLoad %uint %i + %134 = OpAccessChain %_ptr_StorageBuffer_Light_1 %135 %138 + %133 = OpAccessChain %_ptr_StorageBuffer_v4float_0 %134 %int_1_0 + %132 = OpAccessChain %_ptr_StorageBuffer_float_0 %133 %int_1_1 + %144 = OpLoad %float %132 + %148 = OpAccessChain %_ptr_StorageBuffer__runtimearr_Light_2 %s_lights %int_0_6 + %151 = OpLoad %uint %i + %147 = OpAccessChain %_ptr_StorageBuffer_Light_2 %148 %151 + %146 = OpAccessChain %_ptr_StorageBuffer_v4float_1 %147 %int_1_2 + %145 = OpAccessChain %_ptr_StorageBuffer_float_1 %146 %int_2 + %157 = OpLoad %float %145 + %158 = OpCompositeConstruct %v3float %131 %144 %157 + %159 = OpAccessChain %_ptr_Input_float %in_position_fs %int_0_7 + %162 = OpLoad %float %159 + %163 = OpAccessChain %_ptr_Input_float_0 %in_position_fs %int_1_3 + %166 = OpLoad %float %163 + %167 = OpAccessChain %_ptr_Input_float_1 %in_position_fs %int_2_0 + %170 = OpLoad %float %167 + %171 = OpCompositeConstruct %v3float %162 %166 %170 + %118 = OpFSub %v3float %158 %171 + %172 = OpExtInst %v3float %1 Normalize %118 + %173 = OpDot %float %117 %172 + %174 = OpExtInst %float %1 FMax %float_0 %173 + %91 = OpFMul %float %92 %174 + %178 = OpAccessChain %_ptr_StorageBuffer__runtimearr_Light_3 %s_lights %int_0_8 + %181 = OpLoad %uint %i + %177 = OpAccessChain %_ptr_StorageBuffer_Light_3 %178 %181 + %176 = OpAccessChain %_ptr_StorageBuffer_v4float_2 %177 %int_2_1 + %175 = OpAccessChain %_ptr_StorageBuffer_float_2 %176 %int_0_9 + %187 = OpLoad %float %175 + %191 = OpAccessChain %_ptr_StorageBuffer__runtimearr_Light_4 %s_lights %int_0_10 + %194 = OpLoad %uint %i + %190 = OpAccessChain %_ptr_StorageBuffer_Light_4 %191 %194 + %189 = OpAccessChain %_ptr_StorageBuffer_v4float_3 %190 %int_2_2 + %188 = OpAccessChain %_ptr_StorageBuffer_float_3 %189 %int_1_4 + %200 = OpLoad %float %188 + %204 = OpAccessChain %_ptr_StorageBuffer__runtimearr_Light_5 %s_lights %int_0_11 + %207 = OpLoad %uint %i + %203 = OpAccessChain %_ptr_StorageBuffer_Light_5 %204 %207 + %202 = OpAccessChain %_ptr_StorageBuffer_v4float_4 %203 %int_2_3 + %201 = OpAccessChain %_ptr_StorageBuffer_float_4 %202 %int_2_4 + %213 = OpLoad %float %201 + %214 = OpCompositeConstruct %v3float %187 %200 %213 + %90 = OpVectorTimesScalar %v3float %214 %91 + %88 = OpFAdd %v3float %89 %90 + OpStore %color %88 + OpBranch %70 + %70 = OpLabel + %216 = OpLoad %uint %i + %215 = OpIAdd %uint %216 %uint_1 + OpStore %i %215 + OpBranch %67 + %68 = OpLabel + %219 = OpLoad %v3float %color + %220 = OpCompositeConstruct %v4float %219 %float_1 + OpStore %out_color_fs %220 + OpReturn + OpFunctionEnd diff --git a/naga/tests/in/standard.param.ron b/naga/tests/in/standard.param.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/standard.param.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/standard.wgsl b/naga/tests/in/standard.wgsl new file mode 100644 index 0000000000..9fdc344bc9 --- /dev/null +++ b/naga/tests/in/standard.wgsl @@ -0,0 +1,26 @@ +// Standard functions. + +fn test_any_and_all_for_bool() -> bool { + let a = any(true); + return all(a); +} + + +@fragment +fn derivatives(@builtin(position) foo: vec4) -> @location(0) vec4 { + var x = dpdxCoarse(foo); + var y = dpdyCoarse(foo); + var z = fwidthCoarse(foo); + + x = dpdxFine(foo); + y = dpdyFine(foo); + z = fwidthFine(foo); + + x = dpdx(foo); + y = dpdy(foo); + z = fwidth(foo); + + let a = test_any_and_all_for_bool(); + + return (x + y) * z; +} diff --git a/naga/tests/in/struct-layout.wgsl b/naga/tests/in/struct-layout.wgsl new file mode 100644 index 0000000000..fcf639a3b4 --- /dev/null +++ b/naga/tests/in/struct-layout.wgsl @@ -0,0 +1,50 @@ +// Create several type definitions to test `align` and `size` layout. + +struct NoPadding { + @location(0) + v3: vec3f, // align 16, size 12; no start padding needed + @location(1) + f3: f32, // align 4, size 4; no start padding needed +} +@fragment +fn no_padding_frag(input: NoPadding) -> @location(0) vec4f { + _ = input; + return vec4f(0.0); +} +@vertex +fn no_padding_vert(input: NoPadding) -> @builtin(position) vec4f { + _ = input; + return vec4f(0.0); +} +@group(0) @binding(0) var no_padding_uniform: NoPadding; +@group(0) @binding(1) var no_padding_storage: NoPadding; +@compute @workgroup_size(16,1,1) +fn no_padding_comp() { + var x: NoPadding; + x = no_padding_uniform; + x = no_padding_storage; +} + +struct NeedsPadding { + @location(0) f3_forces_padding: f32, // align 4, size 4; no start padding needed + @location(1) v3_needs_padding: vec3f, // align 16, size 12; needs 12 bytes of padding + @location(2) f3: f32, // align 4, size 4; no start padding needed +} +@fragment +fn needs_padding_frag(input: NeedsPadding) -> @location(0) vec4f { + _ = input; + return vec4f(0.0); +} +@vertex +fn needs_padding_vert(input: NeedsPadding) -> @builtin(position) vec4f { + _ = input; + return vec4f(0.0); +} +@group(0) @binding(2) var needs_padding_uniform: NeedsPadding; +@group(0) @binding(3) var needs_padding_storage: NeedsPadding; +@compute @workgroup_size(16,1,1) +fn needs_padding_comp() { + var x: NeedsPadding; + x = needs_padding_uniform; + x = needs_padding_storage; +} diff --git a/naga/tests/in/texture-arg.param.ron b/naga/tests/in/texture-arg.param.ron new file mode 100644 index 0000000000..4fc2cfe566 --- /dev/null +++ b/naga/tests/in/texture-arg.param.ron @@ -0,0 +1,7 @@ +( + spv: ( + version: (1, 0), + debug: true, + adjust_coordinate_space: true, + ), +) diff --git a/naga/tests/in/texture-arg.wgsl b/naga/tests/in/texture-arg.wgsl new file mode 100644 index 0000000000..3b1bbe6809 --- /dev/null +++ b/naga/tests/in/texture-arg.wgsl @@ -0,0 +1,13 @@ +@group(0) @binding(0) +var Texture: texture_2d; +@group(0) @binding(1) +var Sampler: sampler; + +fn test(Passed_Texture: texture_2d, Passed_Sampler: sampler) -> vec4 { + return textureSample(Passed_Texture, Passed_Sampler, vec2(0.0, 0.0)); +} + +@fragment +fn main() -> @location(0) vec4 { + return test(Texture, Sampler); +} diff --git a/naga/tests/in/type-alias.wgsl b/naga/tests/in/type-alias.wgsl new file mode 100644 index 0000000000..69c1eae4ef --- /dev/null +++ b/naga/tests/in/type-alias.wgsl @@ -0,0 +1,15 @@ +alias FVec3 = vec3; +alias IVec3 = vec3i; +alias Mat2 = mat2x2; +alias Mat3 = mat3x3f; + +fn main() { + let a = FVec3(0.0, 0.0, 0.0); + let c = FVec3(0.0); + let b = FVec3(vec2(0.0), 0.0); + let d = FVec3(vec2(0.0), 0.0); + let e = IVec3(d); + + let f = Mat2(1.0, 2.0, 3.0, 4.0); + let g = Mat3(a, a, a); +} diff --git a/naga/tests/in/variations.glsl b/naga/tests/in/variations.glsl new file mode 100644 index 0000000000..11904137f3 --- /dev/null +++ b/naga/tests/in/variations.glsl @@ -0,0 +1,9 @@ +#version 460 core + +layout(set = 0, binding = 0) uniform textureCube texCube; +layout(set = 0, binding = 1) uniform sampler samp; + +void main() { + ivec2 sizeCube = textureSize(samplerCube(texCube, samp), 0); + float a = ceil(1.0); +} diff --git a/naga/tests/in/workgroup-uniform-load.wgsl b/naga/tests/in/workgroup-uniform-load.wgsl new file mode 100644 index 0000000000..4b7f22a09a --- /dev/null +++ b/naga/tests/in/workgroup-uniform-load.wgsl @@ -0,0 +1,12 @@ +const SIZE: u32 = 128u; + +var arr_i32: array; + +@compute @workgroup_size(4) +fn test_workgroupUniformLoad(@builtin(workgroup_id) workgroup_id: vec3) { + let x = &arr_i32[workgroup_id.x]; + let val = workgroupUniformLoad(x); + if val > 10 { + workgroupBarrier(); + } +} diff --git a/naga/tests/in/workgroup-var-init.param.ron b/naga/tests/in/workgroup-var-init.param.ron new file mode 100644 index 0000000000..a00ecf6bfd --- /dev/null +++ b/naga/tests/in/workgroup-var-init.param.ron @@ -0,0 +1,22 @@ +( + spv: ( + version: (1, 1), + debug: true, + adjust_coordinate_space: false, + ), + msl: ( + lang_version: (1, 0), + per_entry_point_map: { + "main": ( + resources: { + (group: 0, binding: 0): (buffer: Some(0), mutable: true), + }, + sizes_buffer: None, + ), + }, + inline_samplers: [], + spirv_cross_compatibility: false, + fake_missing_bindings: false, + zero_initialize_workgroup_memory: true, + ), +) diff --git a/naga/tests/in/workgroup-var-init.wgsl b/naga/tests/in/workgroup-var-init.wgsl new file mode 100644 index 0000000000..8df2bcf4a8 --- /dev/null +++ b/naga/tests/in/workgroup-var-init.wgsl @@ -0,0 +1,15 @@ +struct WStruct { + arr: array, + atom: atomic, + atom_arr: array, 8>, 8>, +} + +var w_mem: WStruct; + +@group(0) @binding(0) +var output: array; + +@compute @workgroup_size(1) +fn main() { + output = w_mem.arr; +} \ No newline at end of file diff --git a/naga/tests/out/analysis/access.info.ron b/naga/tests/out/analysis/access.info.ron new file mode 100644 index 0000000000..d59fb2a509 --- /dev/null +++ b/naga/tests/out/analysis/access.info.ron @@ -0,0 +1,4009 @@ +( + type_flags: [ + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | HOST_SHAREABLE"), + ("DATA | SIZED | HOST_SHAREABLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | COPY | HOST_SHAREABLE"), + ("DATA | HOST_SHAREABLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("SIZED | COPY | ARGUMENT"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("SIZED | COPY | ARGUMENT"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("SIZED | COPY | ARGUMENT"), + ], + functions: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + (""), + ("READ"), + (""), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 14, + assignable_global: None, + ty: Value(Pointer( + base: 3, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 16, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 15, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(15), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 16, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 15, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 16, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 15, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 16, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 15, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 16, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 15, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 16, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 15, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 16, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 15, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(15), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(16), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 7, + assignable_global: None, + ty: Value(Pointer( + base: 16, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 15, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(15), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 15, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 15, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 15, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 15, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 15, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 15, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + (""), + (""), + (""), + ("READ"), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 14, + assignable_global: None, + ty: Value(Pointer( + base: 3, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 20, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 19, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(19), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 20, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 19, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 18, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(18), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 20, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 19, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 18, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 20, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 19, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 18, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 20, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 19, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 18, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 20, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 19, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 18, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 20, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 19, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 18, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 20, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 19, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 18, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(19), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(20), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 8, + assignable_global: None, + ty: Value(Pointer( + base: 20, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 19, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(19), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 19, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 18, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(18), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 19, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 18, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 19, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 18, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 19, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 18, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 19, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 18, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 19, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 18, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 19, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 18, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: Some(Bi), + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + (""), + (""), + (""), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(22), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(21), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + (""), + (""), + (""), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(24), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(23), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(21), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + (""), + (""), + (""), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(27), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + (""), + (""), + (""), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(29), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(28), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ], + entry_points: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + ("READ | QUERY"), + ("READ"), + ("READ"), + ("READ"), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 2, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 3, + assignable_global: None, + ty: Value(Pointer( + base: 21, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(21), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 6, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(6), + ), + ( + uniformity: ( + non_uniform_result: Some(9), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(9), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 12, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(9), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(12), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(13), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(13), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 6, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(13), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(ValuePointer( + size: Some(Tri), + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(13), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(13), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(18), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(18), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 13, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(20), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(20), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 13, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(20), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(20), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(18), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 5, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(18), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 3, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(18), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(28), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 17, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(28), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(17), + ), + ( + uniformity: ( + non_uniform_result: Some(30), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(30), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 13, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(30), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 5, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(30), + requirements: (""), + ), + ref_count: 0, + assignable_global: Some(2), + ty: Value(Pointer( + base: 3, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(21), + ), + ( + uniformity: ( + non_uniform_result: Some(13), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(18), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(26), + ), + ( + uniformity: ( + non_uniform_result: Some(40), + requirements: (""), + ), + ref_count: 3, + assignable_global: None, + ty: Value(Pointer( + base: 26, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(40), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 3, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(40), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 3, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(40), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(24), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(21), + ), + ( + uniformity: ( + non_uniform_result: Some(40), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Quad, + scalar: ( + kind: Sint, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(40), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Tri, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(25), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + ("WRITE"), + (""), + ("WRITE"), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 6, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(ValuePointer( + size: Some(Tri), + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 6, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Tri, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Tri, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Tri, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Tri, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(6), + ), + ( + uniformity: ( + non_uniform_result: Some(17), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(17), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 12, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(12), + ), + ( + uniformity: ( + non_uniform_result: Some(24), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 14, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(24), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 13, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(24), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 5, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(24), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 3, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(29), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 17, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(17), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + (""), + (""), + (""), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(28), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 28, + space: Function, + )), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ], + const_expression_types: [ + Value(Scalar(( + kind: Uint, + width: 4, + ))), + Value(Scalar(( + kind: Uint, + width: 4, + ))), + Value(Scalar(( + kind: Uint, + width: 4, + ))), + Value(Scalar(( + kind: Uint, + width: 4, + ))), + Handle(2), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Handle(4), + ], +) \ No newline at end of file diff --git a/naga/tests/out/analysis/collatz.info.ron b/naga/tests/out/analysis/collatz.info.ron new file mode 100644 index 0000000000..040e71c7e7 --- /dev/null +++ b/naga/tests/out/analysis/collatz.info.ron @@ -0,0 +1,434 @@ +( + type_flags: [ + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | COPY | HOST_SHAREABLE"), + ("DATA | COPY | HOST_SHAREABLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ], + functions: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: Some(4), + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 7, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(4), + requirements: (""), + ), + ref_count: 3, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Bool, + width: 1, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Bool, + width: 1, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(4), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(4), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(4), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ], + entry_points: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: Some(4), + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ | WRITE"), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 2, + assignable_global: None, + ty: Handle(4), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(1), + ty: Value(Pointer( + base: 3, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(1), + ty: Value(Pointer( + base: 2, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(1), + ty: Value(Pointer( + base: 1, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(1), + ty: Value(Pointer( + base: 3, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(1), + ty: Value(Pointer( + base: 2, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(1), + ty: Value(Pointer( + base: 1, + space: Storage( + access: ("LOAD | STORE"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(4), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ], + const_expression_types: [], +) \ No newline at end of file diff --git a/naga/tests/out/analysis/shadow.info.ron b/naga/tests/out/analysis/shadow.info.ron new file mode 100644 index 0000000000..bd46d9187f --- /dev/null +++ b/naga/tests/out/analysis/shadow.info.ron @@ -0,0 +1,1747 @@ +( + type_flags: [ + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("ARGUMENT"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | COPY | HOST_SHAREABLE"), + ("DATA | COPY | HOST_SHAREABLE"), + ("ARGUMENT"), + ], + functions: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + may_kill: false, + sampling_set: [ + ( + image: 1, + sampler: 2, + ), + ], + global_uses: [ + ("READ"), + ("READ"), + (""), + (""), + (""), + (""), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(1), + ty: Handle(6), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Handle(14), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 2, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 3, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(7), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 6, + assignable_global: None, + ty: Handle(4), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Bool, + width: 1, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(5), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(5), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(5), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(5), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 2, + assignable_global: None, + ty: Handle(5), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(7), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(7), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 3, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(5), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(8), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Sint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + may_kill: false, + sampling_set: [ + ( + image: 1, + sampler: 2, + ), + ], + global_uses: [ + ("READ"), + ("READ"), + ("READ"), + ("READ"), + ("READ"), + ("READ"), + ("WRITE"), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 9, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(6), + ty: Value(Pointer( + base: 2, + space: Private, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 4, + assignable_global: Some(5), + ty: Value(Pointer( + base: 4, + space: Private, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 7, + assignable_global: Some(4), + ty: Value(Pointer( + base: 13, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(5), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(7), + ty: Value(Pointer( + base: 4, + space: Private, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(21), + requirements: (""), + ), + ref_count: 3, + assignable_global: None, + ty: Value(Pointer( + base: 2, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 11, + assignable_global: None, + ty: Value(Pointer( + base: 3, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(Pointer( + base: 8, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Uint, + width: 4, + ), + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Bool, + width: 1, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(21), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 12, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 11, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 10, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(10), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(4), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 12, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 11, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 4, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 12, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 11, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 4, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 12, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 11, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 4, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Private, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Private, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Private, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 12, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 11, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 4, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 12, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 11, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 4, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 12, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 11, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(Pointer( + base: 4, + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(4), + ty: Value(ValuePointer( + size: None, + scalar: ( + kind: Float, + width: 4, + ), + space: Storage( + access: ("LOAD"), + ), + )), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(21), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(23), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(3), + ), + ( + uniformity: ( + non_uniform_result: Some(21), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(21), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(4), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ], + entry_points: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + may_kill: false, + sampling_set: [ + ( + image: 1, + sampler: 2, + ), + ], + global_uses: [ + ("READ"), + ("READ"), + ("READ"), + ("READ"), + ("READ | WRITE"), + ("READ | WRITE"), + ("READ | WRITE"), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(6), + ty: Value(Pointer( + base: 2, + space: Private, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(3), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(4), + ), + ( + uniformity: ( + non_uniform_result: Some(4), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(Pointer( + base: 4, + space: Private, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(5), + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(7), + ty: Value(Pointer( + base: 4, + space: Private, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(5), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(4), + ), + ], + sampling: [], + dual_source_blending: false, + ), + ], + const_expression_types: [ + Value(Scalar(( + kind: Float, + width: 4, + ))), + Value(Scalar(( + kind: Float, + width: 4, + ))), + Value(Scalar(( + kind: Float, + width: 4, + ))), + Value(Scalar(( + kind: Float, + width: 4, + ))), + Value(Scalar(( + kind: Float, + width: 4, + ))), + Handle(1), + Handle(1), + Handle(1), + Handle(2), + Value(Scalar(( + kind: Uint, + width: 4, + ))), + Value(Scalar(( + kind: Uint, + width: 4, + ))), + Value(Scalar(( + kind: Uint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + Value(Scalar(( + kind: Sint, + width: 4, + ))), + ], +) \ No newline at end of file diff --git a/naga/tests/out/dot/quad.dot b/naga/tests/out/dot/quad.dot new file mode 100644 index 0000000000..9864089781 --- /dev/null +++ b/naga/tests/out/dot/quad.dot @@ -0,0 +1,104 @@ +digraph Module { + subgraph cluster_globals { + label="Globals" + g0 [ shape=hexagon label="[1] Handle/'u_texture'" ] + g1 [ shape=hexagon label="[2] Handle/'u_sampler'" ] + } + subgraph cluster_ep0 { + label="Vertex/'vert_main'" + node [ style=filled ] + ep0_e0 [ color="#8dd3c7" label="[1] Argument[0]" ] + ep0_e1 [ color="#8dd3c7" label="[2] Argument[1]" ] + ep0_e2 [ fillcolor="#ffffb3" label="[3] Constant" ] + ep0_e3 [ color="#fdb462" label="[4] Multiply" ] + ep0_e0 -> ep0_e3 [ label="right" ] + ep0_e2 -> ep0_e3 [ label="left" ] + ep0_e4 [ fillcolor="#ffffb3" label="[5] Literal" ] + ep0_e5 [ fillcolor="#ffffb3" label="[6] Literal" ] + ep0_e6 [ color="#bebada" label="[7] Compose" ] + { ep0_e3 ep0_e4 ep0_e5 } -> ep0_e6 + ep0_e7 [ color="#bebada" label="[8] Compose" ] + { ep0_e1 ep0_e6 } -> ep0_e7 + ep0_s0 [ shape=square label="Root" ] + ep0_s1 [ shape=square label="Emit" ] + ep0_s2 [ shape=square label="Emit" ] + ep0_s3 [ shape=square label="Return" ] + ep0_s0 -> ep0_s1 [ arrowhead=tee label="" ] + ep0_s1 -> ep0_s2 [ arrowhead=tee label="" ] + ep0_s2 -> ep0_s3 [ arrowhead=tee label="" ] + ep0_e7 -> ep0_s3 [ label="value" ] + ep0_s1 -> ep0_e3 [ style=dotted ] + ep0_s2 -> ep0_e6 [ style=dotted ] + ep0_s2 -> ep0_e7 [ style=dotted ] + } + subgraph cluster_ep1 { + label="Fragment/'frag_main'" + node [ style=filled ] + ep1_e0 [ color="#8dd3c7" label="[1] Argument[0]" ] + ep1_e1 [ color="#ffffb3" label="[2] Global" ] + g0 -> ep1_e1 [fillcolor=gray] + ep1_e2 [ color="#ffffb3" label="[3] Global" ] + g1 -> ep1_e2 [fillcolor=gray] + ep1_e3 [ color="#80b1d3" label="[4] ImageSample" ] + ep1_e2 -> ep1_e3 [ label="sampler" ] + ep1_e1 -> ep1_e3 [ label="image" ] + ep1_e0 -> ep1_e3 [ label="coordinate" ] + ep1_e4 [ color="#8dd3c7" label="[5] AccessIndex[3]" ] + ep1_e3 -> ep1_e4 [ label="base" ] + ep1_e5 [ fillcolor="#ffffb3" label="[6] Literal" ] + ep1_e6 [ color="#fdb462" label="[7] Equal" ] + ep1_e5 -> ep1_e6 [ label="right" ] + ep1_e4 -> ep1_e6 [ label="left" ] + ep1_e7 [ color="#8dd3c7" label="[8] AccessIndex[3]" ] + ep1_e3 -> ep1_e7 [ label="base" ] + ep1_e8 [ color="#fdb462" label="[9] Multiply" ] + ep1_e3 -> ep1_e8 [ label="right" ] + ep1_e7 -> ep1_e8 [ label="left" ] + ep1_s0 [ shape=square label="Root" ] + ep1_s1 [ shape=square label="Emit" ] + ep1_s2 [ shape=square label="Emit" ] + ep1_s3 [ shape=square label="Emit" ] + ep1_s4 [ shape=square label="If" ] + ep1_s5 [ shape=square label="Node" ] + ep1_s6 [ shape=square label="Kill" ] + ep1_s7 [ shape=square label="Node" ] + ep1_s8 [ shape=square label="Merge" ] + ep1_s9 [ shape=square label="Emit" ] + ep1_s10 [ shape=square label="Return" ] + ep1_s0 -> ep1_s1 [ arrowhead=tee label="" ] + ep1_s1 -> ep1_s2 [ arrowhead=tee label="" ] + ep1_s2 -> ep1_s3 [ arrowhead=tee label="" ] + ep1_s3 -> ep1_s4 [ arrowhead=tee label="" ] + ep1_s5 -> ep1_s6 [ arrowhead=tee label="" ] + ep1_s4 -> ep1_s5 [ arrowhead=tee label="accept" ] + ep1_s4 -> ep1_s7 [ arrowhead=tee label="reject" ] + ep1_s6 -> ep1_s8 [ arrowhead=tee label="" ] + ep1_s7 -> ep1_s8 [ arrowhead=tee label="" ] + ep1_s8 -> ep1_s9 [ arrowhead=tee label="" ] + ep1_s9 -> ep1_s10 [ arrowhead=tee label="" ] + ep1_e6 -> ep1_s4 [ label="condition" ] + ep1_e8 -> ep1_s10 [ label="value" ] + ep1_s1 -> ep1_e3 [ style=dotted ] + ep1_s2 -> ep1_e4 [ style=dotted ] + ep1_s3 -> ep1_e6 [ style=dotted ] + ep1_s9 -> ep1_e7 [ style=dotted ] + ep1_s9 -> ep1_e8 [ style=dotted ] + } + subgraph cluster_ep2 { + label="Fragment/'fs_extra'" + node [ style=filled ] + ep2_e0 [ fillcolor="#ffffb3" label="[1] Literal" ] + ep2_e1 [ fillcolor="#ffffb3" label="[2] Literal" ] + ep2_e2 [ fillcolor="#ffffb3" label="[3] Literal" ] + ep2_e3 [ fillcolor="#ffffb3" label="[4] Literal" ] + ep2_e4 [ fillcolor="#bebada" label="[5] Compose" ] + { ep2_e0 ep2_e1 ep2_e2 ep2_e3 } -> ep2_e4 + ep2_s0 [ shape=square label="Root" ] + ep2_s1 [ shape=square label="Emit" ] + ep2_s2 [ shape=square label="Return" ] + ep2_s0 -> ep2_s1 [ arrowhead=tee label="" ] + ep2_s1 -> ep2_s2 [ arrowhead=tee label="" ] + ep2_e4 -> ep2_s2 [ label="value" ] + ep2_s1 -> ep2_e4 [ style=dotted ] + } +} diff --git a/naga/tests/out/glsl/access.assign_through_ptr.Compute.glsl b/naga/tests/out/glsl/access.assign_through_ptr.Compute.glsl new file mode 100644 index 0000000000..2e51bbde63 --- /dev/null +++ b/naga/tests/out/glsl/access.assign_through_ptr.Compute.glsl @@ -0,0 +1,49 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + +struct GlobalConst { + uint a; + uvec3 b; + int c; +}; +struct AlignedWrapper { + int value; +}; +struct Baz { + mat3x2 m; +}; +struct MatCx2InArray { + mat4x2 am[2]; +}; + +float read_from_private(inout float foo_1) { + float _e1 = foo_1; + return _e1; +} + +float test_arr_as_arg(float a[5][10]) { + return a[4][9]; +} + +void assign_through_ptr_fn(inout uint p) { + p = 42u; + return; +} + +void assign_array_through_ptr_fn(inout vec4 foo_2[2]) { + foo_2 = vec4[2](vec4(1.0), vec4(2.0)); + return; +} + +void main() { + uint val = 33u; + vec4 arr[2] = vec4[2](vec4(6.0), vec4(7.0)); + assign_through_ptr_fn(val); + assign_array_through_ptr_fn(arr); + return; +} + diff --git a/naga/tests/out/glsl/access.foo_frag.Fragment.glsl b/naga/tests/out/glsl/access.foo_frag.Fragment.glsl new file mode 100644 index 0000000000..3d52fa56b0 --- /dev/null +++ b/naga/tests/out/glsl/access.foo_frag.Fragment.glsl @@ -0,0 +1,61 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct GlobalConst { + uint a; + uvec3 b; + int c; +}; +struct AlignedWrapper { + int value; +}; +struct Baz { + mat3x2 m; +}; +struct MatCx2InArray { + mat4x2 am[2]; +}; +layout(std430) buffer Bar_block_0Fragment { + mat4x3 _matrix; + mat2x2 matrix_array[2]; + int atom; + int atom_arr[10]; + uvec2 arr[2]; + AlignedWrapper data[]; +} _group_0_binding_0_fs; + +layout(std430) buffer type_12_block_1Fragment { ivec2 _group_0_binding_2_fs; }; + +layout(location = 0) out vec4 _fs2p_location0; + +float read_from_private(inout float foo_1) { + float _e1 = foo_1; + return _e1; +} + +float test_arr_as_arg(float a[5][10]) { + return a[4][9]; +} + +void assign_through_ptr_fn(inout uint p) { + p = 42u; + return; +} + +void assign_array_through_ptr_fn(inout vec4 foo_2[2]) { + foo_2 = vec4[2](vec4(1.0), vec4(2.0)); + return; +} + +void main() { + _group_0_binding_0_fs._matrix[1][2] = 1.0; + _group_0_binding_0_fs._matrix = mat4x3(vec3(0.0), vec3(1.0), vec3(2.0), vec3(3.0)); + _group_0_binding_0_fs.arr = uvec2[2](uvec2(0u), uvec2(1u)); + _group_0_binding_0_fs.data[1].value = 1; + _group_0_binding_2_fs = ivec2(0); + _fs2p_location0 = vec4(0.0); + return; +} + diff --git a/naga/tests/out/glsl/access.foo_vert.Vertex.glsl b/naga/tests/out/glsl/access.foo_vert.Vertex.glsl new file mode 100644 index 0000000000..edc7ce1e6b --- /dev/null +++ b/naga/tests/out/glsl/access.foo_vert.Vertex.glsl @@ -0,0 +1,147 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct GlobalConst { + uint a; + uvec3 b; + int c; +}; +struct AlignedWrapper { + int value; +}; +struct Baz { + mat3x2 m; +}; +struct MatCx2InArray { + mat4x2 am[2]; +}; +layout(std430) buffer Bar_block_0Vertex { + mat4x3 _matrix; + mat2x2 matrix_array[2]; + int atom; + int atom_arr[10]; + uvec2 arr[2]; + AlignedWrapper data[]; +} _group_0_binding_0_vs; + +uniform Baz_block_1Vertex { Baz _group_0_binding_1_vs; }; + +layout(std430) buffer type_12_block_2Vertex { ivec2 _group_0_binding_2_vs; }; + +uniform MatCx2InArray_block_3Vertex { MatCx2InArray _group_0_binding_3_vs; }; + + +void test_matrix_within_struct_accesses() { + int idx = 1; + Baz t = Baz(mat3x2(vec2(1.0), vec2(2.0), vec2(3.0))); + int _e3 = idx; + idx = (_e3 - 1); + mat3x2 l0_ = _group_0_binding_1_vs.m; + vec2 l1_ = _group_0_binding_1_vs.m[0]; + int _e14 = idx; + vec2 l2_ = _group_0_binding_1_vs.m[_e14]; + float l3_ = _group_0_binding_1_vs.m[0][1]; + int _e25 = idx; + float l4_ = _group_0_binding_1_vs.m[0][_e25]; + int _e30 = idx; + float l5_ = _group_0_binding_1_vs.m[_e30][1]; + int _e36 = idx; + int _e38 = idx; + float l6_ = _group_0_binding_1_vs.m[_e36][_e38]; + int _e51 = idx; + idx = (_e51 + 1); + t.m = mat3x2(vec2(6.0), vec2(5.0), vec2(4.0)); + t.m[0] = vec2(9.0); + int _e66 = idx; + t.m[_e66] = vec2(90.0); + t.m[0][1] = 10.0; + int _e76 = idx; + t.m[0][_e76] = 20.0; + int _e80 = idx; + t.m[_e80][1] = 30.0; + int _e85 = idx; + int _e87 = idx; + t.m[_e85][_e87] = 40.0; + return; +} + +void test_matrix_within_array_within_struct_accesses() { + int idx_1 = 1; + MatCx2InArray t_1 = MatCx2InArray(mat4x2[2](mat4x2(0.0), mat4x2(0.0))); + int _e3 = idx_1; + idx_1 = (_e3 - 1); + mat4x2 l0_1[2] = _group_0_binding_3_vs.am; + mat4x2 l1_1 = _group_0_binding_3_vs.am[0]; + vec2 l2_1 = _group_0_binding_3_vs.am[0][0]; + int _e20 = idx_1; + vec2 l3_1 = _group_0_binding_3_vs.am[0][_e20]; + float l4_1 = _group_0_binding_3_vs.am[0][0][1]; + int _e33 = idx_1; + float l5_1 = _group_0_binding_3_vs.am[0][0][_e33]; + int _e39 = idx_1; + float l6_1 = _group_0_binding_3_vs.am[0][_e39][1]; + int _e46 = idx_1; + int _e48 = idx_1; + float l7_ = _group_0_binding_3_vs.am[0][_e46][_e48]; + int _e55 = idx_1; + idx_1 = (_e55 + 1); + t_1.am = mat4x2[2](mat4x2(0.0), mat4x2(0.0)); + t_1.am[0] = mat4x2(vec2(8.0), vec2(7.0), vec2(6.0), vec2(5.0)); + t_1.am[0][0] = vec2(9.0); + int _e77 = idx_1; + t_1.am[0][_e77] = vec2(90.0); + t_1.am[0][0][1] = 10.0; + int _e89 = idx_1; + t_1.am[0][0][_e89] = 20.0; + int _e94 = idx_1; + t_1.am[0][_e94][1] = 30.0; + int _e100 = idx_1; + int _e102 = idx_1; + t_1.am[0][_e100][_e102] = 40.0; + return; +} + +float read_from_private(inout float foo_1) { + float _e1 = foo_1; + return _e1; +} + +float test_arr_as_arg(float a[5][10]) { + return a[4][9]; +} + +void assign_through_ptr_fn(inout uint p) { + p = 42u; + return; +} + +void assign_array_through_ptr_fn(inout vec4 foo_2[2]) { + foo_2 = vec4[2](vec4(1.0), vec4(2.0)); + return; +} + +void main() { + uint vi = uint(gl_VertexID); + float foo = 0.0; + int c2_[5] = int[5](0, 0, 0, 0, 0); + float baz_1 = foo; + foo = 1.0; + test_matrix_within_struct_accesses(); + test_matrix_within_array_within_struct_accesses(); + mat4x3 _matrix = _group_0_binding_0_vs._matrix; + uvec2 arr_1[2] = _group_0_binding_0_vs.arr; + float b = _group_0_binding_0_vs._matrix[3u][0]; + int a_1 = _group_0_binding_0_vs.data[(uint(_group_0_binding_0_vs.data.length()) - 2u)].value; + ivec2 c = _group_0_binding_2_vs; + float _e33 = read_from_private(foo); + c2_ = int[5](a_1, int(b), 3, 4, 5); + c2_[(vi + 1u)] = 42; + int value = c2_[vi]; + float _e47 = test_arr_as_arg(float[5][10](float[10](0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), float[10](0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), float[10](0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), float[10](0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), float[10](0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0))); + gl_Position = vec4((_matrix * vec4(ivec4(value))), 2.0); + gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w); + return; +} + diff --git a/naga/tests/out/glsl/array-in-ctor.cs_main.Compute.glsl b/naga/tests/out/glsl/array-in-ctor.cs_main.Compute.glsl new file mode 100644 index 0000000000..bd918087b8 --- /dev/null +++ b/naga/tests/out/glsl/array-in-ctor.cs_main.Compute.glsl @@ -0,0 +1,17 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + +struct Ah { + float inner[2]; +}; +layout(std430) readonly buffer Ah_block_0Compute { Ah _group_0_binding_0_cs; }; + + +void main() { + Ah ah_1 = _group_0_binding_0_cs; +} + diff --git a/naga/tests/out/glsl/array-in-function-return-type.main.Fragment.glsl b/naga/tests/out/glsl/array-in-function-return-type.main.Fragment.glsl new file mode 100644 index 0000000000..45fc31a622 --- /dev/null +++ b/naga/tests/out/glsl/array-in-function-return-type.main.Fragment.glsl @@ -0,0 +1,17 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(location = 0) out vec4 _fs2p_location0; + +float[2] ret_array() { + return float[2](1.0, 2.0); +} + +void main() { + float _e0[2] = ret_array(); + _fs2p_location0 = vec4(_e0[0], _e0[1], 0.0, 1.0); + return; +} + diff --git a/naga/tests/out/glsl/atomicOps.cs_main.Compute.glsl b/naga/tests/out/glsl/atomicOps.cs_main.Compute.glsl new file mode 100644 index 0000000000..b69c5107ce --- /dev/null +++ b/naga/tests/out/glsl/atomicOps.cs_main.Compute.glsl @@ -0,0 +1,132 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 2, local_size_y = 1, local_size_z = 1) in; + +struct Struct { + uint atomic_scalar; + int atomic_arr[2]; +}; +layout(std430) buffer type_block_0Compute { uint _group_0_binding_0_cs; }; + +layout(std430) buffer type_2_block_1Compute { int _group_0_binding_1_cs[2]; }; + +layout(std430) buffer Struct_block_2Compute { Struct _group_0_binding_2_cs; }; + +shared uint workgroup_atomic_scalar; + +shared int workgroup_atomic_arr[2]; + +shared Struct workgroup_struct; + + +void main() { + if (gl_LocalInvocationID == uvec3(0u)) { + workgroup_atomic_scalar = 0u; + workgroup_atomic_arr = int[2](0, 0); + workgroup_struct = Struct(0u, int[2](0, 0)); + } + memoryBarrierShared(); + barrier(); + uvec3 id = gl_LocalInvocationID; + _group_0_binding_0_cs = 1u; + _group_0_binding_1_cs[1] = 1; + _group_0_binding_2_cs.atomic_scalar = 1u; + _group_0_binding_2_cs.atomic_arr[1] = 1; + workgroup_atomic_scalar = 1u; + workgroup_atomic_arr[1] = 1; + workgroup_struct.atomic_scalar = 1u; + workgroup_struct.atomic_arr[1] = 1; + memoryBarrierShared(); + barrier(); + uint l0_ = _group_0_binding_0_cs; + int l1_ = _group_0_binding_1_cs[1]; + uint l2_ = _group_0_binding_2_cs.atomic_scalar; + int l3_ = _group_0_binding_2_cs.atomic_arr[1]; + uint l4_ = workgroup_atomic_scalar; + int l5_ = workgroup_atomic_arr[1]; + uint l6_ = workgroup_struct.atomic_scalar; + int l7_ = workgroup_struct.atomic_arr[1]; + memoryBarrierShared(); + barrier(); + uint _e51 = atomicAdd(_group_0_binding_0_cs, 1u); + int _e55 = atomicAdd(_group_0_binding_1_cs[1], 1); + uint _e59 = atomicAdd(_group_0_binding_2_cs.atomic_scalar, 1u); + int _e64 = atomicAdd(_group_0_binding_2_cs.atomic_arr[1], 1); + uint _e67 = atomicAdd(workgroup_atomic_scalar, 1u); + int _e71 = atomicAdd(workgroup_atomic_arr[1], 1); + uint _e75 = atomicAdd(workgroup_struct.atomic_scalar, 1u); + int _e80 = atomicAdd(workgroup_struct.atomic_arr[1], 1); + memoryBarrierShared(); + barrier(); + uint _e83 = atomicAdd(_group_0_binding_0_cs, -1u); + int _e87 = atomicAdd(_group_0_binding_1_cs[1], -1); + uint _e91 = atomicAdd(_group_0_binding_2_cs.atomic_scalar, -1u); + int _e96 = atomicAdd(_group_0_binding_2_cs.atomic_arr[1], -1); + uint _e99 = atomicAdd(workgroup_atomic_scalar, -1u); + int _e103 = atomicAdd(workgroup_atomic_arr[1], -1); + uint _e107 = atomicAdd(workgroup_struct.atomic_scalar, -1u); + int _e112 = atomicAdd(workgroup_struct.atomic_arr[1], -1); + memoryBarrierShared(); + barrier(); + uint _e115 = atomicMax(_group_0_binding_0_cs, 1u); + int _e119 = atomicMax(_group_0_binding_1_cs[1], 1); + uint _e123 = atomicMax(_group_0_binding_2_cs.atomic_scalar, 1u); + int _e128 = atomicMax(_group_0_binding_2_cs.atomic_arr[1], 1); + uint _e131 = atomicMax(workgroup_atomic_scalar, 1u); + int _e135 = atomicMax(workgroup_atomic_arr[1], 1); + uint _e139 = atomicMax(workgroup_struct.atomic_scalar, 1u); + int _e144 = atomicMax(workgroup_struct.atomic_arr[1], 1); + memoryBarrierShared(); + barrier(); + uint _e147 = atomicMin(_group_0_binding_0_cs, 1u); + int _e151 = atomicMin(_group_0_binding_1_cs[1], 1); + uint _e155 = atomicMin(_group_0_binding_2_cs.atomic_scalar, 1u); + int _e160 = atomicMin(_group_0_binding_2_cs.atomic_arr[1], 1); + uint _e163 = atomicMin(workgroup_atomic_scalar, 1u); + int _e167 = atomicMin(workgroup_atomic_arr[1], 1); + uint _e171 = atomicMin(workgroup_struct.atomic_scalar, 1u); + int _e176 = atomicMin(workgroup_struct.atomic_arr[1], 1); + memoryBarrierShared(); + barrier(); + uint _e179 = atomicAnd(_group_0_binding_0_cs, 1u); + int _e183 = atomicAnd(_group_0_binding_1_cs[1], 1); + uint _e187 = atomicAnd(_group_0_binding_2_cs.atomic_scalar, 1u); + int _e192 = atomicAnd(_group_0_binding_2_cs.atomic_arr[1], 1); + uint _e195 = atomicAnd(workgroup_atomic_scalar, 1u); + int _e199 = atomicAnd(workgroup_atomic_arr[1], 1); + uint _e203 = atomicAnd(workgroup_struct.atomic_scalar, 1u); + int _e208 = atomicAnd(workgroup_struct.atomic_arr[1], 1); + memoryBarrierShared(); + barrier(); + uint _e211 = atomicOr(_group_0_binding_0_cs, 1u); + int _e215 = atomicOr(_group_0_binding_1_cs[1], 1); + uint _e219 = atomicOr(_group_0_binding_2_cs.atomic_scalar, 1u); + int _e224 = atomicOr(_group_0_binding_2_cs.atomic_arr[1], 1); + uint _e227 = atomicOr(workgroup_atomic_scalar, 1u); + int _e231 = atomicOr(workgroup_atomic_arr[1], 1); + uint _e235 = atomicOr(workgroup_struct.atomic_scalar, 1u); + int _e240 = atomicOr(workgroup_struct.atomic_arr[1], 1); + memoryBarrierShared(); + barrier(); + uint _e243 = atomicXor(_group_0_binding_0_cs, 1u); + int _e247 = atomicXor(_group_0_binding_1_cs[1], 1); + uint _e251 = atomicXor(_group_0_binding_2_cs.atomic_scalar, 1u); + int _e256 = atomicXor(_group_0_binding_2_cs.atomic_arr[1], 1); + uint _e259 = atomicXor(workgroup_atomic_scalar, 1u); + int _e263 = atomicXor(workgroup_atomic_arr[1], 1); + uint _e267 = atomicXor(workgroup_struct.atomic_scalar, 1u); + int _e272 = atomicXor(workgroup_struct.atomic_arr[1], 1); + uint _e275 = atomicExchange(_group_0_binding_0_cs, 1u); + int _e279 = atomicExchange(_group_0_binding_1_cs[1], 1); + uint _e283 = atomicExchange(_group_0_binding_2_cs.atomic_scalar, 1u); + int _e288 = atomicExchange(_group_0_binding_2_cs.atomic_arr[1], 1); + uint _e291 = atomicExchange(workgroup_atomic_scalar, 1u); + int _e295 = atomicExchange(workgroup_atomic_arr[1], 1); + uint _e299 = atomicExchange(workgroup_struct.atomic_scalar, 1u); + int _e304 = atomicExchange(workgroup_struct.atomic_arr[1], 1); + return; +} + diff --git a/naga/tests/out/glsl/bitcast.main.Compute.glsl b/naga/tests/out/glsl/bitcast.main.Compute.glsl new file mode 100644 index 0000000000..57e7e221b7 --- /dev/null +++ b/naga/tests/out/glsl/bitcast.main.Compute.glsl @@ -0,0 +1,39 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + +void main() { + ivec2 i2_ = ivec2(0); + ivec3 i3_ = ivec3(0); + ivec4 i4_ = ivec4(0); + uvec2 u2_ = uvec2(0u); + uvec3 u3_ = uvec3(0u); + uvec4 u4_ = uvec4(0u); + vec2 f2_ = vec2(0.0); + vec3 f3_ = vec3(0.0); + vec4 f4_ = vec4(0.0); + ivec2 _e27 = i2_; + u2_ = uvec2(_e27); + ivec3 _e29 = i3_; + u3_ = uvec3(_e29); + ivec4 _e31 = i4_; + u4_ = uvec4(_e31); + uvec2 _e33 = u2_; + i2_ = ivec2(_e33); + uvec3 _e35 = u3_; + i3_ = ivec3(_e35); + uvec4 _e37 = u4_; + i4_ = ivec4(_e37); + ivec2 _e39 = i2_; + f2_ = intBitsToFloat(_e39); + ivec3 _e41 = i3_; + f3_ = intBitsToFloat(_e41); + ivec4 _e43 = i4_; + f4_ = intBitsToFloat(_e43); + return; +} + diff --git a/naga/tests/out/glsl/bits.main.Compute.glsl b/naga/tests/out/glsl/bits.main.Compute.glsl new file mode 100644 index 0000000000..f991f532ac --- /dev/null +++ b/naga/tests/out/glsl/bits.main.Compute.glsl @@ -0,0 +1,126 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + +void main() { + int i = 0; + ivec2 i2_ = ivec2(0); + ivec3 i3_ = ivec3(0); + ivec4 i4_ = ivec4(0); + uint u = 0u; + uvec2 u2_ = uvec2(0u); + uvec3 u3_ = uvec3(0u); + uvec4 u4_ = uvec4(0u); + vec2 f2_ = vec2(0.0); + vec4 f4_ = vec4(0.0); + vec4 _e28 = f4_; + u = packSnorm4x8(_e28); + vec4 _e30 = f4_; + u = packUnorm4x8(_e30); + vec2 _e32 = f2_; + u = packSnorm2x16(_e32); + vec2 _e34 = f2_; + u = packUnorm2x16(_e34); + vec2 _e36 = f2_; + u = packHalf2x16(_e36); + uint _e38 = u; + f4_ = unpackSnorm4x8(_e38); + uint _e40 = u; + f4_ = unpackUnorm4x8(_e40); + uint _e42 = u; + f2_ = unpackSnorm2x16(_e42); + uint _e44 = u; + f2_ = unpackUnorm2x16(_e44); + uint _e46 = u; + f2_ = unpackHalf2x16(_e46); + int _e48 = i; + int _e49 = i; + i = bitfieldInsert(_e48, _e49, int(5u), int(10u)); + ivec2 _e53 = i2_; + ivec2 _e54 = i2_; + i2_ = bitfieldInsert(_e53, _e54, int(5u), int(10u)); + ivec3 _e58 = i3_; + ivec3 _e59 = i3_; + i3_ = bitfieldInsert(_e58, _e59, int(5u), int(10u)); + ivec4 _e63 = i4_; + ivec4 _e64 = i4_; + i4_ = bitfieldInsert(_e63, _e64, int(5u), int(10u)); + uint _e68 = u; + uint _e69 = u; + u = bitfieldInsert(_e68, _e69, int(5u), int(10u)); + uvec2 _e73 = u2_; + uvec2 _e74 = u2_; + u2_ = bitfieldInsert(_e73, _e74, int(5u), int(10u)); + uvec3 _e78 = u3_; + uvec3 _e79 = u3_; + u3_ = bitfieldInsert(_e78, _e79, int(5u), int(10u)); + uvec4 _e83 = u4_; + uvec4 _e84 = u4_; + u4_ = bitfieldInsert(_e83, _e84, int(5u), int(10u)); + int _e88 = i; + i = bitfieldExtract(_e88, int(5u), int(10u)); + ivec2 _e92 = i2_; + i2_ = bitfieldExtract(_e92, int(5u), int(10u)); + ivec3 _e96 = i3_; + i3_ = bitfieldExtract(_e96, int(5u), int(10u)); + ivec4 _e100 = i4_; + i4_ = bitfieldExtract(_e100, int(5u), int(10u)); + uint _e104 = u; + u = bitfieldExtract(_e104, int(5u), int(10u)); + uvec2 _e108 = u2_; + u2_ = bitfieldExtract(_e108, int(5u), int(10u)); + uvec3 _e112 = u3_; + u3_ = bitfieldExtract(_e112, int(5u), int(10u)); + uvec4 _e116 = u4_; + u4_ = bitfieldExtract(_e116, int(5u), int(10u)); + int _e120 = i; + i = findLSB(_e120); + uvec2 _e122 = u2_; + u2_ = uvec2(findLSB(_e122)); + ivec3 _e124 = i3_; + i3_ = findMSB(_e124); + uvec3 _e126 = u3_; + u3_ = uvec3(findMSB(_e126)); + int _e128 = i; + i = findMSB(_e128); + uint _e130 = u; + u = uint(findMSB(_e130)); + int _e132 = i; + i = bitCount(_e132); + ivec2 _e134 = i2_; + i2_ = bitCount(_e134); + ivec3 _e136 = i3_; + i3_ = bitCount(_e136); + ivec4 _e138 = i4_; + i4_ = bitCount(_e138); + uint _e140 = u; + u = uint(bitCount(_e140)); + uvec2 _e142 = u2_; + u2_ = uvec2(bitCount(_e142)); + uvec3 _e144 = u3_; + u3_ = uvec3(bitCount(_e144)); + uvec4 _e146 = u4_; + u4_ = uvec4(bitCount(_e146)); + int _e148 = i; + i = bitfieldReverse(_e148); + ivec2 _e150 = i2_; + i2_ = bitfieldReverse(_e150); + ivec3 _e152 = i3_; + i3_ = bitfieldReverse(_e152); + ivec4 _e154 = i4_; + i4_ = bitfieldReverse(_e154); + uint _e156 = u; + u = bitfieldReverse(_e156); + uvec2 _e158 = u2_; + u2_ = bitfieldReverse(_e158); + uvec3 _e160 = u3_; + u3_ = bitfieldReverse(_e160); + uvec4 _e162 = u4_; + u4_ = bitfieldReverse(_e162); + return; +} + diff --git a/naga/tests/out/glsl/boids.main.Compute.glsl b/naga/tests/out/glsl/boids.main.Compute.glsl new file mode 100644 index 0000000000..c42358bfef --- /dev/null +++ b/naga/tests/out/glsl/boids.main.Compute.glsl @@ -0,0 +1,155 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +struct Particle { + vec2 pos; + vec2 vel; +}; +struct SimParams { + float deltaT; + float rule1Distance; + float rule2Distance; + float rule3Distance; + float rule1Scale; + float rule2Scale; + float rule3Scale; +}; +const uint NUM_PARTICLES = 1500u; + +uniform SimParams_block_0Compute { SimParams _group_0_binding_0_cs; }; + +layout(std430) readonly buffer Particles_block_1Compute { + Particle particles[]; +} _group_0_binding_1_cs; + +layout(std430) buffer Particles_block_2Compute { + Particle particles[]; +} _group_0_binding_2_cs; + + +void main() { + uvec3 global_invocation_id = gl_GlobalInvocationID; + vec2 vPos = vec2(0.0); + vec2 vVel = vec2(0.0); + vec2 cMass = vec2(0.0, 0.0); + vec2 cVel = vec2(0.0, 0.0); + vec2 colVel = vec2(0.0, 0.0); + int cMassCount = 0; + int cVelCount = 0; + vec2 pos = vec2(0.0); + vec2 vel = vec2(0.0); + uint i = 0u; + uint index = global_invocation_id.x; + if ((index >= NUM_PARTICLES)) { + return; + } + vec2 _e8 = _group_0_binding_1_cs.particles[index].pos; + vPos = _e8; + vec2 _e14 = _group_0_binding_1_cs.particles[index].vel; + vVel = _e14; + bool loop_init = true; + while(true) { + if (!loop_init) { + uint _e91 = i; + i = (_e91 + 1u); + } + loop_init = false; + uint _e36 = i; + if ((_e36 >= NUM_PARTICLES)) { + break; + } + uint _e39 = i; + if ((_e39 == index)) { + continue; + } + uint _e43 = i; + vec2 _e46 = _group_0_binding_1_cs.particles[_e43].pos; + pos = _e46; + uint _e49 = i; + vec2 _e52 = _group_0_binding_1_cs.particles[_e49].vel; + vel = _e52; + vec2 _e53 = pos; + vec2 _e54 = vPos; + float _e58 = _group_0_binding_0_cs.rule1Distance; + if ((distance(_e53, _e54) < _e58)) { + vec2 _e60 = cMass; + vec2 _e61 = pos; + cMass = (_e60 + _e61); + int _e63 = cMassCount; + cMassCount = (_e63 + 1); + } + vec2 _e66 = pos; + vec2 _e67 = vPos; + float _e71 = _group_0_binding_0_cs.rule2Distance; + if ((distance(_e66, _e67) < _e71)) { + vec2 _e73 = colVel; + vec2 _e74 = pos; + vec2 _e75 = vPos; + colVel = (_e73 - (_e74 - _e75)); + } + vec2 _e78 = pos; + vec2 _e79 = vPos; + float _e83 = _group_0_binding_0_cs.rule3Distance; + if ((distance(_e78, _e79) < _e83)) { + vec2 _e85 = cVel; + vec2 _e86 = vel; + cVel = (_e85 + _e86); + int _e88 = cVelCount; + cVelCount = (_e88 + 1); + } + } + int _e94 = cMassCount; + if ((_e94 > 0)) { + vec2 _e97 = cMass; + int _e98 = cMassCount; + vec2 _e102 = vPos; + cMass = ((_e97 / vec2(float(_e98))) - _e102); + } + int _e104 = cVelCount; + if ((_e104 > 0)) { + vec2 _e107 = cVel; + int _e108 = cVelCount; + cVel = (_e107 / vec2(float(_e108))); + } + vec2 _e112 = vVel; + vec2 _e113 = cMass; + float _e116 = _group_0_binding_0_cs.rule1Scale; + vec2 _e119 = colVel; + float _e122 = _group_0_binding_0_cs.rule2Scale; + vec2 _e125 = cVel; + float _e128 = _group_0_binding_0_cs.rule3Scale; + vVel = (((_e112 + (_e113 * _e116)) + (_e119 * _e122)) + (_e125 * _e128)); + vec2 _e131 = vVel; + vec2 _e133 = vVel; + vVel = (normalize(_e131) * clamp(length(_e133), 0.0, 0.1)); + vec2 _e139 = vPos; + vec2 _e140 = vVel; + float _e143 = _group_0_binding_0_cs.deltaT; + vPos = (_e139 + (_e140 * _e143)); + float _e147 = vPos.x; + if ((_e147 < -1.0)) { + vPos.x = 1.0; + } + float _e153 = vPos.x; + if ((_e153 > 1.0)) { + vPos.x = -1.0; + } + float _e159 = vPos.y; + if ((_e159 < -1.0)) { + vPos.y = 1.0; + } + float _e165 = vPos.y; + if ((_e165 > 1.0)) { + vPos.y = -1.0; + } + vec2 _e174 = vPos; + _group_0_binding_2_cs.particles[index].pos = _e174; + vec2 _e179 = vVel; + _group_0_binding_2_cs.particles[index].vel = _e179; + return; +} + diff --git a/naga/tests/out/glsl/bounds-check-image-restrict.fragment_shader.Fragment.glsl b/naga/tests/out/glsl/bounds-check-image-restrict.fragment_shader.Fragment.glsl new file mode 100644 index 0000000000..f582e85ec7 --- /dev/null +++ b/naga/tests/out/glsl/bounds-check-image-restrict.fragment_shader.Fragment.glsl @@ -0,0 +1,99 @@ +#version 430 core +#extension GL_ARB_shader_texture_image_samples : require +uniform sampler1D _group_0_binding_0_fs; + +uniform sampler2D _group_0_binding_1_fs; + +uniform sampler2DArray _group_0_binding_2_fs; + +uniform sampler3D _group_0_binding_3_fs; + +uniform sampler2DMS _group_0_binding_4_fs; + +layout(rgba8) writeonly uniform image1D _group_0_binding_8_fs; + +layout(rgba8) writeonly uniform image2D _group_0_binding_9_fs; + +layout(rgba8) writeonly uniform image2DArray _group_0_binding_10_fs; + +layout(rgba8) writeonly uniform image3D _group_0_binding_11_fs; + +layout(location = 0) out vec4 _fs2p_location0; + +vec4 test_textureLoad_1d(int coords, int level) { + int _e3_clamped_lod = clamp(level, 0, textureQueryLevels(_group_0_binding_0_fs) - 1); + vec4 _e3 = texelFetch(_group_0_binding_0_fs, clamp(coords, 0, textureSize(_group_0_binding_0_fs, _e3_clamped_lod) - 1), _e3_clamped_lod); + return _e3; +} + +vec4 test_textureLoad_2d(ivec2 coords_1, int level_1) { + int _e3_clamped_lod = clamp(level_1, 0, textureQueryLevels(_group_0_binding_1_fs) - 1); + vec4 _e3 = texelFetch(_group_0_binding_1_fs, clamp(coords_1, ivec2(0), textureSize(_group_0_binding_1_fs, _e3_clamped_lod) - ivec2(1)), _e3_clamped_lod); + return _e3; +} + +vec4 test_textureLoad_2d_array_u(ivec2 coords_2, uint index, int level_2) { + int _e4_clamped_lod = clamp(level_2, 0, textureQueryLevels(_group_0_binding_2_fs) - 1); + vec4 _e4 = texelFetch(_group_0_binding_2_fs, clamp(ivec3(coords_2, index), ivec3(0), textureSize(_group_0_binding_2_fs, _e4_clamped_lod) - ivec3(1)), _e4_clamped_lod); + return _e4; +} + +vec4 test_textureLoad_2d_array_s(ivec2 coords_3, int index_1, int level_3) { + int _e4_clamped_lod = clamp(level_3, 0, textureQueryLevels(_group_0_binding_2_fs) - 1); + vec4 _e4 = texelFetch(_group_0_binding_2_fs, clamp(ivec3(coords_3, index_1), ivec3(0), textureSize(_group_0_binding_2_fs, _e4_clamped_lod) - ivec3(1)), _e4_clamped_lod); + return _e4; +} + +vec4 test_textureLoad_3d(ivec3 coords_4, int level_4) { + int _e3_clamped_lod = clamp(level_4, 0, textureQueryLevels(_group_0_binding_3_fs) - 1); + vec4 _e3 = texelFetch(_group_0_binding_3_fs, clamp(coords_4, ivec3(0), textureSize(_group_0_binding_3_fs, _e3_clamped_lod) - ivec3(1)), _e3_clamped_lod); + return _e3; +} + +vec4 test_textureLoad_multisampled_2d(ivec2 coords_5, int _sample) { + vec4 _e3 = texelFetch(_group_0_binding_4_fs, clamp(coords_5, ivec2(0), textureSize(_group_0_binding_4_fs) - ivec2(1)), clamp(_sample, 0, textureSamples(_group_0_binding_4_fs) - 1) +); + return _e3; +} + +void test_textureStore_1d(int coords_10, vec4 value) { + imageStore(_group_0_binding_8_fs, coords_10, value); + return; +} + +void test_textureStore_2d(ivec2 coords_11, vec4 value_1) { + imageStore(_group_0_binding_9_fs, coords_11, value_1); + return; +} + +void test_textureStore_2d_array_u(ivec2 coords_12, uint array_index, vec4 value_2) { + imageStore(_group_0_binding_10_fs, ivec3(coords_12, array_index), value_2); + return; +} + +void test_textureStore_2d_array_s(ivec2 coords_13, int array_index_1, vec4 value_3) { + imageStore(_group_0_binding_10_fs, ivec3(coords_13, array_index_1), value_3); + return; +} + +void test_textureStore_3d(ivec3 coords_14, vec4 value_4) { + imageStore(_group_0_binding_11_fs, coords_14, value_4); + return; +} + +void main() { + vec4 _e2 = test_textureLoad_1d(0, 0); + vec4 _e5 = test_textureLoad_2d(ivec2(0), 0); + vec4 _e9 = test_textureLoad_2d_array_u(ivec2(0), 0u, 0); + vec4 _e13 = test_textureLoad_2d_array_s(ivec2(0), 0, 0); + vec4 _e16 = test_textureLoad_3d(ivec3(0), 0); + vec4 _e19 = test_textureLoad_multisampled_2d(ivec2(0), 0); + test_textureStore_1d(0, vec4(0.0)); + test_textureStore_2d(ivec2(0), vec4(0.0)); + test_textureStore_2d_array_u(ivec2(0), 0u, vec4(0.0)); + test_textureStore_2d_array_s(ivec2(0), 0, vec4(0.0)); + test_textureStore_3d(ivec3(0), vec4(0.0)); + _fs2p_location0 = vec4(0.0, 0.0, 0.0, 0.0); + return; +} + diff --git a/naga/tests/out/glsl/bounds-check-image-rzsw.fragment_shader.Fragment.glsl b/naga/tests/out/glsl/bounds-check-image-rzsw.fragment_shader.Fragment.glsl new file mode 100644 index 0000000000..b2096add79 --- /dev/null +++ b/naga/tests/out/glsl/bounds-check-image-rzsw.fragment_shader.Fragment.glsl @@ -0,0 +1,93 @@ +#version 430 core +#extension GL_ARB_shader_texture_image_samples : require +uniform sampler1D _group_0_binding_0_fs; + +uniform sampler2D _group_0_binding_1_fs; + +uniform sampler2DArray _group_0_binding_2_fs; + +uniform sampler3D _group_0_binding_3_fs; + +uniform sampler2DMS _group_0_binding_4_fs; + +layout(rgba8) writeonly uniform image1D _group_0_binding_8_fs; + +layout(rgba8) writeonly uniform image2D _group_0_binding_9_fs; + +layout(rgba8) writeonly uniform image2DArray _group_0_binding_10_fs; + +layout(rgba8) writeonly uniform image3D _group_0_binding_11_fs; + +layout(location = 0) out vec4 _fs2p_location0; + +vec4 test_textureLoad_1d(int coords, int level) { + vec4 _e3 = (level < textureQueryLevels(_group_0_binding_0_fs) && coords < textureSize(_group_0_binding_0_fs, level) ? texelFetch(_group_0_binding_0_fs, coords, level) : vec4(0.0)); + return _e3; +} + +vec4 test_textureLoad_2d(ivec2 coords_1, int level_1) { + vec4 _e3 = (level_1 < textureQueryLevels(_group_0_binding_1_fs) && all(lessThan(coords_1, textureSize(_group_0_binding_1_fs, level_1))) ? texelFetch(_group_0_binding_1_fs, coords_1, level_1) : vec4(0.0)); + return _e3; +} + +vec4 test_textureLoad_2d_array_u(ivec2 coords_2, uint index, int level_2) { + vec4 _e4 = (level_2 < textureQueryLevels(_group_0_binding_2_fs) && all(lessThan(ivec3(coords_2, index), textureSize(_group_0_binding_2_fs, level_2))) ? texelFetch(_group_0_binding_2_fs, ivec3(coords_2, index), level_2) : vec4(0.0)); + return _e4; +} + +vec4 test_textureLoad_2d_array_s(ivec2 coords_3, int index_1, int level_3) { + vec4 _e4 = (level_3 < textureQueryLevels(_group_0_binding_2_fs) && all(lessThan(ivec3(coords_3, index_1), textureSize(_group_0_binding_2_fs, level_3))) ? texelFetch(_group_0_binding_2_fs, ivec3(coords_3, index_1), level_3) : vec4(0.0)); + return _e4; +} + +vec4 test_textureLoad_3d(ivec3 coords_4, int level_4) { + vec4 _e3 = (level_4 < textureQueryLevels(_group_0_binding_3_fs) && all(lessThan(coords_4, textureSize(_group_0_binding_3_fs, level_4))) ? texelFetch(_group_0_binding_3_fs, coords_4, level_4) : vec4(0.0)); + return _e3; +} + +vec4 test_textureLoad_multisampled_2d(ivec2 coords_5, int _sample) { + vec4 _e3 = (_sample < textureSamples(_group_0_binding_4_fs) && all(lessThan(coords_5, textureSize(_group_0_binding_4_fs))) ? texelFetch(_group_0_binding_4_fs, coords_5, _sample) : vec4(0.0)); + return _e3; +} + +void test_textureStore_1d(int coords_10, vec4 value) { + imageStore(_group_0_binding_8_fs, coords_10, value); + return; +} + +void test_textureStore_2d(ivec2 coords_11, vec4 value_1) { + imageStore(_group_0_binding_9_fs, coords_11, value_1); + return; +} + +void test_textureStore_2d_array_u(ivec2 coords_12, uint array_index, vec4 value_2) { + imageStore(_group_0_binding_10_fs, ivec3(coords_12, array_index), value_2); + return; +} + +void test_textureStore_2d_array_s(ivec2 coords_13, int array_index_1, vec4 value_3) { + imageStore(_group_0_binding_10_fs, ivec3(coords_13, array_index_1), value_3); + return; +} + +void test_textureStore_3d(ivec3 coords_14, vec4 value_4) { + imageStore(_group_0_binding_11_fs, coords_14, value_4); + return; +} + +void main() { + vec4 _e2 = test_textureLoad_1d(0, 0); + vec4 _e5 = test_textureLoad_2d(ivec2(0), 0); + vec4 _e9 = test_textureLoad_2d_array_u(ivec2(0), 0u, 0); + vec4 _e13 = test_textureLoad_2d_array_s(ivec2(0), 0, 0); + vec4 _e16 = test_textureLoad_3d(ivec3(0), 0); + vec4 _e19 = test_textureLoad_multisampled_2d(ivec2(0), 0); + test_textureStore_1d(0, vec4(0.0)); + test_textureStore_2d(ivec2(0), vec4(0.0)); + test_textureStore_2d_array_u(ivec2(0), 0u, vec4(0.0)); + test_textureStore_2d_array_s(ivec2(0), 0, vec4(0.0)); + test_textureStore_3d(ivec3(0), vec4(0.0)); + _fs2p_location0 = vec4(0.0, 0.0, 0.0, 0.0); + return; +} + diff --git a/naga/tests/out/glsl/break-if.main.Compute.glsl b/naga/tests/out/glsl/break-if.main.Compute.glsl new file mode 100644 index 0000000000..ba96de4879 --- /dev/null +++ b/naga/tests/out/glsl/break-if.main.Compute.glsl @@ -0,0 +1,80 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + +void breakIfEmpty() { + bool loop_init = true; + while(true) { + if (!loop_init) { + if (true) { + break; + } + } + loop_init = false; + } + return; +} + +void breakIfEmptyBody(bool a) { + bool b = false; + bool c = false; + bool loop_init_1 = true; + while(true) { + if (!loop_init_1) { + b = a; + bool _e2 = b; + c = (a != _e2); + bool _e5 = c; + if ((a == _e5)) { + break; + } + } + loop_init_1 = false; + } + return; +} + +void breakIf(bool a_1) { + bool d = false; + bool e = false; + bool loop_init_2 = true; + while(true) { + if (!loop_init_2) { + bool _e5 = e; + if ((a_1 == _e5)) { + break; + } + } + loop_init_2 = false; + d = a_1; + bool _e2 = d; + e = (a_1 != _e2); + } + return; +} + +void breakIfSeparateVariable() { + uint counter = 0u; + bool loop_init_3 = true; + while(true) { + if (!loop_init_3) { + uint _e5 = counter; + if ((_e5 == 5u)) { + break; + } + } + loop_init_3 = false; + uint _e3 = counter; + counter = (_e3 + 1u); + } + return; +} + +void main() { + return; +} + diff --git a/naga/tests/out/glsl/const-exprs.main.Compute.glsl b/naga/tests/out/glsl/const-exprs.main.Compute.glsl new file mode 100644 index 0000000000..b095345de9 --- /dev/null +++ b/naga/tests/out/glsl/const-exprs.main.Compute.glsl @@ -0,0 +1,93 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 2, local_size_y = 3, local_size_z = 1) in; + +const uint TWO = 2u; +const int THREE = 3; +const int FOUR = 4; +const int FOUR_ALIAS = 4; +const int TEST_CONSTANT_ADDITION = 8; +const int TEST_CONSTANT_ALIAS_ADDITION = 8; +const float PI = 3.141; +const float phi_sun = 6.282; +const vec4 DIV = vec4(0.44444445, 0.0, 0.0, 0.0); +const int TEXTURE_KIND_REGULAR = 0; +const int TEXTURE_KIND_WARP = 1; +const int TEXTURE_KIND_SKY = 2; +const vec2 add_vec = vec2(4.0, 5.0); +const bvec2 compare_vec = bvec2(true, false); + + +void swizzle_of_compose() { + ivec4 out_ = ivec4(4, 3, 2, 1); +} + +void index_of_compose() { + int out_1 = 2; +} + +void compose_three_deep() { + int out_2 = 6; +} + +void non_constant_initializers() { + int w = 30; + int x = 0; + int y = 0; + int z = 70; + ivec4 out_3 = ivec4(0); + int _e2 = w; + x = _e2; + int _e4 = x; + y = _e4; + int _e8 = w; + int _e9 = x; + int _e10 = y; + int _e11 = z; + out_3 = ivec4(_e8, _e9, _e10, _e11); + return; +} + +void splat_of_constant() { + ivec4 out_4 = ivec4(-4, -4, -4, -4); +} + +void compose_of_constant() { + ivec4 out_5 = ivec4(-4, -4, -4, -4); +} + +void compose_of_splat() { + vec4 x_1 = vec4(2.0, 1.0, 1.0, 1.0); +} + +uint map_texture_kind(int texture_kind) { + switch(texture_kind) { + case 0: { + return 10u; + } + case 1: { + return 20u; + } + case 2: { + return 30u; + } + default: { + return 0u; + } + } +} + +void main() { + swizzle_of_compose(); + index_of_compose(); + compose_three_deep(); + non_constant_initializers(); + splat_of_constant(); + compose_of_constant(); + compose_of_splat(); + return; +} + diff --git a/naga/tests/out/glsl/constructors.main.Compute.glsl b/naga/tests/out/glsl/constructors.main.Compute.glsl new file mode 100644 index 0000000000..4b4b0e71a4 --- /dev/null +++ b/naga/tests/out/glsl/constructors.main.Compute.glsl @@ -0,0 +1,38 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + +struct Foo { + vec4 a; + int b; +}; +const vec3 const2_ = vec3(0.0, 1.0, 2.0); +const mat2x2 const3_ = mat2x2(vec2(0.0, 1.0), vec2(2.0, 3.0)); +const mat2x2 const4_[1] = mat2x2[1](mat2x2(vec2(0.0, 1.0), vec2(2.0, 3.0))); +const bool cz0_ = false; +const int cz1_ = 0; +const uint cz2_ = 0u; +const float cz3_ = 0.0; +const uvec2 cz4_ = uvec2(0u); +const mat2x2 cz5_ = mat2x2(0.0); +const Foo cz6_[3] = Foo[3](Foo(vec4(0.0), 0), Foo(vec4(0.0), 0), Foo(vec4(0.0), 0)); +const Foo cz7_ = Foo(vec4(0.0), 0); +const int cp3_[4] = int[4](0, 1, 2, 3); + + +void main() { + Foo foo = Foo(vec4(0.0), 0); + foo = Foo(vec4(1.0), 1); + mat2x2 m0_ = mat2x2(vec2(1.0, 0.0), vec2(0.0, 1.0)); + mat4x4 m1_ = mat4x4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)); + uvec2 cit0_ = uvec2(0u); + mat2x2 cit1_ = mat2x2(vec2(0.0), vec2(0.0)); + int cit2_[4] = int[4](0, 1, 2, 3); + bool ic0_ = bool(false); + uvec2 ic4_ = uvec2(0u, 0u); + mat2x3 ic5_ = mat2x3(vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.0)); +} + diff --git a/naga/tests/out/glsl/control-flow.main.Compute.glsl b/naga/tests/out/glsl/control-flow.main.Compute.glsl new file mode 100644 index 0000000000..b877f9cb69 --- /dev/null +++ b/naga/tests/out/glsl/control-flow.main.Compute.glsl @@ -0,0 +1,112 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + +void switch_default_break(int i) { + switch(i) { + default: { + break; + } + } +} + +void switch_case_break() { + switch(0) { + case 0: { + break; + } + default: { + break; + } + } + return; +} + +void loop_switch_continue(int x) { + while(true) { + switch(x) { + case 1: { + continue; + } + default: { + break; + } + } + } + return; +} + +void main() { + uvec3 global_id = gl_GlobalInvocationID; + int pos = 0; + memoryBarrierBuffer(); + barrier(); + memoryBarrierShared(); + barrier(); + switch(1) { + default: { + pos = 1; + break; + } + } + int _e4 = pos; + switch(_e4) { + case 1: { + pos = 0; + break; + } + case 2: { + pos = 1; + break; + } + case 3: + case 4: { + pos = 2; + break; + } + case 5: { + pos = 3; + break; + } + default: + case 6: { + pos = 4; + break; + } + } + switch(0u) { + case 0u: { + break; + } + default: { + break; + } + } + int _e11 = pos; + switch(_e11) { + case 1: { + pos = 0; + break; + } + case 2: { + pos = 1; + return; + } + case 3: { + pos = 2; + return; + } + case 4: { + return; + } + default: { + pos = 3; + return; + } + } +} + diff --git a/naga/tests/out/glsl/cubeArrayShadow.fragment.Fragment.glsl b/naga/tests/out/glsl/cubeArrayShadow.fragment.Fragment.glsl new file mode 100644 index 0000000000..9339532831 --- /dev/null +++ b/naga/tests/out/glsl/cubeArrayShadow.fragment.Fragment.glsl @@ -0,0 +1,17 @@ +#version 310 es +#extension GL_EXT_texture_cube_map_array : require + +precision highp float; +precision highp int; + +uniform highp samplerCubeArrayShadow _group_0_binding_4_fs; + +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + vec3 frag_ls = vec3(1.0, 1.0, 2.0); + float a = texture(_group_0_binding_4_fs, vec4(frag_ls, 1), 1.0); + _fs2p_location0 = vec4(a, 1.0, 1.0, 1.0); + return; +} + diff --git a/naga/tests/out/glsl/do-while.main.Fragment.glsl b/naga/tests/out/glsl/do-while.main.Fragment.glsl new file mode 100644 index 0000000000..fb8f6e1efa --- /dev/null +++ b/naga/tests/out/glsl/do-while.main.Fragment.glsl @@ -0,0 +1,32 @@ +#version 310 es + +precision highp float; +precision highp int; + + +void fb1_(inout bool cond) { + bool loop_init = true; + while(true) { + if (!loop_init) { + bool _e1 = cond; + if (!(_e1)) { + break; + } + } + loop_init = false; + continue; + } + return; +} + +void main_1() { + bool param = false; + param = false; + fb1_(param); + return; +} + +void main() { + main_1(); +} + diff --git a/naga/tests/out/glsl/dualsource.main.Fragment.glsl b/naga/tests/out/glsl/dualsource.main.Fragment.glsl new file mode 100644 index 0000000000..ef57922798 --- /dev/null +++ b/naga/tests/out/glsl/dualsource.main.Fragment.glsl @@ -0,0 +1,25 @@ +#version 310 es +#extension GL_EXT_blend_func_extended : require + +precision highp float; +precision highp int; + +struct FragmentOutput { + vec4 color; + vec4 mask; +}; +layout(location = 0) out vec4 _fs2p_location0; +layout(location = 0, index = 1) out vec4 _fs2p_location1; + +void main() { + vec4 position = gl_FragCoord; + vec4 color = vec4(0.4, 0.3, 0.2, 0.1); + vec4 mask = vec4(0.9, 0.8, 0.7, 0.6); + vec4 _e13 = color; + vec4 _e14 = mask; + FragmentOutput _tmp_return = FragmentOutput(_e13, _e14); + _fs2p_location0 = _tmp_return.color; + _fs2p_location1 = _tmp_return.mask; + return; +} + diff --git a/naga/tests/out/glsl/empty.main.Compute.glsl b/naga/tests/out/glsl/empty.main.Compute.glsl new file mode 100644 index 0000000000..f6b9108cc6 --- /dev/null +++ b/naga/tests/out/glsl/empty.main.Compute.glsl @@ -0,0 +1,12 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + +void main() { + return; +} + diff --git a/naga/tests/out/glsl/f64.main.Compute.glsl b/naga/tests/out/glsl/f64.main.Compute.glsl new file mode 100644 index 0000000000..055965d153 --- /dev/null +++ b/naga/tests/out/glsl/f64.main.Compute.glsl @@ -0,0 +1,19 @@ +#version 420 core +#extension GL_ARB_compute_shader : require +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + +const double k = 2.0LF; + + +double f(double x) { + double z = 0.0; + double y = (30.0LF + 400.0LF); + z = (y + 5.0LF); + return (((x + y) + k) + 5.0LF); +} + +void main() { + double _e1 = f(6.0LF); + return; +} + diff --git a/naga/tests/out/glsl/force_point_size_vertex_shader_webgl.fs_main.Fragment.glsl b/naga/tests/out/glsl/force_point_size_vertex_shader_webgl.fs_main.Fragment.glsl new file mode 100644 index 0000000000..5d0f7a8072 --- /dev/null +++ b/naga/tests/out/glsl/force_point_size_vertex_shader_webgl.fs_main.Fragment.glsl @@ -0,0 +1,12 @@ +#version 300 es + +precision highp float; +precision highp int; + +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + _fs2p_location0 = vec4(1.0, 0.0, 0.0, 1.0); + return; +} + diff --git a/naga/tests/out/glsl/force_point_size_vertex_shader_webgl.vs_main.Vertex.glsl b/naga/tests/out/glsl/force_point_size_vertex_shader_webgl.vs_main.Vertex.glsl new file mode 100644 index 0000000000..069595bc81 --- /dev/null +++ b/naga/tests/out/glsl/force_point_size_vertex_shader_webgl.vs_main.Vertex.glsl @@ -0,0 +1,15 @@ +#version 300 es + +precision highp float; +precision highp int; + + +void main() { + uint in_vertex_index = uint(gl_VertexID); + float x = float((int(in_vertex_index) - 1)); + float y = float(((int((in_vertex_index & 1u)) * 2) - 1)); + gl_Position = vec4(x, y, 0.0, 1.0); + gl_PointSize = 1.0; + return; +} + diff --git a/naga/tests/out/glsl/fragment-output.main_vec2scalar.Fragment.glsl b/naga/tests/out/glsl/fragment-output.main_vec2scalar.Fragment.glsl new file mode 100644 index 0000000000..c68a89f162 --- /dev/null +++ b/naga/tests/out/glsl/fragment-output.main_vec2scalar.Fragment.glsl @@ -0,0 +1,46 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct FragmentOutputVec4Vec3_ { + vec4 vec4f; + ivec4 vec4i; + uvec4 vec4u; + vec3 vec3f; + ivec3 vec3i; + uvec3 vec3u; +}; +struct FragmentOutputVec2Scalar { + vec2 vec2f; + ivec2 vec2i; + uvec2 vec2u; + float scalarf; + int scalari; + uint scalaru; +}; +layout(location = 0) out vec2 _fs2p_location0; +layout(location = 1) out ivec2 _fs2p_location1; +layout(location = 2) out uvec2 _fs2p_location2; +layout(location = 3) out float _fs2p_location3; +layout(location = 4) out int _fs2p_location4; +layout(location = 5) out uint _fs2p_location5; + +void main() { + FragmentOutputVec2Scalar output_1 = FragmentOutputVec2Scalar(vec2(0.0), ivec2(0), uvec2(0u), 0.0, 0, 0u); + output_1.vec2f = vec2(0.0); + output_1.vec2i = ivec2(0); + output_1.vec2u = uvec2(0u); + output_1.scalarf = 0.0; + output_1.scalari = 0; + output_1.scalaru = 0u; + FragmentOutputVec2Scalar _e16 = output_1; + _fs2p_location0 = _e16.vec2f; + _fs2p_location1 = _e16.vec2i; + _fs2p_location2 = _e16.vec2u; + _fs2p_location3 = _e16.scalarf; + _fs2p_location4 = _e16.scalari; + _fs2p_location5 = _e16.scalaru; + return; +} + diff --git a/naga/tests/out/glsl/fragment-output.main_vec4vec3.Fragment.glsl b/naga/tests/out/glsl/fragment-output.main_vec4vec3.Fragment.glsl new file mode 100644 index 0000000000..c0398f399a --- /dev/null +++ b/naga/tests/out/glsl/fragment-output.main_vec4vec3.Fragment.glsl @@ -0,0 +1,46 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct FragmentOutputVec4Vec3_ { + vec4 vec4f; + ivec4 vec4i; + uvec4 vec4u; + vec3 vec3f; + ivec3 vec3i; + uvec3 vec3u; +}; +struct FragmentOutputVec2Scalar { + vec2 vec2f; + ivec2 vec2i; + uvec2 vec2u; + float scalarf; + int scalari; + uint scalaru; +}; +layout(location = 0) out vec4 _fs2p_location0; +layout(location = 1) out ivec4 _fs2p_location1; +layout(location = 2) out uvec4 _fs2p_location2; +layout(location = 3) out vec3 _fs2p_location3; +layout(location = 4) out ivec3 _fs2p_location4; +layout(location = 5) out uvec3 _fs2p_location5; + +void main() { + FragmentOutputVec4Vec3_ output_ = FragmentOutputVec4Vec3_(vec4(0.0), ivec4(0), uvec4(0u), vec3(0.0), ivec3(0), uvec3(0u)); + output_.vec4f = vec4(0.0); + output_.vec4i = ivec4(0); + output_.vec4u = uvec4(0u); + output_.vec3f = vec3(0.0); + output_.vec3i = ivec3(0); + output_.vec3u = uvec3(0u); + FragmentOutputVec4Vec3_ _e19 = output_; + _fs2p_location0 = _e19.vec4f; + _fs2p_location1 = _e19.vec4i; + _fs2p_location2 = _e19.vec4u; + _fs2p_location3 = _e19.vec3f; + _fs2p_location4 = _e19.vec3i; + _fs2p_location5 = _e19.vec3u; + return; +} + diff --git a/naga/tests/out/glsl/functions-webgl.main.Fragment.glsl b/naga/tests/out/glsl/functions-webgl.main.Fragment.glsl new file mode 100644 index 0000000000..9dd084c32a --- /dev/null +++ b/naga/tests/out/glsl/functions-webgl.main.Fragment.glsl @@ -0,0 +1,18 @@ +#version 320 es + +precision highp float; +precision highp int; + + +vec2 test_fma() { + vec2 a = vec2(2.0, 2.0); + vec2 b = vec2(0.5, 0.5); + vec2 c = vec2(0.5, 0.5); + return fma(a, b, c); +} + +void main() { + vec2 _e0 = test_fma(); + return; +} + diff --git a/naga/tests/out/glsl/functions.main.Compute.glsl b/naga/tests/out/glsl/functions.main.Compute.glsl new file mode 100644 index 0000000000..b0f23125f7 --- /dev/null +++ b/naga/tests/out/glsl/functions.main.Compute.glsl @@ -0,0 +1,34 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + +vec2 test_fma() { + vec2 a = vec2(2.0, 2.0); + vec2 b = vec2(0.5, 0.5); + vec2 c = vec2(0.5, 0.5); + return (a * b + c); +} + +int test_integer_dot_product() { + ivec2 a_2_ = ivec2(1); + ivec2 b_2_ = ivec2(1); + int c_2_ = ( + a_2_.x * b_2_.x + a_2_.y * b_2_.y); + uvec3 a_3_ = uvec3(1u); + uvec3 b_3_ = uvec3(1u); + uint c_3_ = ( + a_3_.x * b_3_.x + a_3_.y * b_3_.y + a_3_.z * b_3_.z); + ivec4 _e11 = ivec4(4); + ivec4 _e13 = ivec4(2); + int c_4_ = ( + _e11.x * _e13.x + _e11.y * _e13.y + _e11.z * _e13.z + _e11.w * _e13.w); + return c_4_; +} + +void main() { + vec2 _e0 = test_fma(); + int _e1 = test_integer_dot_product(); + return; +} + diff --git a/naga/tests/out/glsl/globals.main.Compute.glsl b/naga/tests/out/glsl/globals.main.Compute.glsl new file mode 100644 index 0000000000..b7ef8bd295 --- /dev/null +++ b/naga/tests/out/glsl/globals.main.Compute.glsl @@ -0,0 +1,83 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + +struct FooStruct { + vec3 v3_; + float v1_; +}; +const bool Foo_1 = true; + +shared float wg[10]; + +shared uint at_1; + +layout(std430) buffer FooStruct_block_0Compute { FooStruct _group_0_binding_1_cs; }; + +layout(std430) readonly buffer type_6_block_1Compute { vec2 _group_0_binding_2_cs[]; }; + +uniform type_8_block_2Compute { vec4 _group_0_binding_3_cs[20]; }; + +uniform type_4_block_3Compute { vec3 _group_0_binding_4_cs; }; + +uniform type_9_block_4Compute { mat3x2 _group_0_binding_5_cs; }; + +uniform type_12_block_5Compute { mat2x4 _group_0_binding_6_cs[2][2]; }; + +uniform type_15_block_6Compute { mat4x2 _group_0_binding_7_cs[2][2]; }; + + +void test_msl_packed_vec3_as_arg(vec3 arg) { + return; +} + +void test_msl_packed_vec3_() { + int idx = 1; + _group_0_binding_1_cs.v3_ = vec3(1.0); + _group_0_binding_1_cs.v3_.x = 1.0; + _group_0_binding_1_cs.v3_.x = 2.0; + int _e16 = idx; + _group_0_binding_1_cs.v3_[_e16] = 3.0; + FooStruct data = _group_0_binding_1_cs; + vec3 l0_ = data.v3_; + vec2 l1_ = data.v3_.zx; + test_msl_packed_vec3_as_arg(data.v3_); + vec3 mvm0_ = (data.v3_ * mat3x3(0.0)); + vec3 mvm1_ = (mat3x3(0.0) * data.v3_); + vec3 svm0_ = (data.v3_ * 2.0); + vec3 svm1_ = (2.0 * data.v3_); +} + +void main() { + if (gl_LocalInvocationID == uvec3(0u)) { + wg = float[10](0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + at_1 = 0u; + } + memoryBarrierShared(); + barrier(); + float Foo = 1.0; + bool at = true; + test_msl_packed_vec3_(); + mat4x2 _e5 = _group_0_binding_7_cs[0][0]; + vec4 _e10 = _group_0_binding_6_cs[0][0][0]; + wg[7] = (_e5 * _e10).x; + mat3x2 _e16 = _group_0_binding_5_cs; + vec3 _e18 = _group_0_binding_4_cs; + wg[6] = (_e16 * _e18).x; + float _e26 = _group_0_binding_2_cs[1].y; + wg[5] = _e26; + float _e32 = _group_0_binding_3_cs[0].w; + wg[4] = _e32; + float _e37 = _group_0_binding_1_cs.v1_; + wg[3] = _e37; + float _e43 = _group_0_binding_1_cs.v3_.x; + wg[2] = _e43; + _group_0_binding_1_cs.v1_ = 4.0; + wg[1] = float(uint(_group_0_binding_2_cs.length())); + at_1 = 2u; + return; +} + diff --git a/naga/tests/out/glsl/image.gather.Fragment.glsl b/naga/tests/out/glsl/image.gather.Fragment.glsl new file mode 100644 index 0000000000..c7c2fc5348 --- /dev/null +++ b/naga/tests/out/glsl/image.gather.Fragment.glsl @@ -0,0 +1,29 @@ +#version 310 es +#extension GL_EXT_texture_cube_map_array : require + +precision highp float; +precision highp int; + +uniform highp sampler2D _group_0_binding_1_fs; + +uniform highp usampler2D _group_0_binding_2_fs; + +uniform highp isampler2D _group_0_binding_3_fs; + +uniform highp sampler2DShadow _group_1_binding_2_fs; + +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + vec2 tc = vec2(0.5); + vec4 s2d = textureGather(_group_0_binding_1_fs, vec2(tc), 1); + vec4 s2d_offset = textureGatherOffset(_group_0_binding_1_fs, vec2(tc), ivec2(3, 1), 3); + vec4 s2d_depth = textureGather(_group_1_binding_2_fs, vec2(tc), 0.5); + vec4 s2d_depth_offset = textureGatherOffset(_group_1_binding_2_fs, vec2(tc), 0.5, ivec2(3, 1)); + uvec4 u = textureGather(_group_0_binding_2_fs, vec2(tc), 0); + ivec4 i = textureGather(_group_0_binding_3_fs, vec2(tc), 0); + vec4 f = (vec4(u) + vec4(i)); + _fs2p_location0 = ((((s2d + s2d_offset) + s2d_depth) + s2d_depth_offset) + f); + return; +} + diff --git a/naga/tests/out/glsl/image.main.Compute.glsl b/naga/tests/out/glsl/image.main.Compute.glsl new file mode 100644 index 0000000000..78324dd4cc --- /dev/null +++ b/naga/tests/out/glsl/image.main.Compute.glsl @@ -0,0 +1,42 @@ +#version 310 es +#extension GL_EXT_texture_cube_map_array : require + +precision highp float; +precision highp int; + +layout(local_size_x = 16, local_size_y = 1, local_size_z = 1) in; + +uniform highp usampler2D _group_0_binding_0_cs; + +uniform highp usampler2DMS _group_0_binding_3_cs; + +layout(rgba8ui) readonly uniform highp uimage2D _group_0_binding_1_cs; + +uniform highp usampler2DArray _group_0_binding_5_cs; + +uniform highp usampler2D _group_0_binding_7_cs; + +layout(r32ui) writeonly uniform highp uimage2D _group_0_binding_2_cs; + + +void main() { + uvec3 local_id = gl_LocalInvocationID; + uvec2 dim = uvec2(imageSize(_group_0_binding_1_cs).xy); + ivec2 itc = (ivec2((dim * local_id.xy)) % ivec2(10, 20)); + uvec4 value1_ = texelFetch(_group_0_binding_0_cs, itc, int(local_id.z)); + uvec4 value2_ = texelFetch(_group_0_binding_3_cs, itc, int(local_id.z)); + uvec4 value4_ = imageLoad(_group_0_binding_1_cs, itc); + uvec4 value5_ = texelFetch(_group_0_binding_5_cs, ivec3(itc, local_id.z), (int(local_id.z) + 1)); + uvec4 value6_ = texelFetch(_group_0_binding_5_cs, ivec3(itc, int(local_id.z)), (int(local_id.z) + 1)); + uvec4 value7_ = texelFetch(_group_0_binding_7_cs, ivec2(int(local_id.x), 0), int(local_id.z)); + uvec4 value1u = texelFetch(_group_0_binding_0_cs, ivec2(uvec2(itc)), int(local_id.z)); + uvec4 value2u = texelFetch(_group_0_binding_3_cs, ivec2(uvec2(itc)), int(local_id.z)); + uvec4 value4u = imageLoad(_group_0_binding_1_cs, ivec2(uvec2(itc))); + uvec4 value5u = texelFetch(_group_0_binding_5_cs, ivec3(uvec2(itc), local_id.z), (int(local_id.z) + 1)); + uvec4 value6u = texelFetch(_group_0_binding_5_cs, ivec3(uvec2(itc), int(local_id.z)), (int(local_id.z) + 1)); + uvec4 value7u = texelFetch(_group_0_binding_7_cs, ivec2(uint(local_id.x), 0), int(local_id.z)); + imageStore(_group_0_binding_2_cs, ivec2(itc.x, 0), ((((value1_ + value2_) + value4_) + value5_) + value6_)); + imageStore(_group_0_binding_2_cs, ivec2(uint(itc.x), 0), ((((value1u + value2u) + value4u) + value5u) + value6u)); + return; +} + diff --git a/naga/tests/out/glsl/image.queries.Vertex.glsl b/naga/tests/out/glsl/image.queries.Vertex.glsl new file mode 100644 index 0000000000..932a0a3bc3 --- /dev/null +++ b/naga/tests/out/glsl/image.queries.Vertex.glsl @@ -0,0 +1,41 @@ +#version 310 es +#extension GL_EXT_texture_cube_map_array : require + +precision highp float; +precision highp int; + +uniform highp sampler2D _group_0_binding_0_vs; + +uniform highp sampler2D _group_0_binding_1_vs; + +uniform highp sampler2DArray _group_0_binding_4_vs; + +uniform highp samplerCube _group_0_binding_5_vs; + +uniform highp samplerCubeArray _group_0_binding_6_vs; + +uniform highp sampler3D _group_0_binding_7_vs; + +uniform highp sampler2DMS _group_0_binding_8_vs; + + +void main() { + uint dim_1d = uint(textureSize(_group_0_binding_0_vs, 0).x); + uint dim_1d_lod = uint(textureSize(_group_0_binding_0_vs, int(dim_1d)).x); + uvec2 dim_2d = uvec2(textureSize(_group_0_binding_1_vs, 0).xy); + uvec2 dim_2d_lod = uvec2(textureSize(_group_0_binding_1_vs, 1).xy); + uvec2 dim_2d_array = uvec2(textureSize(_group_0_binding_4_vs, 0).xy); + uvec2 dim_2d_array_lod = uvec2(textureSize(_group_0_binding_4_vs, 1).xy); + uvec2 dim_cube = uvec2(textureSize(_group_0_binding_5_vs, 0).xy); + uvec2 dim_cube_lod = uvec2(textureSize(_group_0_binding_5_vs, 1).xy); + uvec2 dim_cube_array = uvec2(textureSize(_group_0_binding_6_vs, 0).xy); + uvec2 dim_cube_array_lod = uvec2(textureSize(_group_0_binding_6_vs, 1).xy); + uvec3 dim_3d = uvec3(textureSize(_group_0_binding_7_vs, 0).xyz); + uvec3 dim_3d_lod = uvec3(textureSize(_group_0_binding_7_vs, 1).xyz); + uvec2 dim_2s_ms = uvec2(textureSize(_group_0_binding_8_vs).xy); + uint sum = ((((((((((dim_1d + dim_2d.y) + dim_2d_lod.y) + dim_2d_array.y) + dim_2d_array_lod.y) + dim_cube.y) + dim_cube_lod.y) + dim_cube_array.y) + dim_cube_array_lod.y) + dim_3d.z) + dim_3d_lod.z); + gl_Position = vec4(float(sum)); + gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w); + return; +} + diff --git a/naga/tests/out/glsl/image.texture_sample.Fragment.glsl b/naga/tests/out/glsl/image.texture_sample.Fragment.glsl new file mode 100644 index 0000000000..97be5a59d0 --- /dev/null +++ b/naga/tests/out/glsl/image.texture_sample.Fragment.glsl @@ -0,0 +1,91 @@ +#version 310 es +#extension GL_EXT_texture_cube_map_array : require + +precision highp float; +precision highp int; + +uniform highp sampler2D _group_0_binding_0_fs; + +uniform highp sampler2D _group_0_binding_1_fs; + +uniform highp sampler2DArray _group_0_binding_4_fs; + +uniform highp samplerCubeArray _group_0_binding_6_fs; + +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + vec4 a = vec4(0.0); + vec2 tc = vec2(0.5); + vec3 tc3_ = vec3(0.5); + vec4 _e9 = texture(_group_0_binding_0_fs, vec2(tc.x, 0.0)); + vec4 _e10 = a; + a = (_e10 + _e9); + vec4 _e14 = texture(_group_0_binding_1_fs, vec2(tc)); + vec4 _e15 = a; + a = (_e15 + _e14); + vec4 _e19 = textureOffset(_group_0_binding_1_fs, vec2(tc), ivec2(3, 1)); + vec4 _e20 = a; + a = (_e20 + _e19); + vec4 _e24 = textureLod(_group_0_binding_1_fs, vec2(tc), 2.3); + vec4 _e25 = a; + a = (_e25 + _e24); + vec4 _e29 = textureLodOffset(_group_0_binding_1_fs, vec2(tc), 2.3, ivec2(3, 1)); + vec4 _e30 = a; + a = (_e30 + _e29); + vec4 _e35 = textureOffset(_group_0_binding_1_fs, vec2(tc), ivec2(3, 1), 2.0); + vec4 _e36 = a; + a = (_e36 + _e35); + vec4 _e41 = texture(_group_0_binding_4_fs, vec3(tc, 0u)); + vec4 _e42 = a; + a = (_e42 + _e41); + vec4 _e47 = textureOffset(_group_0_binding_4_fs, vec3(tc, 0u), ivec2(3, 1)); + vec4 _e48 = a; + a = (_e48 + _e47); + vec4 _e53 = textureLod(_group_0_binding_4_fs, vec3(tc, 0u), 2.3); + vec4 _e54 = a; + a = (_e54 + _e53); + vec4 _e59 = textureLodOffset(_group_0_binding_4_fs, vec3(tc, 0u), 2.3, ivec2(3, 1)); + vec4 _e60 = a; + a = (_e60 + _e59); + vec4 _e66 = textureOffset(_group_0_binding_4_fs, vec3(tc, 0u), ivec2(3, 1), 2.0); + vec4 _e67 = a; + a = (_e67 + _e66); + vec4 _e72 = texture(_group_0_binding_4_fs, vec3(tc, 0)); + vec4 _e73 = a; + a = (_e73 + _e72); + vec4 _e78 = textureOffset(_group_0_binding_4_fs, vec3(tc, 0), ivec2(3, 1)); + vec4 _e79 = a; + a = (_e79 + _e78); + vec4 _e84 = textureLod(_group_0_binding_4_fs, vec3(tc, 0), 2.3); + vec4 _e85 = a; + a = (_e85 + _e84); + vec4 _e90 = textureLodOffset(_group_0_binding_4_fs, vec3(tc, 0), 2.3, ivec2(3, 1)); + vec4 _e91 = a; + a = (_e91 + _e90); + vec4 _e97 = textureOffset(_group_0_binding_4_fs, vec3(tc, 0), ivec2(3, 1), 2.0); + vec4 _e98 = a; + a = (_e98 + _e97); + vec4 _e103 = texture(_group_0_binding_6_fs, vec4(tc3_, 0u)); + vec4 _e104 = a; + a = (_e104 + _e103); + vec4 _e109 = textureLod(_group_0_binding_6_fs, vec4(tc3_, 0u), 2.3); + vec4 _e110 = a; + a = (_e110 + _e109); + vec4 _e116 = texture(_group_0_binding_6_fs, vec4(tc3_, 0u), 2.0); + vec4 _e117 = a; + a = (_e117 + _e116); + vec4 _e122 = texture(_group_0_binding_6_fs, vec4(tc3_, 0)); + vec4 _e123 = a; + a = (_e123 + _e122); + vec4 _e128 = textureLod(_group_0_binding_6_fs, vec4(tc3_, 0), 2.3); + vec4 _e129 = a; + a = (_e129 + _e128); + vec4 _e135 = texture(_group_0_binding_6_fs, vec4(tc3_, 0), 2.0); + vec4 _e136 = a; + a = (_e136 + _e135); + vec4 _e138 = a; + _fs2p_location0 = _e138; + return; +} + diff --git a/naga/tests/out/glsl/image.texture_sample_comparison.Fragment.glsl b/naga/tests/out/glsl/image.texture_sample_comparison.Fragment.glsl new file mode 100644 index 0000000000..1dc303ed6f --- /dev/null +++ b/naga/tests/out/glsl/image.texture_sample_comparison.Fragment.glsl @@ -0,0 +1,47 @@ +#version 310 es +#extension GL_EXT_texture_cube_map_array : require + +precision highp float; +precision highp int; + +uniform highp sampler2DShadow _group_1_binding_2_fs; + +uniform highp sampler2DArrayShadow _group_1_binding_3_fs; + +uniform highp samplerCubeShadow _group_1_binding_4_fs; + +layout(location = 0) out float _fs2p_location0; + +void main() { + float a_1 = 0.0; + vec2 tc = vec2(0.5); + vec3 tc3_ = vec3(0.5); + float _e8 = texture(_group_1_binding_2_fs, vec3(tc, 0.5)); + float _e9 = a_1; + a_1 = (_e9 + _e8); + float _e14 = texture(_group_1_binding_3_fs, vec4(tc, 0u, 0.5)); + float _e15 = a_1; + a_1 = (_e15 + _e14); + float _e20 = texture(_group_1_binding_3_fs, vec4(tc, 0, 0.5)); + float _e21 = a_1; + a_1 = (_e21 + _e20); + float _e25 = texture(_group_1_binding_4_fs, vec4(tc3_, 0.5)); + float _e26 = a_1; + a_1 = (_e26 + _e25); + float _e30 = textureLod(_group_1_binding_2_fs, vec3(tc, 0.5), 0.0); + float _e31 = a_1; + a_1 = (_e31 + _e30); + float _e36 = textureGrad(_group_1_binding_3_fs, vec4(tc, 0u, 0.5), vec2(0.0), vec2(0.0)); + float _e37 = a_1; + a_1 = (_e37 + _e36); + float _e42 = textureGrad(_group_1_binding_3_fs, vec4(tc, 0, 0.5), vec2(0.0), vec2(0.0)); + float _e43 = a_1; + a_1 = (_e43 + _e42); + float _e47 = textureGrad(_group_1_binding_4_fs, vec4(tc3_, 0.5), vec3(0.0), vec3(0.0)); + float _e48 = a_1; + a_1 = (_e48 + _e47); + float _e50 = a_1; + _fs2p_location0 = _e50; + return; +} + diff --git a/naga/tests/out/glsl/interpolate.frag_main.Fragment.glsl b/naga/tests/out/glsl/interpolate.frag_main.Fragment.glsl new file mode 100644 index 0000000000..d1662da493 --- /dev/null +++ b/naga/tests/out/glsl/interpolate.frag_main.Fragment.glsl @@ -0,0 +1,24 @@ +#version 400 core +struct FragmentInput { + vec4 position; + uint _flat; + float _linear; + vec2 linear_centroid; + vec3 linear_sample; + vec4 perspective; + float perspective_centroid; + float perspective_sample; +}; +flat in uint _vs2fs_location0; +noperspective in float _vs2fs_location1; +noperspective centroid in vec2 _vs2fs_location2; +noperspective sample in vec3 _vs2fs_location3; +smooth in vec4 _vs2fs_location4; +smooth centroid in float _vs2fs_location5; +smooth sample in float _vs2fs_location6; + +void main() { + FragmentInput val = FragmentInput(gl_FragCoord, _vs2fs_location0, _vs2fs_location1, _vs2fs_location2, _vs2fs_location3, _vs2fs_location4, _vs2fs_location5, _vs2fs_location6); + return; +} + diff --git a/naga/tests/out/glsl/interpolate.vert_main.Vertex.glsl b/naga/tests/out/glsl/interpolate.vert_main.Vertex.glsl new file mode 100644 index 0000000000..f423a3dc18 --- /dev/null +++ b/naga/tests/out/glsl/interpolate.vert_main.Vertex.glsl @@ -0,0 +1,41 @@ +#version 400 core +struct FragmentInput { + vec4 position; + uint _flat; + float _linear; + vec2 linear_centroid; + vec3 linear_sample; + vec4 perspective; + float perspective_centroid; + float perspective_sample; +}; +flat out uint _vs2fs_location0; +noperspective out float _vs2fs_location1; +noperspective centroid out vec2 _vs2fs_location2; +noperspective sample out vec3 _vs2fs_location3; +smooth out vec4 _vs2fs_location4; +smooth centroid out float _vs2fs_location5; +smooth sample out float _vs2fs_location6; + +void main() { + FragmentInput out_ = FragmentInput(vec4(0.0), 0u, 0.0, vec2(0.0), vec3(0.0), vec4(0.0), 0.0, 0.0); + out_.position = vec4(2.0, 4.0, 5.0, 6.0); + out_._flat = 8u; + out_._linear = 27.0; + out_.linear_centroid = vec2(64.0, 125.0); + out_.linear_sample = vec3(216.0, 343.0, 512.0); + out_.perspective = vec4(729.0, 1000.0, 1331.0, 1728.0); + out_.perspective_centroid = 2197.0; + out_.perspective_sample = 2744.0; + FragmentInput _e30 = out_; + gl_Position = _e30.position; + _vs2fs_location0 = _e30._flat; + _vs2fs_location1 = _e30._linear; + _vs2fs_location2 = _e30.linear_centroid; + _vs2fs_location3 = _e30.linear_sample; + _vs2fs_location4 = _e30.perspective; + _vs2fs_location5 = _e30.perspective_centroid; + _vs2fs_location6 = _e30.perspective_sample; + return; +} + diff --git a/naga/tests/out/glsl/invariant.fs.Fragment.glsl b/naga/tests/out/glsl/invariant.fs.Fragment.glsl new file mode 100644 index 0000000000..9936a28ad3 --- /dev/null +++ b/naga/tests/out/glsl/invariant.fs.Fragment.glsl @@ -0,0 +1,11 @@ +#version 300 es + +precision highp float; +precision highp int; + + +void main() { + vec4 position = gl_FragCoord; + return; +} + diff --git a/naga/tests/out/glsl/invariant.vs.Vertex.glsl b/naga/tests/out/glsl/invariant.vs.Vertex.glsl new file mode 100644 index 0000000000..a34eab3479 --- /dev/null +++ b/naga/tests/out/glsl/invariant.vs.Vertex.glsl @@ -0,0 +1,12 @@ +#version 300 es + +precision highp float; +precision highp int; + +invariant gl_Position; + +void main() { + gl_Position = vec4(0.0); + return; +} + diff --git a/naga/tests/out/glsl/math-functions.main.Fragment.glsl b/naga/tests/out/glsl/math-functions.main.Fragment.glsl new file mode 100644 index 0000000000..ed81535ab5 --- /dev/null +++ b/naga/tests/out/glsl/math-functions.main.Fragment.glsl @@ -0,0 +1,104 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct _modf_result_f32_ { + float fract_; + float whole; +}; +struct _modf_result_vec2_f32_ { + vec2 fract_; + vec2 whole; +}; +struct _modf_result_vec4_f32_ { + vec4 fract_; + vec4 whole; +}; +struct _frexp_result_f32_ { + float fract_; + int exp_; +}; +struct _frexp_result_vec4_f32_ { + vec4 fract_; + ivec4 exp_; +}; + +_modf_result_f32_ naga_modf(float arg) { + float other; + float fract = modf(arg, other); + return _modf_result_f32_(fract, other); +} + +_modf_result_vec2_f32_ naga_modf(vec2 arg) { + vec2 other; + vec2 fract = modf(arg, other); + return _modf_result_vec2_f32_(fract, other); +} + +_modf_result_vec4_f32_ naga_modf(vec4 arg) { + vec4 other; + vec4 fract = modf(arg, other); + return _modf_result_vec4_f32_(fract, other); +} + +_frexp_result_f32_ naga_frexp(float arg) { + int other; + float fract = frexp(arg, other); + return _frexp_result_f32_(fract, other); +} + +_frexp_result_vec4_f32_ naga_frexp(vec4 arg) { + ivec4 other; + vec4 fract = frexp(arg, other); + return _frexp_result_vec4_f32_(fract, other); +} + +void main() { + vec4 v = vec4(0.0); + float a = degrees(1.0); + float b = radians(1.0); + vec4 c = degrees(v); + vec4 d = radians(v); + vec4 e = clamp(v, vec4(0.0), vec4(1.0)); + vec4 g = refract(v, v, 1.0); + int sign_a = sign(-1); + ivec4 sign_b = sign(ivec4(-1)); + float sign_c = sign(-1.0); + vec4 sign_d = sign(vec4(-1.0)); + int const_dot = ( + ivec2(0).x * ivec2(0).x + ivec2(0).y * ivec2(0).y); + uint first_leading_bit_abs = uint(findMSB(uint(abs(int(0u))))); + int flb_a = findMSB(-1); + ivec2 flb_b = findMSB(ivec2(-1)); + uvec2 flb_c = uvec2(findMSB(uvec2(1u))); + int ftb_a = findLSB(-1); + uint ftb_b = uint(findLSB(1u)); + ivec2 ftb_c = findLSB(ivec2(-1)); + uvec2 ftb_d = uvec2(findLSB(uvec2(1u))); + uint ctz_a = min(uint(findLSB(0u)), 32u); + int ctz_b = int(min(uint(findLSB(0)), 32u)); + uint ctz_c = min(uint(findLSB(4294967295u)), 32u); + int ctz_d = int(min(uint(findLSB(-1)), 32u)); + uvec2 ctz_e = min(uvec2(findLSB(uvec2(0u))), uvec2(32u)); + ivec2 ctz_f = ivec2(min(uvec2(findLSB(ivec2(0))), uvec2(32u))); + uvec2 ctz_g = min(uvec2(findLSB(uvec2(1u))), uvec2(32u)); + ivec2 ctz_h = ivec2(min(uvec2(findLSB(ivec2(1))), uvec2(32u))); + int clz_a = (-1 < 0 ? 0 : 31 - findMSB(-1)); + uint clz_b = uint(31 - findMSB(1u)); + ivec2 _e68 = ivec2(-1); + ivec2 clz_c = mix(ivec2(31) - findMSB(_e68), ivec2(0), lessThan(_e68, ivec2(0))); + uvec2 clz_d = uvec2(ivec2(31) - findMSB(uvec2(1u))); + float lde_a = ldexp(1.0, 2); + vec2 lde_b = ldexp(vec2(1.0, 2.0), ivec2(3, 4)); + _modf_result_f32_ modf_a = naga_modf(1.5); + float modf_b = naga_modf(1.5).fract_; + float modf_c = naga_modf(1.5).whole; + _modf_result_vec2_f32_ modf_d = naga_modf(vec2(1.5, 1.5)); + float modf_e = naga_modf(vec4(1.5, 1.5, 1.5, 1.5)).whole.x; + float modf_f = naga_modf(vec2(1.5, 1.5)).fract_.y; + _frexp_result_f32_ frexp_a = naga_frexp(1.5); + float frexp_b = naga_frexp(1.5).fract_; + int frexp_c = naga_frexp(1.5).exp_; + int frexp_d = naga_frexp(vec4(1.5, 1.5, 1.5, 1.5)).exp_.x; +} + diff --git a/naga/tests/out/glsl/multiview.main.Fragment.glsl b/naga/tests/out/glsl/multiview.main.Fragment.glsl new file mode 100644 index 0000000000..466aea062f --- /dev/null +++ b/naga/tests/out/glsl/multiview.main.Fragment.glsl @@ -0,0 +1,12 @@ +#version 310 es +#extension GL_EXT_multiview : require + +precision highp float; +precision highp int; + + +void main() { + int view_index = gl_ViewIndex; + return; +} + diff --git a/naga/tests/out/glsl/multiview_webgl.main.Fragment.glsl b/naga/tests/out/glsl/multiview_webgl.main.Fragment.glsl new file mode 100644 index 0000000000..30515289c9 --- /dev/null +++ b/naga/tests/out/glsl/multiview_webgl.main.Fragment.glsl @@ -0,0 +1,12 @@ +#version 300 es +#extension GL_OVR_multiview2 : require + +precision highp float; +precision highp int; + + +void main() { + int view_index = int(gl_ViewID_OVR); + return; +} + diff --git a/naga/tests/out/glsl/operators.main.Compute.glsl b/naga/tests/out/glsl/operators.main.Compute.glsl new file mode 100644 index 0000000000..006bce205e --- /dev/null +++ b/naga/tests/out/glsl/operators.main.Compute.glsl @@ -0,0 +1,261 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + +const vec4 v_f32_one = vec4(1.0, 1.0, 1.0, 1.0); +const vec4 v_f32_zero = vec4(0.0, 0.0, 0.0, 0.0); +const vec4 v_f32_half = vec4(0.5, 0.5, 0.5, 0.5); +const ivec4 v_i32_one = ivec4(1, 1, 1, 1); + + +vec4 builtins() { + int s1_ = (true ? 1 : 0); + vec4 s2_ = (true ? v_f32_one : v_f32_zero); + vec4 s3_ = mix(v_f32_one, v_f32_zero, bvec4(false, false, false, false)); + vec4 m1_ = mix(v_f32_zero, v_f32_one, v_f32_half); + vec4 m2_ = mix(v_f32_zero, v_f32_one, 0.1); + float b1_ = intBitsToFloat(1); + vec4 b2_ = intBitsToFloat(v_i32_one); + ivec4 v_i32_zero = ivec4(0, 0, 0, 0); + return (((((vec4((ivec4(s1_) + v_i32_zero)) + s2_) + m1_) + m2_) + vec4(b1_)) + b2_); +} + +vec4 splat(float m, int n) { + vec2 a_2 = (((vec2(2.0) + vec2(m)) - vec2(4.0)) / vec2(8.0)); + ivec4 b = (ivec4(n) % ivec4(2)); + return (a_2.xyxy + vec4(b)); +} + +vec2 splat_assignment() { + vec2 a = vec2(2.0); + vec2 _e4 = a; + a = (_e4 + vec2(1.0)); + vec2 _e8 = a; + a = (_e8 - vec2(3.0)); + vec2 _e12 = a; + a = (_e12 / vec2(4.0)); + vec2 _e15 = a; + return _e15; +} + +vec3 bool_cast(vec3 x) { + bvec3 y = bvec3(x); + return vec3(y); +} + +void logical() { + bool neg0_ = !(true); + bvec2 neg1_ = not(bvec2(true)); + bool or = (true || false); + bool and = (true && false); + bool bitwise_or0_ = (true || false); + bvec3 bitwise_or1_ = bvec3(bvec3(true).x || bvec3(false).x, bvec3(true).y || bvec3(false).y, bvec3(true).z || bvec3(false).z); + bool bitwise_and0_ = (true && false); + bvec4 bitwise_and1_ = bvec4(bvec4(true).x && bvec4(false).x, bvec4(true).y && bvec4(false).y, bvec4(true).z && bvec4(false).z, bvec4(true).w && bvec4(false).w); +} + +void arithmetic() { + float neg0_1 = -(1.0); + ivec2 neg1_1 = -(ivec2(1)); + vec2 neg2_ = -(vec2(1.0)); + int add0_ = (2 + 1); + uint add1_ = (2u + 1u); + float add2_ = (2.0 + 1.0); + ivec2 add3_ = (ivec2(2) + ivec2(1)); + uvec3 add4_ = (uvec3(2u) + uvec3(1u)); + vec4 add5_ = (vec4(2.0) + vec4(1.0)); + int sub0_ = (2 - 1); + uint sub1_ = (2u - 1u); + float sub2_ = (2.0 - 1.0); + ivec2 sub3_ = (ivec2(2) - ivec2(1)); + uvec3 sub4_ = (uvec3(2u) - uvec3(1u)); + vec4 sub5_ = (vec4(2.0) - vec4(1.0)); + int mul0_ = (2 * 1); + uint mul1_ = (2u * 1u); + float mul2_ = (2.0 * 1.0); + ivec2 mul3_ = (ivec2(2) * ivec2(1)); + uvec3 mul4_ = (uvec3(2u) * uvec3(1u)); + vec4 mul5_ = (vec4(2.0) * vec4(1.0)); + int div0_ = (2 / 1); + uint div1_ = (2u / 1u); + float div2_ = (2.0 / 1.0); + ivec2 div3_ = (ivec2(2) / ivec2(1)); + uvec3 div4_ = (uvec3(2u) / uvec3(1u)); + vec4 div5_ = (vec4(2.0) / vec4(1.0)); + int rem0_ = (2 % 1); + uint rem1_ = (2u % 1u); + float rem2_ = (2.0 - 1.0 * trunc(2.0 / 1.0)); + ivec2 rem3_ = (ivec2(2) % ivec2(1)); + uvec3 rem4_ = (uvec3(2u) % uvec3(1u)); + vec4 rem5_ = (vec4(2.0) - vec4(1.0) * trunc(vec4(2.0) / vec4(1.0))); + { + ivec2 add0_1 = (ivec2(2) + ivec2(1)); + ivec2 add1_1 = (ivec2(2) + ivec2(1)); + uvec2 add2_1 = (uvec2(2u) + uvec2(1u)); + uvec2 add3_1 = (uvec2(2u) + uvec2(1u)); + vec2 add4_1 = (vec2(2.0) + vec2(1.0)); + vec2 add5_1 = (vec2(2.0) + vec2(1.0)); + ivec2 sub0_1 = (ivec2(2) - ivec2(1)); + ivec2 sub1_1 = (ivec2(2) - ivec2(1)); + uvec2 sub2_1 = (uvec2(2u) - uvec2(1u)); + uvec2 sub3_1 = (uvec2(2u) - uvec2(1u)); + vec2 sub4_1 = (vec2(2.0) - vec2(1.0)); + vec2 sub5_1 = (vec2(2.0) - vec2(1.0)); + ivec2 mul0_1 = (ivec2(2) * 1); + ivec2 mul1_1 = (2 * ivec2(1)); + uvec2 mul2_1 = (uvec2(2u) * 1u); + uvec2 mul3_1 = (2u * uvec2(1u)); + vec2 mul4_1 = (vec2(2.0) * 1.0); + vec2 mul5_1 = (2.0 * vec2(1.0)); + ivec2 div0_1 = (ivec2(2) / ivec2(1)); + ivec2 div1_1 = (ivec2(2) / ivec2(1)); + uvec2 div2_1 = (uvec2(2u) / uvec2(1u)); + uvec2 div3_1 = (uvec2(2u) / uvec2(1u)); + vec2 div4_1 = (vec2(2.0) / vec2(1.0)); + vec2 div5_1 = (vec2(2.0) / vec2(1.0)); + ivec2 rem0_1 = (ivec2(2) % ivec2(1)); + ivec2 rem1_1 = (ivec2(2) % ivec2(1)); + uvec2 rem2_1 = (uvec2(2u) % uvec2(1u)); + uvec2 rem3_1 = (uvec2(2u) % uvec2(1u)); + vec2 rem4_1 = (vec2(2.0) - vec2(1.0) * trunc(vec2(2.0) / vec2(1.0))); + vec2 rem5_1 = (vec2(2.0) - vec2(1.0) * trunc(vec2(2.0) / vec2(1.0))); + } + mat3x3 add = (mat3x3(0.0) + mat3x3(0.0)); + mat3x3 sub = (mat3x3(0.0) - mat3x3(0.0)); + mat3x3 mul_scalar0_ = (mat3x3(0.0) * 1.0); + mat3x3 mul_scalar1_ = (2.0 * mat3x3(0.0)); + vec3 mul_vector0_ = (mat4x3(0.0) * vec4(1.0)); + vec4 mul_vector1_ = (vec3(2.0) * mat4x3(0.0)); + mat3x3 mul = (mat4x3(0.0) * mat3x4(0.0)); +} + +void bit() { + int flip0_ = ~(1); + uint flip1_ = ~(1u); + ivec2 flip2_ = ~(ivec2(1)); + uvec3 flip3_ = ~(uvec3(1u)); + int or0_ = (2 | 1); + uint or1_ = (2u | 1u); + ivec2 or2_ = (ivec2(2) | ivec2(1)); + uvec3 or3_ = (uvec3(2u) | uvec3(1u)); + int and0_ = (2 & 1); + uint and1_ = (2u & 1u); + ivec2 and2_ = (ivec2(2) & ivec2(1)); + uvec3 and3_ = (uvec3(2u) & uvec3(1u)); + int xor0_ = (2 ^ 1); + uint xor1_ = (2u ^ 1u); + ivec2 xor2_ = (ivec2(2) ^ ivec2(1)); + uvec3 xor3_ = (uvec3(2u) ^ uvec3(1u)); + int shl0_ = (2 << 1u); + uint shl1_ = (2u << 1u); + ivec2 shl2_ = (ivec2(2) << uvec2(1u)); + uvec3 shl3_ = (uvec3(2u) << uvec3(1u)); + int shr0_ = (2 >> 1u); + uint shr1_ = (2u >> 1u); + ivec2 shr2_ = (ivec2(2) >> uvec2(1u)); + uvec3 shr3_ = (uvec3(2u) >> uvec3(1u)); +} + +void comparison() { + bool eq0_ = (2 == 1); + bool eq1_ = (2u == 1u); + bool eq2_ = (2.0 == 1.0); + bvec2 eq3_ = equal(ivec2(2), ivec2(1)); + bvec3 eq4_ = equal(uvec3(2u), uvec3(1u)); + bvec4 eq5_ = equal(vec4(2.0), vec4(1.0)); + bool neq0_ = (2 != 1); + bool neq1_ = (2u != 1u); + bool neq2_ = (2.0 != 1.0); + bvec2 neq3_ = notEqual(ivec2(2), ivec2(1)); + bvec3 neq4_ = notEqual(uvec3(2u), uvec3(1u)); + bvec4 neq5_ = notEqual(vec4(2.0), vec4(1.0)); + bool lt0_ = (2 < 1); + bool lt1_ = (2u < 1u); + bool lt2_ = (2.0 < 1.0); + bvec2 lt3_ = lessThan(ivec2(2), ivec2(1)); + bvec3 lt4_ = lessThan(uvec3(2u), uvec3(1u)); + bvec4 lt5_ = lessThan(vec4(2.0), vec4(1.0)); + bool lte0_ = (2 <= 1); + bool lte1_ = (2u <= 1u); + bool lte2_ = (2.0 <= 1.0); + bvec2 lte3_ = lessThanEqual(ivec2(2), ivec2(1)); + bvec3 lte4_ = lessThanEqual(uvec3(2u), uvec3(1u)); + bvec4 lte5_ = lessThanEqual(vec4(2.0), vec4(1.0)); + bool gt0_ = (2 > 1); + bool gt1_ = (2u > 1u); + bool gt2_ = (2.0 > 1.0); + bvec2 gt3_ = greaterThan(ivec2(2), ivec2(1)); + bvec3 gt4_ = greaterThan(uvec3(2u), uvec3(1u)); + bvec4 gt5_ = greaterThan(vec4(2.0), vec4(1.0)); + bool gte0_ = (2 >= 1); + bool gte1_ = (2u >= 1u); + bool gte2_ = (2.0 >= 1.0); + bvec2 gte3_ = greaterThanEqual(ivec2(2), ivec2(1)); + bvec3 gte4_ = greaterThanEqual(uvec3(2u), uvec3(1u)); + bvec4 gte5_ = greaterThanEqual(vec4(2.0), vec4(1.0)); +} + +void assignment() { + int a_1 = 0; + ivec3 vec0_ = ivec3(0); + a_1 = 1; + int _e5 = a_1; + a_1 = (_e5 + 1); + int _e7 = a_1; + a_1 = (_e7 - 1); + int _e9 = a_1; + int _e10 = a_1; + a_1 = (_e10 * _e9); + int _e12 = a_1; + int _e13 = a_1; + a_1 = (_e13 / _e12); + int _e15 = a_1; + a_1 = (_e15 % 1); + int _e17 = a_1; + a_1 = (_e17 & 0); + int _e19 = a_1; + a_1 = (_e19 | 0); + int _e21 = a_1; + a_1 = (_e21 ^ 0); + int _e23 = a_1; + a_1 = (_e23 << 2u); + int _e25 = a_1; + a_1 = (_e25 >> 1u); + int _e28 = a_1; + a_1 = (_e28 + 1); + int _e31 = a_1; + a_1 = (_e31 - 1); + int _e37 = vec0_[1]; + vec0_[1] = (_e37 + 1); + int _e41 = vec0_[1]; + vec0_[1] = (_e41 - 1); + return; +} + +void negation_avoids_prefix_decrement() { + int p0_ = -(1); + int p1_ = -(-(1)); + int p2_ = -(-(1)); + int p3_ = -(-(1)); + int p4_ = -(-(-(1))); + int p5_ = -(-(-(-(1)))); + int p6_ = -(-(-(-(-(1))))); + int p7_ = -(-(-(-(-(1))))); +} + +void main() { + uvec3 id = gl_WorkGroupID; + vec4 _e1 = builtins(); + vec4 _e6 = splat(float(id.x), int(id.y)); + vec3 _e11 = bool_cast(vec3(1.0, 1.0, 1.0)); + logical(); + arithmetic(); + bit(); + comparison(); + assignment(); + return; +} + diff --git a/naga/tests/out/glsl/padding.vertex.Vertex.glsl b/naga/tests/out/glsl/padding.vertex.Vertex.glsl new file mode 100644 index 0000000000..82d8d31fa9 --- /dev/null +++ b/naga/tests/out/glsl/padding.vertex.Vertex.glsl @@ -0,0 +1,36 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct S { + vec3 a; +}; +struct Test { + S a; + float b; +}; +struct Test2_ { + vec3 a[2]; + float b; +}; +struct Test3_ { + mat4x3 a; + float b; +}; +uniform Test_block_0Vertex { Test _group_0_binding_0_vs; }; + +uniform Test2_block_1Vertex { Test2_ _group_0_binding_1_vs; }; + +uniform Test3_block_2Vertex { Test3_ _group_0_binding_2_vs; }; + + +void main() { + float _e4 = _group_0_binding_0_vs.b; + float _e8 = _group_0_binding_1_vs.b; + float _e12 = _group_0_binding_2_vs.b; + gl_Position = (((vec4(1.0) * _e4) * _e8) * _e12); + gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w); + return; +} + diff --git a/naga/tests/out/glsl/push-constants.main.Fragment.glsl b/naga/tests/out/glsl/push-constants.main.Fragment.glsl new file mode 100644 index 0000000000..8131e9e897 --- /dev/null +++ b/naga/tests/out/glsl/push-constants.main.Fragment.glsl @@ -0,0 +1,23 @@ +#version 320 es + +precision highp float; +precision highp int; + +struct PushConstants { + float multiplier; +}; +struct FragmentIn { + vec4 color; +}; +uniform PushConstants _push_constant_binding_fs; + +layout(location = 0) smooth in vec4 _vs2fs_location0; +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + FragmentIn in_ = FragmentIn(_vs2fs_location0); + float _e4 = _push_constant_binding_fs.multiplier; + _fs2p_location0 = (in_.color * _e4); + return; +} + diff --git a/naga/tests/out/glsl/push-constants.vert_main.Vertex.glsl b/naga/tests/out/glsl/push-constants.vert_main.Vertex.glsl new file mode 100644 index 0000000000..53b218a946 --- /dev/null +++ b/naga/tests/out/glsl/push-constants.vert_main.Vertex.glsl @@ -0,0 +1,26 @@ +#version 320 es + +precision highp float; +precision highp int; + +uniform uint naga_vs_first_instance; + +struct PushConstants { + float multiplier; +}; +struct FragmentIn { + vec4 color; +}; +uniform PushConstants _push_constant_binding_vs; + +layout(location = 0) in vec2 _p2vs_location0; + +void main() { + vec2 pos = _p2vs_location0; + uint ii = (uint(gl_InstanceID) + naga_vs_first_instance); + uint vi = uint(gl_VertexID); + float _e8 = _push_constant_binding_vs.multiplier; + gl_Position = vec4((((float(ii) * float(vi)) * _e8) * pos), 0.0, 1.0); + return; +} + diff --git a/naga/tests/out/glsl/quad-vert.main.Vertex.glsl b/naga/tests/out/glsl/quad-vert.main.Vertex.glsl new file mode 100644 index 0000000000..db5089c2df --- /dev/null +++ b/naga/tests/out/glsl/quad-vert.main.Vertex.glsl @@ -0,0 +1,50 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct gen_gl_PerVertex { + vec4 gen_gl_Position; + float gen_gl_PointSize; + float gen_gl_ClipDistance[1]; + float gen_gl_CullDistance[1]; +}; +struct type_4 { + vec2 member; + vec4 gen_gl_Position; +}; +vec2 v_uv = vec2(0.0); + +vec2 a_uv_1 = vec2(0.0); + +gen_gl_PerVertex perVertexStruct = gen_gl_PerVertex(vec4(0.0, 0.0, 0.0, 1.0), 1.0, float[1](0.0), float[1](0.0)); + +vec2 a_pos_1 = vec2(0.0); + +layout(location = 1) in vec2 _p2vs_location1; +layout(location = 0) in vec2 _p2vs_location0; +layout(location = 0) smooth out vec2 _vs2fs_location0; + +void main_1() { + vec2 _e6 = a_uv_1; + v_uv = _e6; + vec2 _e7 = a_pos_1; + perVertexStruct.gen_gl_Position = vec4(_e7.x, _e7.y, 0.0, 1.0); + return; +} + +void main() { + vec2 a_uv = _p2vs_location1; + vec2 a_pos = _p2vs_location0; + a_uv_1 = a_uv; + a_pos_1 = a_pos; + main_1(); + vec2 _e7 = v_uv; + vec4 _e8 = perVertexStruct.gen_gl_Position; + type_4 _tmp_return = type_4(_e7, _e8); + _vs2fs_location0 = _tmp_return.member; + gl_Position = _tmp_return.gen_gl_Position; + gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w); + return; +} + diff --git a/naga/tests/out/glsl/quad.frag_main.Fragment.glsl b/naga/tests/out/glsl/quad.frag_main.Fragment.glsl new file mode 100644 index 0000000000..c69b9eb5fe --- /dev/null +++ b/naga/tests/out/glsl/quad.frag_main.Fragment.glsl @@ -0,0 +1,27 @@ +#version 300 es + +precision highp float; +precision highp int; + +struct VertexOutput { + vec2 uv; + vec4 position; +}; +const float c_scale = 1.2; + +uniform highp sampler2D _group_0_binding_0_fs; + +smooth in vec2 _vs2fs_location0; +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + vec2 uv_1 = _vs2fs_location0; + vec4 color = texture(_group_0_binding_0_fs, vec2(uv_1)); + if ((color.w == 0.0)) { + discard; + } + vec4 premultiplied = (color.w * color); + _fs2p_location0 = premultiplied; + return; +} + diff --git a/naga/tests/out/glsl/quad.fs_extra.Fragment.glsl b/naga/tests/out/glsl/quad.fs_extra.Fragment.glsl new file mode 100644 index 0000000000..31674b12d6 --- /dev/null +++ b/naga/tests/out/glsl/quad.fs_extra.Fragment.glsl @@ -0,0 +1,18 @@ +#version 300 es + +precision highp float; +precision highp int; + +struct VertexOutput { + vec2 uv; + vec4 position; +}; +const float c_scale = 1.2; + +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + _fs2p_location0 = vec4(0.0, 0.5, 0.0, 0.5); + return; +} + diff --git a/naga/tests/out/glsl/quad.vert_main.Vertex.glsl b/naga/tests/out/glsl/quad.vert_main.Vertex.glsl new file mode 100644 index 0000000000..c5bca7f666 --- /dev/null +++ b/naga/tests/out/glsl/quad.vert_main.Vertex.glsl @@ -0,0 +1,24 @@ +#version 300 es + +precision highp float; +precision highp int; + +struct VertexOutput { + vec2 uv; + vec4 position; +}; +const float c_scale = 1.2; + +layout(location = 0) in vec2 _p2vs_location0; +layout(location = 1) in vec2 _p2vs_location1; +smooth out vec2 _vs2fs_location0; + +void main() { + vec2 pos = _p2vs_location0; + vec2 uv = _p2vs_location1; + VertexOutput _tmp_return = VertexOutput(uv, vec4((c_scale * pos), 0.0, 1.0)); + _vs2fs_location0 = _tmp_return.uv; + gl_Position = _tmp_return.position; + return; +} + diff --git a/naga/tests/out/glsl/separate-entry-points.compute.Compute.glsl b/naga/tests/out/glsl/separate-entry-points.compute.Compute.glsl new file mode 100644 index 0000000000..869b7ca418 --- /dev/null +++ b/naga/tests/out/glsl/separate-entry-points.compute.Compute.glsl @@ -0,0 +1,21 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + +void barriers() { + memoryBarrierBuffer(); + barrier(); + memoryBarrierShared(); + barrier(); + return; +} + +void main() { + barriers(); + return; +} + diff --git a/naga/tests/out/glsl/separate-entry-points.fragment.Fragment.glsl b/naga/tests/out/glsl/separate-entry-points.fragment.Fragment.glsl new file mode 100644 index 0000000000..9ea32684cd --- /dev/null +++ b/naga/tests/out/glsl/separate-entry-points.fragment.Fragment.glsl @@ -0,0 +1,19 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(location = 0) out vec4 _fs2p_location0; + +void derivatives() { + float x = dFdx(0.0); + float y = dFdy(0.0); + float width = fwidth(0.0); +} + +void main() { + derivatives(); + _fs2p_location0 = vec4(0.0); + return; +} + diff --git a/naga/tests/out/glsl/shadow.fs_main.Fragment.glsl b/naga/tests/out/glsl/shadow.fs_main.Fragment.glsl new file mode 100644 index 0000000000..61c14561d5 --- /dev/null +++ b/naga/tests/out/glsl/shadow.fs_main.Fragment.glsl @@ -0,0 +1,84 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct Globals { + mat4x4 view_proj; + uvec4 num_lights; +}; +struct Entity { + mat4x4 world; + vec4 color; +}; +struct VertexOutput { + vec4 proj_position; + vec3 world_normal; + vec4 world_position; +}; +struct Light { + mat4x4 proj; + vec4 pos; + vec4 color; +}; +const vec3 c_ambient = vec3(0.05, 0.05, 0.05); +const uint c_max_lights = 10u; + +uniform Globals_block_0Fragment { Globals _group_0_binding_0_fs; }; + +uniform Entity_block_1Fragment { Entity _group_1_binding_0_fs; }; + +layout(std430) readonly buffer type_6_block_2Fragment { Light _group_0_binding_1_fs[]; }; + +uniform highp sampler2DArrayShadow _group_0_binding_2_fs; + +layout(location = 0) smooth in vec3 _vs2fs_location0; +layout(location = 1) smooth in vec4 _vs2fs_location1; +layout(location = 0) out vec4 _fs2p_location0; + +float fetch_shadow(uint light_id, vec4 homogeneous_coords) { + if ((homogeneous_coords.w <= 0.0)) { + return 1.0; + } + vec2 flip_correction = vec2(0.5, -0.5); + float proj_correction = (1.0 / homogeneous_coords.w); + vec2 light_local = (((homogeneous_coords.xy * flip_correction) * proj_correction) + vec2(0.5, 0.5)); + float _e24 = textureGrad(_group_0_binding_2_fs, vec4(light_local, int(light_id), (homogeneous_coords.z * proj_correction)), vec2(0.0), vec2(0.0)); + return _e24; +} + +void main() { + VertexOutput in_ = VertexOutput(gl_FragCoord, _vs2fs_location0, _vs2fs_location1); + vec3 color = c_ambient; + uint i = 0u; + vec3 normal_1 = normalize(in_.world_normal); + bool loop_init = true; + while(true) { + if (!loop_init) { + uint _e40 = i; + i = (_e40 + 1u); + } + loop_init = false; + uint _e7 = i; + uint _e11 = _group_0_binding_0_fs.num_lights.x; + if ((_e7 < min(_e11, c_max_lights))) { + } else { + break; + } + { + uint _e16 = i; + Light light = _group_0_binding_1_fs[_e16]; + uint _e19 = i; + float _e23 = fetch_shadow(_e19, (light.proj * in_.world_position)); + vec3 light_dir = normalize((light.pos.xyz - in_.world_position.xyz)); + float diffuse = max(0.0, dot(normal_1, light_dir)); + vec3 _e37 = color; + color = (_e37 + ((_e23 * diffuse) * light.color.xyz)); + } + } + vec3 _e42 = color; + vec4 _e47 = _group_1_binding_0_fs.color; + _fs2p_location0 = (vec4(_e42, 1.0) * _e47); + return; +} + diff --git a/naga/tests/out/glsl/shadow.fs_main_without_storage.Fragment.glsl b/naga/tests/out/glsl/shadow.fs_main_without_storage.Fragment.glsl new file mode 100644 index 0000000000..57677c91a6 --- /dev/null +++ b/naga/tests/out/glsl/shadow.fs_main_without_storage.Fragment.glsl @@ -0,0 +1,84 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct Globals { + mat4x4 view_proj; + uvec4 num_lights; +}; +struct Entity { + mat4x4 world; + vec4 color; +}; +struct VertexOutput { + vec4 proj_position; + vec3 world_normal; + vec4 world_position; +}; +struct Light { + mat4x4 proj; + vec4 pos; + vec4 color; +}; +const vec3 c_ambient = vec3(0.05, 0.05, 0.05); +const uint c_max_lights = 10u; + +uniform Globals_block_0Fragment { Globals _group_0_binding_0_fs; }; + +uniform Entity_block_1Fragment { Entity _group_1_binding_0_fs; }; + +uniform type_7_block_2Fragment { Light _group_0_binding_1_fs[10]; }; + +uniform highp sampler2DArrayShadow _group_0_binding_2_fs; + +layout(location = 0) smooth in vec3 _vs2fs_location0; +layout(location = 1) smooth in vec4 _vs2fs_location1; +layout(location = 0) out vec4 _fs2p_location0; + +float fetch_shadow(uint light_id, vec4 homogeneous_coords) { + if ((homogeneous_coords.w <= 0.0)) { + return 1.0; + } + vec2 flip_correction = vec2(0.5, -0.5); + float proj_correction = (1.0 / homogeneous_coords.w); + vec2 light_local = (((homogeneous_coords.xy * flip_correction) * proj_correction) + vec2(0.5, 0.5)); + float _e24 = textureGrad(_group_0_binding_2_fs, vec4(light_local, int(light_id), (homogeneous_coords.z * proj_correction)), vec2(0.0), vec2(0.0)); + return _e24; +} + +void main() { + VertexOutput in_1 = VertexOutput(gl_FragCoord, _vs2fs_location0, _vs2fs_location1); + vec3 color_1 = c_ambient; + uint i_1 = 0u; + vec3 normal_1 = normalize(in_1.world_normal); + bool loop_init = true; + while(true) { + if (!loop_init) { + uint _e40 = i_1; + i_1 = (_e40 + 1u); + } + loop_init = false; + uint _e7 = i_1; + uint _e11 = _group_0_binding_0_fs.num_lights.x; + if ((_e7 < min(_e11, c_max_lights))) { + } else { + break; + } + { + uint _e16 = i_1; + Light light = _group_0_binding_1_fs[_e16]; + uint _e19 = i_1; + float _e23 = fetch_shadow(_e19, (light.proj * in_1.world_position)); + vec3 light_dir = normalize((light.pos.xyz - in_1.world_position.xyz)); + float diffuse = max(0.0, dot(normal_1, light_dir)); + vec3 _e37 = color_1; + color_1 = (_e37 + ((_e23 * diffuse) * light.color.xyz)); + } + } + vec3 _e42 = color_1; + vec4 _e47 = _group_1_binding_0_fs.color; + _fs2p_location0 = (vec4(_e42, 1.0) * _e47); + return; +} + diff --git a/naga/tests/out/glsl/shadow.vs_main.Vertex.glsl b/naga/tests/out/glsl/shadow.vs_main.Vertex.glsl new file mode 100644 index 0000000000..631c412f2a --- /dev/null +++ b/naga/tests/out/glsl/shadow.vs_main.Vertex.glsl @@ -0,0 +1,54 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct Globals { + mat4x4 view_proj; + uvec4 num_lights; +}; +struct Entity { + mat4x4 world; + vec4 color; +}; +struct VertexOutput { + vec4 proj_position; + vec3 world_normal; + vec4 world_position; +}; +struct Light { + mat4x4 proj; + vec4 pos; + vec4 color; +}; +const vec3 c_ambient = vec3(0.05, 0.05, 0.05); +const uint c_max_lights = 10u; + +uniform Globals_block_0Vertex { Globals _group_0_binding_0_vs; }; + +uniform Entity_block_1Vertex { Entity _group_1_binding_0_vs; }; + +layout(location = 0) in ivec4 _p2vs_location0; +layout(location = 1) in ivec4 _p2vs_location1; +layout(location = 0) smooth out vec3 _vs2fs_location0; +layout(location = 1) smooth out vec4 _vs2fs_location1; + +void main() { + ivec4 position = _p2vs_location0; + ivec4 normal = _p2vs_location1; + VertexOutput out_ = VertexOutput(vec4(0.0), vec3(0.0), vec4(0.0)); + mat4x4 w = _group_1_binding_0_vs.world; + mat4x4 _e7 = _group_1_binding_0_vs.world; + vec4 world_pos = (_e7 * vec4(position)); + out_.world_normal = (mat3x3(w[0].xyz, w[1].xyz, w[2].xyz) * vec3(normal.xyz)); + out_.world_position = world_pos; + mat4x4 _e26 = _group_0_binding_0_vs.view_proj; + out_.proj_position = (_e26 * world_pos); + VertexOutput _e28 = out_; + gl_Position = _e28.proj_position; + _vs2fs_location0 = _e28.world_normal; + _vs2fs_location1 = _e28.world_position; + gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w); + return; +} + diff --git a/naga/tests/out/glsl/skybox.fs_main.Fragment.glsl b/naga/tests/out/glsl/skybox.fs_main.Fragment.glsl new file mode 100644 index 0000000000..1334614815 --- /dev/null +++ b/naga/tests/out/glsl/skybox.fs_main.Fragment.glsl @@ -0,0 +1,25 @@ +#version 320 es + +precision highp float; +precision highp int; + +struct VertexOutput { + vec4 position; + vec3 uv; +}; +struct Data { + mat4x4 proj_inv; + mat4x4 view; +}; +layout(binding = 0) uniform highp samplerCube _group_0_binding_1_fs; + +layout(location = 0) smooth in vec3 _vs2fs_location0; +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + VertexOutput in_ = VertexOutput(gl_FragCoord, _vs2fs_location0); + vec4 _e4 = texture(_group_0_binding_1_fs, vec3(in_.uv)); + _fs2p_location0 = _e4; + return; +} + diff --git a/naga/tests/out/glsl/skybox.vs_main.Vertex.glsl b/naga/tests/out/glsl/skybox.vs_main.Vertex.glsl new file mode 100644 index 0000000000..e40addaa2f --- /dev/null +++ b/naga/tests/out/glsl/skybox.vs_main.Vertex.glsl @@ -0,0 +1,38 @@ +#version 320 es + +precision highp float; +precision highp int; + +struct VertexOutput { + vec4 position; + vec3 uv; +}; +struct Data { + mat4x4 proj_inv; + mat4x4 view; +}; +layout(std140, binding = 0) uniform Data_block_0Vertex { Data _group_0_binding_0_vs; }; + +layout(location = 0) smooth out vec3 _vs2fs_location0; + +void main() { + uint vertex_index = uint(gl_VertexID); + int tmp1_ = 0; + int tmp2_ = 0; + tmp1_ = (int(vertex_index) / 2); + tmp2_ = (int(vertex_index) & 1); + int _e9 = tmp1_; + int _e15 = tmp2_; + vec4 pos = vec4(((float(_e9) * 4.0) - 1.0), ((float(_e15) * 4.0) - 1.0), 0.0, 1.0); + vec4 _e27 = _group_0_binding_0_vs.view[0]; + vec4 _e32 = _group_0_binding_0_vs.view[1]; + vec4 _e37 = _group_0_binding_0_vs.view[2]; + mat3x3 inv_model_view = transpose(mat3x3(_e27.xyz, _e32.xyz, _e37.xyz)); + mat4x4 _e43 = _group_0_binding_0_vs.proj_inv; + vec4 unprojected = (_e43 * pos); + VertexOutput _tmp_return = VertexOutput(pos, (inv_model_view * unprojected.xyz)); + gl_Position = _tmp_return.position; + _vs2fs_location0 = _tmp_return.uv; + return; +} + diff --git a/naga/tests/out/glsl/standard.derivatives.Fragment.glsl b/naga/tests/out/glsl/standard.derivatives.Fragment.glsl new file mode 100644 index 0000000000..18e150a3fe --- /dev/null +++ b/naga/tests/out/glsl/standard.derivatives.Fragment.glsl @@ -0,0 +1,42 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(location = 0) out vec4 _fs2p_location0; + +bool test_any_and_all_for_bool() { + return true; +} + +void main() { + vec4 foo = gl_FragCoord; + vec4 x = vec4(0.0); + vec4 y = vec4(0.0); + vec4 z = vec4(0.0); + vec4 _e1 = dFdx(foo); + x = _e1; + vec4 _e3 = dFdy(foo); + y = _e3; + vec4 _e5 = fwidth(foo); + z = _e5; + vec4 _e7 = dFdx(foo); + x = _e7; + vec4 _e8 = dFdy(foo); + y = _e8; + vec4 _e9 = fwidth(foo); + z = _e9; + vec4 _e10 = dFdx(foo); + x = _e10; + vec4 _e11 = dFdy(foo); + y = _e11; + vec4 _e12 = fwidth(foo); + z = _e12; + bool _e13 = test_any_and_all_for_bool(); + vec4 _e14 = x; + vec4 _e15 = y; + vec4 _e17 = z; + _fs2p_location0 = ((_e14 + _e15) * _e17); + return; +} + diff --git a/naga/tests/out/glsl/struct-layout.needs_padding_comp.Compute.glsl b/naga/tests/out/glsl/struct-layout.needs_padding_comp.Compute.glsl new file mode 100644 index 0000000000..7fe97f2982 --- /dev/null +++ b/naga/tests/out/glsl/struct-layout.needs_padding_comp.Compute.glsl @@ -0,0 +1,30 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 16, local_size_y = 1, local_size_z = 1) in; + +struct NoPadding { + vec3 v3_; + float f3_; +}; +struct NeedsPadding { + float f3_forces_padding; + vec3 v3_needs_padding; + float f3_; +}; +uniform NeedsPadding_block_0Compute { NeedsPadding _group_0_binding_2_cs; }; + +layout(std430) buffer NeedsPadding_block_1Compute { NeedsPadding _group_0_binding_3_cs; }; + + +void main() { + NeedsPadding x_1 = NeedsPadding(0.0, vec3(0.0), 0.0); + NeedsPadding _e2 = _group_0_binding_2_cs; + x_1 = _e2; + NeedsPadding _e4 = _group_0_binding_3_cs; + x_1 = _e4; + return; +} + diff --git a/naga/tests/out/glsl/struct-layout.needs_padding_frag.Fragment.glsl b/naga/tests/out/glsl/struct-layout.needs_padding_frag.Fragment.glsl new file mode 100644 index 0000000000..72222cef17 --- /dev/null +++ b/naga/tests/out/glsl/struct-layout.needs_padding_frag.Fragment.glsl @@ -0,0 +1,25 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct NoPadding { + vec3 v3_; + float f3_; +}; +struct NeedsPadding { + float f3_forces_padding; + vec3 v3_needs_padding; + float f3_; +}; +layout(location = 0) smooth in float _vs2fs_location0; +layout(location = 1) smooth in vec3 _vs2fs_location1; +layout(location = 2) smooth in float _vs2fs_location2; +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + NeedsPadding input_2 = NeedsPadding(_vs2fs_location0, _vs2fs_location1, _vs2fs_location2); + _fs2p_location0 = vec4(0.0); + return; +} + diff --git a/naga/tests/out/glsl/struct-layout.needs_padding_vert.Vertex.glsl b/naga/tests/out/glsl/struct-layout.needs_padding_vert.Vertex.glsl new file mode 100644 index 0000000000..e7cc416488 --- /dev/null +++ b/naga/tests/out/glsl/struct-layout.needs_padding_vert.Vertex.glsl @@ -0,0 +1,25 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct NoPadding { + vec3 v3_; + float f3_; +}; +struct NeedsPadding { + float f3_forces_padding; + vec3 v3_needs_padding; + float f3_; +}; +layout(location = 0) in float _p2vs_location0; +layout(location = 1) in vec3 _p2vs_location1; +layout(location = 2) in float _p2vs_location2; + +void main() { + NeedsPadding input_3 = NeedsPadding(_p2vs_location0, _p2vs_location1, _p2vs_location2); + gl_Position = vec4(0.0); + gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w); + return; +} + diff --git a/naga/tests/out/glsl/struct-layout.no_padding_comp.Compute.glsl b/naga/tests/out/glsl/struct-layout.no_padding_comp.Compute.glsl new file mode 100644 index 0000000000..c7bbcf7b70 --- /dev/null +++ b/naga/tests/out/glsl/struct-layout.no_padding_comp.Compute.glsl @@ -0,0 +1,30 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 16, local_size_y = 1, local_size_z = 1) in; + +struct NoPadding { + vec3 v3_; + float f3_; +}; +struct NeedsPadding { + float f3_forces_padding; + vec3 v3_needs_padding; + float f3_; +}; +uniform NoPadding_block_0Compute { NoPadding _group_0_binding_0_cs; }; + +layout(std430) buffer NoPadding_block_1Compute { NoPadding _group_0_binding_1_cs; }; + + +void main() { + NoPadding x = NoPadding(vec3(0.0), 0.0); + NoPadding _e2 = _group_0_binding_0_cs; + x = _e2; + NoPadding _e4 = _group_0_binding_1_cs; + x = _e4; + return; +} + diff --git a/naga/tests/out/glsl/struct-layout.no_padding_frag.Fragment.glsl b/naga/tests/out/glsl/struct-layout.no_padding_frag.Fragment.glsl new file mode 100644 index 0000000000..135aece262 --- /dev/null +++ b/naga/tests/out/glsl/struct-layout.no_padding_frag.Fragment.glsl @@ -0,0 +1,24 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct NoPadding { + vec3 v3_; + float f3_; +}; +struct NeedsPadding { + float f3_forces_padding; + vec3 v3_needs_padding; + float f3_; +}; +layout(location = 0) smooth in vec3 _vs2fs_location0; +layout(location = 1) smooth in float _vs2fs_location1; +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + NoPadding input_ = NoPadding(_vs2fs_location0, _vs2fs_location1); + _fs2p_location0 = vec4(0.0); + return; +} + diff --git a/naga/tests/out/glsl/struct-layout.no_padding_vert.Vertex.glsl b/naga/tests/out/glsl/struct-layout.no_padding_vert.Vertex.glsl new file mode 100644 index 0000000000..46f9ef61c2 --- /dev/null +++ b/naga/tests/out/glsl/struct-layout.no_padding_vert.Vertex.glsl @@ -0,0 +1,24 @@ +#version 310 es + +precision highp float; +precision highp int; + +struct NoPadding { + vec3 v3_; + float f3_; +}; +struct NeedsPadding { + float f3_forces_padding; + vec3 v3_needs_padding; + float f3_; +}; +layout(location = 0) in vec3 _p2vs_location0; +layout(location = 1) in float _p2vs_location1; + +void main() { + NoPadding input_1 = NoPadding(_p2vs_location0, _p2vs_location1); + gl_Position = vec4(0.0); + gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w); + return; +} + diff --git a/naga/tests/out/glsl/texture-arg.main.Fragment.glsl b/naga/tests/out/glsl/texture-arg.main.Fragment.glsl new file mode 100644 index 0000000000..234043d95c --- /dev/null +++ b/naga/tests/out/glsl/texture-arg.main.Fragment.glsl @@ -0,0 +1,20 @@ +#version 310 es + +precision highp float; +precision highp int; + +uniform highp sampler2D _group_0_binding_0_fs; + +layout(location = 0) out vec4 _fs2p_location0; + +vec4 test(highp sampler2D Passed_Texture) { + vec4 _e5 = texture(Passed_Texture, vec2(vec2(0.0, 0.0))); + return _e5; +} + +void main() { + vec4 _e2 = test(_group_0_binding_0_fs); + _fs2p_location0 = _e2; + return; +} + diff --git a/naga/tests/out/glsl/variations.main.Fragment.glsl b/naga/tests/out/glsl/variations.main.Fragment.glsl new file mode 100644 index 0000000000..5ea3eb03cf --- /dev/null +++ b/naga/tests/out/glsl/variations.main.Fragment.glsl @@ -0,0 +1,21 @@ +#version 310 es + +precision highp float; +precision highp int; + +uniform highp samplerCube _group_0_binding_0_fs; + + +void main_1() { + ivec2 sizeCube = ivec2(0); + float a = 0.0; + sizeCube = ivec2(uvec2(textureSize(_group_0_binding_0_fs, 0).xy)); + a = ceil(1.0); + return; +} + +void main() { + main_1(); + return; +} + diff --git a/naga/tests/out/glsl/workgroup-uniform-load.test_workgroupUniformLoad.Compute.glsl b/naga/tests/out/glsl/workgroup-uniform-load.test_workgroupUniformLoad.Compute.glsl new file mode 100644 index 0000000000..6315309c99 --- /dev/null +++ b/naga/tests/out/glsl/workgroup-uniform-load.test_workgroupUniformLoad.Compute.glsl @@ -0,0 +1,33 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 4, local_size_y = 1, local_size_z = 1) in; + +const uint SIZE = 128u; + +shared int arr_i32_[128]; + + +void main() { + if (gl_LocalInvocationID == uvec3(0u)) { + arr_i32_ = int[128](0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + memoryBarrierShared(); + barrier(); + uvec3 workgroup_id = gl_WorkGroupID; + memoryBarrierShared(); + barrier(); + int _e4 = arr_i32_[workgroup_id.x]; + memoryBarrierShared(); + barrier(); + if ((_e4 > 10)) { + memoryBarrierShared(); + barrier(); + return; + } else { + return; + } +} + diff --git a/naga/tests/out/glsl/workgroup-var-init.main.Compute.glsl b/naga/tests/out/glsl/workgroup-var-init.main.Compute.glsl new file mode 100644 index 0000000000..de136c1109 --- /dev/null +++ b/naga/tests/out/glsl/workgroup-var-init.main.Compute.glsl @@ -0,0 +1,28 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + +struct WStruct { + uint arr[512]; + int atom; + int atom_arr[8][8]; +}; +shared WStruct w_mem; + +layout(std430) buffer type_1_block_0Compute { uint _group_0_binding_0_cs[512]; }; + + +void main() { + if (gl_LocalInvocationID == uvec3(0u)) { + w_mem = WStruct(uint[512](0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u), 0, int[8][8](int[8](0, 0, 0, 0, 0, 0, 0, 0), int[8](0, 0, 0, 0, 0, 0, 0, 0), int[8](0, 0, 0, 0, 0, 0, 0, 0), int[8](0, 0, 0, 0, 0, 0, 0, 0), int[8](0, 0, 0, 0, 0, 0, 0, 0), int[8](0, 0, 0, 0, 0, 0, 0, 0), int[8](0, 0, 0, 0, 0, 0, 0, 0), int[8](0, 0, 0, 0, 0, 0, 0, 0))); + } + memoryBarrierShared(); + barrier(); + uint _e3[512] = w_mem.arr; + _group_0_binding_0_cs = _e3; + return; +} + diff --git a/naga/tests/out/hlsl/access.hlsl b/naga/tests/out/hlsl/access.hlsl new file mode 100644 index 0000000000..47d9cc24f7 --- /dev/null +++ b/naga/tests/out/hlsl/access.hlsl @@ -0,0 +1,298 @@ +typedef struct { float2 _0; float2 _1; } __mat2x2; +float2 __get_col_of_mat2x2(__mat2x2 mat, uint idx) { + switch(idx) { + case 0: { return mat._0; } + case 1: { return mat._1; } + default: { return (float2)0; } + } +} +void __set_col_of_mat2x2(__mat2x2 mat, uint idx, float2 value) { + switch(idx) { + case 0: { mat._0 = value; break; } + case 1: { mat._1 = value; break; } + } +} +void __set_el_of_mat2x2(__mat2x2 mat, uint idx, uint vec_idx, float value) { + switch(idx) { + case 0: { mat._0[vec_idx] = value; break; } + case 1: { mat._1[vec_idx] = value; break; } + } +} + +typedef struct { float2 _0; float2 _1; float2 _2; float2 _3; } __mat4x2; +float2 __get_col_of_mat4x2(__mat4x2 mat, uint idx) { + switch(idx) { + case 0: { return mat._0; } + case 1: { return mat._1; } + case 2: { return mat._2; } + case 3: { return mat._3; } + default: { return (float2)0; } + } +} +void __set_col_of_mat4x2(__mat4x2 mat, uint idx, float2 value) { + switch(idx) { + case 0: { mat._0 = value; break; } + case 1: { mat._1 = value; break; } + case 2: { mat._2 = value; break; } + case 3: { mat._3 = value; break; } + } +} +void __set_el_of_mat4x2(__mat4x2 mat, uint idx, uint vec_idx, float value) { + switch(idx) { + case 0: { mat._0[vec_idx] = value; break; } + case 1: { mat._1[vec_idx] = value; break; } + case 2: { mat._2[vec_idx] = value; break; } + case 3: { mat._3[vec_idx] = value; break; } + } +} + +struct GlobalConst { + uint a; + int _pad1_0; + int _pad1_1; + int _pad1_2; + uint3 b; + int c; +}; + +struct AlignedWrapper { + int value; + int _end_pad_0; +}; + +struct Baz { + float2 m_0; float2 m_1; float2 m_2; +}; + +struct MatCx2InArray { + __mat4x2 am[2]; +}; + +GlobalConst ConstructGlobalConst(uint arg0, uint3 arg1, int arg2) { + GlobalConst ret = (GlobalConst)0; + ret.a = arg0; + ret.b = arg1; + ret.c = arg2; + return ret; +} + +static GlobalConst global_const = ConstructGlobalConst(0u, uint3(0u, 0u, 0u), 0); +RWByteAddressBuffer bar : register(u0); +cbuffer baz : register(b1) { Baz baz; } +RWByteAddressBuffer qux : register(u2); +cbuffer nested_mat_cx2_ : register(b3) { MatCx2InArray nested_mat_cx2_; } + +Baz ConstructBaz(float3x2 arg0) { + Baz ret = (Baz)0; + ret.m_0 = arg0[0]; + ret.m_1 = arg0[1]; + ret.m_2 = arg0[2]; + return ret; +} + +float3x2 GetMatmOnBaz(Baz obj) { + return float3x2(obj.m_0, obj.m_1, obj.m_2); +} + +void SetMatmOnBaz(Baz obj, float3x2 mat) { + obj.m_0 = mat[0]; + obj.m_1 = mat[1]; + obj.m_2 = mat[2]; +} + +void SetMatVecmOnBaz(Baz obj, float2 vec, uint mat_idx) { + switch(mat_idx) { + case 0: { obj.m_0 = vec; break; } + case 1: { obj.m_1 = vec; break; } + case 2: { obj.m_2 = vec; break; } + } +} + +void SetMatScalarmOnBaz(Baz obj, float scalar, uint mat_idx, uint vec_idx) { + switch(mat_idx) { + case 0: { obj.m_0[vec_idx] = scalar; break; } + case 1: { obj.m_1[vec_idx] = scalar; break; } + case 2: { obj.m_2[vec_idx] = scalar; break; } + } +} + +void test_matrix_within_struct_accesses() +{ + int idx = 1; + Baz t = ConstructBaz(float3x2((1.0).xx, (2.0).xx, (3.0).xx)); + + int _expr3 = idx; + idx = (_expr3 - 1); + float3x2 l0_ = GetMatmOnBaz(baz); + float2 l1_ = GetMatmOnBaz(baz)[0]; + int _expr14 = idx; + float2 l2_ = GetMatmOnBaz(baz)[_expr14]; + float l3_ = GetMatmOnBaz(baz)[0].y; + int _expr25 = idx; + float l4_ = GetMatmOnBaz(baz)[0][_expr25]; + int _expr30 = idx; + float l5_ = GetMatmOnBaz(baz)[_expr30].y; + int _expr36 = idx; + int _expr38 = idx; + float l6_ = GetMatmOnBaz(baz)[_expr36][_expr38]; + int _expr51 = idx; + idx = (_expr51 + 1); + SetMatmOnBaz(t, float3x2((6.0).xx, (5.0).xx, (4.0).xx)); + t.m_0 = (9.0).xx; + int _expr66 = idx; + SetMatVecmOnBaz(t, (90.0).xx, _expr66); + t.m_0[1] = 10.0; + int _expr76 = idx; + t.m_0[_expr76] = 20.0; + int _expr80 = idx; + SetMatScalarmOnBaz(t, 30.0, _expr80, 1); + int _expr85 = idx; + int _expr87 = idx; + SetMatScalarmOnBaz(t, 40.0, _expr85, _expr87); + return; +} + +MatCx2InArray ConstructMatCx2InArray(float4x2 arg0[2]) { + MatCx2InArray ret = (MatCx2InArray)0; + ret.am = (__mat4x2[2])arg0; + return ret; +} + +void test_matrix_within_array_within_struct_accesses() +{ + int idx_1 = 1; + MatCx2InArray t_1 = ConstructMatCx2InArray((float4x2[2])0); + + int _expr3 = idx_1; + idx_1 = (_expr3 - 1); + float4x2 l0_1[2] = ((float4x2[2])nested_mat_cx2_.am); + float4x2 l1_1 = ((float4x2)nested_mat_cx2_.am[0]); + float2 l2_1 = nested_mat_cx2_.am[0]._0; + int _expr20 = idx_1; + float2 l3_1 = __get_col_of_mat4x2(nested_mat_cx2_.am[0], _expr20); + float l4_1 = nested_mat_cx2_.am[0]._0.y; + int _expr33 = idx_1; + float l5_1 = nested_mat_cx2_.am[0]._0[_expr33]; + int _expr39 = idx_1; + float l6_1 = __get_col_of_mat4x2(nested_mat_cx2_.am[0], _expr39).y; + int _expr46 = idx_1; + int _expr48 = idx_1; + float l7_ = __get_col_of_mat4x2(nested_mat_cx2_.am[0], _expr46)[_expr48]; + int _expr55 = idx_1; + idx_1 = (_expr55 + 1); + t_1.am = (__mat4x2[2])(float4x2[2])0; + t_1.am[0] = (__mat4x2)float4x2((8.0).xx, (7.0).xx, (6.0).xx, (5.0).xx); + t_1.am[0]._0 = (9.0).xx; + int _expr77 = idx_1; + __set_col_of_mat4x2(t_1.am[0], _expr77, (90.0).xx); + t_1.am[0]._0.y = 10.0; + int _expr89 = idx_1; + t_1.am[0]._0[_expr89] = 20.0; + int _expr94 = idx_1; + __set_el_of_mat4x2(t_1.am[0], _expr94, 1, 30.0); + int _expr100 = idx_1; + int _expr102 = idx_1; + __set_el_of_mat4x2(t_1.am[0], _expr100, _expr102, 40.0); + return; +} + +float read_from_private(inout float foo_1) +{ + float _expr1 = foo_1; + return _expr1; +} + +float test_arr_as_arg(float a[5][10]) +{ + return a[4][9]; +} + +void assign_through_ptr_fn(inout uint p) +{ + p = 42u; + return; +} + +typedef float4 ret_Constructarray2_float4_[2]; +ret_Constructarray2_float4_ Constructarray2_float4_(float4 arg0, float4 arg1) { + float4 ret[2] = { arg0, arg1 }; + return ret; +} + +void assign_array_through_ptr_fn(inout float4 foo_2[2]) +{ + foo_2 = Constructarray2_float4_((1.0).xxxx, (2.0).xxxx); + return; +} + +typedef int ret_Constructarray5_int_[5]; +ret_Constructarray5_int_ Constructarray5_int_(int arg0, int arg1, int arg2, int arg3, int arg4) { + int ret[5] = { arg0, arg1, arg2, arg3, arg4 }; + return ret; +} + +typedef uint2 ret_Constructarray2_uint2_[2]; +ret_Constructarray2_uint2_ Constructarray2_uint2_(uint2 arg0, uint2 arg1) { + uint2 ret[2] = { arg0, arg1 }; + return ret; +} + +uint NagaBufferLengthRW(RWByteAddressBuffer buffer) +{ + uint ret; + buffer.GetDimensions(ret); + return ret; +} + +float4 foo_vert(uint vi : SV_VertexID) : SV_Position +{ + float foo = 0.0; + int c2_[5] = (int[5])0; + + float baz_1 = foo; + foo = 1.0; + test_matrix_within_struct_accesses(); + test_matrix_within_array_within_struct_accesses(); + float4x3 _matrix = float4x3(asfloat(bar.Load3(0+0)), asfloat(bar.Load3(0+16)), asfloat(bar.Load3(0+32)), asfloat(bar.Load3(0+48))); + uint2 arr_1[2] = Constructarray2_uint2_(asuint(bar.Load2(144+0)), asuint(bar.Load2(144+8))); + float b = asfloat(bar.Load(0+3u*16+0)); + int a_1 = asint(bar.Load(0+(((NagaBufferLengthRW(bar) - 160) / 8) - 2u)*8+160)); + int2 c = asint(qux.Load2(0)); + const float _e33 = read_from_private(foo); + c2_ = Constructarray5_int_(a_1, int(b), 3, 4, 5); + c2_[(vi + 1u)] = 42; + int value = c2_[vi]; + const float _e47 = test_arr_as_arg((float[5][10])0); + return float4(mul(float4((value).xxxx), _matrix), 2.0); +} + +float4 foo_frag() : SV_Target0 +{ + bar.Store(8+16+0, asuint(1.0)); + { + float4x3 _value2 = float4x3((0.0).xxx, (1.0).xxx, (2.0).xxx, (3.0).xxx); + bar.Store3(0+0, asuint(_value2[0])); + bar.Store3(0+16, asuint(_value2[1])); + bar.Store3(0+32, asuint(_value2[2])); + bar.Store3(0+48, asuint(_value2[3])); + } + { + uint2 _value2[2] = Constructarray2_uint2_((0u).xx, (1u).xx); + bar.Store2(144+0, asuint(_value2[0])); + bar.Store2(144+8, asuint(_value2[1])); + } + bar.Store(0+8+160, asuint(1)); + qux.Store2(0, asuint((int2)0)); + return (0.0).xxxx; +} + +[numthreads(1, 1, 1)] +void assign_through_ptr() +{ + uint val = 33u; + float4 arr[2] = Constructarray2_float4_((6.0).xxxx, (7.0).xxxx); + + assign_through_ptr_fn(val); + assign_array_through_ptr_fn(arr); + return; +} diff --git a/naga/tests/out/hlsl/access.ron b/naga/tests/out/hlsl/access.ron new file mode 100644 index 0000000000..73c9e44448 --- /dev/null +++ b/naga/tests/out/hlsl/access.ron @@ -0,0 +1,20 @@ +( + vertex:[ + ( + entry_point:"foo_vert", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"foo_frag", + target_profile:"ps_5_1", + ), + ], + compute:[ + ( + entry_point:"assign_through_ptr", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/array-in-ctor.hlsl b/naga/tests/out/hlsl/array-in-ctor.hlsl new file mode 100644 index 0000000000..1079262a01 --- /dev/null +++ b/naga/tests/out/hlsl/array-in-ctor.hlsl @@ -0,0 +1,23 @@ +struct Ah { + float inner[2]; +}; + +ByteAddressBuffer ah : register(t0); + +typedef float ret_Constructarray2_float_[2]; +ret_Constructarray2_float_ Constructarray2_float_(float arg0, float arg1) { + float ret[2] = { arg0, arg1 }; + return ret; +} + +Ah ConstructAh(float arg0[2]) { + Ah ret = (Ah)0; + ret.inner = arg0; + return ret; +} + +[numthreads(1, 1, 1)] +void cs_main() +{ + Ah ah_1 = ConstructAh(Constructarray2_float_(asfloat(ah.Load(0+0)), asfloat(ah.Load(0+4)))); +} diff --git a/naga/tests/out/hlsl/array-in-ctor.ron b/naga/tests/out/hlsl/array-in-ctor.ron new file mode 100644 index 0000000000..5c261e59b2 --- /dev/null +++ b/naga/tests/out/hlsl/array-in-ctor.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"cs_main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/atomicOps.hlsl b/naga/tests/out/hlsl/atomicOps.hlsl new file mode 100644 index 0000000000..640972a2fa --- /dev/null +++ b/naga/tests/out/hlsl/atomicOps.hlsl @@ -0,0 +1,111 @@ +struct Struct { + uint atomic_scalar; + int atomic_arr[2]; +}; + +RWByteAddressBuffer storage_atomic_scalar : register(u0); +RWByteAddressBuffer storage_atomic_arr : register(u1); +RWByteAddressBuffer storage_struct : register(u2); +groupshared uint workgroup_atomic_scalar; +groupshared int workgroup_atomic_arr[2]; +groupshared Struct workgroup_struct; + +[numthreads(2, 1, 1)] +void cs_main(uint3 id : SV_GroupThreadID, uint3 __local_invocation_id : SV_GroupThreadID) +{ + if (all(__local_invocation_id == uint3(0u, 0u, 0u))) { + workgroup_atomic_scalar = (uint)0; + workgroup_atomic_arr = (int[2])0; + workgroup_struct = (Struct)0; + } + GroupMemoryBarrierWithGroupSync(); + storage_atomic_scalar.Store(0, asuint(1u)); + storage_atomic_arr.Store(4, asuint(1)); + storage_struct.Store(0, asuint(1u)); + storage_struct.Store(4+4, asuint(1)); + workgroup_atomic_scalar = 1u; + workgroup_atomic_arr[1] = 1; + workgroup_struct.atomic_scalar = 1u; + workgroup_struct.atomic_arr[1] = 1; + GroupMemoryBarrierWithGroupSync(); + uint l0_ = asuint(storage_atomic_scalar.Load(0)); + int l1_ = asint(storage_atomic_arr.Load(4)); + uint l2_ = asuint(storage_struct.Load(0)); + int l3_ = asint(storage_struct.Load(4+4)); + uint l4_ = workgroup_atomic_scalar; + int l5_ = workgroup_atomic_arr[1]; + uint l6_ = workgroup_struct.atomic_scalar; + int l7_ = workgroup_struct.atomic_arr[1]; + GroupMemoryBarrierWithGroupSync(); + uint _e51; storage_atomic_scalar.InterlockedAdd(0, 1u, _e51); + int _e55; storage_atomic_arr.InterlockedAdd(4, 1, _e55); + uint _e59; storage_struct.InterlockedAdd(0, 1u, _e59); + int _e64; storage_struct.InterlockedAdd(4+4, 1, _e64); + uint _e67; InterlockedAdd(workgroup_atomic_scalar, 1u, _e67); + int _e71; InterlockedAdd(workgroup_atomic_arr[1], 1, _e71); + uint _e75; InterlockedAdd(workgroup_struct.atomic_scalar, 1u, _e75); + int _e80; InterlockedAdd(workgroup_struct.atomic_arr[1], 1, _e80); + GroupMemoryBarrierWithGroupSync(); + uint _e83; storage_atomic_scalar.InterlockedAdd(0, -1u, _e83); + int _e87; storage_atomic_arr.InterlockedAdd(4, -1, _e87); + uint _e91; storage_struct.InterlockedAdd(0, -1u, _e91); + int _e96; storage_struct.InterlockedAdd(4+4, -1, _e96); + uint _e99; InterlockedAdd(workgroup_atomic_scalar, -1u, _e99); + int _e103; InterlockedAdd(workgroup_atomic_arr[1], -1, _e103); + uint _e107; InterlockedAdd(workgroup_struct.atomic_scalar, -1u, _e107); + int _e112; InterlockedAdd(workgroup_struct.atomic_arr[1], -1, _e112); + GroupMemoryBarrierWithGroupSync(); + uint _e115; storage_atomic_scalar.InterlockedMax(0, 1u, _e115); + int _e119; storage_atomic_arr.InterlockedMax(4, 1, _e119); + uint _e123; storage_struct.InterlockedMax(0, 1u, _e123); + int _e128; storage_struct.InterlockedMax(4+4, 1, _e128); + uint _e131; InterlockedMax(workgroup_atomic_scalar, 1u, _e131); + int _e135; InterlockedMax(workgroup_atomic_arr[1], 1, _e135); + uint _e139; InterlockedMax(workgroup_struct.atomic_scalar, 1u, _e139); + int _e144; InterlockedMax(workgroup_struct.atomic_arr[1], 1, _e144); + GroupMemoryBarrierWithGroupSync(); + uint _e147; storage_atomic_scalar.InterlockedMin(0, 1u, _e147); + int _e151; storage_atomic_arr.InterlockedMin(4, 1, _e151); + uint _e155; storage_struct.InterlockedMin(0, 1u, _e155); + int _e160; storage_struct.InterlockedMin(4+4, 1, _e160); + uint _e163; InterlockedMin(workgroup_atomic_scalar, 1u, _e163); + int _e167; InterlockedMin(workgroup_atomic_arr[1], 1, _e167); + uint _e171; InterlockedMin(workgroup_struct.atomic_scalar, 1u, _e171); + int _e176; InterlockedMin(workgroup_struct.atomic_arr[1], 1, _e176); + GroupMemoryBarrierWithGroupSync(); + uint _e179; storage_atomic_scalar.InterlockedAnd(0, 1u, _e179); + int _e183; storage_atomic_arr.InterlockedAnd(4, 1, _e183); + uint _e187; storage_struct.InterlockedAnd(0, 1u, _e187); + int _e192; storage_struct.InterlockedAnd(4+4, 1, _e192); + uint _e195; InterlockedAnd(workgroup_atomic_scalar, 1u, _e195); + int _e199; InterlockedAnd(workgroup_atomic_arr[1], 1, _e199); + uint _e203; InterlockedAnd(workgroup_struct.atomic_scalar, 1u, _e203); + int _e208; InterlockedAnd(workgroup_struct.atomic_arr[1], 1, _e208); + GroupMemoryBarrierWithGroupSync(); + uint _e211; storage_atomic_scalar.InterlockedOr(0, 1u, _e211); + int _e215; storage_atomic_arr.InterlockedOr(4, 1, _e215); + uint _e219; storage_struct.InterlockedOr(0, 1u, _e219); + int _e224; storage_struct.InterlockedOr(4+4, 1, _e224); + uint _e227; InterlockedOr(workgroup_atomic_scalar, 1u, _e227); + int _e231; InterlockedOr(workgroup_atomic_arr[1], 1, _e231); + uint _e235; InterlockedOr(workgroup_struct.atomic_scalar, 1u, _e235); + int _e240; InterlockedOr(workgroup_struct.atomic_arr[1], 1, _e240); + GroupMemoryBarrierWithGroupSync(); + uint _e243; storage_atomic_scalar.InterlockedXor(0, 1u, _e243); + int _e247; storage_atomic_arr.InterlockedXor(4, 1, _e247); + uint _e251; storage_struct.InterlockedXor(0, 1u, _e251); + int _e256; storage_struct.InterlockedXor(4+4, 1, _e256); + uint _e259; InterlockedXor(workgroup_atomic_scalar, 1u, _e259); + int _e263; InterlockedXor(workgroup_atomic_arr[1], 1, _e263); + uint _e267; InterlockedXor(workgroup_struct.atomic_scalar, 1u, _e267); + int _e272; InterlockedXor(workgroup_struct.atomic_arr[1], 1, _e272); + uint _e275; storage_atomic_scalar.InterlockedExchange(0, 1u, _e275); + int _e279; storage_atomic_arr.InterlockedExchange(4, 1, _e279); + uint _e283; storage_struct.InterlockedExchange(0, 1u, _e283); + int _e288; storage_struct.InterlockedExchange(4+4, 1, _e288); + uint _e291; InterlockedExchange(workgroup_atomic_scalar, 1u, _e291); + int _e295; InterlockedExchange(workgroup_atomic_arr[1], 1, _e295); + uint _e299; InterlockedExchange(workgroup_struct.atomic_scalar, 1u, _e299); + int _e304; InterlockedExchange(workgroup_struct.atomic_arr[1], 1, _e304); + return; +} diff --git a/naga/tests/out/hlsl/atomicOps.ron b/naga/tests/out/hlsl/atomicOps.ron new file mode 100644 index 0000000000..5c261e59b2 --- /dev/null +++ b/naga/tests/out/hlsl/atomicOps.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"cs_main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/binding-arrays.hlsl b/naga/tests/out/hlsl/binding-arrays.hlsl new file mode 100644 index 0000000000..aa631b3225 --- /dev/null +++ b/naga/tests/out/hlsl/binding-arrays.hlsl @@ -0,0 +1,180 @@ +struct UniformIndex { + uint index; +}; + +struct FragmentIn { + nointerpolation uint index : LOC0; +}; + +Texture2D texture_array_unbounded[10] : register(t0); +Texture2D texture_array_bounded[5] : register(t0, space1); +Texture2DArray texture_array_2darray[5] : register(t0, space2); +Texture2DMS texture_array_multisampled[5] : register(t0, space3); +Texture2D texture_array_depth[5] : register(t0, space4); +RWTexture2D texture_array_storage[5] : register(u0, space5); +SamplerState samp[5] : register(s0, space6); +SamplerComparisonState samp_comp[5] : register(s0, space7); +cbuffer uni : register(b0, space8) { UniformIndex uni; } + +struct FragmentInput_main { + nointerpolation uint index : LOC0; +}; + +uint2 NagaDimensions2D(Texture2D tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z); + return ret.xy; +} + +uint NagaNumLayers2DArray(Texture2DArray tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); + return ret.w; +} + +uint NagaNumLevels2D(Texture2D tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z); + return ret.z; +} + +uint NagaMSNumSamples2D(Texture2DMS tex) +{ + uint4 ret; + tex.GetDimensions(ret.x, ret.y, ret.z); + return ret.z; +} + +float4 main(FragmentInput_main fragmentinput_main) : SV_Target0 +{ + FragmentIn fragment_in = { fragmentinput_main.index }; + uint u1_ = 0u; + uint2 u2_ = (0u).xx; + float v1_ = 0.0; + float4 v4_ = (0.0).xxxx; + + uint uniform_index = uni.index; + uint non_uniform_index = fragment_in.index; + float2 uv = (0.0).xx; + int2 pix = (0).xx; + uint2 _expr22 = u2_; + u2_ = (_expr22 + NagaDimensions2D(texture_array_unbounded[0])); + uint2 _expr27 = u2_; + u2_ = (_expr27 + NagaDimensions2D(texture_array_unbounded[uniform_index])); + uint2 _expr32 = u2_; + u2_ = (_expr32 + NagaDimensions2D(texture_array_unbounded[NonUniformResourceIndex(non_uniform_index)])); + float4 _expr38 = texture_array_bounded[0].Gather(samp[0], uv); + float4 _expr39 = v4_; + v4_ = (_expr39 + _expr38); + float4 _expr45 = texture_array_bounded[uniform_index].Gather(samp[uniform_index], uv); + float4 _expr46 = v4_; + v4_ = (_expr46 + _expr45); + float4 _expr52 = texture_array_bounded[NonUniformResourceIndex(non_uniform_index)].Gather(samp[NonUniformResourceIndex(non_uniform_index)], uv); + float4 _expr53 = v4_; + v4_ = (_expr53 + _expr52); + float4 _expr60 = texture_array_depth[0].GatherCmp(samp_comp[0], uv, 0.0); + float4 _expr61 = v4_; + v4_ = (_expr61 + _expr60); + float4 _expr68 = texture_array_depth[uniform_index].GatherCmp(samp_comp[uniform_index], uv, 0.0); + float4 _expr69 = v4_; + v4_ = (_expr69 + _expr68); + float4 _expr76 = texture_array_depth[NonUniformResourceIndex(non_uniform_index)].GatherCmp(samp_comp[NonUniformResourceIndex(non_uniform_index)], uv, 0.0); + float4 _expr77 = v4_; + v4_ = (_expr77 + _expr76); + float4 _expr82 = texture_array_unbounded[0].Load(int3(pix, 0)); + float4 _expr83 = v4_; + v4_ = (_expr83 + _expr82); + float4 _expr88 = texture_array_unbounded[uniform_index].Load(int3(pix, 0)); + float4 _expr89 = v4_; + v4_ = (_expr89 + _expr88); + float4 _expr94 = texture_array_unbounded[NonUniformResourceIndex(non_uniform_index)].Load(int3(pix, 0)); + float4 _expr95 = v4_; + v4_ = (_expr95 + _expr94); + uint _expr100 = u1_; + u1_ = (_expr100 + NagaNumLayers2DArray(texture_array_2darray[0])); + uint _expr105 = u1_; + u1_ = (_expr105 + NagaNumLayers2DArray(texture_array_2darray[uniform_index])); + uint _expr110 = u1_; + u1_ = (_expr110 + NagaNumLayers2DArray(texture_array_2darray[NonUniformResourceIndex(non_uniform_index)])); + uint _expr115 = u1_; + u1_ = (_expr115 + NagaNumLevels2D(texture_array_bounded[0])); + uint _expr120 = u1_; + u1_ = (_expr120 + NagaNumLevels2D(texture_array_bounded[uniform_index])); + uint _expr125 = u1_; + u1_ = (_expr125 + NagaNumLevels2D(texture_array_bounded[NonUniformResourceIndex(non_uniform_index)])); + uint _expr130 = u1_; + u1_ = (_expr130 + NagaMSNumSamples2D(texture_array_multisampled[0])); + uint _expr135 = u1_; + u1_ = (_expr135 + NagaMSNumSamples2D(texture_array_multisampled[uniform_index])); + uint _expr140 = u1_; + u1_ = (_expr140 + NagaMSNumSamples2D(texture_array_multisampled[NonUniformResourceIndex(non_uniform_index)])); + float4 _expr146 = texture_array_bounded[0].Sample(samp[0], uv); + float4 _expr147 = v4_; + v4_ = (_expr147 + _expr146); + float4 _expr153 = texture_array_bounded[uniform_index].Sample(samp[uniform_index], uv); + float4 _expr154 = v4_; + v4_ = (_expr154 + _expr153); + float4 _expr160 = texture_array_bounded[NonUniformResourceIndex(non_uniform_index)].Sample(samp[NonUniformResourceIndex(non_uniform_index)], uv); + float4 _expr161 = v4_; + v4_ = (_expr161 + _expr160); + float4 _expr168 = texture_array_bounded[0].SampleBias(samp[0], uv, 0.0); + float4 _expr169 = v4_; + v4_ = (_expr169 + _expr168); + float4 _expr176 = texture_array_bounded[uniform_index].SampleBias(samp[uniform_index], uv, 0.0); + float4 _expr177 = v4_; + v4_ = (_expr177 + _expr176); + float4 _expr184 = texture_array_bounded[NonUniformResourceIndex(non_uniform_index)].SampleBias(samp[NonUniformResourceIndex(non_uniform_index)], uv, 0.0); + float4 _expr185 = v4_; + v4_ = (_expr185 + _expr184); + float _expr192 = texture_array_depth[0].SampleCmp(samp_comp[0], uv, 0.0); + float _expr193 = v1_; + v1_ = (_expr193 + _expr192); + float _expr200 = texture_array_depth[uniform_index].SampleCmp(samp_comp[uniform_index], uv, 0.0); + float _expr201 = v1_; + v1_ = (_expr201 + _expr200); + float _expr208 = texture_array_depth[NonUniformResourceIndex(non_uniform_index)].SampleCmp(samp_comp[NonUniformResourceIndex(non_uniform_index)], uv, 0.0); + float _expr209 = v1_; + v1_ = (_expr209 + _expr208); + float _expr216 = texture_array_depth[0].SampleCmpLevelZero(samp_comp[0], uv, 0.0); + float _expr217 = v1_; + v1_ = (_expr217 + _expr216); + float _expr224 = texture_array_depth[uniform_index].SampleCmpLevelZero(samp_comp[uniform_index], uv, 0.0); + float _expr225 = v1_; + v1_ = (_expr225 + _expr224); + float _expr232 = texture_array_depth[NonUniformResourceIndex(non_uniform_index)].SampleCmpLevelZero(samp_comp[NonUniformResourceIndex(non_uniform_index)], uv, 0.0); + float _expr233 = v1_; + v1_ = (_expr233 + _expr232); + float4 _expr239 = texture_array_bounded[0].SampleGrad(samp[0], uv, uv, uv); + float4 _expr240 = v4_; + v4_ = (_expr240 + _expr239); + float4 _expr246 = texture_array_bounded[uniform_index].SampleGrad(samp[uniform_index], uv, uv, uv); + float4 _expr247 = v4_; + v4_ = (_expr247 + _expr246); + float4 _expr253 = texture_array_bounded[NonUniformResourceIndex(non_uniform_index)].SampleGrad(samp[NonUniformResourceIndex(non_uniform_index)], uv, uv, uv); + float4 _expr254 = v4_; + v4_ = (_expr254 + _expr253); + float4 _expr261 = texture_array_bounded[0].SampleLevel(samp[0], uv, 0.0); + float4 _expr262 = v4_; + v4_ = (_expr262 + _expr261); + float4 _expr269 = texture_array_bounded[uniform_index].SampleLevel(samp[uniform_index], uv, 0.0); + float4 _expr270 = v4_; + v4_ = (_expr270 + _expr269); + float4 _expr277 = texture_array_bounded[NonUniformResourceIndex(non_uniform_index)].SampleLevel(samp[NonUniformResourceIndex(non_uniform_index)], uv, 0.0); + float4 _expr278 = v4_; + v4_ = (_expr278 + _expr277); + float4 _expr282 = v4_; + texture_array_storage[0][pix] = _expr282; + float4 _expr285 = v4_; + texture_array_storage[uniform_index][pix] = _expr285; + float4 _expr288 = v4_; + texture_array_storage[NonUniformResourceIndex(non_uniform_index)][pix] = _expr288; + uint2 _expr289 = u2_; + uint _expr290 = u1_; + float2 v2_ = float2((_expr289 + (_expr290).xx)); + float4 _expr294 = v4_; + float _expr301 = v1_; + return ((_expr294 + float4(v2_.x, v2_.y, v2_.x, v2_.y)) + (_expr301).xxxx); +} diff --git a/naga/tests/out/hlsl/binding-arrays.ron b/naga/tests/out/hlsl/binding-arrays.ron new file mode 100644 index 0000000000..341a4c528e --- /dev/null +++ b/naga/tests/out/hlsl/binding-arrays.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/bitcast.hlsl b/naga/tests/out/hlsl/bitcast.hlsl new file mode 100644 index 0000000000..5208074002 --- /dev/null +++ b/naga/tests/out/hlsl/bitcast.hlsl @@ -0,0 +1,33 @@ +[numthreads(1, 1, 1)] +void main() +{ + int2 i2_ = (0).xx; + int3 i3_ = (0).xxx; + int4 i4_ = (0).xxxx; + uint2 u2_ = (0u).xx; + uint3 u3_ = (0u).xxx; + uint4 u4_ = (0u).xxxx; + float2 f2_ = (0.0).xx; + float3 f3_ = (0.0).xxx; + float4 f4_ = (0.0).xxxx; + + int2 _expr27 = i2_; + u2_ = asuint(_expr27); + int3 _expr29 = i3_; + u3_ = asuint(_expr29); + int4 _expr31 = i4_; + u4_ = asuint(_expr31); + uint2 _expr33 = u2_; + i2_ = asint(_expr33); + uint3 _expr35 = u3_; + i3_ = asint(_expr35); + uint4 _expr37 = u4_; + i4_ = asint(_expr37); + int2 _expr39 = i2_; + f2_ = asfloat(_expr39); + int3 _expr41 = i3_; + f3_ = asfloat(_expr41); + int4 _expr43 = i4_; + f4_ = asfloat(_expr43); + return; +} diff --git a/naga/tests/out/hlsl/bitcast.ron b/naga/tests/out/hlsl/bitcast.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/bitcast.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/bits.hlsl b/naga/tests/out/hlsl/bits.hlsl new file mode 100644 index 0000000000..8ae2f7e1fc --- /dev/null +++ b/naga/tests/out/hlsl/bits.hlsl @@ -0,0 +1,120 @@ +[numthreads(1, 1, 1)] +void main() +{ + int i = 0; + int2 i2_ = (0).xx; + int3 i3_ = (0).xxx; + int4 i4_ = (0).xxxx; + uint u = 0u; + uint2 u2_ = (0u).xx; + uint3 u3_ = (0u).xxx; + uint4 u4_ = (0u).xxxx; + float2 f2_ = (0.0).xx; + float4 f4_ = (0.0).xxxx; + + float4 _expr28 = f4_; + u = uint((int(round(clamp(_expr28[0], -1.0, 1.0) * 127.0)) & 0xFF) | ((int(round(clamp(_expr28[1], -1.0, 1.0) * 127.0)) & 0xFF) << 8) | ((int(round(clamp(_expr28[2], -1.0, 1.0) * 127.0)) & 0xFF) << 16) | ((int(round(clamp(_expr28[3], -1.0, 1.0) * 127.0)) & 0xFF) << 24)); + float4 _expr30 = f4_; + u = (uint(round(clamp(_expr30[0], 0.0, 1.0) * 255.0)) | uint(round(clamp(_expr30[1], 0.0, 1.0) * 255.0)) << 8 | uint(round(clamp(_expr30[2], 0.0, 1.0) * 255.0)) << 16 | uint(round(clamp(_expr30[3], 0.0, 1.0) * 255.0)) << 24); + float2 _expr32 = f2_; + u = uint((int(round(clamp(_expr32[0], -1.0, 1.0) * 32767.0)) & 0xFFFF) | ((int(round(clamp(_expr32[1], -1.0, 1.0) * 32767.0)) & 0xFFFF) << 16)); + float2 _expr34 = f2_; + u = (uint(round(clamp(_expr34[0], 0.0, 1.0) * 65535.0)) | uint(round(clamp(_expr34[1], 0.0, 1.0) * 65535.0)) << 16); + float2 _expr36 = f2_; + u = (f32tof16(_expr36[0]) | f32tof16(_expr36[1]) << 16); + uint _expr38 = u; + f4_ = (float4(int4(_expr38 << 24, _expr38 << 16, _expr38 << 8, _expr38) >> 24) / 127.0); + uint _expr40 = u; + f4_ = (float4(_expr40 & 0xFF, _expr40 >> 8 & 0xFF, _expr40 >> 16 & 0xFF, _expr40 >> 24) / 255.0); + uint _expr42 = u; + f2_ = (float2(int2(_expr42 << 16, _expr42) >> 16) / 32767.0); + uint _expr44 = u; + f2_ = (float2(_expr44 & 0xFFFF, _expr44 >> 16) / 65535.0); + uint _expr46 = u; + f2_ = float2(f16tof32(_expr46), f16tof32((_expr46) >> 16)); + int _expr48 = i; + int _expr49 = i; + i = (10u == 0 ? _expr48 : (_expr48 & ~((4294967295u >> (32u - 10u)) << 5u)) | ((_expr49 << 5u) & ((4294967295u >> (32u - 10u)) << 5u))); + int2 _expr53 = i2_; + int2 _expr54 = i2_; + i2_ = (10u == 0 ? _expr53 : (_expr53 & ~((4294967295u >> (32u - 10u)) << 5u)) | ((_expr54 << 5u) & ((4294967295u >> (32u - 10u)) << 5u))); + int3 _expr58 = i3_; + int3 _expr59 = i3_; + i3_ = (10u == 0 ? _expr58 : (_expr58 & ~((4294967295u >> (32u - 10u)) << 5u)) | ((_expr59 << 5u) & ((4294967295u >> (32u - 10u)) << 5u))); + int4 _expr63 = i4_; + int4 _expr64 = i4_; + i4_ = (10u == 0 ? _expr63 : (_expr63 & ~((4294967295u >> (32u - 10u)) << 5u)) | ((_expr64 << 5u) & ((4294967295u >> (32u - 10u)) << 5u))); + uint _expr68 = u; + uint _expr69 = u; + u = (10u == 0 ? _expr68 : (_expr68 & ~((4294967295u >> (32u - 10u)) << 5u)) | ((_expr69 << 5u) & ((4294967295u >> (32u - 10u)) << 5u))); + uint2 _expr73 = u2_; + uint2 _expr74 = u2_; + u2_ = (10u == 0 ? _expr73 : (_expr73 & ~((4294967295u >> (32u - 10u)) << 5u)) | ((_expr74 << 5u) & ((4294967295u >> (32u - 10u)) << 5u))); + uint3 _expr78 = u3_; + uint3 _expr79 = u3_; + u3_ = (10u == 0 ? _expr78 : (_expr78 & ~((4294967295u >> (32u - 10u)) << 5u)) | ((_expr79 << 5u) & ((4294967295u >> (32u - 10u)) << 5u))); + uint4 _expr83 = u4_; + uint4 _expr84 = u4_; + u4_ = (10u == 0 ? _expr83 : (_expr83 & ~((4294967295u >> (32u - 10u)) << 5u)) | ((_expr84 << 5u) & ((4294967295u >> (32u - 10u)) << 5u))); + int _expr88 = i; + i = (10u == 0 ? 0 : (_expr88 << (32 - 10u - 5u)) >> (32 - 10u)); + int2 _expr92 = i2_; + i2_ = (10u == 0 ? 0 : (_expr92 << (32 - 10u - 5u)) >> (32 - 10u)); + int3 _expr96 = i3_; + i3_ = (10u == 0 ? 0 : (_expr96 << (32 - 10u - 5u)) >> (32 - 10u)); + int4 _expr100 = i4_; + i4_ = (10u == 0 ? 0 : (_expr100 << (32 - 10u - 5u)) >> (32 - 10u)); + uint _expr104 = u; + u = (10u == 0 ? 0 : (_expr104 << (32 - 10u - 5u)) >> (32 - 10u)); + uint2 _expr108 = u2_; + u2_ = (10u == 0 ? 0 : (_expr108 << (32 - 10u - 5u)) >> (32 - 10u)); + uint3 _expr112 = u3_; + u3_ = (10u == 0 ? 0 : (_expr112 << (32 - 10u - 5u)) >> (32 - 10u)); + uint4 _expr116 = u4_; + u4_ = (10u == 0 ? 0 : (_expr116 << (32 - 10u - 5u)) >> (32 - 10u)); + int _expr120 = i; + i = asint(firstbitlow(_expr120)); + uint2 _expr122 = u2_; + u2_ = firstbitlow(_expr122); + int3 _expr124 = i3_; + i3_ = asint(firstbithigh(_expr124)); + uint3 _expr126 = u3_; + u3_ = firstbithigh(_expr126); + int _expr128 = i; + i = asint(firstbithigh(_expr128)); + uint _expr130 = u; + u = firstbithigh(_expr130); + int _expr132 = i; + i = asint(countbits(asuint(_expr132))); + int2 _expr134 = i2_; + i2_ = asint(countbits(asuint(_expr134))); + int3 _expr136 = i3_; + i3_ = asint(countbits(asuint(_expr136))); + int4 _expr138 = i4_; + i4_ = asint(countbits(asuint(_expr138))); + uint _expr140 = u; + u = countbits(_expr140); + uint2 _expr142 = u2_; + u2_ = countbits(_expr142); + uint3 _expr144 = u3_; + u3_ = countbits(_expr144); + uint4 _expr146 = u4_; + u4_ = countbits(_expr146); + int _expr148 = i; + i = asint(reversebits(asuint(_expr148))); + int2 _expr150 = i2_; + i2_ = asint(reversebits(asuint(_expr150))); + int3 _expr152 = i3_; + i3_ = asint(reversebits(asuint(_expr152))); + int4 _expr154 = i4_; + i4_ = asint(reversebits(asuint(_expr154))); + uint _expr156 = u; + u = reversebits(_expr156); + uint2 _expr158 = u2_; + u2_ = reversebits(_expr158); + uint3 _expr160 = u3_; + u3_ = reversebits(_expr160); + uint4 _expr162 = u4_; + u4_ = reversebits(_expr162); + return; +} diff --git a/naga/tests/out/hlsl/bits.ron b/naga/tests/out/hlsl/bits.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/bits.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/boids.hlsl b/naga/tests/out/hlsl/boids.hlsl new file mode 100644 index 0000000000..bb6f6f9d1b --- /dev/null +++ b/naga/tests/out/hlsl/boids.hlsl @@ -0,0 +1,144 @@ +struct Particle { + float2 pos; + float2 vel; +}; + +struct SimParams { + float deltaT; + float rule1Distance; + float rule2Distance; + float rule3Distance; + float rule1Scale; + float rule2Scale; + float rule3Scale; +}; + +static const uint NUM_PARTICLES = 1500u; + +cbuffer params : register(b0) { SimParams params; } +ByteAddressBuffer particlesSrc : register(t1); +RWByteAddressBuffer particlesDst : register(u2); + +[numthreads(64, 1, 1)] +void main(uint3 global_invocation_id : SV_DispatchThreadID) +{ + float2 vPos = (float2)0; + float2 vVel = (float2)0; + float2 cMass = float2(0.0, 0.0); + float2 cVel = float2(0.0, 0.0); + float2 colVel = float2(0.0, 0.0); + int cMassCount = 0; + int cVelCount = 0; + float2 pos = (float2)0; + float2 vel = (float2)0; + uint i = 0u; + + uint index = global_invocation_id.x; + if ((index >= NUM_PARTICLES)) { + return; + } + float2 _expr8 = asfloat(particlesSrc.Load2(0+index*16+0)); + vPos = _expr8; + float2 _expr14 = asfloat(particlesSrc.Load2(8+index*16+0)); + vVel = _expr14; + bool loop_init = true; + while(true) { + if (!loop_init) { + uint _expr91 = i; + i = (_expr91 + 1u); + } + loop_init = false; + uint _expr36 = i; + if ((_expr36 >= NUM_PARTICLES)) { + break; + } + uint _expr39 = i; + if ((_expr39 == index)) { + continue; + } + uint _expr43 = i; + float2 _expr46 = asfloat(particlesSrc.Load2(0+_expr43*16+0)); + pos = _expr46; + uint _expr49 = i; + float2 _expr52 = asfloat(particlesSrc.Load2(8+_expr49*16+0)); + vel = _expr52; + float2 _expr53 = pos; + float2 _expr54 = vPos; + float _expr58 = params.rule1Distance; + if ((distance(_expr53, _expr54) < _expr58)) { + float2 _expr60 = cMass; + float2 _expr61 = pos; + cMass = (_expr60 + _expr61); + int _expr63 = cMassCount; + cMassCount = (_expr63 + 1); + } + float2 _expr66 = pos; + float2 _expr67 = vPos; + float _expr71 = params.rule2Distance; + if ((distance(_expr66, _expr67) < _expr71)) { + float2 _expr73 = colVel; + float2 _expr74 = pos; + float2 _expr75 = vPos; + colVel = (_expr73 - (_expr74 - _expr75)); + } + float2 _expr78 = pos; + float2 _expr79 = vPos; + float _expr83 = params.rule3Distance; + if ((distance(_expr78, _expr79) < _expr83)) { + float2 _expr85 = cVel; + float2 _expr86 = vel; + cVel = (_expr85 + _expr86); + int _expr88 = cVelCount; + cVelCount = (_expr88 + 1); + } + } + int _expr94 = cMassCount; + if ((_expr94 > 0)) { + float2 _expr97 = cMass; + int _expr98 = cMassCount; + float2 _expr102 = vPos; + cMass = ((_expr97 / (float(_expr98)).xx) - _expr102); + } + int _expr104 = cVelCount; + if ((_expr104 > 0)) { + float2 _expr107 = cVel; + int _expr108 = cVelCount; + cVel = (_expr107 / (float(_expr108)).xx); + } + float2 _expr112 = vVel; + float2 _expr113 = cMass; + float _expr116 = params.rule1Scale; + float2 _expr119 = colVel; + float _expr122 = params.rule2Scale; + float2 _expr125 = cVel; + float _expr128 = params.rule3Scale; + vVel = (((_expr112 + (_expr113 * _expr116)) + (_expr119 * _expr122)) + (_expr125 * _expr128)); + float2 _expr131 = vVel; + float2 _expr133 = vVel; + vVel = (normalize(_expr131) * clamp(length(_expr133), 0.0, 0.1)); + float2 _expr139 = vPos; + float2 _expr140 = vVel; + float _expr143 = params.deltaT; + vPos = (_expr139 + (_expr140 * _expr143)); + float _expr147 = vPos.x; + if ((_expr147 < -1.0)) { + vPos.x = 1.0; + } + float _expr153 = vPos.x; + if ((_expr153 > 1.0)) { + vPos.x = -1.0; + } + float _expr159 = vPos.y; + if ((_expr159 < -1.0)) { + vPos.y = 1.0; + } + float _expr165 = vPos.y; + if ((_expr165 > 1.0)) { + vPos.y = -1.0; + } + float2 _expr174 = vPos; + particlesDst.Store2(0+index*16+0, asuint(_expr174)); + float2 _expr179 = vVel; + particlesDst.Store2(8+index*16+0, asuint(_expr179)); + return; +} diff --git a/naga/tests/out/hlsl/boids.ron b/naga/tests/out/hlsl/boids.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/boids.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/break-if.hlsl b/naga/tests/out/hlsl/break-if.hlsl new file mode 100644 index 0000000000..56b7b48a2f --- /dev/null +++ b/naga/tests/out/hlsl/break-if.hlsl @@ -0,0 +1,80 @@ +void breakIfEmpty() +{ + bool loop_init = true; + while(true) { + if (!loop_init) { + if (true) { + break; + } + } + loop_init = false; + } + return; +} + +void breakIfEmptyBody(bool a) +{ + bool b = (bool)0; + bool c = (bool)0; + + bool loop_init_1 = true; + while(true) { + if (!loop_init_1) { + b = a; + bool _expr2 = b; + c = (a != _expr2); + bool _expr5 = c; + if ((a == _expr5)) { + break; + } + } + loop_init_1 = false; + } + return; +} + +void breakIf(bool a_1) +{ + bool d = (bool)0; + bool e = (bool)0; + + bool loop_init_2 = true; + while(true) { + if (!loop_init_2) { + bool _expr5 = e; + if ((a_1 == _expr5)) { + break; + } + } + loop_init_2 = false; + d = a_1; + bool _expr2 = d; + e = (a_1 != _expr2); + } + return; +} + +void breakIfSeparateVariable() +{ + uint counter = 0u; + + bool loop_init_3 = true; + while(true) { + if (!loop_init_3) { + uint _expr5 = counter; + if ((_expr5 == 5u)) { + break; + } + } + loop_init_3 = false; + uint _expr3 = counter; + counter = (_expr3 + 1u); + } + return; +} + +[numthreads(1, 1, 1)] +void main() +{ + return; +} diff --git a/naga/tests/out/hlsl/break-if.ron b/naga/tests/out/hlsl/break-if.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/break-if.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/collatz.hlsl b/naga/tests/out/hlsl/collatz.hlsl new file mode 100644 index 0000000000..a8a5a776e3 --- /dev/null +++ b/naga/tests/out/hlsl/collatz.hlsl @@ -0,0 +1,39 @@ +RWByteAddressBuffer v_indices : register(u0); + +uint collatz_iterations(uint n_base) +{ + uint n = (uint)0; + uint i = 0u; + + n = n_base; + while(true) { + uint _expr4 = n; + if ((_expr4 > 1u)) { + } else { + break; + } + { + uint _expr7 = n; + if (((_expr7 % 2u) == 0u)) { + uint _expr12 = n; + n = (_expr12 / 2u); + } else { + uint _expr16 = n; + n = ((3u * _expr16) + 1u); + } + uint _expr20 = i; + i = (_expr20 + 1u); + } + } + uint _expr23 = i; + return _expr23; +} + +[numthreads(1, 1, 1)] +void main(uint3 global_id : SV_DispatchThreadID) +{ + uint _expr9 = asuint(v_indices.Load(global_id.x*4+0)); + const uint _e10 = collatz_iterations(_expr9); + v_indices.Store(global_id.x*4+0, asuint(_e10)); + return; +} diff --git a/naga/tests/out/hlsl/collatz.ron b/naga/tests/out/hlsl/collatz.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/collatz.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/const-exprs.hlsl b/naga/tests/out/hlsl/const-exprs.hlsl new file mode 100644 index 0000000000..4cc2491ce8 --- /dev/null +++ b/naga/tests/out/hlsl/const-exprs.hlsl @@ -0,0 +1,101 @@ +static const uint TWO = 2u; +static const int THREE = 3; +static const int FOUR = 4; +static const int FOUR_ALIAS = 4; +static const int TEST_CONSTANT_ADDITION = 8; +static const int TEST_CONSTANT_ALIAS_ADDITION = 8; +static const float PI = 3.141; +static const float phi_sun = 6.282; +static const float4 DIV = float4(0.44444445, 0.0, 0.0, 0.0); +static const int TEXTURE_KIND_REGULAR = 0; +static const int TEXTURE_KIND_WARP = 1; +static const int TEXTURE_KIND_SKY = 2; +static const float2 add_vec = float2(4.0, 5.0); +static const bool2 compare_vec = bool2(true, false); + +void swizzle_of_compose() +{ + int4 out_ = int4(4, 3, 2, 1); + +} + +void index_of_compose() +{ + int out_1 = 2; + +} + +void compose_three_deep() +{ + int out_2 = 6; + +} + +void non_constant_initializers() +{ + int w = 30; + int x = (int)0; + int y = (int)0; + int z = 70; + int4 out_3 = (int4)0; + + int _expr2 = w; + x = _expr2; + int _expr4 = x; + y = _expr4; + int _expr8 = w; + int _expr9 = x; + int _expr10 = y; + int _expr11 = z; + out_3 = int4(_expr8, _expr9, _expr10, _expr11); + return; +} + +void splat_of_constant() +{ + int4 out_4 = int4(-4, -4, -4, -4); + +} + +void compose_of_constant() +{ + int4 out_5 = int4(-4, -4, -4, -4); + +} + +void compose_of_splat() +{ + float4 x_1 = float4(2.0, 1.0, 1.0, 1.0); + +} + +uint map_texture_kind(int texture_kind) +{ + switch(texture_kind) { + case 0: { + return 10u; + } + case 1: { + return 20u; + } + case 2: { + return 30u; + } + default: { + return 0u; + } + } +} + +[numthreads(2, 3, 1)] +void main() +{ + swizzle_of_compose(); + index_of_compose(); + compose_three_deep(); + non_constant_initializers(); + splat_of_constant(); + compose_of_constant(); + compose_of_splat(); + return; +} diff --git a/naga/tests/out/hlsl/const-exprs.ron b/naga/tests/out/hlsl/const-exprs.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/const-exprs.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/constructors.hlsl b/naga/tests/out/hlsl/constructors.hlsl new file mode 100644 index 0000000000..232494fa21 --- /dev/null +++ b/naga/tests/out/hlsl/constructors.hlsl @@ -0,0 +1,55 @@ +struct Foo { + float4 a; + int b; + int _end_pad_0; + int _end_pad_1; + int _end_pad_2; +}; + +typedef float2x2 ret_Constructarray1_float2x2_[1]; +ret_Constructarray1_float2x2_ Constructarray1_float2x2_(float2x2 arg0) { + float2x2 ret[1] = { arg0 }; + return ret; +} + +typedef int ret_Constructarray4_int_[4]; +ret_Constructarray4_int_ Constructarray4_int_(int arg0, int arg1, int arg2, int arg3) { + int ret[4] = { arg0, arg1, arg2, arg3 }; + return ret; +} + +static const float3 const2_ = float3(0.0, 1.0, 2.0); +static const float2x2 const3_ = float2x2(float2(0.0, 1.0), float2(2.0, 3.0)); +static const float2x2 const4_[1] = Constructarray1_float2x2_(float2x2(float2(0.0, 1.0), float2(2.0, 3.0))); +static const bool cz0_ = (bool)0; +static const int cz1_ = (int)0; +static const uint cz2_ = (uint)0; +static const float cz3_ = (float)0; +static const uint2 cz4_ = (uint2)0; +static const float2x2 cz5_ = (float2x2)0; +static const Foo cz6_[3] = (Foo[3])0; +static const Foo cz7_ = (Foo)0; +static const int cp3_[4] = Constructarray4_int_(0, 1, 2, 3); + +Foo ConstructFoo(float4 arg0, int arg1) { + Foo ret = (Foo)0; + ret.a = arg0; + ret.b = arg1; + return ret; +} + +[numthreads(1, 1, 1)] +void main() +{ + Foo foo = (Foo)0; + + foo = ConstructFoo((1.0).xxxx, 1); + float2x2 m0_ = float2x2(float2(1.0, 0.0), float2(0.0, 1.0)); + float4x4 m1_ = float4x4(float4(1.0, 0.0, 0.0, 0.0), float4(0.0, 1.0, 0.0, 0.0), float4(0.0, 0.0, 1.0, 0.0), float4(0.0, 0.0, 0.0, 1.0)); + uint2 cit0_ = (0u).xx; + float2x2 cit1_ = float2x2((0.0).xx, (0.0).xx); + int cit2_[4] = Constructarray4_int_(0, 1, 2, 3); + bool ic0_ = bool((bool)0); + uint2 ic4_ = uint2(0u, 0u); + float2x3 ic5_ = float2x3(float3(0.0, 0.0, 0.0), float3(0.0, 0.0, 0.0)); +} diff --git a/naga/tests/out/hlsl/constructors.ron b/naga/tests/out/hlsl/constructors.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/constructors.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/control-flow.hlsl b/naga/tests/out/hlsl/control-flow.hlsl new file mode 100644 index 0000000000..8d71388c43 --- /dev/null +++ b/naga/tests/out/hlsl/control-flow.hlsl @@ -0,0 +1,106 @@ +void switch_default_break(int i) +{ + switch(i) { + default: { + break; + } + } +} + +void switch_case_break() +{ + switch(0) { + case 0: { + break; + } + default: { + break; + } + } + return; +} + +void loop_switch_continue(int x) +{ + while(true) { + switch(x) { + case 1: { + continue; + } + default: { + break; + } + } + } + return; +} + +[numthreads(1, 1, 1)] +void main(uint3 global_id : SV_DispatchThreadID) +{ + int pos = (int)0; + + DeviceMemoryBarrierWithGroupSync(); + GroupMemoryBarrierWithGroupSync(); + switch(1) { + default: { + pos = 1; + break; + } + } + int _expr4 = pos; + switch(_expr4) { + case 1: { + pos = 0; + break; + } + case 2: { + pos = 1; + break; + } + case 3: + case 4: { + pos = 2; + break; + } + case 5: { + pos = 3; + break; + } + default: + case 6: { + pos = 4; + break; + } + } + switch(0u) { + case 0u: { + break; + } + default: { + break; + } + } + int _expr11 = pos; + switch(_expr11) { + case 1: { + pos = 0; + break; + } + case 2: { + pos = 1; + return; + } + case 3: { + pos = 2; + return; + } + case 4: { + return; + } + default: { + pos = 3; + return; + } + } +} diff --git a/naga/tests/out/hlsl/control-flow.ron b/naga/tests/out/hlsl/control-flow.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/control-flow.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/do-while.hlsl b/naga/tests/out/hlsl/do-while.hlsl new file mode 100644 index 0000000000..b09e62457f --- /dev/null +++ b/naga/tests/out/hlsl/do-while.hlsl @@ -0,0 +1,29 @@ +void fb1_(inout bool cond) +{ + bool loop_init = true; + while(true) { + if (!loop_init) { + bool _expr1 = cond; + if (!(_expr1)) { + break; + } + } + loop_init = false; + continue; + } + return; +} + +void main_1() +{ + bool param = (bool)0; + + param = false; + fb1_(param); + return; +} + +void main() +{ + main_1(); +} diff --git a/naga/tests/out/hlsl/do-while.ron b/naga/tests/out/hlsl/do-while.ron new file mode 100644 index 0000000000..341a4c528e --- /dev/null +++ b/naga/tests/out/hlsl/do-while.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/dualsource.hlsl b/naga/tests/out/hlsl/dualsource.hlsl new file mode 100644 index 0000000000..36784b13d2 --- /dev/null +++ b/naga/tests/out/hlsl/dualsource.hlsl @@ -0,0 +1,27 @@ +struct FragmentOutput { + float4 color : SV_Target0; + float4 mask : SV_Target1; +}; + +struct FragmentInput_main { + float4 position_1 : SV_Position; +}; + +FragmentOutput ConstructFragmentOutput(float4 arg0, float4 arg1) { + FragmentOutput ret = (FragmentOutput)0; + ret.color = arg0; + ret.mask = arg1; + return ret; +} + +FragmentOutput main(FragmentInput_main fragmentinput_main) +{ + float4 position = fragmentinput_main.position_1; + float4 color = float4(0.4, 0.3, 0.2, 0.1); + float4 mask = float4(0.9, 0.8, 0.7, 0.6); + + float4 _expr13 = color; + float4 _expr14 = mask; + const FragmentOutput fragmentoutput = ConstructFragmentOutput(_expr13, _expr14); + return fragmentoutput; +} diff --git a/naga/tests/out/hlsl/dualsource.ron b/naga/tests/out/hlsl/dualsource.ron new file mode 100644 index 0000000000..341a4c528e --- /dev/null +++ b/naga/tests/out/hlsl/dualsource.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/empty-global-name.hlsl b/naga/tests/out/hlsl/empty-global-name.hlsl new file mode 100644 index 0000000000..8bb32b3648 --- /dev/null +++ b/naga/tests/out/hlsl/empty-global-name.hlsl @@ -0,0 +1,18 @@ +struct type_1 { + int member; +}; + +RWByteAddressBuffer unnamed : register(u0); + +void function() +{ + int _expr3 = asint(unnamed.Load(0)); + unnamed.Store(0, asuint((_expr3 + 1))); + return; +} + +[numthreads(1, 1, 1)] +void main() +{ + function(); +} diff --git a/naga/tests/out/hlsl/empty-global-name.ron b/naga/tests/out/hlsl/empty-global-name.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/empty-global-name.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/empty.hlsl b/naga/tests/out/hlsl/empty.hlsl new file mode 100644 index 0000000000..e79d36b9c8 --- /dev/null +++ b/naga/tests/out/hlsl/empty.hlsl @@ -0,0 +1,5 @@ +[numthreads(1, 1, 1)] +void main() +{ + return; +} diff --git a/naga/tests/out/hlsl/empty.ron b/naga/tests/out/hlsl/empty.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/empty.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/f64.hlsl b/naga/tests/out/hlsl/f64.hlsl new file mode 100644 index 0000000000..7b0fb9f67e --- /dev/null +++ b/naga/tests/out/hlsl/f64.hlsl @@ -0,0 +1,19 @@ +static const double k = 2.0L; + +static double v = 1.0L; + +double f(double x) +{ + double z = (double)0; + + double y = (30.0L + 400.0L); + z = (y + 5.0L); + return (((x + y) + k) + 5.0L); +} + +[numthreads(1, 1, 1)] +void main() +{ + const double _e1 = f(6.0L); + return; +} diff --git a/naga/tests/out/hlsl/f64.ron b/naga/tests/out/hlsl/f64.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/f64.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/fragment-output.hlsl b/naga/tests/out/hlsl/fragment-output.hlsl new file mode 100644 index 0000000000..be425a5ef7 --- /dev/null +++ b/naga/tests/out/hlsl/fragment-output.hlsl @@ -0,0 +1,47 @@ +struct FragmentOutputVec4Vec3_ { + float4 vec4f : SV_Target0; + nointerpolation int4 vec4i : SV_Target1; + nointerpolation uint4 vec4u : SV_Target2; + float3 vec3f : SV_Target3; + nointerpolation int3 vec3i : SV_Target4; + nointerpolation uint3 vec3u : SV_Target5; +}; + +struct FragmentOutputVec2Scalar { + float2 vec2f : SV_Target0; + nointerpolation int2 vec2i : SV_Target1; + nointerpolation uint2 vec2u : SV_Target2; + float scalarf : SV_Target3; + nointerpolation int scalari : SV_Target4; + nointerpolation uint scalaru : SV_Target5; +}; + +FragmentOutputVec4Vec3_ main_vec4vec3_() +{ + FragmentOutputVec4Vec3_ output = (FragmentOutputVec4Vec3_)0; + + output.vec4f = (0.0).xxxx; + output.vec4i = (0).xxxx; + output.vec4u = (0u).xxxx; + output.vec3f = (0.0).xxx; + output.vec3i = (0).xxx; + output.vec3u = (0u).xxx; + FragmentOutputVec4Vec3_ _expr19 = output; + const FragmentOutputVec4Vec3_ fragmentoutputvec4vec3_ = _expr19; + return fragmentoutputvec4vec3_; +} + +FragmentOutputVec2Scalar main_vec2scalar() +{ + FragmentOutputVec2Scalar output_1 = (FragmentOutputVec2Scalar)0; + + output_1.vec2f = (0.0).xx; + output_1.vec2i = (0).xx; + output_1.vec2u = (0u).xx; + output_1.scalarf = 0.0; + output_1.scalari = 0; + output_1.scalaru = 0u; + FragmentOutputVec2Scalar _expr16 = output_1; + const FragmentOutputVec2Scalar fragmentoutputvec2scalar = _expr16; + return fragmentoutputvec2scalar; +} diff --git a/naga/tests/out/hlsl/fragment-output.ron b/naga/tests/out/hlsl/fragment-output.ron new file mode 100644 index 0000000000..9dfaf7393b --- /dev/null +++ b/naga/tests/out/hlsl/fragment-output.ron @@ -0,0 +1,16 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main_vec4vec3_", + target_profile:"ps_5_1", + ), + ( + entry_point:"main_vec2scalar", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/functions.hlsl b/naga/tests/out/hlsl/functions.hlsl new file mode 100644 index 0000000000..6d7e210307 --- /dev/null +++ b/naga/tests/out/hlsl/functions.hlsl @@ -0,0 +1,27 @@ +float2 test_fma() +{ + float2 a = float2(2.0, 2.0); + float2 b = float2(0.5, 0.5); + float2 c = float2(0.5, 0.5); + return mad(a, b, c); +} + +int test_integer_dot_product() +{ + int2 a_2_ = (1).xx; + int2 b_2_ = (1).xx; + int c_2_ = dot(a_2_, b_2_); + uint3 a_3_ = (1u).xxx; + uint3 b_3_ = (1u).xxx; + uint c_3_ = dot(a_3_, b_3_); + int c_4_ = dot((4).xxxx, (2).xxxx); + return c_4_; +} + +[numthreads(1, 1, 1)] +void main() +{ + const float2 _e0 = test_fma(); + const int _e1 = test_integer_dot_product(); + return; +} diff --git a/naga/tests/out/hlsl/functions.ron b/naga/tests/out/hlsl/functions.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/functions.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/globals.hlsl b/naga/tests/out/hlsl/globals.hlsl new file mode 100644 index 0000000000..55faf060d0 --- /dev/null +++ b/naga/tests/out/hlsl/globals.hlsl @@ -0,0 +1,137 @@ +typedef struct { float2 _0; float2 _1; float2 _2; } __mat3x2; +float2 __get_col_of_mat3x2(__mat3x2 mat, uint idx) { + switch(idx) { + case 0: { return mat._0; } + case 1: { return mat._1; } + case 2: { return mat._2; } + default: { return (float2)0; } + } +} +void __set_col_of_mat3x2(__mat3x2 mat, uint idx, float2 value) { + switch(idx) { + case 0: { mat._0 = value; break; } + case 1: { mat._1 = value; break; } + case 2: { mat._2 = value; break; } + } +} +void __set_el_of_mat3x2(__mat3x2 mat, uint idx, uint vec_idx, float value) { + switch(idx) { + case 0: { mat._0[vec_idx] = value; break; } + case 1: { mat._1[vec_idx] = value; break; } + case 2: { mat._2[vec_idx] = value; break; } + } +} + +typedef struct { float2 _0; float2 _1; float2 _2; float2 _3; } __mat4x2; +float2 __get_col_of_mat4x2(__mat4x2 mat, uint idx) { + switch(idx) { + case 0: { return mat._0; } + case 1: { return mat._1; } + case 2: { return mat._2; } + case 3: { return mat._3; } + default: { return (float2)0; } + } +} +void __set_col_of_mat4x2(__mat4x2 mat, uint idx, float2 value) { + switch(idx) { + case 0: { mat._0 = value; break; } + case 1: { mat._1 = value; break; } + case 2: { mat._2 = value; break; } + case 3: { mat._3 = value; break; } + } +} +void __set_el_of_mat4x2(__mat4x2 mat, uint idx, uint vec_idx, float value) { + switch(idx) { + case 0: { mat._0[vec_idx] = value; break; } + case 1: { mat._1[vec_idx] = value; break; } + case 2: { mat._2[vec_idx] = value; break; } + case 3: { mat._3[vec_idx] = value; break; } + } +} + +struct FooStruct { + float3 v3_; + float v1_; +}; + +static const bool Foo_1 = true; + +groupshared float wg[10]; +groupshared uint at_1; +RWByteAddressBuffer alignment : register(u1); +ByteAddressBuffer dummy : register(t2); +cbuffer float_vecs : register(b3) { float4 float_vecs[20]; } +cbuffer global_vec : register(b4) { float3 global_vec; } +cbuffer global_mat : register(b5) { __mat3x2 global_mat; } +cbuffer global_nested_arrays_of_matrices_2x4_ : register(b6) { row_major float2x4 global_nested_arrays_of_matrices_2x4_[2][2]; } +cbuffer global_nested_arrays_of_matrices_4x2_ : register(b7) { __mat4x2 global_nested_arrays_of_matrices_4x2_[2][2]; } + +void test_msl_packed_vec3_as_arg(float3 arg) +{ + return; +} + +FooStruct ConstructFooStruct(float3 arg0, float arg1) { + FooStruct ret = (FooStruct)0; + ret.v3_ = arg0; + ret.v1_ = arg1; + return ret; +} + +void test_msl_packed_vec3_() +{ + int idx = 1; + + alignment.Store3(0, asuint((1.0).xxx)); + alignment.Store(0+0, asuint(1.0)); + alignment.Store(0+0, asuint(2.0)); + int _expr16 = idx; + alignment.Store(_expr16*4+0, asuint(3.0)); + FooStruct data = ConstructFooStruct(asfloat(alignment.Load3(0)), asfloat(alignment.Load(12))); + float3 l0_ = data.v3_; + float2 l1_ = data.v3_.zx; + test_msl_packed_vec3_as_arg(data.v3_); + float3 mvm0_ = mul((float3x3)0, data.v3_); + float3 mvm1_ = mul(data.v3_, (float3x3)0); + float3 svm0_ = (data.v3_ * 2.0); + float3 svm1_ = (2.0 * data.v3_); +} + +uint NagaBufferLength(ByteAddressBuffer buffer) +{ + uint ret; + buffer.GetDimensions(ret); + return ret; +} + +[numthreads(1, 1, 1)] +void main(uint3 __local_invocation_id : SV_GroupThreadID) +{ + if (all(__local_invocation_id == uint3(0u, 0u, 0u))) { + wg = (float[10])0; + at_1 = (uint)0; + } + GroupMemoryBarrierWithGroupSync(); + float Foo = 1.0; + bool at = true; + + test_msl_packed_vec3_(); + float4x2 _expr5 = ((float4x2)global_nested_arrays_of_matrices_4x2_[0][0]); + float4 _expr10 = global_nested_arrays_of_matrices_2x4_[0][0][0]; + wg[7] = mul(_expr10, _expr5).x; + float3x2 _expr16 = ((float3x2)global_mat); + float3 _expr18 = global_vec; + wg[6] = mul(_expr18, _expr16).x; + float _expr26 = asfloat(dummy.Load(4+8)); + wg[5] = _expr26; + float _expr32 = float_vecs[0].w; + wg[4] = _expr32; + float _expr37 = asfloat(alignment.Load(12)); + wg[3] = _expr37; + float _expr43 = asfloat(alignment.Load(0+0)); + wg[2] = _expr43; + alignment.Store(12, asuint(4.0)); + wg[1] = float(((NagaBufferLength(dummy) - 0) / 8)); + at_1 = 2u; + return; +} diff --git a/naga/tests/out/hlsl/globals.ron b/naga/tests/out/hlsl/globals.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/globals.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/hlsl-keyword.hlsl b/naga/tests/out/hlsl/hlsl-keyword.hlsl new file mode 100644 index 0000000000..9259549ab2 --- /dev/null +++ b/naga/tests/out/hlsl/hlsl-keyword.hlsl @@ -0,0 +1,7 @@ +float4 fs_main() : SV_Target0 +{ + float4 Pass_ = float4(1.0, 1.0, 1.0, 1.0); + + float4 _expr6 = Pass_; + return _expr6; +} diff --git a/naga/tests/out/hlsl/hlsl-keyword.ron b/naga/tests/out/hlsl/hlsl-keyword.ron new file mode 100644 index 0000000000..eac1b945d2 --- /dev/null +++ b/naga/tests/out/hlsl/hlsl-keyword.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"fs_main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/image.hlsl b/naga/tests/out/hlsl/image.hlsl new file mode 100644 index 0000000000..7fbd68b105 --- /dev/null +++ b/naga/tests/out/hlsl/image.hlsl @@ -0,0 +1,372 @@ +Texture2D image_mipmapped_src : register(t0); +Texture2DMS image_multisampled_src : register(t3); +Texture2DMS image_depth_multisampled_src : register(t4); +RWTexture2D image_storage_src : register(u1); +Texture2DArray image_array_src : register(t5); +RWTexture1D image_dup_src : register(u6); +Texture1D image_1d_src : register(t7); +RWTexture1D image_dst : register(u2); +Texture1D image_1d : register(t0); +Texture2D image_2d : register(t1); +Texture2D image_2d_u32_ : register(t2); +Texture2D image_2d_i32_ : register(t3); +Texture2DArray image_2d_array : register(t4); +TextureCube image_cube : register(t5); +TextureCubeArray image_cube_array : register(t6); +Texture3D image_3d : register(t7); +Texture2DMS image_aa : register(t8); +SamplerState sampler_reg : register(s0, space1); +SamplerComparisonState sampler_cmp : register(s1, space1); +Texture2D image_2d_depth : register(t2, space1); +Texture2DArray image_2d_array_depth : register(t3, space1); +TextureCube image_cube_depth : register(t4, space1); + +uint2 NagaRWDimensions2D(RWTexture2D tex) +{ + uint4 ret; + tex.GetDimensions(ret.x, ret.y); + return ret.xy; +} + +[numthreads(16, 1, 1)] +void main(uint3 local_id : SV_GroupThreadID) +{ + uint2 dim = NagaRWDimensions2D(image_storage_src); + int2 itc = (int2((dim * local_id.xy)) % int2(10, 20)); + uint4 value1_ = image_mipmapped_src.Load(int3(itc, int(local_id.z))); + uint4 value2_ = image_multisampled_src.Load(itc, int(local_id.z)); + uint4 value4_ = image_storage_src.Load(itc); + uint4 value5_ = image_array_src.Load(int4(itc, local_id.z, (int(local_id.z) + 1))); + uint4 value6_ = image_array_src.Load(int4(itc, int(local_id.z), (int(local_id.z) + 1))); + uint4 value7_ = image_1d_src.Load(int2(int(local_id.x), int(local_id.z))); + uint4 value1u = image_mipmapped_src.Load(int3(uint2(itc), int(local_id.z))); + uint4 value2u = image_multisampled_src.Load(uint2(itc), int(local_id.z)); + uint4 value4u = image_storage_src.Load(uint2(itc)); + uint4 value5u = image_array_src.Load(int4(uint2(itc), local_id.z, (int(local_id.z) + 1))); + uint4 value6u = image_array_src.Load(int4(uint2(itc), int(local_id.z), (int(local_id.z) + 1))); + uint4 value7u = image_1d_src.Load(int2(uint(local_id.x), int(local_id.z))); + image_dst[itc.x] = ((((value1_ + value2_) + value4_) + value5_) + value6_); + image_dst[uint(itc.x)] = ((((value1u + value2u) + value4u) + value5u) + value6u); + return; +} + +[numthreads(16, 1, 1)] +void depth_load(uint3 local_id_1 : SV_GroupThreadID) +{ + uint2 dim_1 = NagaRWDimensions2D(image_storage_src); + int2 itc_1 = (int2((dim_1 * local_id_1.xy)) % int2(10, 20)); + float val = image_depth_multisampled_src.Load(itc_1, int(local_id_1.z)).x; + image_dst[itc_1.x] = (uint(val)).xxxx; + return; +} + +uint NagaDimensions1D(Texture1D tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y); + return ret.x; +} + +uint NagaMipDimensions1D(Texture1D tex, uint mip_level) +{ + uint4 ret; + tex.GetDimensions(mip_level, ret.x, ret.y); + return ret.x; +} + +uint2 NagaDimensions2D(Texture2D tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z); + return ret.xy; +} + +uint2 NagaMipDimensions2D(Texture2D tex, uint mip_level) +{ + uint4 ret; + tex.GetDimensions(mip_level, ret.x, ret.y, ret.z); + return ret.xy; +} + +uint2 NagaDimensions2DArray(Texture2DArray tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); + return ret.xy; +} + +uint2 NagaMipDimensions2DArray(Texture2DArray tex, uint mip_level) +{ + uint4 ret; + tex.GetDimensions(mip_level, ret.x, ret.y, ret.z, ret.w); + return ret.xy; +} + +uint2 NagaDimensionsCube(TextureCube tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z); + return ret.xy; +} + +uint2 NagaMipDimensionsCube(TextureCube tex, uint mip_level) +{ + uint4 ret; + tex.GetDimensions(mip_level, ret.x, ret.y, ret.z); + return ret.xy; +} + +uint2 NagaDimensionsCubeArray(TextureCubeArray tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); + return ret.xy; +} + +uint2 NagaMipDimensionsCubeArray(TextureCubeArray tex, uint mip_level) +{ + uint4 ret; + tex.GetDimensions(mip_level, ret.x, ret.y, ret.z, ret.w); + return ret.xy; +} + +uint3 NagaDimensions3D(Texture3D tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); + return ret.xyz; +} + +uint3 NagaMipDimensions3D(Texture3D tex, uint mip_level) +{ + uint4 ret; + tex.GetDimensions(mip_level, ret.x, ret.y, ret.z, ret.w); + return ret.xyz; +} + +uint2 NagaMSDimensions2D(Texture2DMS tex) +{ + uint4 ret; + tex.GetDimensions(ret.x, ret.y, ret.z); + return ret.xy; +} + +float4 queries() : SV_Position +{ + uint dim_1d = NagaDimensions1D(image_1d); + uint dim_1d_lod = NagaMipDimensions1D(image_1d, int(dim_1d)); + uint2 dim_2d = NagaDimensions2D(image_2d); + uint2 dim_2d_lod = NagaMipDimensions2D(image_2d, 1); + uint2 dim_2d_array = NagaDimensions2DArray(image_2d_array); + uint2 dim_2d_array_lod = NagaMipDimensions2DArray(image_2d_array, 1); + uint2 dim_cube = NagaDimensionsCube(image_cube); + uint2 dim_cube_lod = NagaMipDimensionsCube(image_cube, 1); + uint2 dim_cube_array = NagaDimensionsCubeArray(image_cube_array); + uint2 dim_cube_array_lod = NagaMipDimensionsCubeArray(image_cube_array, 1); + uint3 dim_3d = NagaDimensions3D(image_3d); + uint3 dim_3d_lod = NagaMipDimensions3D(image_3d, 1); + uint2 dim_2s_ms = NagaMSDimensions2D(image_aa); + uint sum = ((((((((((dim_1d + dim_2d.y) + dim_2d_lod.y) + dim_2d_array.y) + dim_2d_array_lod.y) + dim_cube.y) + dim_cube_lod.y) + dim_cube_array.y) + dim_cube_array_lod.y) + dim_3d.z) + dim_3d_lod.z); + return (float(sum)).xxxx; +} + +uint NagaNumLevels2D(Texture2D tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z); + return ret.z; +} + +uint NagaNumLevels2DArray(Texture2DArray tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); + return ret.w; +} + +uint NagaNumLayers2DArray(Texture2DArray tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); + return ret.w; +} + +uint NagaNumLevelsCube(TextureCube tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z); + return ret.z; +} + +uint NagaNumLevelsCubeArray(TextureCubeArray tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); + return ret.w; +} + +uint NagaNumLayersCubeArray(TextureCubeArray tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); + return ret.w; +} + +uint NagaNumLevels3D(Texture3D tex) +{ + uint4 ret; + tex.GetDimensions(0, ret.x, ret.y, ret.z, ret.w); + return ret.w; +} + +uint NagaMSNumSamples2D(Texture2DMS tex) +{ + uint4 ret; + tex.GetDimensions(ret.x, ret.y, ret.z); + return ret.z; +} + +float4 levels_queries() : SV_Position +{ + uint num_levels_2d = NagaNumLevels2D(image_2d); + uint num_levels_2d_array = NagaNumLevels2DArray(image_2d_array); + uint num_layers_2d = NagaNumLayers2DArray(image_2d_array); + uint num_levels_cube = NagaNumLevelsCube(image_cube); + uint num_levels_cube_array = NagaNumLevelsCubeArray(image_cube_array); + uint num_layers_cube = NagaNumLayersCubeArray(image_cube_array); + uint num_levels_3d = NagaNumLevels3D(image_3d); + uint num_samples_aa = NagaMSNumSamples2D(image_aa); + uint sum_1 = (((((((num_layers_2d + num_layers_cube) + num_samples_aa) + num_levels_2d) + num_levels_2d_array) + num_levels_3d) + num_levels_cube) + num_levels_cube_array); + return (float(sum_1)).xxxx; +} + +float4 texture_sample() : SV_Target0 +{ + float4 a = (float4)0; + + float2 tc = (0.5).xx; + float3 tc3_ = (0.5).xxx; + float4 _expr9 = image_1d.Sample(sampler_reg, tc.x); + float4 _expr10 = a; + a = (_expr10 + _expr9); + float4 _expr14 = image_2d.Sample(sampler_reg, tc); + float4 _expr15 = a; + a = (_expr15 + _expr14); + float4 _expr19 = image_2d.Sample(sampler_reg, tc, int2(int2(3, 1))); + float4 _expr20 = a; + a = (_expr20 + _expr19); + float4 _expr24 = image_2d.SampleLevel(sampler_reg, tc, 2.3); + float4 _expr25 = a; + a = (_expr25 + _expr24); + float4 _expr29 = image_2d.SampleLevel(sampler_reg, tc, 2.3, int2(int2(3, 1))); + float4 _expr30 = a; + a = (_expr30 + _expr29); + float4 _expr35 = image_2d.SampleBias(sampler_reg, tc, 2.0, int2(int2(3, 1))); + float4 _expr36 = a; + a = (_expr36 + _expr35); + float4 _expr41 = image_2d_array.Sample(sampler_reg, float3(tc, 0u)); + float4 _expr42 = a; + a = (_expr42 + _expr41); + float4 _expr47 = image_2d_array.Sample(sampler_reg, float3(tc, 0u), int2(int2(3, 1))); + float4 _expr48 = a; + a = (_expr48 + _expr47); + float4 _expr53 = image_2d_array.SampleLevel(sampler_reg, float3(tc, 0u), 2.3); + float4 _expr54 = a; + a = (_expr54 + _expr53); + float4 _expr59 = image_2d_array.SampleLevel(sampler_reg, float3(tc, 0u), 2.3, int2(int2(3, 1))); + float4 _expr60 = a; + a = (_expr60 + _expr59); + float4 _expr66 = image_2d_array.SampleBias(sampler_reg, float3(tc, 0u), 2.0, int2(int2(3, 1))); + float4 _expr67 = a; + a = (_expr67 + _expr66); + float4 _expr72 = image_2d_array.Sample(sampler_reg, float3(tc, 0)); + float4 _expr73 = a; + a = (_expr73 + _expr72); + float4 _expr78 = image_2d_array.Sample(sampler_reg, float3(tc, 0), int2(int2(3, 1))); + float4 _expr79 = a; + a = (_expr79 + _expr78); + float4 _expr84 = image_2d_array.SampleLevel(sampler_reg, float3(tc, 0), 2.3); + float4 _expr85 = a; + a = (_expr85 + _expr84); + float4 _expr90 = image_2d_array.SampleLevel(sampler_reg, float3(tc, 0), 2.3, int2(int2(3, 1))); + float4 _expr91 = a; + a = (_expr91 + _expr90); + float4 _expr97 = image_2d_array.SampleBias(sampler_reg, float3(tc, 0), 2.0, int2(int2(3, 1))); + float4 _expr98 = a; + a = (_expr98 + _expr97); + float4 _expr103 = image_cube_array.Sample(sampler_reg, float4(tc3_, 0u)); + float4 _expr104 = a; + a = (_expr104 + _expr103); + float4 _expr109 = image_cube_array.SampleLevel(sampler_reg, float4(tc3_, 0u), 2.3); + float4 _expr110 = a; + a = (_expr110 + _expr109); + float4 _expr116 = image_cube_array.SampleBias(sampler_reg, float4(tc3_, 0u), 2.0); + float4 _expr117 = a; + a = (_expr117 + _expr116); + float4 _expr122 = image_cube_array.Sample(sampler_reg, float4(tc3_, 0)); + float4 _expr123 = a; + a = (_expr123 + _expr122); + float4 _expr128 = image_cube_array.SampleLevel(sampler_reg, float4(tc3_, 0), 2.3); + float4 _expr129 = a; + a = (_expr129 + _expr128); + float4 _expr135 = image_cube_array.SampleBias(sampler_reg, float4(tc3_, 0), 2.0); + float4 _expr136 = a; + a = (_expr136 + _expr135); + float4 _expr138 = a; + return _expr138; +} + +float texture_sample_comparison() : SV_Target0 +{ + float a_1 = (float)0; + + float2 tc_1 = (0.5).xx; + float3 tc3_1 = (0.5).xxx; + float _expr8 = image_2d_depth.SampleCmp(sampler_cmp, tc_1, 0.5); + float _expr9 = a_1; + a_1 = (_expr9 + _expr8); + float _expr14 = image_2d_array_depth.SampleCmp(sampler_cmp, float3(tc_1, 0u), 0.5); + float _expr15 = a_1; + a_1 = (_expr15 + _expr14); + float _expr20 = image_2d_array_depth.SampleCmp(sampler_cmp, float3(tc_1, 0), 0.5); + float _expr21 = a_1; + a_1 = (_expr21 + _expr20); + float _expr25 = image_cube_depth.SampleCmp(sampler_cmp, tc3_1, 0.5); + float _expr26 = a_1; + a_1 = (_expr26 + _expr25); + float _expr30 = image_2d_depth.SampleCmpLevelZero(sampler_cmp, tc_1, 0.5); + float _expr31 = a_1; + a_1 = (_expr31 + _expr30); + float _expr36 = image_2d_array_depth.SampleCmpLevelZero(sampler_cmp, float3(tc_1, 0u), 0.5); + float _expr37 = a_1; + a_1 = (_expr37 + _expr36); + float _expr42 = image_2d_array_depth.SampleCmpLevelZero(sampler_cmp, float3(tc_1, 0), 0.5); + float _expr43 = a_1; + a_1 = (_expr43 + _expr42); + float _expr47 = image_cube_depth.SampleCmpLevelZero(sampler_cmp, tc3_1, 0.5); + float _expr48 = a_1; + a_1 = (_expr48 + _expr47); + float _expr50 = a_1; + return _expr50; +} + +float4 gather() : SV_Target0 +{ + float2 tc_2 = (0.5).xx; + float4 s2d = image_2d.GatherGreen(sampler_reg, tc_2); + float4 s2d_offset = image_2d.GatherAlpha(sampler_reg, tc_2, int2(int2(3, 1))); + float4 s2d_depth = image_2d_depth.GatherCmp(sampler_cmp, tc_2, 0.5); + float4 s2d_depth_offset = image_2d_depth.GatherCmp(sampler_cmp, tc_2, 0.5, int2(int2(3, 1))); + uint4 u = image_2d_u32_.Gather(sampler_reg, tc_2); + int4 i = image_2d_i32_.Gather(sampler_reg, tc_2); + float4 f = (float4(u) + float4(i)); + return ((((s2d + s2d_offset) + s2d_depth) + s2d_depth_offset) + f); +} + +float4 depth_no_comparison() : SV_Target0 +{ + float2 tc_3 = (0.5).xx; + float s2d_1 = image_2d_depth.Sample(sampler_reg, tc_3); + float4 s2d_gather = image_2d_depth.Gather(sampler_reg, tc_3); + return ((s2d_1).xxxx + s2d_gather); +} diff --git a/naga/tests/out/hlsl/image.ron b/naga/tests/out/hlsl/image.ron new file mode 100644 index 0000000000..f5ca4931d4 --- /dev/null +++ b/naga/tests/out/hlsl/image.ron @@ -0,0 +1,40 @@ +( + vertex:[ + ( + entry_point:"queries", + target_profile:"vs_5_1", + ), + ( + entry_point:"levels_queries", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"texture_sample", + target_profile:"ps_5_1", + ), + ( + entry_point:"texture_sample_comparison", + target_profile:"ps_5_1", + ), + ( + entry_point:"gather", + target_profile:"ps_5_1", + ), + ( + entry_point:"depth_no_comparison", + target_profile:"ps_5_1", + ), + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ( + entry_point:"depth_load", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/interface.hlsl b/naga/tests/out/hlsl/interface.hlsl new file mode 100644 index 0000000000..bbf330d4d6 --- /dev/null +++ b/naga/tests/out/hlsl/interface.hlsl @@ -0,0 +1,94 @@ +struct NagaConstants { + int first_vertex; + int first_instance; + uint other; +}; +ConstantBuffer _NagaConstants: register(b0, space1); + +struct VertexOutput { + precise float4 position : SV_Position; + float _varying : LOC1; +}; + +struct FragmentOutput { + float depth : SV_Depth; + uint sample_mask : SV_Coverage; + float color : SV_Target0; +}; + +struct Input1_ { + uint index : SV_VertexID; +}; + +struct Input2_ { + uint index : SV_InstanceID; +}; + +groupshared uint output[1]; + +struct VertexOutput_vertex { + float _varying : LOC1; + precise float4 position : SV_Position; +}; + +struct FragmentInput_fragment { + float _varying_1 : LOC1; + precise float4 position_1 : SV_Position; + bool front_facing_1 : SV_IsFrontFace; + uint sample_index_1 : SV_SampleIndex; + uint sample_mask_1 : SV_Coverage; +}; + +VertexOutput ConstructVertexOutput(float4 arg0, float arg1) { + VertexOutput ret = (VertexOutput)0; + ret.position = arg0; + ret._varying = arg1; + return ret; +} + +VertexOutput_vertex vertex(uint vertex_index : SV_VertexID, uint instance_index : SV_InstanceID, uint color : LOC10) +{ + uint tmp = (((_NagaConstants.first_vertex + vertex_index) + (_NagaConstants.first_instance + instance_index)) + color); + const VertexOutput vertexoutput = ConstructVertexOutput((1.0).xxxx, float(tmp)); + const VertexOutput_vertex vertexoutput_1 = { vertexoutput._varying, vertexoutput.position }; + return vertexoutput_1; +} + +FragmentOutput ConstructFragmentOutput(float arg0, uint arg1, float arg2) { + FragmentOutput ret = (FragmentOutput)0; + ret.depth = arg0; + ret.sample_mask = arg1; + ret.color = arg2; + return ret; +} + +FragmentOutput fragment(FragmentInput_fragment fragmentinput_fragment) +{ + VertexOutput in_ = { fragmentinput_fragment.position_1, fragmentinput_fragment._varying_1 }; + bool front_facing = fragmentinput_fragment.front_facing_1; + uint sample_index = fragmentinput_fragment.sample_index_1; + uint sample_mask = fragmentinput_fragment.sample_mask_1; + uint mask = (sample_mask & (1u << sample_index)); + float color_1 = (front_facing ? 1.0 : 0.0); + const FragmentOutput fragmentoutput = ConstructFragmentOutput(in_._varying, mask, color_1); + return fragmentoutput; +} + +[numthreads(1, 1, 1)] +void compute(uint3 global_id : SV_DispatchThreadID, uint3 local_id : SV_GroupThreadID, uint local_index : SV_GroupIndex, uint3 wg_id : SV_GroupID, uint3 num_wgs : SV_GroupID, uint3 __local_invocation_id : SV_GroupThreadID) +{ + if (all(__local_invocation_id == uint3(0u, 0u, 0u))) { + output = (uint[1])0; + } + GroupMemoryBarrierWithGroupSync(); + output[0] = ((((global_id.x + local_id.x) + local_index) + wg_id.x) + uint3(_NagaConstants.first_vertex, _NagaConstants.first_instance, _NagaConstants.other).x); + return; +} + +precise float4 vertex_two_structs(Input1_ in1_, Input2_ in2_) : SV_Position +{ + uint index = 2u; + + uint _expr8 = index; + return float4(float((_NagaConstants.first_vertex + in1_.index)), float((_NagaConstants.first_instance + in2_.index)), float(_expr8), 0.0); +} diff --git a/naga/tests/out/hlsl/interface.ron b/naga/tests/out/hlsl/interface.ron new file mode 100644 index 0000000000..948962b991 --- /dev/null +++ b/naga/tests/out/hlsl/interface.ron @@ -0,0 +1,24 @@ +( + vertex:[ + ( + entry_point:"vertex", + target_profile:"vs_5_1", + ), + ( + entry_point:"vertex_two_structs", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"fragment", + target_profile:"ps_5_1", + ), + ], + compute:[ + ( + entry_point:"compute", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/interpolate.hlsl b/naga/tests/out/hlsl/interpolate.hlsl new file mode 100644 index 0000000000..aa1986f5d2 --- /dev/null +++ b/naga/tests/out/hlsl/interpolate.hlsl @@ -0,0 +1,56 @@ +struct FragmentInput { + float4 position : SV_Position; + nointerpolation uint _flat : LOC0; + noperspective float _linear : LOC1; + noperspective centroid float2 linear_centroid : LOC2; + noperspective sample float3 linear_sample : LOC3; + float4 perspective : LOC4; + centroid float perspective_centroid : LOC5; + sample float perspective_sample : LOC6; +}; + +struct VertexOutput_vert_main { + nointerpolation uint _flat : LOC0; + noperspective float _linear : LOC1; + noperspective centroid float2 linear_centroid : LOC2; + noperspective sample float3 linear_sample : LOC3; + float4 perspective : LOC4; + centroid float perspective_centroid : LOC5; + sample float perspective_sample : LOC6; + float4 position : SV_Position; +}; + +struct FragmentInput_frag_main { + nointerpolation uint _flat_1 : LOC0; + noperspective float _linear_1 : LOC1; + noperspective centroid float2 linear_centroid_1 : LOC2; + noperspective sample float3 linear_sample_1 : LOC3; + float4 perspective_1 : LOC4; + centroid float perspective_centroid_1 : LOC5; + sample float perspective_sample_1 : LOC6; + float4 position_1 : SV_Position; +}; + +VertexOutput_vert_main vert_main() +{ + FragmentInput out_ = (FragmentInput)0; + + out_.position = float4(2.0, 4.0, 5.0, 6.0); + out_._flat = 8u; + out_._linear = 27.0; + out_.linear_centroid = float2(64.0, 125.0); + out_.linear_sample = float3(216.0, 343.0, 512.0); + out_.perspective = float4(729.0, 1000.0, 1331.0, 1728.0); + out_.perspective_centroid = 2197.0; + out_.perspective_sample = 2744.0; + FragmentInput _expr30 = out_; + const FragmentInput fragmentinput = _expr30; + const VertexOutput_vert_main fragmentinput_1 = { fragmentinput._flat, fragmentinput._linear, fragmentinput.linear_centroid, fragmentinput.linear_sample, fragmentinput.perspective, fragmentinput.perspective_centroid, fragmentinput.perspective_sample, fragmentinput.position }; + return fragmentinput_1; +} + +void frag_main(FragmentInput_frag_main fragmentinput_frag_main) +{ + FragmentInput val = { fragmentinput_frag_main.position_1, fragmentinput_frag_main._flat_1, fragmentinput_frag_main._linear_1, fragmentinput_frag_main.linear_centroid_1, fragmentinput_frag_main.linear_sample_1, fragmentinput_frag_main.perspective_1, fragmentinput_frag_main.perspective_centroid_1, fragmentinput_frag_main.perspective_sample_1 }; + return; +} diff --git a/naga/tests/out/hlsl/interpolate.ron b/naga/tests/out/hlsl/interpolate.ron new file mode 100644 index 0000000000..d0046b04dd --- /dev/null +++ b/naga/tests/out/hlsl/interpolate.ron @@ -0,0 +1,16 @@ +( + vertex:[ + ( + entry_point:"vert_main", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"frag_main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/inv-hyperbolic-trig-functions.hlsl b/naga/tests/out/hlsl/inv-hyperbolic-trig-functions.hlsl new file mode 100644 index 0000000000..d086bf14b4 --- /dev/null +++ b/naga/tests/out/hlsl/inv-hyperbolic-trig-functions.hlsl @@ -0,0 +1,21 @@ +static float a = (float)0; + +void main_1() +{ + float b = (float)0; + float c = (float)0; + float d = (float)0; + + float _expr4 = a; + b = log(_expr4 + sqrt(_expr4 * _expr4 + 1.0)); + float _expr6 = a; + c = log(_expr6 + sqrt(_expr6 * _expr6 - 1.0)); + float _expr8 = a; + d = 0.5 * log((1.0 + _expr8) / (1.0 - _expr8)); + return; +} + +void main() +{ + main_1(); +} diff --git a/naga/tests/out/hlsl/inv-hyperbolic-trig-functions.ron b/naga/tests/out/hlsl/inv-hyperbolic-trig-functions.ron new file mode 100644 index 0000000000..341a4c528e --- /dev/null +++ b/naga/tests/out/hlsl/inv-hyperbolic-trig-functions.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/math-functions.hlsl b/naga/tests/out/hlsl/math-functions.hlsl new file mode 100644 index 0000000000..53d3acf0c1 --- /dev/null +++ b/naga/tests/out/hlsl/math-functions.hlsl @@ -0,0 +1,113 @@ +struct _modf_result_f32_ { + float fract; + float whole; +}; + +struct _modf_result_vec2_f32_ { + float2 fract; + float2 whole; +}; + +struct _modf_result_vec4_f32_ { + float4 fract; + float4 whole; +}; + +struct _frexp_result_f32_ { + float fract; + int exp_; +}; + +struct _frexp_result_vec4_f32_ { + float4 fract; + int4 exp_; +}; + +_modf_result_f32_ naga_modf(float arg) { + float other; + _modf_result_f32_ result; + result.fract = modf(arg, other); + result.whole = other; + return result; +} + +_modf_result_vec2_f32_ naga_modf(float2 arg) { + float2 other; + _modf_result_vec2_f32_ result; + result.fract = modf(arg, other); + result.whole = other; + return result; +} + +_modf_result_vec4_f32_ naga_modf(float4 arg) { + float4 other; + _modf_result_vec4_f32_ result; + result.fract = modf(arg, other); + result.whole = other; + return result; +} + +_frexp_result_f32_ naga_frexp(float arg) { + float other; + _frexp_result_f32_ result; + result.fract = sign(arg) * frexp(arg, other); + result.exp_ = other; + return result; +} + +_frexp_result_vec4_f32_ naga_frexp(float4 arg) { + float4 other; + _frexp_result_vec4_f32_ result; + result.fract = sign(arg) * frexp(arg, other); + result.exp_ = other; + return result; +} + +void main() +{ + float4 v = (0.0).xxxx; + float a = degrees(1.0); + float b = radians(1.0); + float4 c = degrees(v); + float4 d = radians(v); + float4 e = saturate(v); + float4 g = refract(v, v, 1.0); + int sign_a = sign(-1); + int4 sign_b = sign((-1).xxxx); + float sign_c = sign(-1.0); + float4 sign_d = sign((-1.0).xxxx); + int const_dot = dot((int2)0, (int2)0); + uint first_leading_bit_abs = firstbithigh(abs(0u)); + int flb_a = asint(firstbithigh(-1)); + int2 flb_b = asint(firstbithigh((-1).xx)); + uint2 flb_c = firstbithigh((1u).xx); + int ftb_a = asint(firstbitlow(-1)); + uint ftb_b = firstbitlow(1u); + int2 ftb_c = asint(firstbitlow((-1).xx)); + uint2 ftb_d = firstbitlow((1u).xx); + uint ctz_a = min(32u, firstbitlow(0u)); + int ctz_b = asint(min(32u, firstbitlow(0))); + uint ctz_c = min(32u, firstbitlow(4294967295u)); + int ctz_d = asint(min(32u, firstbitlow(-1))); + uint2 ctz_e = min((32u).xx, firstbitlow((0u).xx)); + int2 ctz_f = asint(min((32u).xx, firstbitlow((0).xx))); + uint2 ctz_g = min((32u).xx, firstbitlow((1u).xx)); + int2 ctz_h = asint(min((32u).xx, firstbitlow((1).xx))); + int clz_a = (-1 < 0 ? 0 : 31 - asint(firstbithigh(-1))); + uint clz_b = (31u - firstbithigh(1u)); + int2 _expr68 = (-1).xx; + int2 clz_c = (_expr68 < (0).xx ? (0).xx : (31).xx - asint(firstbithigh(_expr68))); + uint2 clz_d = ((31u).xx - firstbithigh((1u).xx)); + float lde_a = ldexp(1.0, 2); + float2 lde_b = ldexp(float2(1.0, 2.0), int2(3, 4)); + _modf_result_f32_ modf_a = naga_modf(1.5); + float modf_b = naga_modf(1.5).fract; + float modf_c = naga_modf(1.5).whole; + _modf_result_vec2_f32_ modf_d = naga_modf(float2(1.5, 1.5)); + float modf_e = naga_modf(float4(1.5, 1.5, 1.5, 1.5)).whole.x; + float modf_f = naga_modf(float2(1.5, 1.5)).fract.y; + _frexp_result_f32_ frexp_a = naga_frexp(1.5); + float frexp_b = naga_frexp(1.5).fract; + int frexp_c = naga_frexp(1.5).exp_; + int frexp_d = naga_frexp(float4(1.5, 1.5, 1.5, 1.5)).exp_.x; +} diff --git a/naga/tests/out/hlsl/math-functions.ron b/naga/tests/out/hlsl/math-functions.ron new file mode 100644 index 0000000000..341a4c528e --- /dev/null +++ b/naga/tests/out/hlsl/math-functions.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/operators.hlsl b/naga/tests/out/hlsl/operators.hlsl new file mode 100644 index 0000000000..58ec5a170d --- /dev/null +++ b/naga/tests/out/hlsl/operators.hlsl @@ -0,0 +1,265 @@ +static const float4 v_f32_one = float4(1.0, 1.0, 1.0, 1.0); +static const float4 v_f32_zero = float4(0.0, 0.0, 0.0, 0.0); +static const float4 v_f32_half = float4(0.5, 0.5, 0.5, 0.5); +static const int4 v_i32_one = int4(1, 1, 1, 1); + +float4 builtins() +{ + int s1_ = (true ? 1 : 0); + float4 s2_ = (true ? v_f32_one : v_f32_zero); + float4 s3_ = (bool4(false, false, false, false) ? v_f32_zero : v_f32_one); + float4 m1_ = lerp(v_f32_zero, v_f32_one, v_f32_half); + float4 m2_ = lerp(v_f32_zero, v_f32_one, 0.1); + float b1_ = asfloat(1); + float4 b2_ = asfloat(v_i32_one); + int4 v_i32_zero = int4(0, 0, 0, 0); + return (((((float4(((s1_).xxxx + v_i32_zero)) + s2_) + m1_) + m2_) + (b1_).xxxx) + b2_); +} + +float4 splat(float m, int n) +{ + float2 a_2 = ((((2.0).xx + (m).xx) - (4.0).xx) / (8.0).xx); + int4 b = ((n).xxxx % (2).xxxx); + return (a_2.xyxy + float4(b)); +} + +float2 splat_assignment() +{ + float2 a = (2.0).xx; + + float2 _expr4 = a; + a = (_expr4 + (1.0).xx); + float2 _expr8 = a; + a = (_expr8 - (3.0).xx); + float2 _expr12 = a; + a = (_expr12 / (4.0).xx); + float2 _expr15 = a; + return _expr15; +} + +float3 bool_cast(float3 x) +{ + bool3 y = bool3(x); + return float3(y); +} + +void logical() +{ + bool neg0_ = !(true); + bool2 neg1_ = !((true).xx); + bool or_ = (true || false); + bool and_ = (true && false); + bool bitwise_or0_ = (true | false); + bool3 bitwise_or1_ = ((true).xxx | (false).xxx); + bool bitwise_and0_ = (true & false); + bool4 bitwise_and1_ = ((true).xxxx & (false).xxxx); +} + +void arithmetic() +{ + float neg0_1 = -(1.0); + int2 neg1_1 = -((1).xx); + float2 neg2_ = -((1.0).xx); + int add0_ = (2 + 1); + uint add1_ = (2u + 1u); + float add2_ = (2.0 + 1.0); + int2 add3_ = ((2).xx + (1).xx); + uint3 add4_ = ((2u).xxx + (1u).xxx); + float4 add5_ = ((2.0).xxxx + (1.0).xxxx); + int sub0_ = (2 - 1); + uint sub1_ = (2u - 1u); + float sub2_ = (2.0 - 1.0); + int2 sub3_ = ((2).xx - (1).xx); + uint3 sub4_ = ((2u).xxx - (1u).xxx); + float4 sub5_ = ((2.0).xxxx - (1.0).xxxx); + int mul0_ = (2 * 1); + uint mul1_ = (2u * 1u); + float mul2_ = (2.0 * 1.0); + int2 mul3_ = ((2).xx * (1).xx); + uint3 mul4_ = ((2u).xxx * (1u).xxx); + float4 mul5_ = ((2.0).xxxx * (1.0).xxxx); + int div0_ = (2 / 1); + uint div1_ = (2u / 1u); + float div2_ = (2.0 / 1.0); + int2 div3_ = ((2).xx / (1).xx); + uint3 div4_ = ((2u).xxx / (1u).xxx); + float4 div5_ = ((2.0).xxxx / (1.0).xxxx); + int rem0_ = (2 % 1); + uint rem1_ = (2u % 1u); + float rem2_ = fmod(2.0, 1.0); + int2 rem3_ = ((2).xx % (1).xx); + uint3 rem4_ = ((2u).xxx % (1u).xxx); + float4 rem5_ = fmod((2.0).xxxx, (1.0).xxxx); + { + int2 add0_1 = ((2).xx + (1).xx); + int2 add1_1 = ((2).xx + (1).xx); + uint2 add2_1 = ((2u).xx + (1u).xx); + uint2 add3_1 = ((2u).xx + (1u).xx); + float2 add4_1 = ((2.0).xx + (1.0).xx); + float2 add5_1 = ((2.0).xx + (1.0).xx); + int2 sub0_1 = ((2).xx - (1).xx); + int2 sub1_1 = ((2).xx - (1).xx); + uint2 sub2_1 = ((2u).xx - (1u).xx); + uint2 sub3_1 = ((2u).xx - (1u).xx); + float2 sub4_1 = ((2.0).xx - (1.0).xx); + float2 sub5_1 = ((2.0).xx - (1.0).xx); + int2 mul0_1 = ((2).xx * 1); + int2 mul1_1 = (2 * (1).xx); + uint2 mul2_1 = ((2u).xx * 1u); + uint2 mul3_1 = (2u * (1u).xx); + float2 mul4_1 = ((2.0).xx * 1.0); + float2 mul5_1 = (2.0 * (1.0).xx); + int2 div0_1 = ((2).xx / (1).xx); + int2 div1_1 = ((2).xx / (1).xx); + uint2 div2_1 = ((2u).xx / (1u).xx); + uint2 div3_1 = ((2u).xx / (1u).xx); + float2 div4_1 = ((2.0).xx / (1.0).xx); + float2 div5_1 = ((2.0).xx / (1.0).xx); + int2 rem0_1 = ((2).xx % (1).xx); + int2 rem1_1 = ((2).xx % (1).xx); + uint2 rem2_1 = ((2u).xx % (1u).xx); + uint2 rem3_1 = ((2u).xx % (1u).xx); + float2 rem4_1 = fmod((2.0).xx, (1.0).xx); + float2 rem5_1 = fmod((2.0).xx, (1.0).xx); + } + float3x3 add = ((float3x3)0 + (float3x3)0); + float3x3 sub = ((float3x3)0 - (float3x3)0); + float3x3 mul_scalar0_ = mul(1.0, (float3x3)0); + float3x3 mul_scalar1_ = mul((float3x3)0, 2.0); + float3 mul_vector0_ = mul((1.0).xxxx, (float4x3)0); + float4 mul_vector1_ = mul((float4x3)0, (2.0).xxx); + float3x3 mul_ = mul((float3x4)0, (float4x3)0); +} + +void bit() +{ + int flip0_ = ~(1); + uint flip1_ = ~(1u); + int2 flip2_ = ~((1).xx); + uint3 flip3_ = ~((1u).xxx); + int or0_ = (2 | 1); + uint or1_ = (2u | 1u); + int2 or2_ = ((2).xx | (1).xx); + uint3 or3_ = ((2u).xxx | (1u).xxx); + int and0_ = (2 & 1); + uint and1_ = (2u & 1u); + int2 and2_ = ((2).xx & (1).xx); + uint3 and3_ = ((2u).xxx & (1u).xxx); + int xor0_ = (2 ^ 1); + uint xor1_ = (2u ^ 1u); + int2 xor2_ = ((2).xx ^ (1).xx); + uint3 xor3_ = ((2u).xxx ^ (1u).xxx); + int shl0_ = (2 << 1u); + uint shl1_ = (2u << 1u); + int2 shl2_ = ((2).xx << (1u).xx); + uint3 shl3_ = ((2u).xxx << (1u).xxx); + int shr0_ = (2 >> 1u); + uint shr1_ = (2u >> 1u); + int2 shr2_ = ((2).xx >> (1u).xx); + uint3 shr3_ = ((2u).xxx >> (1u).xxx); +} + +void comparison() +{ + bool eq0_ = (2 == 1); + bool eq1_ = (2u == 1u); + bool eq2_ = (2.0 == 1.0); + bool2 eq3_ = ((2).xx == (1).xx); + bool3 eq4_ = ((2u).xxx == (1u).xxx); + bool4 eq5_ = ((2.0).xxxx == (1.0).xxxx); + bool neq0_ = (2 != 1); + bool neq1_ = (2u != 1u); + bool neq2_ = (2.0 != 1.0); + bool2 neq3_ = ((2).xx != (1).xx); + bool3 neq4_ = ((2u).xxx != (1u).xxx); + bool4 neq5_ = ((2.0).xxxx != (1.0).xxxx); + bool lt0_ = (2 < 1); + bool lt1_ = (2u < 1u); + bool lt2_ = (2.0 < 1.0); + bool2 lt3_ = ((2).xx < (1).xx); + bool3 lt4_ = ((2u).xxx < (1u).xxx); + bool4 lt5_ = ((2.0).xxxx < (1.0).xxxx); + bool lte0_ = (2 <= 1); + bool lte1_ = (2u <= 1u); + bool lte2_ = (2.0 <= 1.0); + bool2 lte3_ = ((2).xx <= (1).xx); + bool3 lte4_ = ((2u).xxx <= (1u).xxx); + bool4 lte5_ = ((2.0).xxxx <= (1.0).xxxx); + bool gt0_ = (2 > 1); + bool gt1_ = (2u > 1u); + bool gt2_ = (2.0 > 1.0); + bool2 gt3_ = ((2).xx > (1).xx); + bool3 gt4_ = ((2u).xxx > (1u).xxx); + bool4 gt5_ = ((2.0).xxxx > (1.0).xxxx); + bool gte0_ = (2 >= 1); + bool gte1_ = (2u >= 1u); + bool gte2_ = (2.0 >= 1.0); + bool2 gte3_ = ((2).xx >= (1).xx); + bool3 gte4_ = ((2u).xxx >= (1u).xxx); + bool4 gte5_ = ((2.0).xxxx >= (1.0).xxxx); +} + +void assignment() +{ + int a_1 = (int)0; + int3 vec0_ = (int3)0; + + a_1 = 1; + int _expr5 = a_1; + a_1 = (_expr5 + 1); + int _expr7 = a_1; + a_1 = (_expr7 - 1); + int _expr9 = a_1; + int _expr10 = a_1; + a_1 = (_expr10 * _expr9); + int _expr12 = a_1; + int _expr13 = a_1; + a_1 = (_expr13 / _expr12); + int _expr15 = a_1; + a_1 = (_expr15 % 1); + int _expr17 = a_1; + a_1 = (_expr17 & 0); + int _expr19 = a_1; + a_1 = (_expr19 | 0); + int _expr21 = a_1; + a_1 = (_expr21 ^ 0); + int _expr23 = a_1; + a_1 = (_expr23 << 2u); + int _expr25 = a_1; + a_1 = (_expr25 >> 1u); + int _expr28 = a_1; + a_1 = (_expr28 + 1); + int _expr31 = a_1; + a_1 = (_expr31 - 1); + int _expr37 = vec0_[1]; + vec0_[1] = (_expr37 + 1); + int _expr41 = vec0_[1]; + vec0_[1] = (_expr41 - 1); + return; +} + +void negation_avoids_prefix_decrement() +{ + int p0_ = -(1); + int p1_ = -(-(1)); + int p2_ = -(-(1)); + int p3_ = -(-(1)); + int p4_ = -(-(-(1))); + int p5_ = -(-(-(-(1)))); + int p6_ = -(-(-(-(-(1))))); + int p7_ = -(-(-(-(-(1))))); +} + +[numthreads(1, 1, 1)] +void main(uint3 id : SV_GroupID) +{ + const float4 _e1 = builtins(); + const float4 _e6 = splat(float(id.x), int(id.y)); + const float3 _e11 = bool_cast(float3(1.0, 1.0, 1.0)); + logical(); + arithmetic(); + bit(); + comparison(); + assignment(); + return; +} diff --git a/naga/tests/out/hlsl/operators.ron b/naga/tests/out/hlsl/operators.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/operators.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/padding.hlsl b/naga/tests/out/hlsl/padding.hlsl new file mode 100644 index 0000000000..e3271e5663 --- /dev/null +++ b/naga/tests/out/hlsl/padding.hlsl @@ -0,0 +1,42 @@ +struct S { + float3 a; + int _end_pad_0; +}; + +struct Test { + S a; + float b; + int _end_pad_0; + int _end_pad_1; + int _end_pad_2; +}; + +struct Test2_ { + float3 a[2]; + int _pad1_0; + float b; + int _end_pad_0; + int _end_pad_1; + int _end_pad_2; +}; + +struct Test3_ { + row_major float4x3 a; + int _pad1_0; + float b; + int _end_pad_0; + int _end_pad_1; + int _end_pad_2; +}; + +cbuffer input1_ : register(b0) { Test input1_; } +cbuffer input2_ : register(b1) { Test2_ input2_; } +cbuffer input3_ : register(b2) { Test3_ input3_; } + +float4 vertex() : SV_Position +{ + float _expr4 = input1_.b; + float _expr8 = input2_.b; + float _expr12 = input3_.b; + return ((((1.0).xxxx * _expr4) * _expr8) * _expr12); +} diff --git a/naga/tests/out/hlsl/padding.ron b/naga/tests/out/hlsl/padding.ron new file mode 100644 index 0000000000..46dfdd83e3 --- /dev/null +++ b/naga/tests/out/hlsl/padding.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ( + entry_point:"vertex", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/push-constants.hlsl b/naga/tests/out/hlsl/push-constants.hlsl new file mode 100644 index 0000000000..187eb5b2fc --- /dev/null +++ b/naga/tests/out/hlsl/push-constants.hlsl @@ -0,0 +1,33 @@ +struct NagaConstants { + int first_vertex; + int first_instance; + uint other; +}; +ConstantBuffer _NagaConstants: register(b0, space1); + +struct PushConstants { + float multiplier; +}; + +struct FragmentIn { + float4 color : LOC0; +}; + +ConstantBuffer pc: register(b0); + +struct FragmentInput_main { + float4 color : LOC0; +}; + +float4 vert_main(float2 pos : LOC0, uint ii : SV_InstanceID, uint vi : SV_VertexID) : SV_Position +{ + float _expr8 = pc.multiplier; + return float4((((float((_NagaConstants.first_instance + ii)) * float((_NagaConstants.first_vertex + vi))) * _expr8) * pos), 0.0, 1.0); +} + +float4 main(FragmentInput_main fragmentinput_main) : SV_Target0 +{ + FragmentIn in_ = { fragmentinput_main.color }; + float _expr4 = pc.multiplier; + return (in_.color * _expr4); +} diff --git a/naga/tests/out/hlsl/push-constants.ron b/naga/tests/out/hlsl/push-constants.ron new file mode 100644 index 0000000000..e444486559 --- /dev/null +++ b/naga/tests/out/hlsl/push-constants.ron @@ -0,0 +1,16 @@ +( + vertex:[ + ( + entry_point:"vert_main", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/quad-vert.hlsl b/naga/tests/out/hlsl/quad-vert.hlsl new file mode 100644 index 0000000000..da2ff49dab --- /dev/null +++ b/naga/tests/out/hlsl/quad-vert.hlsl @@ -0,0 +1,59 @@ +struct gl_PerVertex { + float4 gl_Position : SV_Position; + float gl_PointSize; + float gl_ClipDistance[1]; + float gl_CullDistance[1]; + int _end_pad_0; +}; + +struct type_4 { + float2 member : LOC0; + float4 gl_Position : SV_Position; +}; + +gl_PerVertex Constructgl_PerVertex(float4 arg0, float arg1, float arg2[1], float arg3[1]) { + gl_PerVertex ret = (gl_PerVertex)0; + ret.gl_Position = arg0; + ret.gl_PointSize = arg1; + ret.gl_ClipDistance = arg2; + ret.gl_CullDistance = arg3; + return ret; +} + +static float2 v_uv = (float2)0; +static float2 a_uv_1 = (float2)0; +static gl_PerVertex perVertexStruct = Constructgl_PerVertex(float4(0.0, 0.0, 0.0, 1.0), 1.0, (float[1])0, (float[1])0); +static float2 a_pos_1 = (float2)0; + +struct VertexOutput_main { + float2 member : LOC0; + float4 gl_Position : SV_Position; +}; + +void main_1() +{ + float2 _expr6 = a_uv_1; + v_uv = _expr6; + float2 _expr7 = a_pos_1; + perVertexStruct.gl_Position = float4(_expr7.x, _expr7.y, 0.0, 1.0); + return; +} + +type_4 Constructtype_4(float2 arg0, float4 arg1) { + type_4 ret = (type_4)0; + ret.member = arg0; + ret.gl_Position = arg1; + return ret; +} + +VertexOutput_main main(float2 a_uv : LOC1, float2 a_pos : LOC0) +{ + a_uv_1 = a_uv; + a_pos_1 = a_pos; + main_1(); + float2 _expr7 = v_uv; + float4 _expr8 = perVertexStruct.gl_Position; + const type_4 type_4_ = Constructtype_4(_expr7, _expr8); + const VertexOutput_main type_4_1 = { type_4_.member, type_4_.gl_Position }; + return type_4_1; +} diff --git a/naga/tests/out/hlsl/quad-vert.ron b/naga/tests/out/hlsl/quad-vert.ron new file mode 100644 index 0000000000..8240856a5c --- /dev/null +++ b/naga/tests/out/hlsl/quad-vert.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ( + entry_point:"main", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/quad.hlsl b/naga/tests/out/hlsl/quad.hlsl new file mode 100644 index 0000000000..5bd8530c68 --- /dev/null +++ b/naga/tests/out/hlsl/quad.hlsl @@ -0,0 +1,48 @@ +struct VertexOutput { + float2 uv : LOC0; + float4 position : SV_Position; +}; + +static const float c_scale = 1.2; + +Texture2D u_texture : register(t0); +SamplerState u_sampler : register(s1); + +struct VertexOutput_vert_main { + float2 uv_2 : LOC0; + float4 position : SV_Position; +}; + +struct FragmentInput_frag_main { + float2 uv_3 : LOC0; +}; + +VertexOutput ConstructVertexOutput(float2 arg0, float4 arg1) { + VertexOutput ret = (VertexOutput)0; + ret.uv = arg0; + ret.position = arg1; + return ret; +} + +VertexOutput_vert_main vert_main(float2 pos : LOC0, float2 uv : LOC1) +{ + const VertexOutput vertexoutput = ConstructVertexOutput(uv, float4((c_scale * pos), 0.0, 1.0)); + const VertexOutput_vert_main vertexoutput_1 = { vertexoutput.uv, vertexoutput.position }; + return vertexoutput_1; +} + +float4 frag_main(FragmentInput_frag_main fragmentinput_frag_main) : SV_Target0 +{ + float2 uv_1 = fragmentinput_frag_main.uv_3; + float4 color = u_texture.Sample(u_sampler, uv_1); + if ((color.w == 0.0)) { + discard; + } + float4 premultiplied = (color.w * color); + return premultiplied; +} + +float4 fs_extra() : SV_Target0 +{ + return float4(0.0, 0.5, 0.0, 0.5); +} diff --git a/naga/tests/out/hlsl/quad.ron b/naga/tests/out/hlsl/quad.ron new file mode 100644 index 0000000000..de90552356 --- /dev/null +++ b/naga/tests/out/hlsl/quad.ron @@ -0,0 +1,20 @@ +( + vertex:[ + ( + entry_point:"vert_main", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"frag_main", + target_profile:"ps_5_1", + ), + ( + entry_point:"fs_extra", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/shadow.hlsl b/naga/tests/out/hlsl/shadow.hlsl new file mode 100644 index 0000000000..91a918283b --- /dev/null +++ b/naga/tests/out/hlsl/shadow.hlsl @@ -0,0 +1,158 @@ +struct Globals { + row_major float4x4 view_proj; + uint4 num_lights; +}; + +struct Entity { + row_major float4x4 world; + float4 color; +}; + +struct VertexOutput { + float4 proj_position : SV_Position; + float3 world_normal : LOC0; + float4 world_position : LOC1; +}; + +struct Light { + row_major float4x4 proj; + float4 pos; + float4 color; +}; + +static const float3 c_ambient = float3(0.05, 0.05, 0.05); +static const uint c_max_lights = 10u; + +cbuffer u_globals : register(b0) { Globals u_globals; } +cbuffer u_entity : register(b0, space1) { Entity u_entity; } +ByteAddressBuffer s_lights : register(t1); +cbuffer u_lights : register(b1) { Light u_lights[10]; } +Texture2DArray t_shadow : register(t2); +SamplerComparisonState sampler_shadow : register(s3); + +struct VertexOutput_vs_main { + float3 world_normal : LOC0; + float4 world_position : LOC1; + float4 proj_position : SV_Position; +}; + +struct FragmentInput_fs_main { + float3 world_normal_1 : LOC0; + float4 world_position_1 : LOC1; + float4 proj_position_1 : SV_Position; +}; + +struct FragmentInput_fs_main_without_storage { + float3 world_normal_2 : LOC0; + float4 world_position_2 : LOC1; + float4 proj_position_2 : SV_Position; +}; + +float fetch_shadow(uint light_id, float4 homogeneous_coords) +{ + if ((homogeneous_coords.w <= 0.0)) { + return 1.0; + } + float2 flip_correction = float2(0.5, -0.5); + float proj_correction = (1.0 / homogeneous_coords.w); + float2 light_local = (((homogeneous_coords.xy * flip_correction) * proj_correction) + float2(0.5, 0.5)); + float _expr24 = t_shadow.SampleCmpLevelZero(sampler_shadow, float3(light_local, int(light_id)), (homogeneous_coords.z * proj_correction)); + return _expr24; +} + +VertexOutput_vs_main vs_main(int4 position : LOC0, int4 normal : LOC1) +{ + VertexOutput out_ = (VertexOutput)0; + + float4x4 w = u_entity.world; + float4x4 _expr7 = u_entity.world; + float4 world_pos = mul(float4(position), _expr7); + out_.world_normal = mul(float3(normal.xyz), float3x3(w[0].xyz, w[1].xyz, w[2].xyz)); + out_.world_position = world_pos; + float4x4 _expr26 = u_globals.view_proj; + out_.proj_position = mul(world_pos, _expr26); + VertexOutput _expr28 = out_; + const VertexOutput vertexoutput = _expr28; + const VertexOutput_vs_main vertexoutput_1 = { vertexoutput.world_normal, vertexoutput.world_position, vertexoutput.proj_position }; + return vertexoutput_1; +} + +Light ConstructLight(float4x4 arg0, float4 arg1, float4 arg2) { + Light ret = (Light)0; + ret.proj = arg0; + ret.pos = arg1; + ret.color = arg2; + return ret; +} + +float4 fs_main(FragmentInput_fs_main fragmentinput_fs_main) : SV_Target0 +{ + VertexOutput in_ = { fragmentinput_fs_main.proj_position_1, fragmentinput_fs_main.world_normal_1, fragmentinput_fs_main.world_position_1 }; + float3 color = c_ambient; + uint i = 0u; + + float3 normal_1 = normalize(in_.world_normal); + bool loop_init = true; + while(true) { + if (!loop_init) { + uint _expr40 = i; + i = (_expr40 + 1u); + } + loop_init = false; + uint _expr7 = i; + uint _expr11 = u_globals.num_lights.x; + if ((_expr7 < min(_expr11, c_max_lights))) { + } else { + break; + } + { + uint _expr16 = i; + Light light = ConstructLight(float4x4(asfloat(s_lights.Load4(_expr16*96+0+0)), asfloat(s_lights.Load4(_expr16*96+0+16)), asfloat(s_lights.Load4(_expr16*96+0+32)), asfloat(s_lights.Load4(_expr16*96+0+48))), asfloat(s_lights.Load4(_expr16*96+64)), asfloat(s_lights.Load4(_expr16*96+80))); + uint _expr19 = i; + const float _e23 = fetch_shadow(_expr19, mul(in_.world_position, light.proj)); + float3 light_dir = normalize((light.pos.xyz - in_.world_position.xyz)); + float diffuse = max(0.0, dot(normal_1, light_dir)); + float3 _expr37 = color; + color = (_expr37 + ((_e23 * diffuse) * light.color.xyz)); + } + } + float3 _expr42 = color; + float4 _expr47 = u_entity.color; + return (float4(_expr42, 1.0) * _expr47); +} + +float4 fs_main_without_storage(FragmentInput_fs_main_without_storage fragmentinput_fs_main_without_storage) : SV_Target0 +{ + VertexOutput in_1 = { fragmentinput_fs_main_without_storage.proj_position_2, fragmentinput_fs_main_without_storage.world_normal_2, fragmentinput_fs_main_without_storage.world_position_2 }; + float3 color_1 = c_ambient; + uint i_1 = 0u; + + float3 normal_2 = normalize(in_1.world_normal); + bool loop_init_1 = true; + while(true) { + if (!loop_init_1) { + uint _expr40 = i_1; + i_1 = (_expr40 + 1u); + } + loop_init_1 = false; + uint _expr7 = i_1; + uint _expr11 = u_globals.num_lights.x; + if ((_expr7 < min(_expr11, c_max_lights))) { + } else { + break; + } + { + uint _expr16 = i_1; + Light light_1 = u_lights[_expr16]; + uint _expr19 = i_1; + const float _e23 = fetch_shadow(_expr19, mul(in_1.world_position, light_1.proj)); + float3 light_dir_1 = normalize((light_1.pos.xyz - in_1.world_position.xyz)); + float diffuse_1 = max(0.0, dot(normal_2, light_dir_1)); + float3 _expr37 = color_1; + color_1 = (_expr37 + ((_e23 * diffuse_1) * light_1.color.xyz)); + } + } + float3 _expr42 = color_1; + float4 _expr47 = u_entity.color; + return (float4(_expr42, 1.0) * _expr47); +} diff --git a/naga/tests/out/hlsl/shadow.ron b/naga/tests/out/hlsl/shadow.ron new file mode 100644 index 0000000000..69be5b25e0 --- /dev/null +++ b/naga/tests/out/hlsl/shadow.ron @@ -0,0 +1,20 @@ +( + vertex:[ + ( + entry_point:"vs_main", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"fs_main", + target_profile:"ps_5_1", + ), + ( + entry_point:"fs_main_without_storage", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/skybox.hlsl b/naga/tests/out/hlsl/skybox.hlsl new file mode 100644 index 0000000000..8dc97b1e8e --- /dev/null +++ b/naga/tests/out/hlsl/skybox.hlsl @@ -0,0 +1,65 @@ +struct NagaConstants { + int first_vertex; + int first_instance; + uint other; +}; +ConstantBuffer _NagaConstants: register(b1); + +struct VertexOutput { + float4 position : SV_Position; + float3 uv : LOC0; +}; + +struct Data { + row_major float4x4 proj_inv; + row_major float4x4 view; +}; + +cbuffer r_data : register(b0) { Data r_data; } +TextureCube r_texture : register(t0); +SamplerState r_sampler : register(s0, space1); + +struct VertexOutput_vs_main { + float3 uv : LOC0; + float4 position : SV_Position; +}; + +struct FragmentInput_fs_main { + float3 uv_1 : LOC0; + float4 position_1 : SV_Position; +}; + +VertexOutput ConstructVertexOutput(float4 arg0, float3 arg1) { + VertexOutput ret = (VertexOutput)0; + ret.position = arg0; + ret.uv = arg1; + return ret; +} + +VertexOutput_vs_main vs_main(uint vertex_index : SV_VertexID) +{ + int tmp1_ = (int)0; + int tmp2_ = (int)0; + + tmp1_ = (int((_NagaConstants.first_vertex + vertex_index)) / 2); + tmp2_ = (int((_NagaConstants.first_vertex + vertex_index)) & 1); + int _expr9 = tmp1_; + int _expr15 = tmp2_; + float4 pos = float4(((float(_expr9) * 4.0) - 1.0), ((float(_expr15) * 4.0) - 1.0), 0.0, 1.0); + float4 _expr27 = r_data.view[0]; + float4 _expr32 = r_data.view[1]; + float4 _expr37 = r_data.view[2]; + float3x3 inv_model_view = transpose(float3x3(_expr27.xyz, _expr32.xyz, _expr37.xyz)); + float4x4 _expr43 = r_data.proj_inv; + float4 unprojected = mul(pos, _expr43); + const VertexOutput vertexoutput = ConstructVertexOutput(pos, mul(unprojected.xyz, inv_model_view)); + const VertexOutput_vs_main vertexoutput_1 = { vertexoutput.uv, vertexoutput.position }; + return vertexoutput_1; +} + +float4 fs_main(FragmentInput_fs_main fragmentinput_fs_main) : SV_Target0 +{ + VertexOutput in_ = { fragmentinput_fs_main.position_1, fragmentinput_fs_main.uv_1 }; + float4 _expr4 = r_texture.Sample(r_sampler, in_.uv); + return _expr4; +} diff --git a/naga/tests/out/hlsl/skybox.ron b/naga/tests/out/hlsl/skybox.ron new file mode 100644 index 0000000000..27b0c4af4d --- /dev/null +++ b/naga/tests/out/hlsl/skybox.ron @@ -0,0 +1,16 @@ +( + vertex:[ + ( + entry_point:"vs_main", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"fs_main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/standard.hlsl b/naga/tests/out/hlsl/standard.hlsl new file mode 100644 index 0000000000..d3fd537ebe --- /dev/null +++ b/naga/tests/out/hlsl/standard.hlsl @@ -0,0 +1,40 @@ +struct FragmentInput_derivatives { + float4 foo_1 : SV_Position; +}; + +bool test_any_and_all_for_bool() +{ + return true; +} + +float4 derivatives(FragmentInput_derivatives fragmentinput_derivatives) : SV_Target0 +{ + float4 foo = fragmentinput_derivatives.foo_1; + float4 x = (float4)0; + float4 y = (float4)0; + float4 z = (float4)0; + + float4 _expr1 = ddx_coarse(foo); + x = _expr1; + float4 _expr3 = ddy_coarse(foo); + y = _expr3; + float4 _expr5 = abs(ddx_coarse(foo)) + abs(ddy_coarse(foo)); + z = _expr5; + float4 _expr7 = ddx_fine(foo); + x = _expr7; + float4 _expr8 = ddy_fine(foo); + y = _expr8; + float4 _expr9 = abs(ddx_fine(foo)) + abs(ddy_fine(foo)); + z = _expr9; + float4 _expr10 = ddx(foo); + x = _expr10; + float4 _expr11 = ddy(foo); + y = _expr11; + float4 _expr12 = fwidth(foo); + z = _expr12; + const bool _e13 = test_any_and_all_for_bool(); + float4 _expr14 = x; + float4 _expr15 = y; + float4 _expr17 = z; + return ((_expr14 + _expr15) * _expr17); +} diff --git a/naga/tests/out/hlsl/standard.ron b/naga/tests/out/hlsl/standard.ron new file mode 100644 index 0000000000..82373299d8 --- /dev/null +++ b/naga/tests/out/hlsl/standard.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"derivatives", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/struct-layout.hlsl b/naga/tests/out/hlsl/struct-layout.hlsl new file mode 100644 index 0000000000..34bfe269ab --- /dev/null +++ b/naga/tests/out/hlsl/struct-layout.hlsl @@ -0,0 +1,87 @@ +struct NoPadding { + float3 v3_ : LOC0; + float f3_ : LOC1; +}; + +struct NeedsPadding { + float f3_forces_padding : LOC0; + float3 v3_needs_padding : LOC1; + float f3_ : LOC2; +}; + +cbuffer no_padding_uniform : register(b0) { NoPadding no_padding_uniform; } +RWByteAddressBuffer no_padding_storage : register(u1); +cbuffer needs_padding_uniform : register(b2) { NeedsPadding needs_padding_uniform; } +RWByteAddressBuffer needs_padding_storage : register(u3); + +struct FragmentInput_no_padding_frag { + float3 v3_ : LOC0; + float f3_ : LOC1; +}; + +struct FragmentInput_needs_padding_frag { + float f3_forces_padding : LOC0; + float3 v3_needs_padding : LOC1; + float f3_1 : LOC2; +}; + +float4 no_padding_frag(FragmentInput_no_padding_frag fragmentinput_no_padding_frag) : SV_Target0 +{ + NoPadding input = { fragmentinput_no_padding_frag.v3_, fragmentinput_no_padding_frag.f3_ }; + return (0.0).xxxx; +} + +float4 no_padding_vert(NoPadding input_1) : SV_Position +{ + return (0.0).xxxx; +} + +NoPadding ConstructNoPadding(float3 arg0, float arg1) { + NoPadding ret = (NoPadding)0; + ret.v3_ = arg0; + ret.f3_ = arg1; + return ret; +} + +[numthreads(16, 1, 1)] +void no_padding_comp() +{ + NoPadding x = (NoPadding)0; + + NoPadding _expr2 = no_padding_uniform; + x = _expr2; + NoPadding _expr4 = ConstructNoPadding(asfloat(no_padding_storage.Load3(0)), asfloat(no_padding_storage.Load(12))); + x = _expr4; + return; +} + +float4 needs_padding_frag(FragmentInput_needs_padding_frag fragmentinput_needs_padding_frag) : SV_Target0 +{ + NeedsPadding input_2 = { fragmentinput_needs_padding_frag.f3_forces_padding, fragmentinput_needs_padding_frag.v3_needs_padding, fragmentinput_needs_padding_frag.f3_1 }; + return (0.0).xxxx; +} + +float4 needs_padding_vert(NeedsPadding input_3) : SV_Position +{ + return (0.0).xxxx; +} + +NeedsPadding ConstructNeedsPadding(float arg0, float3 arg1, float arg2) { + NeedsPadding ret = (NeedsPadding)0; + ret.f3_forces_padding = arg0; + ret.v3_needs_padding = arg1; + ret.f3_ = arg2; + return ret; +} + +[numthreads(16, 1, 1)] +void needs_padding_comp() +{ + NeedsPadding x_1 = (NeedsPadding)0; + + NeedsPadding _expr2 = needs_padding_uniform; + x_1 = _expr2; + NeedsPadding _expr4 = ConstructNeedsPadding(asfloat(needs_padding_storage.Load(0)), asfloat(needs_padding_storage.Load3(16)), asfloat(needs_padding_storage.Load(28))); + x_1 = _expr4; + return; +} diff --git a/naga/tests/out/hlsl/struct-layout.ron b/naga/tests/out/hlsl/struct-layout.ron new file mode 100644 index 0000000000..04fe25e38a --- /dev/null +++ b/naga/tests/out/hlsl/struct-layout.ron @@ -0,0 +1,32 @@ +( + vertex:[ + ( + entry_point:"no_padding_vert", + target_profile:"vs_5_1", + ), + ( + entry_point:"needs_padding_vert", + target_profile:"vs_5_1", + ), + ], + fragment:[ + ( + entry_point:"no_padding_frag", + target_profile:"ps_5_1", + ), + ( + entry_point:"needs_padding_frag", + target_profile:"ps_5_1", + ), + ], + compute:[ + ( + entry_point:"no_padding_comp", + target_profile:"cs_5_1", + ), + ( + entry_point:"needs_padding_comp", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/texture-arg.hlsl b/naga/tests/out/hlsl/texture-arg.hlsl new file mode 100644 index 0000000000..b3fac887f4 --- /dev/null +++ b/naga/tests/out/hlsl/texture-arg.hlsl @@ -0,0 +1,14 @@ +Texture2D Texture : register(t0); +SamplerState Sampler : register(s1); + +float4 test(Texture2D Passed_Texture, SamplerState Passed_Sampler) +{ + float4 _expr5 = Passed_Texture.Sample(Passed_Sampler, float2(0.0, 0.0)); + return _expr5; +} + +float4 main() : SV_Target0 +{ + const float4 _e2 = test(Texture, Sampler); + return _e2; +} diff --git a/naga/tests/out/hlsl/texture-arg.ron b/naga/tests/out/hlsl/texture-arg.ron new file mode 100644 index 0000000000..341a4c528e --- /dev/null +++ b/naga/tests/out/hlsl/texture-arg.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"main", + target_profile:"ps_5_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/hlsl/workgroup-uniform-load.hlsl b/naga/tests/out/hlsl/workgroup-uniform-load.hlsl new file mode 100644 index 0000000000..663fe33649 --- /dev/null +++ b/naga/tests/out/hlsl/workgroup-uniform-load.hlsl @@ -0,0 +1,21 @@ +static const uint SIZE = 128u; + +groupshared int arr_i32_[128]; + +[numthreads(4, 1, 1)] +void test_workgroupUniformLoad(uint3 workgroup_id : SV_GroupID, uint3 __local_invocation_id : SV_GroupThreadID) +{ + if (all(__local_invocation_id == uint3(0u, 0u, 0u))) { + arr_i32_ = (int[128])0; + } + GroupMemoryBarrierWithGroupSync(); + GroupMemoryBarrierWithGroupSync(); + int _expr4 = arr_i32_[workgroup_id.x]; + GroupMemoryBarrierWithGroupSync(); + if ((_expr4 > 10)) { + GroupMemoryBarrierWithGroupSync(); + return; + } else { + return; + } +} diff --git a/naga/tests/out/hlsl/workgroup-uniform-load.ron b/naga/tests/out/hlsl/workgroup-uniform-load.ron new file mode 100644 index 0000000000..17e926cdeb --- /dev/null +++ b/naga/tests/out/hlsl/workgroup-uniform-load.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"test_workgroupUniformLoad", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/workgroup-var-init.hlsl b/naga/tests/out/hlsl/workgroup-var-init.hlsl new file mode 100644 index 0000000000..e0bd73f8ff --- /dev/null +++ b/naga/tests/out/hlsl/workgroup-var-init.hlsl @@ -0,0 +1,534 @@ +struct WStruct { + uint arr[512]; + int atom; + int atom_arr[8][8]; +}; + +groupshared WStruct w_mem; +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 __local_invocation_id : SV_GroupThreadID) +{ + if (all(__local_invocation_id == uint3(0u, 0u, 0u))) { + w_mem = (WStruct)0; + } + GroupMemoryBarrierWithGroupSync(); + uint _expr3[512] = w_mem.arr; + { + uint _value2[512] = _expr3; + output.Store(0, asuint(_value2[0])); + output.Store(4, asuint(_value2[1])); + output.Store(8, asuint(_value2[2])); + output.Store(12, asuint(_value2[3])); + output.Store(16, asuint(_value2[4])); + output.Store(20, asuint(_value2[5])); + output.Store(24, asuint(_value2[6])); + output.Store(28, asuint(_value2[7])); + output.Store(32, asuint(_value2[8])); + output.Store(36, asuint(_value2[9])); + output.Store(40, asuint(_value2[10])); + output.Store(44, asuint(_value2[11])); + output.Store(48, asuint(_value2[12])); + output.Store(52, asuint(_value2[13])); + output.Store(56, asuint(_value2[14])); + output.Store(60, asuint(_value2[15])); + output.Store(64, asuint(_value2[16])); + output.Store(68, asuint(_value2[17])); + output.Store(72, asuint(_value2[18])); + output.Store(76, asuint(_value2[19])); + output.Store(80, asuint(_value2[20])); + output.Store(84, asuint(_value2[21])); + output.Store(88, asuint(_value2[22])); + output.Store(92, asuint(_value2[23])); + output.Store(96, asuint(_value2[24])); + output.Store(100, asuint(_value2[25])); + output.Store(104, asuint(_value2[26])); + output.Store(108, asuint(_value2[27])); + output.Store(112, asuint(_value2[28])); + output.Store(116, asuint(_value2[29])); + output.Store(120, asuint(_value2[30])); + output.Store(124, asuint(_value2[31])); + output.Store(128, asuint(_value2[32])); + output.Store(132, asuint(_value2[33])); + output.Store(136, asuint(_value2[34])); + output.Store(140, asuint(_value2[35])); + output.Store(144, asuint(_value2[36])); + output.Store(148, asuint(_value2[37])); + output.Store(152, asuint(_value2[38])); + output.Store(156, asuint(_value2[39])); + output.Store(160, asuint(_value2[40])); + output.Store(164, asuint(_value2[41])); + output.Store(168, asuint(_value2[42])); + output.Store(172, asuint(_value2[43])); + output.Store(176, asuint(_value2[44])); + output.Store(180, asuint(_value2[45])); + output.Store(184, asuint(_value2[46])); + output.Store(188, asuint(_value2[47])); + output.Store(192, asuint(_value2[48])); + output.Store(196, asuint(_value2[49])); + output.Store(200, asuint(_value2[50])); + output.Store(204, asuint(_value2[51])); + output.Store(208, asuint(_value2[52])); + output.Store(212, asuint(_value2[53])); + output.Store(216, asuint(_value2[54])); + output.Store(220, asuint(_value2[55])); + output.Store(224, asuint(_value2[56])); + output.Store(228, asuint(_value2[57])); + output.Store(232, asuint(_value2[58])); + output.Store(236, asuint(_value2[59])); + output.Store(240, asuint(_value2[60])); + output.Store(244, asuint(_value2[61])); + output.Store(248, asuint(_value2[62])); + output.Store(252, asuint(_value2[63])); + output.Store(256, asuint(_value2[64])); + output.Store(260, asuint(_value2[65])); + output.Store(264, asuint(_value2[66])); + output.Store(268, asuint(_value2[67])); + output.Store(272, asuint(_value2[68])); + output.Store(276, asuint(_value2[69])); + output.Store(280, asuint(_value2[70])); + output.Store(284, asuint(_value2[71])); + output.Store(288, asuint(_value2[72])); + output.Store(292, asuint(_value2[73])); + output.Store(296, asuint(_value2[74])); + output.Store(300, asuint(_value2[75])); + output.Store(304, asuint(_value2[76])); + output.Store(308, asuint(_value2[77])); + output.Store(312, asuint(_value2[78])); + output.Store(316, asuint(_value2[79])); + output.Store(320, asuint(_value2[80])); + output.Store(324, asuint(_value2[81])); + output.Store(328, asuint(_value2[82])); + output.Store(332, asuint(_value2[83])); + output.Store(336, asuint(_value2[84])); + output.Store(340, asuint(_value2[85])); + output.Store(344, asuint(_value2[86])); + output.Store(348, asuint(_value2[87])); + output.Store(352, asuint(_value2[88])); + output.Store(356, asuint(_value2[89])); + output.Store(360, asuint(_value2[90])); + output.Store(364, asuint(_value2[91])); + output.Store(368, asuint(_value2[92])); + output.Store(372, asuint(_value2[93])); + output.Store(376, asuint(_value2[94])); + output.Store(380, asuint(_value2[95])); + output.Store(384, asuint(_value2[96])); + output.Store(388, asuint(_value2[97])); + output.Store(392, asuint(_value2[98])); + output.Store(396, asuint(_value2[99])); + output.Store(400, asuint(_value2[100])); + output.Store(404, asuint(_value2[101])); + output.Store(408, asuint(_value2[102])); + output.Store(412, asuint(_value2[103])); + output.Store(416, asuint(_value2[104])); + output.Store(420, asuint(_value2[105])); + output.Store(424, asuint(_value2[106])); + output.Store(428, asuint(_value2[107])); + output.Store(432, asuint(_value2[108])); + output.Store(436, asuint(_value2[109])); + output.Store(440, asuint(_value2[110])); + output.Store(444, asuint(_value2[111])); + output.Store(448, asuint(_value2[112])); + output.Store(452, asuint(_value2[113])); + output.Store(456, asuint(_value2[114])); + output.Store(460, asuint(_value2[115])); + output.Store(464, asuint(_value2[116])); + output.Store(468, asuint(_value2[117])); + output.Store(472, asuint(_value2[118])); + output.Store(476, asuint(_value2[119])); + output.Store(480, asuint(_value2[120])); + output.Store(484, asuint(_value2[121])); + output.Store(488, asuint(_value2[122])); + output.Store(492, asuint(_value2[123])); + output.Store(496, asuint(_value2[124])); + output.Store(500, asuint(_value2[125])); + output.Store(504, asuint(_value2[126])); + output.Store(508, asuint(_value2[127])); + output.Store(512, asuint(_value2[128])); + output.Store(516, asuint(_value2[129])); + output.Store(520, asuint(_value2[130])); + output.Store(524, asuint(_value2[131])); + output.Store(528, asuint(_value2[132])); + output.Store(532, asuint(_value2[133])); + output.Store(536, asuint(_value2[134])); + output.Store(540, asuint(_value2[135])); + output.Store(544, asuint(_value2[136])); + output.Store(548, asuint(_value2[137])); + output.Store(552, asuint(_value2[138])); + output.Store(556, asuint(_value2[139])); + output.Store(560, asuint(_value2[140])); + output.Store(564, asuint(_value2[141])); + output.Store(568, asuint(_value2[142])); + output.Store(572, asuint(_value2[143])); + output.Store(576, asuint(_value2[144])); + output.Store(580, asuint(_value2[145])); + output.Store(584, asuint(_value2[146])); + output.Store(588, asuint(_value2[147])); + output.Store(592, asuint(_value2[148])); + output.Store(596, asuint(_value2[149])); + output.Store(600, asuint(_value2[150])); + output.Store(604, asuint(_value2[151])); + output.Store(608, asuint(_value2[152])); + output.Store(612, asuint(_value2[153])); + output.Store(616, asuint(_value2[154])); + output.Store(620, asuint(_value2[155])); + output.Store(624, asuint(_value2[156])); + output.Store(628, asuint(_value2[157])); + output.Store(632, asuint(_value2[158])); + output.Store(636, asuint(_value2[159])); + output.Store(640, asuint(_value2[160])); + output.Store(644, asuint(_value2[161])); + output.Store(648, asuint(_value2[162])); + output.Store(652, asuint(_value2[163])); + output.Store(656, asuint(_value2[164])); + output.Store(660, asuint(_value2[165])); + output.Store(664, asuint(_value2[166])); + output.Store(668, asuint(_value2[167])); + output.Store(672, asuint(_value2[168])); + output.Store(676, asuint(_value2[169])); + output.Store(680, asuint(_value2[170])); + output.Store(684, asuint(_value2[171])); + output.Store(688, asuint(_value2[172])); + output.Store(692, asuint(_value2[173])); + output.Store(696, asuint(_value2[174])); + output.Store(700, asuint(_value2[175])); + output.Store(704, asuint(_value2[176])); + output.Store(708, asuint(_value2[177])); + output.Store(712, asuint(_value2[178])); + output.Store(716, asuint(_value2[179])); + output.Store(720, asuint(_value2[180])); + output.Store(724, asuint(_value2[181])); + output.Store(728, asuint(_value2[182])); + output.Store(732, asuint(_value2[183])); + output.Store(736, asuint(_value2[184])); + output.Store(740, asuint(_value2[185])); + output.Store(744, asuint(_value2[186])); + output.Store(748, asuint(_value2[187])); + output.Store(752, asuint(_value2[188])); + output.Store(756, asuint(_value2[189])); + output.Store(760, asuint(_value2[190])); + output.Store(764, asuint(_value2[191])); + output.Store(768, asuint(_value2[192])); + output.Store(772, asuint(_value2[193])); + output.Store(776, asuint(_value2[194])); + output.Store(780, asuint(_value2[195])); + output.Store(784, asuint(_value2[196])); + output.Store(788, asuint(_value2[197])); + output.Store(792, asuint(_value2[198])); + output.Store(796, asuint(_value2[199])); + output.Store(800, asuint(_value2[200])); + output.Store(804, asuint(_value2[201])); + output.Store(808, asuint(_value2[202])); + output.Store(812, asuint(_value2[203])); + output.Store(816, asuint(_value2[204])); + output.Store(820, asuint(_value2[205])); + output.Store(824, asuint(_value2[206])); + output.Store(828, asuint(_value2[207])); + output.Store(832, asuint(_value2[208])); + output.Store(836, asuint(_value2[209])); + output.Store(840, asuint(_value2[210])); + output.Store(844, asuint(_value2[211])); + output.Store(848, asuint(_value2[212])); + output.Store(852, asuint(_value2[213])); + output.Store(856, asuint(_value2[214])); + output.Store(860, asuint(_value2[215])); + output.Store(864, asuint(_value2[216])); + output.Store(868, asuint(_value2[217])); + output.Store(872, asuint(_value2[218])); + output.Store(876, asuint(_value2[219])); + output.Store(880, asuint(_value2[220])); + output.Store(884, asuint(_value2[221])); + output.Store(888, asuint(_value2[222])); + output.Store(892, asuint(_value2[223])); + output.Store(896, asuint(_value2[224])); + output.Store(900, asuint(_value2[225])); + output.Store(904, asuint(_value2[226])); + output.Store(908, asuint(_value2[227])); + output.Store(912, asuint(_value2[228])); + output.Store(916, asuint(_value2[229])); + output.Store(920, asuint(_value2[230])); + output.Store(924, asuint(_value2[231])); + output.Store(928, asuint(_value2[232])); + output.Store(932, asuint(_value2[233])); + output.Store(936, asuint(_value2[234])); + output.Store(940, asuint(_value2[235])); + output.Store(944, asuint(_value2[236])); + output.Store(948, asuint(_value2[237])); + output.Store(952, asuint(_value2[238])); + output.Store(956, asuint(_value2[239])); + output.Store(960, asuint(_value2[240])); + output.Store(964, asuint(_value2[241])); + output.Store(968, asuint(_value2[242])); + output.Store(972, asuint(_value2[243])); + output.Store(976, asuint(_value2[244])); + output.Store(980, asuint(_value2[245])); + output.Store(984, asuint(_value2[246])); + output.Store(988, asuint(_value2[247])); + output.Store(992, asuint(_value2[248])); + output.Store(996, asuint(_value2[249])); + output.Store(1000, asuint(_value2[250])); + output.Store(1004, asuint(_value2[251])); + output.Store(1008, asuint(_value2[252])); + output.Store(1012, asuint(_value2[253])); + output.Store(1016, asuint(_value2[254])); + output.Store(1020, asuint(_value2[255])); + output.Store(1024, asuint(_value2[256])); + output.Store(1028, asuint(_value2[257])); + output.Store(1032, asuint(_value2[258])); + output.Store(1036, asuint(_value2[259])); + output.Store(1040, asuint(_value2[260])); + output.Store(1044, asuint(_value2[261])); + output.Store(1048, asuint(_value2[262])); + output.Store(1052, asuint(_value2[263])); + output.Store(1056, asuint(_value2[264])); + output.Store(1060, asuint(_value2[265])); + output.Store(1064, asuint(_value2[266])); + output.Store(1068, asuint(_value2[267])); + output.Store(1072, asuint(_value2[268])); + output.Store(1076, asuint(_value2[269])); + output.Store(1080, asuint(_value2[270])); + output.Store(1084, asuint(_value2[271])); + output.Store(1088, asuint(_value2[272])); + output.Store(1092, asuint(_value2[273])); + output.Store(1096, asuint(_value2[274])); + output.Store(1100, asuint(_value2[275])); + output.Store(1104, asuint(_value2[276])); + output.Store(1108, asuint(_value2[277])); + output.Store(1112, asuint(_value2[278])); + output.Store(1116, asuint(_value2[279])); + output.Store(1120, asuint(_value2[280])); + output.Store(1124, asuint(_value2[281])); + output.Store(1128, asuint(_value2[282])); + output.Store(1132, asuint(_value2[283])); + output.Store(1136, asuint(_value2[284])); + output.Store(1140, asuint(_value2[285])); + output.Store(1144, asuint(_value2[286])); + output.Store(1148, asuint(_value2[287])); + output.Store(1152, asuint(_value2[288])); + output.Store(1156, asuint(_value2[289])); + output.Store(1160, asuint(_value2[290])); + output.Store(1164, asuint(_value2[291])); + output.Store(1168, asuint(_value2[292])); + output.Store(1172, asuint(_value2[293])); + output.Store(1176, asuint(_value2[294])); + output.Store(1180, asuint(_value2[295])); + output.Store(1184, asuint(_value2[296])); + output.Store(1188, asuint(_value2[297])); + output.Store(1192, asuint(_value2[298])); + output.Store(1196, asuint(_value2[299])); + output.Store(1200, asuint(_value2[300])); + output.Store(1204, asuint(_value2[301])); + output.Store(1208, asuint(_value2[302])); + output.Store(1212, asuint(_value2[303])); + output.Store(1216, asuint(_value2[304])); + output.Store(1220, asuint(_value2[305])); + output.Store(1224, asuint(_value2[306])); + output.Store(1228, asuint(_value2[307])); + output.Store(1232, asuint(_value2[308])); + output.Store(1236, asuint(_value2[309])); + output.Store(1240, asuint(_value2[310])); + output.Store(1244, asuint(_value2[311])); + output.Store(1248, asuint(_value2[312])); + output.Store(1252, asuint(_value2[313])); + output.Store(1256, asuint(_value2[314])); + output.Store(1260, asuint(_value2[315])); + output.Store(1264, asuint(_value2[316])); + output.Store(1268, asuint(_value2[317])); + output.Store(1272, asuint(_value2[318])); + output.Store(1276, asuint(_value2[319])); + output.Store(1280, asuint(_value2[320])); + output.Store(1284, asuint(_value2[321])); + output.Store(1288, asuint(_value2[322])); + output.Store(1292, asuint(_value2[323])); + output.Store(1296, asuint(_value2[324])); + output.Store(1300, asuint(_value2[325])); + output.Store(1304, asuint(_value2[326])); + output.Store(1308, asuint(_value2[327])); + output.Store(1312, asuint(_value2[328])); + output.Store(1316, asuint(_value2[329])); + output.Store(1320, asuint(_value2[330])); + output.Store(1324, asuint(_value2[331])); + output.Store(1328, asuint(_value2[332])); + output.Store(1332, asuint(_value2[333])); + output.Store(1336, asuint(_value2[334])); + output.Store(1340, asuint(_value2[335])); + output.Store(1344, asuint(_value2[336])); + output.Store(1348, asuint(_value2[337])); + output.Store(1352, asuint(_value2[338])); + output.Store(1356, asuint(_value2[339])); + output.Store(1360, asuint(_value2[340])); + output.Store(1364, asuint(_value2[341])); + output.Store(1368, asuint(_value2[342])); + output.Store(1372, asuint(_value2[343])); + output.Store(1376, asuint(_value2[344])); + output.Store(1380, asuint(_value2[345])); + output.Store(1384, asuint(_value2[346])); + output.Store(1388, asuint(_value2[347])); + output.Store(1392, asuint(_value2[348])); + output.Store(1396, asuint(_value2[349])); + output.Store(1400, asuint(_value2[350])); + output.Store(1404, asuint(_value2[351])); + output.Store(1408, asuint(_value2[352])); + output.Store(1412, asuint(_value2[353])); + output.Store(1416, asuint(_value2[354])); + output.Store(1420, asuint(_value2[355])); + output.Store(1424, asuint(_value2[356])); + output.Store(1428, asuint(_value2[357])); + output.Store(1432, asuint(_value2[358])); + output.Store(1436, asuint(_value2[359])); + output.Store(1440, asuint(_value2[360])); + output.Store(1444, asuint(_value2[361])); + output.Store(1448, asuint(_value2[362])); + output.Store(1452, asuint(_value2[363])); + output.Store(1456, asuint(_value2[364])); + output.Store(1460, asuint(_value2[365])); + output.Store(1464, asuint(_value2[366])); + output.Store(1468, asuint(_value2[367])); + output.Store(1472, asuint(_value2[368])); + output.Store(1476, asuint(_value2[369])); + output.Store(1480, asuint(_value2[370])); + output.Store(1484, asuint(_value2[371])); + output.Store(1488, asuint(_value2[372])); + output.Store(1492, asuint(_value2[373])); + output.Store(1496, asuint(_value2[374])); + output.Store(1500, asuint(_value2[375])); + output.Store(1504, asuint(_value2[376])); + output.Store(1508, asuint(_value2[377])); + output.Store(1512, asuint(_value2[378])); + output.Store(1516, asuint(_value2[379])); + output.Store(1520, asuint(_value2[380])); + output.Store(1524, asuint(_value2[381])); + output.Store(1528, asuint(_value2[382])); + output.Store(1532, asuint(_value2[383])); + output.Store(1536, asuint(_value2[384])); + output.Store(1540, asuint(_value2[385])); + output.Store(1544, asuint(_value2[386])); + output.Store(1548, asuint(_value2[387])); + output.Store(1552, asuint(_value2[388])); + output.Store(1556, asuint(_value2[389])); + output.Store(1560, asuint(_value2[390])); + output.Store(1564, asuint(_value2[391])); + output.Store(1568, asuint(_value2[392])); + output.Store(1572, asuint(_value2[393])); + output.Store(1576, asuint(_value2[394])); + output.Store(1580, asuint(_value2[395])); + output.Store(1584, asuint(_value2[396])); + output.Store(1588, asuint(_value2[397])); + output.Store(1592, asuint(_value2[398])); + output.Store(1596, asuint(_value2[399])); + output.Store(1600, asuint(_value2[400])); + output.Store(1604, asuint(_value2[401])); + output.Store(1608, asuint(_value2[402])); + output.Store(1612, asuint(_value2[403])); + output.Store(1616, asuint(_value2[404])); + output.Store(1620, asuint(_value2[405])); + output.Store(1624, asuint(_value2[406])); + output.Store(1628, asuint(_value2[407])); + output.Store(1632, asuint(_value2[408])); + output.Store(1636, asuint(_value2[409])); + output.Store(1640, asuint(_value2[410])); + output.Store(1644, asuint(_value2[411])); + output.Store(1648, asuint(_value2[412])); + output.Store(1652, asuint(_value2[413])); + output.Store(1656, asuint(_value2[414])); + output.Store(1660, asuint(_value2[415])); + output.Store(1664, asuint(_value2[416])); + output.Store(1668, asuint(_value2[417])); + output.Store(1672, asuint(_value2[418])); + output.Store(1676, asuint(_value2[419])); + output.Store(1680, asuint(_value2[420])); + output.Store(1684, asuint(_value2[421])); + output.Store(1688, asuint(_value2[422])); + output.Store(1692, asuint(_value2[423])); + output.Store(1696, asuint(_value2[424])); + output.Store(1700, asuint(_value2[425])); + output.Store(1704, asuint(_value2[426])); + output.Store(1708, asuint(_value2[427])); + output.Store(1712, asuint(_value2[428])); + output.Store(1716, asuint(_value2[429])); + output.Store(1720, asuint(_value2[430])); + output.Store(1724, asuint(_value2[431])); + output.Store(1728, asuint(_value2[432])); + output.Store(1732, asuint(_value2[433])); + output.Store(1736, asuint(_value2[434])); + output.Store(1740, asuint(_value2[435])); + output.Store(1744, asuint(_value2[436])); + output.Store(1748, asuint(_value2[437])); + output.Store(1752, asuint(_value2[438])); + output.Store(1756, asuint(_value2[439])); + output.Store(1760, asuint(_value2[440])); + output.Store(1764, asuint(_value2[441])); + output.Store(1768, asuint(_value2[442])); + output.Store(1772, asuint(_value2[443])); + output.Store(1776, asuint(_value2[444])); + output.Store(1780, asuint(_value2[445])); + output.Store(1784, asuint(_value2[446])); + output.Store(1788, asuint(_value2[447])); + output.Store(1792, asuint(_value2[448])); + output.Store(1796, asuint(_value2[449])); + output.Store(1800, asuint(_value2[450])); + output.Store(1804, asuint(_value2[451])); + output.Store(1808, asuint(_value2[452])); + output.Store(1812, asuint(_value2[453])); + output.Store(1816, asuint(_value2[454])); + output.Store(1820, asuint(_value2[455])); + output.Store(1824, asuint(_value2[456])); + output.Store(1828, asuint(_value2[457])); + output.Store(1832, asuint(_value2[458])); + output.Store(1836, asuint(_value2[459])); + output.Store(1840, asuint(_value2[460])); + output.Store(1844, asuint(_value2[461])); + output.Store(1848, asuint(_value2[462])); + output.Store(1852, asuint(_value2[463])); + output.Store(1856, asuint(_value2[464])); + output.Store(1860, asuint(_value2[465])); + output.Store(1864, asuint(_value2[466])); + output.Store(1868, asuint(_value2[467])); + output.Store(1872, asuint(_value2[468])); + output.Store(1876, asuint(_value2[469])); + output.Store(1880, asuint(_value2[470])); + output.Store(1884, asuint(_value2[471])); + output.Store(1888, asuint(_value2[472])); + output.Store(1892, asuint(_value2[473])); + output.Store(1896, asuint(_value2[474])); + output.Store(1900, asuint(_value2[475])); + output.Store(1904, asuint(_value2[476])); + output.Store(1908, asuint(_value2[477])); + output.Store(1912, asuint(_value2[478])); + output.Store(1916, asuint(_value2[479])); + output.Store(1920, asuint(_value2[480])); + output.Store(1924, asuint(_value2[481])); + output.Store(1928, asuint(_value2[482])); + output.Store(1932, asuint(_value2[483])); + output.Store(1936, asuint(_value2[484])); + output.Store(1940, asuint(_value2[485])); + output.Store(1944, asuint(_value2[486])); + output.Store(1948, asuint(_value2[487])); + output.Store(1952, asuint(_value2[488])); + output.Store(1956, asuint(_value2[489])); + output.Store(1960, asuint(_value2[490])); + output.Store(1964, asuint(_value2[491])); + output.Store(1968, asuint(_value2[492])); + output.Store(1972, asuint(_value2[493])); + output.Store(1976, asuint(_value2[494])); + output.Store(1980, asuint(_value2[495])); + output.Store(1984, asuint(_value2[496])); + output.Store(1988, asuint(_value2[497])); + output.Store(1992, asuint(_value2[498])); + output.Store(1996, asuint(_value2[499])); + output.Store(2000, asuint(_value2[500])); + output.Store(2004, asuint(_value2[501])); + output.Store(2008, asuint(_value2[502])); + output.Store(2012, asuint(_value2[503])); + output.Store(2016, asuint(_value2[504])); + output.Store(2020, asuint(_value2[505])); + output.Store(2024, asuint(_value2[506])); + output.Store(2028, asuint(_value2[507])); + output.Store(2032, asuint(_value2[508])); + output.Store(2036, asuint(_value2[509])); + output.Store(2040, asuint(_value2[510])); + output.Store(2044, asuint(_value2[511])); + } + return; +} diff --git a/naga/tests/out/hlsl/workgroup-var-init.ron b/naga/tests/out/hlsl/workgroup-var-init.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/workgroup-var-init.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/ir/access.compact.ron b/naga/tests/out/ir/access.compact.ron new file mode 100644 index 0000000000..0670534e90 --- /dev/null +++ b/naga/tests/out/ir/access.compact.ron @@ -0,0 +1,2233 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Sint, + width: 4, + )), + ), + ( + name: Some("GlobalConst"), + inner: Struct( + members: [ + ( + name: Some("a"), + ty: 1, + binding: None, + offset: 0, + ), + ( + name: Some("b"), + ty: 2, + binding: None, + offset: 16, + ), + ( + name: Some("c"), + ty: 3, + binding: None, + offset: 28, + ), + ], + span: 32, + ), + ), + ( + name: Some("AlignedWrapper"), + inner: Struct( + members: [ + ( + name: Some("value"), + ty: 3, + binding: None, + offset: 0, + ), + ], + span: 8, + ), + ), + ( + name: None, + inner: Matrix( + columns: Quad, + rows: Tri, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Matrix( + columns: Bi, + rows: Bi, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 7, + size: Constant(2), + stride: 16, + ), + ), + ( + name: None, + inner: Atomic(( + kind: Sint, + width: 4, + )), + ), + ( + name: None, + inner: Array( + base: 9, + size: Constant(10), + stride: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 11, + size: Constant(2), + stride: 8, + ), + ), + ( + name: None, + inner: Array( + base: 5, + size: Dynamic, + stride: 8, + ), + ), + ( + name: Some("Bar"), + inner: Struct( + members: [ + ( + name: Some("_matrix"), + ty: 6, + binding: None, + offset: 0, + ), + ( + name: Some("matrix_array"), + ty: 8, + binding: None, + offset: 64, + ), + ( + name: Some("atom"), + ty: 9, + binding: None, + offset: 96, + ), + ( + name: Some("atom_arr"), + ty: 10, + binding: None, + offset: 100, + ), + ( + name: Some("arr"), + ty: 12, + binding: None, + offset: 144, + ), + ( + name: Some("data"), + ty: 13, + binding: None, + offset: 160, + ), + ], + span: 176, + ), + ), + ( + name: None, + inner: Matrix( + columns: Tri, + rows: Bi, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: Some("Baz"), + inner: Struct( + members: [ + ( + name: Some("m"), + ty: 15, + binding: None, + offset: 0, + ), + ], + span: 24, + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Sint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Matrix( + columns: Quad, + rows: Bi, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 18, + size: Constant(2), + stride: 32, + ), + ), + ( + name: Some("MatCx2InArray"), + inner: Struct( + members: [ + ( + name: Some("am"), + ty: 19, + binding: None, + offset: 0, + ), + ], + span: 64, + ), + ), + ( + name: None, + inner: Scalar(( + kind: Float, + width: 4, + )), + ), + ( + name: None, + inner: Pointer( + base: 21, + space: Function, + ), + ), + ( + name: None, + inner: Array( + base: 21, + size: Constant(10), + stride: 4, + ), + ), + ( + name: None, + inner: Array( + base: 23, + size: Constant(5), + stride: 40, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 3, + size: Constant(5), + stride: 4, + ), + ), + ( + name: None, + inner: Pointer( + base: 1, + space: Function, + ), + ), + ( + name: None, + inner: Array( + base: 25, + size: Constant(2), + stride: 16, + ), + ), + ( + name: None, + inner: Pointer( + base: 28, + space: Function, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + predeclared_types: {}, + ), + constants: [], + global_variables: [ + ( + name: Some("global_const"), + space: Private, + binding: None, + ty: 4, + init: Some(7), + ), + ( + name: Some("bar"), + space: Storage( + access: ("LOAD | STORE"), + ), + binding: Some(( + group: 0, + binding: 0, + )), + ty: 14, + init: None, + ), + ( + name: Some("baz"), + space: Uniform, + binding: Some(( + group: 0, + binding: 1, + )), + ty: 16, + init: None, + ), + ( + name: Some("qux"), + space: Storage( + access: ("LOAD | STORE"), + ), + binding: Some(( + group: 0, + binding: 2, + )), + ty: 17, + init: None, + ), + ( + name: Some("nested_mat_cx2"), + space: Uniform, + binding: Some(( + group: 0, + binding: 3, + )), + ty: 20, + init: None, + ), + ], + const_expressions: [ + Literal(U32(0)), + Literal(U32(0)), + Literal(U32(0)), + Literal(U32(0)), + Compose( + ty: 2, + components: [ + 2, + 3, + 4, + ], + ), + Literal(I32(0)), + Compose( + ty: 4, + components: [ + 1, + 5, + 6, + ], + ), + ], + functions: [ + ( + name: Some("test_matrix_within_struct_accesses"), + arguments: [], + result: None, + local_variables: [ + ( + name: Some("idx"), + ty: 3, + init: Some(1), + ), + ( + name: Some("t"), + ty: 16, + init: Some(49), + ), + ], + expressions: [ + Literal(I32(1)), + LocalVariable(1), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Subtract, + left: 4, + right: 3, + ), + GlobalVariable(3), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 7, + ), + GlobalVariable(3), + AccessIndex( + base: 9, + index: 0, + ), + AccessIndex( + base: 10, + index: 0, + ), + Load( + pointer: 11, + ), + GlobalVariable(3), + AccessIndex( + base: 13, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 14, + index: 15, + ), + Load( + pointer: 16, + ), + GlobalVariable(3), + AccessIndex( + base: 18, + index: 0, + ), + AccessIndex( + base: 19, + index: 0, + ), + AccessIndex( + base: 20, + index: 1, + ), + Load( + pointer: 21, + ), + GlobalVariable(3), + AccessIndex( + base: 23, + index: 0, + ), + AccessIndex( + base: 24, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 25, + index: 26, + ), + Load( + pointer: 27, + ), + GlobalVariable(3), + AccessIndex( + base: 29, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 30, + index: 31, + ), + AccessIndex( + base: 32, + index: 1, + ), + Load( + pointer: 33, + ), + GlobalVariable(3), + AccessIndex( + base: 35, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 36, + index: 37, + ), + Load( + pointer: 2, + ), + Access( + base: 38, + index: 39, + ), + Load( + pointer: 40, + ), + Literal(F32(1.0)), + Splat( + size: Bi, + value: 42, + ), + Literal(F32(2.0)), + Splat( + size: Bi, + value: 44, + ), + Literal(F32(3.0)), + Splat( + size: Bi, + value: 46, + ), + Compose( + ty: 15, + components: [ + 43, + 45, + 47, + ], + ), + Compose( + ty: 16, + components: [ + 48, + ], + ), + LocalVariable(2), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Add, + left: 52, + right: 51, + ), + AccessIndex( + base: 50, + index: 0, + ), + Literal(F32(6.0)), + Splat( + size: Bi, + value: 55, + ), + Literal(F32(5.0)), + Splat( + size: Bi, + value: 57, + ), + Literal(F32(4.0)), + Splat( + size: Bi, + value: 59, + ), + Compose( + ty: 15, + components: [ + 56, + 58, + 60, + ], + ), + AccessIndex( + base: 50, + index: 0, + ), + AccessIndex( + base: 62, + index: 0, + ), + Literal(F32(9.0)), + Splat( + size: Bi, + value: 64, + ), + AccessIndex( + base: 50, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 66, + index: 67, + ), + Literal(F32(90.0)), + Splat( + size: Bi, + value: 69, + ), + AccessIndex( + base: 50, + index: 0, + ), + AccessIndex( + base: 71, + index: 0, + ), + AccessIndex( + base: 72, + index: 1, + ), + Literal(F32(10.0)), + AccessIndex( + base: 50, + index: 0, + ), + AccessIndex( + base: 75, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 76, + index: 77, + ), + Literal(F32(20.0)), + AccessIndex( + base: 50, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 80, + index: 81, + ), + AccessIndex( + base: 82, + index: 1, + ), + Literal(F32(30.0)), + AccessIndex( + base: 50, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 85, + index: 86, + ), + Load( + pointer: 2, + ), + Access( + base: 87, + index: 88, + ), + Literal(F32(40.0)), + ], + named_expressions: { + 8: "l0", + 12: "l1", + 17: "l2", + 22: "l3", + 28: "l4", + 34: "l5", + 41: "l6", + }, + body: [ + Emit(( + start: 3, + end: 5, + )), + Store( + pointer: 2, + value: 5, + ), + Emit(( + start: 6, + end: 8, + )), + Emit(( + start: 9, + end: 10, + )), + Emit(( + start: 10, + end: 12, + )), + Emit(( + start: 13, + end: 17, + )), + Emit(( + start: 18, + end: 19, + )), + Emit(( + start: 19, + end: 20, + )), + Emit(( + start: 20, + end: 22, + )), + Emit(( + start: 23, + end: 24, + )), + Emit(( + start: 24, + end: 28, + )), + Emit(( + start: 29, + end: 32, + )), + Emit(( + start: 32, + end: 34, + )), + Emit(( + start: 35, + end: 41, + )), + Emit(( + start: 42, + end: 43, + )), + Emit(( + start: 44, + end: 45, + )), + Emit(( + start: 46, + end: 49, + )), + Emit(( + start: 51, + end: 53, + )), + Store( + pointer: 2, + value: 53, + ), + Emit(( + start: 53, + end: 54, + )), + Emit(( + start: 55, + end: 56, + )), + Emit(( + start: 57, + end: 58, + )), + Emit(( + start: 59, + end: 61, + )), + Store( + pointer: 54, + value: 61, + ), + Emit(( + start: 61, + end: 62, + )), + Emit(( + start: 62, + end: 63, + )), + Emit(( + start: 64, + end: 65, + )), + Store( + pointer: 63, + value: 65, + ), + Emit(( + start: 65, + end: 68, + )), + Emit(( + start: 69, + end: 70, + )), + Store( + pointer: 68, + value: 70, + ), + Emit(( + start: 70, + end: 71, + )), + Emit(( + start: 71, + end: 72, + )), + Emit(( + start: 72, + end: 73, + )), + Store( + pointer: 73, + value: 74, + ), + Emit(( + start: 74, + end: 75, + )), + Emit(( + start: 75, + end: 78, + )), + Store( + pointer: 78, + value: 79, + ), + Emit(( + start: 79, + end: 82, + )), + Emit(( + start: 82, + end: 83, + )), + Store( + pointer: 83, + value: 84, + ), + Emit(( + start: 84, + end: 89, + )), + Store( + pointer: 89, + value: 90, + ), + Return( + value: None, + ), + ], + ), + ( + name: Some("test_matrix_within_array_within_struct_accesses"), + arguments: [], + result: None, + local_variables: [ + ( + name: Some("idx"), + ty: 3, + init: Some(1), + ), + ( + name: Some("t"), + ty: 20, + init: Some(53), + ), + ], + expressions: [ + Literal(I32(1)), + LocalVariable(1), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Subtract, + left: 4, + right: 3, + ), + GlobalVariable(5), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 7, + ), + GlobalVariable(5), + AccessIndex( + base: 9, + index: 0, + ), + AccessIndex( + base: 10, + index: 0, + ), + Load( + pointer: 11, + ), + GlobalVariable(5), + AccessIndex( + base: 13, + index: 0, + ), + AccessIndex( + base: 14, + index: 0, + ), + AccessIndex( + base: 15, + index: 0, + ), + Load( + pointer: 16, + ), + GlobalVariable(5), + AccessIndex( + base: 18, + index: 0, + ), + AccessIndex( + base: 19, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 20, + index: 21, + ), + Load( + pointer: 22, + ), + GlobalVariable(5), + AccessIndex( + base: 24, + index: 0, + ), + AccessIndex( + base: 25, + index: 0, + ), + AccessIndex( + base: 26, + index: 0, + ), + AccessIndex( + base: 27, + index: 1, + ), + Load( + pointer: 28, + ), + GlobalVariable(5), + AccessIndex( + base: 30, + index: 0, + ), + AccessIndex( + base: 31, + index: 0, + ), + AccessIndex( + base: 32, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 33, + index: 34, + ), + Load( + pointer: 35, + ), + GlobalVariable(5), + AccessIndex( + base: 37, + index: 0, + ), + AccessIndex( + base: 38, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 39, + index: 40, + ), + AccessIndex( + base: 41, + index: 1, + ), + Load( + pointer: 42, + ), + GlobalVariable(5), + AccessIndex( + base: 44, + index: 0, + ), + AccessIndex( + base: 45, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 46, + index: 47, + ), + Load( + pointer: 2, + ), + Access( + base: 48, + index: 49, + ), + Load( + pointer: 50, + ), + ZeroValue(19), + Compose( + ty: 20, + components: [ + 52, + ], + ), + LocalVariable(2), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Add, + left: 56, + right: 55, + ), + AccessIndex( + base: 54, + index: 0, + ), + ZeroValue(19), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 60, + index: 0, + ), + Literal(F32(8.0)), + Splat( + size: Bi, + value: 62, + ), + Literal(F32(7.0)), + Splat( + size: Bi, + value: 64, + ), + Literal(F32(6.0)), + Splat( + size: Bi, + value: 66, + ), + Literal(F32(5.0)), + Splat( + size: Bi, + value: 68, + ), + Compose( + ty: 18, + components: [ + 63, + 65, + 67, + 69, + ], + ), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 71, + index: 0, + ), + AccessIndex( + base: 72, + index: 0, + ), + Literal(F32(9.0)), + Splat( + size: Bi, + value: 74, + ), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 76, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 77, + index: 78, + ), + Literal(F32(90.0)), + Splat( + size: Bi, + value: 80, + ), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 82, + index: 0, + ), + AccessIndex( + base: 83, + index: 0, + ), + AccessIndex( + base: 84, + index: 1, + ), + Literal(F32(10.0)), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 87, + index: 0, + ), + AccessIndex( + base: 88, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 89, + index: 90, + ), + Literal(F32(20.0)), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 93, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 94, + index: 95, + ), + AccessIndex( + base: 96, + index: 1, + ), + Literal(F32(30.0)), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 99, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 100, + index: 101, + ), + Load( + pointer: 2, + ), + Access( + base: 102, + index: 103, + ), + Literal(F32(40.0)), + ], + named_expressions: { + 8: "l0", + 12: "l1", + 17: "l2", + 23: "l3", + 29: "l4", + 36: "l5", + 43: "l6", + 51: "l7", + }, + body: [ + Emit(( + start: 3, + end: 5, + )), + Store( + pointer: 2, + value: 5, + ), + Emit(( + start: 6, + end: 8, + )), + Emit(( + start: 9, + end: 10, + )), + Emit(( + start: 10, + end: 12, + )), + Emit(( + start: 13, + end: 14, + )), + Emit(( + start: 14, + end: 15, + )), + Emit(( + start: 15, + end: 17, + )), + Emit(( + start: 18, + end: 19, + )), + Emit(( + start: 19, + end: 23, + )), + Emit(( + start: 24, + end: 25, + )), + Emit(( + start: 25, + end: 26, + )), + Emit(( + start: 26, + end: 27, + )), + Emit(( + start: 27, + end: 29, + )), + Emit(( + start: 30, + end: 31, + )), + Emit(( + start: 31, + end: 32, + )), + Emit(( + start: 32, + end: 36, + )), + Emit(( + start: 37, + end: 38, + )), + Emit(( + start: 38, + end: 41, + )), + Emit(( + start: 41, + end: 43, + )), + Emit(( + start: 44, + end: 45, + )), + Emit(( + start: 45, + end: 51, + )), + Emit(( + start: 52, + end: 53, + )), + Emit(( + start: 55, + end: 57, + )), + Store( + pointer: 2, + value: 57, + ), + Emit(( + start: 57, + end: 58, + )), + Store( + pointer: 58, + value: 59, + ), + Emit(( + start: 59, + end: 60, + )), + Emit(( + start: 60, + end: 61, + )), + Emit(( + start: 62, + end: 63, + )), + Emit(( + start: 64, + end: 65, + )), + Emit(( + start: 66, + end: 67, + )), + Emit(( + start: 68, + end: 70, + )), + Store( + pointer: 61, + value: 70, + ), + Emit(( + start: 70, + end: 71, + )), + Emit(( + start: 71, + end: 72, + )), + Emit(( + start: 72, + end: 73, + )), + Emit(( + start: 74, + end: 75, + )), + Store( + pointer: 73, + value: 75, + ), + Emit(( + start: 75, + end: 76, + )), + Emit(( + start: 76, + end: 79, + )), + Emit(( + start: 80, + end: 81, + )), + Store( + pointer: 79, + value: 81, + ), + Emit(( + start: 81, + end: 82, + )), + Emit(( + start: 82, + end: 83, + )), + Emit(( + start: 83, + end: 84, + )), + Emit(( + start: 84, + end: 85, + )), + Store( + pointer: 85, + value: 86, + ), + Emit(( + start: 86, + end: 87, + )), + Emit(( + start: 87, + end: 88, + )), + Emit(( + start: 88, + end: 91, + )), + Store( + pointer: 91, + value: 92, + ), + Emit(( + start: 92, + end: 93, + )), + Emit(( + start: 93, + end: 96, + )), + Emit(( + start: 96, + end: 97, + )), + Store( + pointer: 97, + value: 98, + ), + Emit(( + start: 98, + end: 99, + )), + Emit(( + start: 99, + end: 104, + )), + Store( + pointer: 104, + value: 105, + ), + Return( + value: None, + ), + ], + ), + ( + name: Some("read_from_private"), + arguments: [ + ( + name: Some("foo"), + ty: 22, + binding: None, + ), + ], + result: Some(( + ty: 21, + binding: None, + )), + local_variables: [], + expressions: [ + FunctionArgument(0), + Load( + pointer: 1, + ), + ], + named_expressions: { + 1: "foo", + }, + body: [ + Emit(( + start: 1, + end: 2, + )), + Return( + value: Some(2), + ), + ], + ), + ( + name: Some("test_arr_as_arg"), + arguments: [ + ( + name: Some("a"), + ty: 24, + binding: None, + ), + ], + result: Some(( + ty: 21, + binding: None, + )), + local_variables: [], + expressions: [ + FunctionArgument(0), + AccessIndex( + base: 1, + index: 4, + ), + AccessIndex( + base: 2, + index: 9, + ), + ], + named_expressions: { + 1: "a", + }, + body: [ + Emit(( + start: 1, + end: 2, + )), + Emit(( + start: 2, + end: 3, + )), + Return( + value: Some(3), + ), + ], + ), + ( + name: Some("assign_through_ptr_fn"), + arguments: [ + ( + name: Some("p"), + ty: 27, + binding: None, + ), + ], + result: None, + local_variables: [], + expressions: [ + FunctionArgument(0), + Literal(U32(42)), + ], + named_expressions: { + 1: "p", + }, + body: [ + Store( + pointer: 1, + value: 2, + ), + Return( + value: None, + ), + ], + ), + ( + name: Some("assign_array_through_ptr_fn"), + arguments: [ + ( + name: Some("foo"), + ty: 29, + binding: None, + ), + ], + result: None, + local_variables: [], + expressions: [ + FunctionArgument(0), + Literal(F32(1.0)), + Splat( + size: Quad, + value: 2, + ), + Literal(F32(2.0)), + Splat( + size: Quad, + value: 4, + ), + Compose( + ty: 28, + components: [ + 3, + 5, + ], + ), + ], + named_expressions: { + 1: "foo", + }, + body: [ + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 2, + end: 3, + )), + Emit(( + start: 4, + end: 6, + )), + Store( + pointer: 1, + value: 6, + ), + Return( + value: None, + ), + ], + ), + ], + entry_points: [ + ( + name: "foo_vert", + stage: Vertex, + early_depth_test: None, + workgroup_size: (0, 0, 0), + function: ( + name: Some("foo_vert"), + arguments: [ + ( + name: Some("vi"), + ty: 1, + binding: Some(BuiltIn(VertexIndex)), + ), + ], + result: Some(( + ty: 25, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + )), + local_variables: [ + ( + name: Some("foo"), + ty: 21, + init: Some(2), + ), + ( + name: Some("c2"), + ty: 26, + init: None, + ), + ], + expressions: [ + FunctionArgument(0), + Literal(F32(0.0)), + LocalVariable(1), + Load( + pointer: 3, + ), + Literal(F32(1.0)), + GlobalVariable(2), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 7, + ), + GlobalVariable(2), + AccessIndex( + base: 9, + index: 4, + ), + Load( + pointer: 10, + ), + Literal(U32(3)), + GlobalVariable(2), + AccessIndex( + base: 13, + index: 0, + ), + Access( + base: 14, + index: 12, + ), + AccessIndex( + base: 15, + index: 0, + ), + Load( + pointer: 16, + ), + GlobalVariable(2), + AccessIndex( + base: 18, + index: 5, + ), + GlobalVariable(2), + AccessIndex( + base: 20, + index: 5, + ), + ArrayLength(21), + Literal(U32(2)), + Binary( + op: Subtract, + left: 22, + right: 23, + ), + Access( + base: 19, + index: 24, + ), + AccessIndex( + base: 25, + index: 0, + ), + Load( + pointer: 26, + ), + GlobalVariable(4), + Load( + pointer: 28, + ), + GlobalVariable(2), + AccessIndex( + base: 30, + index: 5, + ), + AccessIndex( + base: 31, + index: 0, + ), + AccessIndex( + base: 32, + index: 0, + ), + CallResult(3), + As( + expr: 17, + kind: Sint, + convert: Some(4), + ), + Literal(I32(3)), + Literal(I32(4)), + Literal(I32(5)), + Compose( + ty: 26, + components: [ + 27, + 35, + 36, + 37, + 38, + ], + ), + LocalVariable(2), + Literal(U32(1)), + Binary( + op: Add, + left: 1, + right: 41, + ), + Access( + base: 40, + index: 42, + ), + Literal(I32(42)), + Access( + base: 40, + index: 1, + ), + Load( + pointer: 45, + ), + ZeroValue(24), + CallResult(4), + Splat( + size: Quad, + value: 46, + ), + As( + expr: 49, + kind: Float, + convert: Some(4), + ), + Binary( + op: Multiply, + left: 8, + right: 50, + ), + Literal(F32(2.0)), + Compose( + ty: 25, + components: [ + 51, + 52, + ], + ), + ], + named_expressions: { + 1: "vi", + 4: "baz", + 8: "_matrix", + 11: "arr", + 12: "index", + 17: "b", + 27: "a", + 29: "c", + 33: "data_pointer", + 34: "foo_value", + 46: "value", + }, + body: [ + Emit(( + start: 3, + end: 4, + )), + Store( + pointer: 3, + value: 5, + ), + Call( + function: 1, + arguments: [], + result: None, + ), + Call( + function: 2, + arguments: [], + result: None, + ), + Emit(( + start: 6, + end: 8, + )), + Emit(( + start: 9, + end: 11, + )), + Emit(( + start: 13, + end: 17, + )), + Emit(( + start: 18, + end: 19, + )), + Emit(( + start: 20, + end: 22, + )), + Emit(( + start: 23, + end: 27, + )), + Emit(( + start: 28, + end: 29, + )), + Emit(( + start: 30, + end: 31, + )), + Emit(( + start: 31, + end: 33, + )), + Call( + function: 3, + arguments: [ + 3, + ], + result: Some(34), + ), + Emit(( + start: 34, + end: 35, + )), + Emit(( + start: 38, + end: 39, + )), + Store( + pointer: 40, + value: 39, + ), + Emit(( + start: 41, + end: 43, + )), + Store( + pointer: 43, + value: 44, + ), + Emit(( + start: 44, + end: 46, + )), + Call( + function: 4, + arguments: [ + 47, + ], + result: Some(48), + ), + Emit(( + start: 48, + end: 51, + )), + Emit(( + start: 52, + end: 53, + )), + Return( + value: Some(53), + ), + ], + ), + ), + ( + name: "foo_frag", + stage: Fragment, + early_depth_test: None, + workgroup_size: (0, 0, 0), + function: ( + name: Some("foo_frag"), + arguments: [], + result: Some(( + ty: 25, + binding: Some(Location( + location: 0, + second_blend_source: false, + interpolation: Some(Perspective), + sampling: Some(Center), + )), + )), + local_variables: [], + expressions: [ + GlobalVariable(2), + AccessIndex( + base: 1, + index: 0, + ), + AccessIndex( + base: 2, + index: 1, + ), + AccessIndex( + base: 3, + index: 2, + ), + Literal(F32(1.0)), + GlobalVariable(2), + AccessIndex( + base: 6, + index: 0, + ), + Literal(F32(0.0)), + Splat( + size: Tri, + value: 8, + ), + Literal(F32(1.0)), + Splat( + size: Tri, + value: 10, + ), + Literal(F32(2.0)), + Splat( + size: Tri, + value: 12, + ), + Literal(F32(3.0)), + Splat( + size: Tri, + value: 14, + ), + Compose( + ty: 6, + components: [ + 9, + 11, + 13, + 15, + ], + ), + GlobalVariable(2), + AccessIndex( + base: 17, + index: 4, + ), + Literal(U32(0)), + Splat( + size: Bi, + value: 19, + ), + Literal(U32(1)), + Splat( + size: Bi, + value: 21, + ), + Compose( + ty: 12, + components: [ + 20, + 22, + ], + ), + GlobalVariable(2), + AccessIndex( + base: 24, + index: 5, + ), + AccessIndex( + base: 25, + index: 1, + ), + AccessIndex( + base: 26, + index: 0, + ), + Literal(I32(1)), + GlobalVariable(4), + ZeroValue(17), + Literal(F32(0.0)), + Splat( + size: Quad, + value: 31, + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 1, + end: 2, + )), + Emit(( + start: 2, + end: 4, + )), + Store( + pointer: 4, + value: 5, + ), + Emit(( + start: 6, + end: 7, + )), + Emit(( + start: 8, + end: 9, + )), + Emit(( + start: 10, + end: 11, + )), + Emit(( + start: 12, + end: 13, + )), + Emit(( + start: 14, + end: 16, + )), + Store( + pointer: 7, + value: 16, + ), + Emit(( + start: 17, + end: 18, + )), + Emit(( + start: 19, + end: 20, + )), + Emit(( + start: 21, + end: 23, + )), + Store( + pointer: 18, + value: 23, + ), + Emit(( + start: 24, + end: 25, + )), + Emit(( + start: 25, + end: 27, + )), + Store( + pointer: 27, + value: 28, + ), + Store( + pointer: 29, + value: 30, + ), + Emit(( + start: 31, + end: 32, + )), + Return( + value: Some(32), + ), + ], + ), + ), + ( + name: "assign_through_ptr", + stage: Compute, + early_depth_test: None, + workgroup_size: (1, 1, 1), + function: ( + name: Some("assign_through_ptr"), + arguments: [], + result: None, + local_variables: [ + ( + name: Some("val"), + ty: 1, + init: Some(1), + ), + ( + name: Some("arr"), + ty: 28, + init: Some(7), + ), + ], + expressions: [ + Literal(U32(33)), + LocalVariable(1), + Literal(F32(6.0)), + Splat( + size: Quad, + value: 3, + ), + Literal(F32(7.0)), + Splat( + size: Quad, + value: 5, + ), + Compose( + ty: 28, + components: [ + 4, + 6, + ], + ), + LocalVariable(2), + ], + named_expressions: {}, + body: [ + Call( + function: 5, + arguments: [ + 2, + ], + result: None, + ), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 3, + end: 4, + )), + Emit(( + start: 5, + end: 7, + )), + Call( + function: 6, + arguments: [ + 8, + ], + result: None, + ), + Return( + value: None, + ), + ], + ), + ), + ], +) \ No newline at end of file diff --git a/naga/tests/out/ir/access.ron b/naga/tests/out/ir/access.ron new file mode 100644 index 0000000000..0670534e90 --- /dev/null +++ b/naga/tests/out/ir/access.ron @@ -0,0 +1,2233 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Sint, + width: 4, + )), + ), + ( + name: Some("GlobalConst"), + inner: Struct( + members: [ + ( + name: Some("a"), + ty: 1, + binding: None, + offset: 0, + ), + ( + name: Some("b"), + ty: 2, + binding: None, + offset: 16, + ), + ( + name: Some("c"), + ty: 3, + binding: None, + offset: 28, + ), + ], + span: 32, + ), + ), + ( + name: Some("AlignedWrapper"), + inner: Struct( + members: [ + ( + name: Some("value"), + ty: 3, + binding: None, + offset: 0, + ), + ], + span: 8, + ), + ), + ( + name: None, + inner: Matrix( + columns: Quad, + rows: Tri, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Matrix( + columns: Bi, + rows: Bi, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 7, + size: Constant(2), + stride: 16, + ), + ), + ( + name: None, + inner: Atomic(( + kind: Sint, + width: 4, + )), + ), + ( + name: None, + inner: Array( + base: 9, + size: Constant(10), + stride: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 11, + size: Constant(2), + stride: 8, + ), + ), + ( + name: None, + inner: Array( + base: 5, + size: Dynamic, + stride: 8, + ), + ), + ( + name: Some("Bar"), + inner: Struct( + members: [ + ( + name: Some("_matrix"), + ty: 6, + binding: None, + offset: 0, + ), + ( + name: Some("matrix_array"), + ty: 8, + binding: None, + offset: 64, + ), + ( + name: Some("atom"), + ty: 9, + binding: None, + offset: 96, + ), + ( + name: Some("atom_arr"), + ty: 10, + binding: None, + offset: 100, + ), + ( + name: Some("arr"), + ty: 12, + binding: None, + offset: 144, + ), + ( + name: Some("data"), + ty: 13, + binding: None, + offset: 160, + ), + ], + span: 176, + ), + ), + ( + name: None, + inner: Matrix( + columns: Tri, + rows: Bi, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: Some("Baz"), + inner: Struct( + members: [ + ( + name: Some("m"), + ty: 15, + binding: None, + offset: 0, + ), + ], + span: 24, + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Sint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Matrix( + columns: Quad, + rows: Bi, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 18, + size: Constant(2), + stride: 32, + ), + ), + ( + name: Some("MatCx2InArray"), + inner: Struct( + members: [ + ( + name: Some("am"), + ty: 19, + binding: None, + offset: 0, + ), + ], + span: 64, + ), + ), + ( + name: None, + inner: Scalar(( + kind: Float, + width: 4, + )), + ), + ( + name: None, + inner: Pointer( + base: 21, + space: Function, + ), + ), + ( + name: None, + inner: Array( + base: 21, + size: Constant(10), + stride: 4, + ), + ), + ( + name: None, + inner: Array( + base: 23, + size: Constant(5), + stride: 40, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 3, + size: Constant(5), + stride: 4, + ), + ), + ( + name: None, + inner: Pointer( + base: 1, + space: Function, + ), + ), + ( + name: None, + inner: Array( + base: 25, + size: Constant(2), + stride: 16, + ), + ), + ( + name: None, + inner: Pointer( + base: 28, + space: Function, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + predeclared_types: {}, + ), + constants: [], + global_variables: [ + ( + name: Some("global_const"), + space: Private, + binding: None, + ty: 4, + init: Some(7), + ), + ( + name: Some("bar"), + space: Storage( + access: ("LOAD | STORE"), + ), + binding: Some(( + group: 0, + binding: 0, + )), + ty: 14, + init: None, + ), + ( + name: Some("baz"), + space: Uniform, + binding: Some(( + group: 0, + binding: 1, + )), + ty: 16, + init: None, + ), + ( + name: Some("qux"), + space: Storage( + access: ("LOAD | STORE"), + ), + binding: Some(( + group: 0, + binding: 2, + )), + ty: 17, + init: None, + ), + ( + name: Some("nested_mat_cx2"), + space: Uniform, + binding: Some(( + group: 0, + binding: 3, + )), + ty: 20, + init: None, + ), + ], + const_expressions: [ + Literal(U32(0)), + Literal(U32(0)), + Literal(U32(0)), + Literal(U32(0)), + Compose( + ty: 2, + components: [ + 2, + 3, + 4, + ], + ), + Literal(I32(0)), + Compose( + ty: 4, + components: [ + 1, + 5, + 6, + ], + ), + ], + functions: [ + ( + name: Some("test_matrix_within_struct_accesses"), + arguments: [], + result: None, + local_variables: [ + ( + name: Some("idx"), + ty: 3, + init: Some(1), + ), + ( + name: Some("t"), + ty: 16, + init: Some(49), + ), + ], + expressions: [ + Literal(I32(1)), + LocalVariable(1), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Subtract, + left: 4, + right: 3, + ), + GlobalVariable(3), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 7, + ), + GlobalVariable(3), + AccessIndex( + base: 9, + index: 0, + ), + AccessIndex( + base: 10, + index: 0, + ), + Load( + pointer: 11, + ), + GlobalVariable(3), + AccessIndex( + base: 13, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 14, + index: 15, + ), + Load( + pointer: 16, + ), + GlobalVariable(3), + AccessIndex( + base: 18, + index: 0, + ), + AccessIndex( + base: 19, + index: 0, + ), + AccessIndex( + base: 20, + index: 1, + ), + Load( + pointer: 21, + ), + GlobalVariable(3), + AccessIndex( + base: 23, + index: 0, + ), + AccessIndex( + base: 24, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 25, + index: 26, + ), + Load( + pointer: 27, + ), + GlobalVariable(3), + AccessIndex( + base: 29, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 30, + index: 31, + ), + AccessIndex( + base: 32, + index: 1, + ), + Load( + pointer: 33, + ), + GlobalVariable(3), + AccessIndex( + base: 35, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 36, + index: 37, + ), + Load( + pointer: 2, + ), + Access( + base: 38, + index: 39, + ), + Load( + pointer: 40, + ), + Literal(F32(1.0)), + Splat( + size: Bi, + value: 42, + ), + Literal(F32(2.0)), + Splat( + size: Bi, + value: 44, + ), + Literal(F32(3.0)), + Splat( + size: Bi, + value: 46, + ), + Compose( + ty: 15, + components: [ + 43, + 45, + 47, + ], + ), + Compose( + ty: 16, + components: [ + 48, + ], + ), + LocalVariable(2), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Add, + left: 52, + right: 51, + ), + AccessIndex( + base: 50, + index: 0, + ), + Literal(F32(6.0)), + Splat( + size: Bi, + value: 55, + ), + Literal(F32(5.0)), + Splat( + size: Bi, + value: 57, + ), + Literal(F32(4.0)), + Splat( + size: Bi, + value: 59, + ), + Compose( + ty: 15, + components: [ + 56, + 58, + 60, + ], + ), + AccessIndex( + base: 50, + index: 0, + ), + AccessIndex( + base: 62, + index: 0, + ), + Literal(F32(9.0)), + Splat( + size: Bi, + value: 64, + ), + AccessIndex( + base: 50, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 66, + index: 67, + ), + Literal(F32(90.0)), + Splat( + size: Bi, + value: 69, + ), + AccessIndex( + base: 50, + index: 0, + ), + AccessIndex( + base: 71, + index: 0, + ), + AccessIndex( + base: 72, + index: 1, + ), + Literal(F32(10.0)), + AccessIndex( + base: 50, + index: 0, + ), + AccessIndex( + base: 75, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 76, + index: 77, + ), + Literal(F32(20.0)), + AccessIndex( + base: 50, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 80, + index: 81, + ), + AccessIndex( + base: 82, + index: 1, + ), + Literal(F32(30.0)), + AccessIndex( + base: 50, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 85, + index: 86, + ), + Load( + pointer: 2, + ), + Access( + base: 87, + index: 88, + ), + Literal(F32(40.0)), + ], + named_expressions: { + 8: "l0", + 12: "l1", + 17: "l2", + 22: "l3", + 28: "l4", + 34: "l5", + 41: "l6", + }, + body: [ + Emit(( + start: 3, + end: 5, + )), + Store( + pointer: 2, + value: 5, + ), + Emit(( + start: 6, + end: 8, + )), + Emit(( + start: 9, + end: 10, + )), + Emit(( + start: 10, + end: 12, + )), + Emit(( + start: 13, + end: 17, + )), + Emit(( + start: 18, + end: 19, + )), + Emit(( + start: 19, + end: 20, + )), + Emit(( + start: 20, + end: 22, + )), + Emit(( + start: 23, + end: 24, + )), + Emit(( + start: 24, + end: 28, + )), + Emit(( + start: 29, + end: 32, + )), + Emit(( + start: 32, + end: 34, + )), + Emit(( + start: 35, + end: 41, + )), + Emit(( + start: 42, + end: 43, + )), + Emit(( + start: 44, + end: 45, + )), + Emit(( + start: 46, + end: 49, + )), + Emit(( + start: 51, + end: 53, + )), + Store( + pointer: 2, + value: 53, + ), + Emit(( + start: 53, + end: 54, + )), + Emit(( + start: 55, + end: 56, + )), + Emit(( + start: 57, + end: 58, + )), + Emit(( + start: 59, + end: 61, + )), + Store( + pointer: 54, + value: 61, + ), + Emit(( + start: 61, + end: 62, + )), + Emit(( + start: 62, + end: 63, + )), + Emit(( + start: 64, + end: 65, + )), + Store( + pointer: 63, + value: 65, + ), + Emit(( + start: 65, + end: 68, + )), + Emit(( + start: 69, + end: 70, + )), + Store( + pointer: 68, + value: 70, + ), + Emit(( + start: 70, + end: 71, + )), + Emit(( + start: 71, + end: 72, + )), + Emit(( + start: 72, + end: 73, + )), + Store( + pointer: 73, + value: 74, + ), + Emit(( + start: 74, + end: 75, + )), + Emit(( + start: 75, + end: 78, + )), + Store( + pointer: 78, + value: 79, + ), + Emit(( + start: 79, + end: 82, + )), + Emit(( + start: 82, + end: 83, + )), + Store( + pointer: 83, + value: 84, + ), + Emit(( + start: 84, + end: 89, + )), + Store( + pointer: 89, + value: 90, + ), + Return( + value: None, + ), + ], + ), + ( + name: Some("test_matrix_within_array_within_struct_accesses"), + arguments: [], + result: None, + local_variables: [ + ( + name: Some("idx"), + ty: 3, + init: Some(1), + ), + ( + name: Some("t"), + ty: 20, + init: Some(53), + ), + ], + expressions: [ + Literal(I32(1)), + LocalVariable(1), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Subtract, + left: 4, + right: 3, + ), + GlobalVariable(5), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 7, + ), + GlobalVariable(5), + AccessIndex( + base: 9, + index: 0, + ), + AccessIndex( + base: 10, + index: 0, + ), + Load( + pointer: 11, + ), + GlobalVariable(5), + AccessIndex( + base: 13, + index: 0, + ), + AccessIndex( + base: 14, + index: 0, + ), + AccessIndex( + base: 15, + index: 0, + ), + Load( + pointer: 16, + ), + GlobalVariable(5), + AccessIndex( + base: 18, + index: 0, + ), + AccessIndex( + base: 19, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 20, + index: 21, + ), + Load( + pointer: 22, + ), + GlobalVariable(5), + AccessIndex( + base: 24, + index: 0, + ), + AccessIndex( + base: 25, + index: 0, + ), + AccessIndex( + base: 26, + index: 0, + ), + AccessIndex( + base: 27, + index: 1, + ), + Load( + pointer: 28, + ), + GlobalVariable(5), + AccessIndex( + base: 30, + index: 0, + ), + AccessIndex( + base: 31, + index: 0, + ), + AccessIndex( + base: 32, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 33, + index: 34, + ), + Load( + pointer: 35, + ), + GlobalVariable(5), + AccessIndex( + base: 37, + index: 0, + ), + AccessIndex( + base: 38, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 39, + index: 40, + ), + AccessIndex( + base: 41, + index: 1, + ), + Load( + pointer: 42, + ), + GlobalVariable(5), + AccessIndex( + base: 44, + index: 0, + ), + AccessIndex( + base: 45, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 46, + index: 47, + ), + Load( + pointer: 2, + ), + Access( + base: 48, + index: 49, + ), + Load( + pointer: 50, + ), + ZeroValue(19), + Compose( + ty: 20, + components: [ + 52, + ], + ), + LocalVariable(2), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Add, + left: 56, + right: 55, + ), + AccessIndex( + base: 54, + index: 0, + ), + ZeroValue(19), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 60, + index: 0, + ), + Literal(F32(8.0)), + Splat( + size: Bi, + value: 62, + ), + Literal(F32(7.0)), + Splat( + size: Bi, + value: 64, + ), + Literal(F32(6.0)), + Splat( + size: Bi, + value: 66, + ), + Literal(F32(5.0)), + Splat( + size: Bi, + value: 68, + ), + Compose( + ty: 18, + components: [ + 63, + 65, + 67, + 69, + ], + ), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 71, + index: 0, + ), + AccessIndex( + base: 72, + index: 0, + ), + Literal(F32(9.0)), + Splat( + size: Bi, + value: 74, + ), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 76, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 77, + index: 78, + ), + Literal(F32(90.0)), + Splat( + size: Bi, + value: 80, + ), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 82, + index: 0, + ), + AccessIndex( + base: 83, + index: 0, + ), + AccessIndex( + base: 84, + index: 1, + ), + Literal(F32(10.0)), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 87, + index: 0, + ), + AccessIndex( + base: 88, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 89, + index: 90, + ), + Literal(F32(20.0)), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 93, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 94, + index: 95, + ), + AccessIndex( + base: 96, + index: 1, + ), + Literal(F32(30.0)), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 99, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 100, + index: 101, + ), + Load( + pointer: 2, + ), + Access( + base: 102, + index: 103, + ), + Literal(F32(40.0)), + ], + named_expressions: { + 8: "l0", + 12: "l1", + 17: "l2", + 23: "l3", + 29: "l4", + 36: "l5", + 43: "l6", + 51: "l7", + }, + body: [ + Emit(( + start: 3, + end: 5, + )), + Store( + pointer: 2, + value: 5, + ), + Emit(( + start: 6, + end: 8, + )), + Emit(( + start: 9, + end: 10, + )), + Emit(( + start: 10, + end: 12, + )), + Emit(( + start: 13, + end: 14, + )), + Emit(( + start: 14, + end: 15, + )), + Emit(( + start: 15, + end: 17, + )), + Emit(( + start: 18, + end: 19, + )), + Emit(( + start: 19, + end: 23, + )), + Emit(( + start: 24, + end: 25, + )), + Emit(( + start: 25, + end: 26, + )), + Emit(( + start: 26, + end: 27, + )), + Emit(( + start: 27, + end: 29, + )), + Emit(( + start: 30, + end: 31, + )), + Emit(( + start: 31, + end: 32, + )), + Emit(( + start: 32, + end: 36, + )), + Emit(( + start: 37, + end: 38, + )), + Emit(( + start: 38, + end: 41, + )), + Emit(( + start: 41, + end: 43, + )), + Emit(( + start: 44, + end: 45, + )), + Emit(( + start: 45, + end: 51, + )), + Emit(( + start: 52, + end: 53, + )), + Emit(( + start: 55, + end: 57, + )), + Store( + pointer: 2, + value: 57, + ), + Emit(( + start: 57, + end: 58, + )), + Store( + pointer: 58, + value: 59, + ), + Emit(( + start: 59, + end: 60, + )), + Emit(( + start: 60, + end: 61, + )), + Emit(( + start: 62, + end: 63, + )), + Emit(( + start: 64, + end: 65, + )), + Emit(( + start: 66, + end: 67, + )), + Emit(( + start: 68, + end: 70, + )), + Store( + pointer: 61, + value: 70, + ), + Emit(( + start: 70, + end: 71, + )), + Emit(( + start: 71, + end: 72, + )), + Emit(( + start: 72, + end: 73, + )), + Emit(( + start: 74, + end: 75, + )), + Store( + pointer: 73, + value: 75, + ), + Emit(( + start: 75, + end: 76, + )), + Emit(( + start: 76, + end: 79, + )), + Emit(( + start: 80, + end: 81, + )), + Store( + pointer: 79, + value: 81, + ), + Emit(( + start: 81, + end: 82, + )), + Emit(( + start: 82, + end: 83, + )), + Emit(( + start: 83, + end: 84, + )), + Emit(( + start: 84, + end: 85, + )), + Store( + pointer: 85, + value: 86, + ), + Emit(( + start: 86, + end: 87, + )), + Emit(( + start: 87, + end: 88, + )), + Emit(( + start: 88, + end: 91, + )), + Store( + pointer: 91, + value: 92, + ), + Emit(( + start: 92, + end: 93, + )), + Emit(( + start: 93, + end: 96, + )), + Emit(( + start: 96, + end: 97, + )), + Store( + pointer: 97, + value: 98, + ), + Emit(( + start: 98, + end: 99, + )), + Emit(( + start: 99, + end: 104, + )), + Store( + pointer: 104, + value: 105, + ), + Return( + value: None, + ), + ], + ), + ( + name: Some("read_from_private"), + arguments: [ + ( + name: Some("foo"), + ty: 22, + binding: None, + ), + ], + result: Some(( + ty: 21, + binding: None, + )), + local_variables: [], + expressions: [ + FunctionArgument(0), + Load( + pointer: 1, + ), + ], + named_expressions: { + 1: "foo", + }, + body: [ + Emit(( + start: 1, + end: 2, + )), + Return( + value: Some(2), + ), + ], + ), + ( + name: Some("test_arr_as_arg"), + arguments: [ + ( + name: Some("a"), + ty: 24, + binding: None, + ), + ], + result: Some(( + ty: 21, + binding: None, + )), + local_variables: [], + expressions: [ + FunctionArgument(0), + AccessIndex( + base: 1, + index: 4, + ), + AccessIndex( + base: 2, + index: 9, + ), + ], + named_expressions: { + 1: "a", + }, + body: [ + Emit(( + start: 1, + end: 2, + )), + Emit(( + start: 2, + end: 3, + )), + Return( + value: Some(3), + ), + ], + ), + ( + name: Some("assign_through_ptr_fn"), + arguments: [ + ( + name: Some("p"), + ty: 27, + binding: None, + ), + ], + result: None, + local_variables: [], + expressions: [ + FunctionArgument(0), + Literal(U32(42)), + ], + named_expressions: { + 1: "p", + }, + body: [ + Store( + pointer: 1, + value: 2, + ), + Return( + value: None, + ), + ], + ), + ( + name: Some("assign_array_through_ptr_fn"), + arguments: [ + ( + name: Some("foo"), + ty: 29, + binding: None, + ), + ], + result: None, + local_variables: [], + expressions: [ + FunctionArgument(0), + Literal(F32(1.0)), + Splat( + size: Quad, + value: 2, + ), + Literal(F32(2.0)), + Splat( + size: Quad, + value: 4, + ), + Compose( + ty: 28, + components: [ + 3, + 5, + ], + ), + ], + named_expressions: { + 1: "foo", + }, + body: [ + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 2, + end: 3, + )), + Emit(( + start: 4, + end: 6, + )), + Store( + pointer: 1, + value: 6, + ), + Return( + value: None, + ), + ], + ), + ], + entry_points: [ + ( + name: "foo_vert", + stage: Vertex, + early_depth_test: None, + workgroup_size: (0, 0, 0), + function: ( + name: Some("foo_vert"), + arguments: [ + ( + name: Some("vi"), + ty: 1, + binding: Some(BuiltIn(VertexIndex)), + ), + ], + result: Some(( + ty: 25, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + )), + local_variables: [ + ( + name: Some("foo"), + ty: 21, + init: Some(2), + ), + ( + name: Some("c2"), + ty: 26, + init: None, + ), + ], + expressions: [ + FunctionArgument(0), + Literal(F32(0.0)), + LocalVariable(1), + Load( + pointer: 3, + ), + Literal(F32(1.0)), + GlobalVariable(2), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 7, + ), + GlobalVariable(2), + AccessIndex( + base: 9, + index: 4, + ), + Load( + pointer: 10, + ), + Literal(U32(3)), + GlobalVariable(2), + AccessIndex( + base: 13, + index: 0, + ), + Access( + base: 14, + index: 12, + ), + AccessIndex( + base: 15, + index: 0, + ), + Load( + pointer: 16, + ), + GlobalVariable(2), + AccessIndex( + base: 18, + index: 5, + ), + GlobalVariable(2), + AccessIndex( + base: 20, + index: 5, + ), + ArrayLength(21), + Literal(U32(2)), + Binary( + op: Subtract, + left: 22, + right: 23, + ), + Access( + base: 19, + index: 24, + ), + AccessIndex( + base: 25, + index: 0, + ), + Load( + pointer: 26, + ), + GlobalVariable(4), + Load( + pointer: 28, + ), + GlobalVariable(2), + AccessIndex( + base: 30, + index: 5, + ), + AccessIndex( + base: 31, + index: 0, + ), + AccessIndex( + base: 32, + index: 0, + ), + CallResult(3), + As( + expr: 17, + kind: Sint, + convert: Some(4), + ), + Literal(I32(3)), + Literal(I32(4)), + Literal(I32(5)), + Compose( + ty: 26, + components: [ + 27, + 35, + 36, + 37, + 38, + ], + ), + LocalVariable(2), + Literal(U32(1)), + Binary( + op: Add, + left: 1, + right: 41, + ), + Access( + base: 40, + index: 42, + ), + Literal(I32(42)), + Access( + base: 40, + index: 1, + ), + Load( + pointer: 45, + ), + ZeroValue(24), + CallResult(4), + Splat( + size: Quad, + value: 46, + ), + As( + expr: 49, + kind: Float, + convert: Some(4), + ), + Binary( + op: Multiply, + left: 8, + right: 50, + ), + Literal(F32(2.0)), + Compose( + ty: 25, + components: [ + 51, + 52, + ], + ), + ], + named_expressions: { + 1: "vi", + 4: "baz", + 8: "_matrix", + 11: "arr", + 12: "index", + 17: "b", + 27: "a", + 29: "c", + 33: "data_pointer", + 34: "foo_value", + 46: "value", + }, + body: [ + Emit(( + start: 3, + end: 4, + )), + Store( + pointer: 3, + value: 5, + ), + Call( + function: 1, + arguments: [], + result: None, + ), + Call( + function: 2, + arguments: [], + result: None, + ), + Emit(( + start: 6, + end: 8, + )), + Emit(( + start: 9, + end: 11, + )), + Emit(( + start: 13, + end: 17, + )), + Emit(( + start: 18, + end: 19, + )), + Emit(( + start: 20, + end: 22, + )), + Emit(( + start: 23, + end: 27, + )), + Emit(( + start: 28, + end: 29, + )), + Emit(( + start: 30, + end: 31, + )), + Emit(( + start: 31, + end: 33, + )), + Call( + function: 3, + arguments: [ + 3, + ], + result: Some(34), + ), + Emit(( + start: 34, + end: 35, + )), + Emit(( + start: 38, + end: 39, + )), + Store( + pointer: 40, + value: 39, + ), + Emit(( + start: 41, + end: 43, + )), + Store( + pointer: 43, + value: 44, + ), + Emit(( + start: 44, + end: 46, + )), + Call( + function: 4, + arguments: [ + 47, + ], + result: Some(48), + ), + Emit(( + start: 48, + end: 51, + )), + Emit(( + start: 52, + end: 53, + )), + Return( + value: Some(53), + ), + ], + ), + ), + ( + name: "foo_frag", + stage: Fragment, + early_depth_test: None, + workgroup_size: (0, 0, 0), + function: ( + name: Some("foo_frag"), + arguments: [], + result: Some(( + ty: 25, + binding: Some(Location( + location: 0, + second_blend_source: false, + interpolation: Some(Perspective), + sampling: Some(Center), + )), + )), + local_variables: [], + expressions: [ + GlobalVariable(2), + AccessIndex( + base: 1, + index: 0, + ), + AccessIndex( + base: 2, + index: 1, + ), + AccessIndex( + base: 3, + index: 2, + ), + Literal(F32(1.0)), + GlobalVariable(2), + AccessIndex( + base: 6, + index: 0, + ), + Literal(F32(0.0)), + Splat( + size: Tri, + value: 8, + ), + Literal(F32(1.0)), + Splat( + size: Tri, + value: 10, + ), + Literal(F32(2.0)), + Splat( + size: Tri, + value: 12, + ), + Literal(F32(3.0)), + Splat( + size: Tri, + value: 14, + ), + Compose( + ty: 6, + components: [ + 9, + 11, + 13, + 15, + ], + ), + GlobalVariable(2), + AccessIndex( + base: 17, + index: 4, + ), + Literal(U32(0)), + Splat( + size: Bi, + value: 19, + ), + Literal(U32(1)), + Splat( + size: Bi, + value: 21, + ), + Compose( + ty: 12, + components: [ + 20, + 22, + ], + ), + GlobalVariable(2), + AccessIndex( + base: 24, + index: 5, + ), + AccessIndex( + base: 25, + index: 1, + ), + AccessIndex( + base: 26, + index: 0, + ), + Literal(I32(1)), + GlobalVariable(4), + ZeroValue(17), + Literal(F32(0.0)), + Splat( + size: Quad, + value: 31, + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 1, + end: 2, + )), + Emit(( + start: 2, + end: 4, + )), + Store( + pointer: 4, + value: 5, + ), + Emit(( + start: 6, + end: 7, + )), + Emit(( + start: 8, + end: 9, + )), + Emit(( + start: 10, + end: 11, + )), + Emit(( + start: 12, + end: 13, + )), + Emit(( + start: 14, + end: 16, + )), + Store( + pointer: 7, + value: 16, + ), + Emit(( + start: 17, + end: 18, + )), + Emit(( + start: 19, + end: 20, + )), + Emit(( + start: 21, + end: 23, + )), + Store( + pointer: 18, + value: 23, + ), + Emit(( + start: 24, + end: 25, + )), + Emit(( + start: 25, + end: 27, + )), + Store( + pointer: 27, + value: 28, + ), + Store( + pointer: 29, + value: 30, + ), + Emit(( + start: 31, + end: 32, + )), + Return( + value: Some(32), + ), + ], + ), + ), + ( + name: "assign_through_ptr", + stage: Compute, + early_depth_test: None, + workgroup_size: (1, 1, 1), + function: ( + name: Some("assign_through_ptr"), + arguments: [], + result: None, + local_variables: [ + ( + name: Some("val"), + ty: 1, + init: Some(1), + ), + ( + name: Some("arr"), + ty: 28, + init: Some(7), + ), + ], + expressions: [ + Literal(U32(33)), + LocalVariable(1), + Literal(F32(6.0)), + Splat( + size: Quad, + value: 3, + ), + Literal(F32(7.0)), + Splat( + size: Quad, + value: 5, + ), + Compose( + ty: 28, + components: [ + 4, + 6, + ], + ), + LocalVariable(2), + ], + named_expressions: {}, + body: [ + Call( + function: 5, + arguments: [ + 2, + ], + result: None, + ), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 3, + end: 4, + )), + Emit(( + start: 5, + end: 7, + )), + Call( + function: 6, + arguments: [ + 8, + ], + result: None, + ), + Return( + value: None, + ), + ], + ), + ), + ], +) \ No newline at end of file diff --git a/naga/tests/out/ir/collatz.compact.ron b/naga/tests/out/ir/collatz.compact.ron new file mode 100644 index 0000000000..cfc3bfa0ee --- /dev/null +++ b/naga/tests/out/ir/collatz.compact.ron @@ -0,0 +1,332 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Array( + base: 1, + size: Dynamic, + stride: 4, + ), + ), + ( + name: Some("PrimeIndices"), + inner: Struct( + members: [ + ( + name: Some("data"), + ty: 2, + binding: None, + offset: 0, + ), + ], + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + predeclared_types: {}, + ), + constants: [], + global_variables: [ + ( + name: Some("v_indices"), + space: Storage( + access: ("LOAD | STORE"), + ), + binding: Some(( + group: 0, + binding: 0, + )), + ty: 3, + init: None, + ), + ], + const_expressions: [], + functions: [ + ( + name: Some("collatz_iterations"), + arguments: [ + ( + name: Some("n_base"), + ty: 1, + binding: None, + ), + ], + result: Some(( + ty: 1, + binding: None, + )), + local_variables: [ + ( + name: Some("n"), + ty: 1, + init: None, + ), + ( + name: Some("i"), + ty: 1, + init: Some(3), + ), + ], + expressions: [ + FunctionArgument(0), + LocalVariable(1), + Literal(U32(0)), + LocalVariable(2), + Load( + pointer: 2, + ), + Literal(U32(1)), + Binary( + op: Greater, + left: 5, + right: 6, + ), + Load( + pointer: 2, + ), + Literal(U32(2)), + Binary( + op: Modulo, + left: 8, + right: 9, + ), + Literal(U32(0)), + Binary( + op: Equal, + left: 10, + right: 11, + ), + Load( + pointer: 2, + ), + Literal(U32(2)), + Binary( + op: Divide, + left: 13, + right: 14, + ), + Literal(U32(3)), + Load( + pointer: 2, + ), + Binary( + op: Multiply, + left: 16, + right: 17, + ), + Literal(U32(1)), + Binary( + op: Add, + left: 18, + right: 19, + ), + Load( + pointer: 4, + ), + Literal(U32(1)), + Binary( + op: Add, + left: 21, + right: 22, + ), + Load( + pointer: 4, + ), + ], + named_expressions: { + 1: "n_base", + }, + body: [ + Store( + pointer: 2, + value: 1, + ), + Loop( + body: [ + Emit(( + start: 4, + end: 5, + )), + Emit(( + start: 6, + end: 7, + )), + If( + condition: 7, + accept: [], + reject: [ + Break, + ], + ), + Block([ + Emit(( + start: 7, + end: 8, + )), + Emit(( + start: 9, + end: 10, + )), + Emit(( + start: 11, + end: 12, + )), + If( + condition: 12, + accept: [ + Emit(( + start: 12, + end: 13, + )), + Emit(( + start: 14, + end: 15, + )), + Store( + pointer: 2, + value: 15, + ), + ], + reject: [ + Emit(( + start: 16, + end: 18, + )), + Emit(( + start: 19, + end: 20, + )), + Store( + pointer: 2, + value: 20, + ), + ], + ), + Emit(( + start: 20, + end: 21, + )), + Emit(( + start: 22, + end: 23, + )), + Store( + pointer: 4, + value: 23, + ), + ]), + ], + continuing: [], + break_if: None, + ), + Emit(( + start: 23, + end: 24, + )), + Return( + value: Some(24), + ), + ], + ), + ], + entry_points: [ + ( + name: "main", + stage: Compute, + early_depth_test: None, + workgroup_size: (1, 1, 1), + function: ( + name: Some("main"), + arguments: [ + ( + name: Some("global_id"), + ty: 4, + binding: Some(BuiltIn(GlobalInvocationId)), + ), + ], + result: None, + local_variables: [], + expressions: [ + FunctionArgument(0), + GlobalVariable(1), + AccessIndex( + base: 2, + index: 0, + ), + AccessIndex( + base: 1, + index: 0, + ), + Access( + base: 3, + index: 4, + ), + GlobalVariable(1), + AccessIndex( + base: 6, + index: 0, + ), + AccessIndex( + base: 1, + index: 0, + ), + Access( + base: 7, + index: 8, + ), + Load( + pointer: 9, + ), + CallResult(1), + ], + named_expressions: { + 1: "global_id", + }, + body: [ + Emit(( + start: 2, + end: 5, + )), + Emit(( + start: 6, + end: 10, + )), + Call( + function: 1, + arguments: [ + 10, + ], + result: Some(11), + ), + Store( + pointer: 5, + value: 11, + ), + Return( + value: None, + ), + ], + ), + ), + ], +) \ No newline at end of file diff --git a/naga/tests/out/ir/collatz.ron b/naga/tests/out/ir/collatz.ron new file mode 100644 index 0000000000..cfc3bfa0ee --- /dev/null +++ b/naga/tests/out/ir/collatz.ron @@ -0,0 +1,332 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Array( + base: 1, + size: Dynamic, + stride: 4, + ), + ), + ( + name: Some("PrimeIndices"), + inner: Struct( + members: [ + ( + name: Some("data"), + ty: 2, + binding: None, + offset: 0, + ), + ], + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + predeclared_types: {}, + ), + constants: [], + global_variables: [ + ( + name: Some("v_indices"), + space: Storage( + access: ("LOAD | STORE"), + ), + binding: Some(( + group: 0, + binding: 0, + )), + ty: 3, + init: None, + ), + ], + const_expressions: [], + functions: [ + ( + name: Some("collatz_iterations"), + arguments: [ + ( + name: Some("n_base"), + ty: 1, + binding: None, + ), + ], + result: Some(( + ty: 1, + binding: None, + )), + local_variables: [ + ( + name: Some("n"), + ty: 1, + init: None, + ), + ( + name: Some("i"), + ty: 1, + init: Some(3), + ), + ], + expressions: [ + FunctionArgument(0), + LocalVariable(1), + Literal(U32(0)), + LocalVariable(2), + Load( + pointer: 2, + ), + Literal(U32(1)), + Binary( + op: Greater, + left: 5, + right: 6, + ), + Load( + pointer: 2, + ), + Literal(U32(2)), + Binary( + op: Modulo, + left: 8, + right: 9, + ), + Literal(U32(0)), + Binary( + op: Equal, + left: 10, + right: 11, + ), + Load( + pointer: 2, + ), + Literal(U32(2)), + Binary( + op: Divide, + left: 13, + right: 14, + ), + Literal(U32(3)), + Load( + pointer: 2, + ), + Binary( + op: Multiply, + left: 16, + right: 17, + ), + Literal(U32(1)), + Binary( + op: Add, + left: 18, + right: 19, + ), + Load( + pointer: 4, + ), + Literal(U32(1)), + Binary( + op: Add, + left: 21, + right: 22, + ), + Load( + pointer: 4, + ), + ], + named_expressions: { + 1: "n_base", + }, + body: [ + Store( + pointer: 2, + value: 1, + ), + Loop( + body: [ + Emit(( + start: 4, + end: 5, + )), + Emit(( + start: 6, + end: 7, + )), + If( + condition: 7, + accept: [], + reject: [ + Break, + ], + ), + Block([ + Emit(( + start: 7, + end: 8, + )), + Emit(( + start: 9, + end: 10, + )), + Emit(( + start: 11, + end: 12, + )), + If( + condition: 12, + accept: [ + Emit(( + start: 12, + end: 13, + )), + Emit(( + start: 14, + end: 15, + )), + Store( + pointer: 2, + value: 15, + ), + ], + reject: [ + Emit(( + start: 16, + end: 18, + )), + Emit(( + start: 19, + end: 20, + )), + Store( + pointer: 2, + value: 20, + ), + ], + ), + Emit(( + start: 20, + end: 21, + )), + Emit(( + start: 22, + end: 23, + )), + Store( + pointer: 4, + value: 23, + ), + ]), + ], + continuing: [], + break_if: None, + ), + Emit(( + start: 23, + end: 24, + )), + Return( + value: Some(24), + ), + ], + ), + ], + entry_points: [ + ( + name: "main", + stage: Compute, + early_depth_test: None, + workgroup_size: (1, 1, 1), + function: ( + name: Some("main"), + arguments: [ + ( + name: Some("global_id"), + ty: 4, + binding: Some(BuiltIn(GlobalInvocationId)), + ), + ], + result: None, + local_variables: [], + expressions: [ + FunctionArgument(0), + GlobalVariable(1), + AccessIndex( + base: 2, + index: 0, + ), + AccessIndex( + base: 1, + index: 0, + ), + Access( + base: 3, + index: 4, + ), + GlobalVariable(1), + AccessIndex( + base: 6, + index: 0, + ), + AccessIndex( + base: 1, + index: 0, + ), + Access( + base: 7, + index: 8, + ), + Load( + pointer: 9, + ), + CallResult(1), + ], + named_expressions: { + 1: "global_id", + }, + body: [ + Emit(( + start: 2, + end: 5, + )), + Emit(( + start: 6, + end: 10, + )), + Call( + function: 1, + arguments: [ + 10, + ], + result: Some(11), + ), + Store( + pointer: 5, + value: 11, + ), + Return( + value: None, + ), + ], + ), + ), + ], +) \ No newline at end of file diff --git a/naga/tests/out/ir/shadow.compact.ron b/naga/tests/out/ir/shadow.compact.ron new file mode 100644 index 0000000000..dc7b2eae78 --- /dev/null +++ b/naga/tests/out/ir/shadow.compact.ron @@ -0,0 +1,1047 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Float, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Image( + dim: D2, + arrayed: true, + class: Depth( + multi: false, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Sint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: Some("Globals"), + inner: Struct( + members: [ + ( + name: Some("num_lights"), + ty: 8, + binding: None, + offset: 0, + ), + ], + span: 16, + ), + ), + ( + name: None, + inner: Matrix( + columns: Quad, + rows: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: Some("Light"), + inner: Struct( + members: [ + ( + name: Some("proj"), + ty: 10, + binding: None, + offset: 0, + ), + ( + name: Some("pos"), + ty: 4, + binding: None, + offset: 64, + ), + ( + name: Some("color"), + ty: 4, + binding: None, + offset: 80, + ), + ], + span: 96, + ), + ), + ( + name: None, + inner: Array( + base: 11, + size: Dynamic, + stride: 96, + ), + ), + ( + name: Some("Lights"), + inner: Struct( + members: [ + ( + name: Some("data"), + ty: 12, + binding: None, + offset: 0, + ), + ], + span: 96, + ), + ), + ( + name: None, + inner: Sampler( + comparison: true, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + predeclared_types: {}, + ), + constants: [ + ( + name: None, + override: None, + ty: 1, + init: 1, + ), + ( + name: None, + override: None, + ty: 1, + init: 2, + ), + ( + name: None, + override: None, + ty: 1, + init: 3, + ), + ( + name: None, + override: None, + ty: 1, + init: 4, + ), + ( + name: None, + override: None, + ty: 1, + init: 5, + ), + ( + name: None, + override: None, + ty: 2, + init: 9, + ), + ( + name: None, + override: None, + ty: 3, + init: 10, + ), + ( + name: None, + override: None, + ty: 3, + init: 11, + ), + ( + name: None, + override: None, + ty: 3, + init: 12, + ), + ( + name: None, + override: None, + ty: 7, + init: 13, + ), + ( + name: None, + override: None, + ty: 7, + init: 14, + ), + ( + name: None, + override: None, + ty: 7, + init: 15, + ), + ( + name: None, + override: None, + ty: 7, + init: 16, + ), + ( + name: None, + override: None, + ty: 7, + init: 17, + ), + ( + name: None, + override: None, + ty: 7, + init: 18, + ), + ( + name: None, + override: None, + ty: 7, + init: 19, + ), + ( + name: None, + override: None, + ty: 7, + init: 20, + ), + ( + name: None, + override: None, + ty: 7, + init: 21, + ), + ( + name: None, + override: None, + ty: 7, + init: 22, + ), + ], + global_variables: [ + ( + name: Some("t_shadow"), + space: Handle, + binding: Some(( + group: 0, + binding: 2, + )), + ty: 6, + init: None, + ), + ( + name: Some("sampler_shadow"), + space: Handle, + binding: Some(( + group: 0, + binding: 3, + )), + ty: 14, + init: None, + ), + ( + name: Some("u_globals"), + space: Uniform, + binding: Some(( + group: 0, + binding: 0, + )), + ty: 9, + init: None, + ), + ( + name: Some("s_lights"), + space: Storage( + access: ("LOAD"), + ), + binding: Some(( + group: 0, + binding: 1, + )), + ty: 13, + init: None, + ), + ( + name: Some("in_position_fs"), + space: Private, + binding: None, + ty: 4, + init: None, + ), + ( + name: Some("in_normal_fs"), + space: Private, + binding: None, + ty: 2, + init: None, + ), + ( + name: Some("out_color_fs"), + space: Private, + binding: None, + ty: 4, + init: None, + ), + ], + const_expressions: [ + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(0.5)), + Literal(F32(-0.5)), + Literal(F32(0.05)), + Constant(5), + Constant(5), + Constant(5), + Compose( + ty: 2, + components: [ + 6, + 7, + 8, + ], + ), + Literal(U32(10)), + Literal(U32(0)), + Literal(U32(1)), + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(1)), + Literal(I32(2)), + Literal(I32(0)), + Literal(I32(1)), + Literal(I32(2)), + Literal(I32(0)), + Literal(I32(1)), + Literal(I32(2)), + ], + functions: [ + ( + name: None, + arguments: [ + ( + name: None, + ty: 3, + binding: None, + ), + ( + name: None, + ty: 4, + binding: None, + ), + ], + result: Some(( + ty: 1, + binding: None, + )), + local_variables: [], + expressions: [ + GlobalVariable(1), + GlobalVariable(2), + Constant(3), + Constant(4), + Constant(2), + Constant(1), + FunctionArgument(0), + FunctionArgument(1), + AccessIndex( + base: 8, + index: 3, + ), + Binary( + op: LessEqual, + left: 9, + right: 6, + ), + AccessIndex( + base: 8, + index: 0, + ), + AccessIndex( + base: 8, + index: 1, + ), + Compose( + ty: 5, + components: [ + 11, + 12, + ], + ), + Compose( + ty: 5, + components: [ + 3, + 4, + ], + ), + Binary( + op: Multiply, + left: 13, + right: 14, + ), + AccessIndex( + base: 8, + index: 3, + ), + Binary( + op: Divide, + left: 5, + right: 16, + ), + Binary( + op: Multiply, + left: 15, + right: 17, + ), + Splat( + size: Bi, + value: 3, + ), + Binary( + op: Add, + left: 18, + right: 19, + ), + AccessIndex( + base: 20, + index: 0, + ), + AccessIndex( + base: 20, + index: 1, + ), + As( + expr: 7, + kind: Sint, + convert: None, + ), + As( + expr: 23, + kind: Float, + convert: Some(4), + ), + Compose( + ty: 2, + components: [ + 21, + 22, + 24, + ], + ), + AccessIndex( + base: 8, + index: 2, + ), + AccessIndex( + base: 8, + index: 3, + ), + Binary( + op: Divide, + left: 5, + right: 27, + ), + Binary( + op: Multiply, + left: 26, + right: 28, + ), + AccessIndex( + base: 25, + index: 0, + ), + AccessIndex( + base: 25, + index: 1, + ), + Compose( + ty: 5, + components: [ + 30, + 31, + ], + ), + AccessIndex( + base: 25, + index: 2, + ), + As( + expr: 33, + kind: Sint, + convert: Some(4), + ), + ImageSample( + image: 1, + sampler: 2, + gather: None, + coordinate: 32, + array_index: Some(34), + offset: None, + level: Zero, + depth_ref: Some(29), + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 8, + end: 10, + )), + If( + condition: 10, + accept: [ + Return( + value: Some(5), + ), + ], + reject: [], + ), + Emit(( + start: 10, + end: 35, + )), + Return( + value: Some(35), + ), + ], + ), + ( + name: Some("fs_main"), + arguments: [], + result: None, + local_variables: [ + ( + name: Some("color"), + ty: 2, + init: Some(20), + ), + ( + name: Some("i"), + ty: 3, + init: Some(22), + ), + ], + expressions: [ + GlobalVariable(3), + GlobalVariable(6), + GlobalVariable(5), + GlobalVariable(4), + GlobalVariable(7), + Constant(17), + Constant(15), + Constant(13), + Constant(18), + Constant(16), + Constant(19), + Constant(9), + Constant(7), + Constant(2), + Constant(11), + Constant(14), + Constant(10), + Constant(12), + Constant(1), + Constant(6), + LocalVariable(1), + Constant(8), + LocalVariable(2), + Load( + pointer: 23, + ), + AccessIndex( + base: 1, + index: 0, + ), + Access( + base: 25, + index: 17, + ), + Load( + pointer: 26, + ), + Math( + fun: Min, + arg: 27, + arg1: Some(13), + arg2: None, + arg3: None, + ), + Binary( + op: GreaterEqual, + left: 24, + right: 28, + ), + Load( + pointer: 21, + ), + Load( + pointer: 23, + ), + AccessIndex( + base: 4, + index: 0, + ), + Load( + pointer: 23, + ), + Access( + base: 32, + index: 33, + ), + AccessIndex( + base: 34, + index: 0, + ), + Load( + pointer: 35, + ), + Load( + pointer: 3, + ), + Binary( + op: Multiply, + left: 36, + right: 37, + ), + CallResult(1), + Load( + pointer: 2, + ), + Math( + fun: Normalize, + arg: 40, + arg1: None, + arg2: None, + arg3: None, + ), + AccessIndex( + base: 4, + index: 0, + ), + Load( + pointer: 23, + ), + Access( + base: 42, + index: 43, + ), + AccessIndex( + base: 44, + index: 1, + ), + Access( + base: 45, + index: 15, + ), + Load( + pointer: 46, + ), + AccessIndex( + base: 4, + index: 0, + ), + Load( + pointer: 23, + ), + Access( + base: 48, + index: 49, + ), + AccessIndex( + base: 50, + index: 1, + ), + Access( + base: 51, + index: 18, + ), + Load( + pointer: 52, + ), + AccessIndex( + base: 4, + index: 0, + ), + Load( + pointer: 23, + ), + Access( + base: 54, + index: 55, + ), + AccessIndex( + base: 56, + index: 1, + ), + Access( + base: 57, + index: 8, + ), + Load( + pointer: 58, + ), + Compose( + ty: 2, + components: [ + 47, + 53, + 59, + ], + ), + Access( + base: 3, + index: 16, + ), + Load( + pointer: 61, + ), + Access( + base: 3, + index: 7, + ), + Load( + pointer: 63, + ), + Access( + base: 3, + index: 10, + ), + Load( + pointer: 65, + ), + Compose( + ty: 2, + components: [ + 62, + 64, + 66, + ], + ), + Binary( + op: Subtract, + left: 60, + right: 67, + ), + Math( + fun: Normalize, + arg: 68, + arg1: None, + arg2: None, + arg3: None, + ), + Math( + fun: Dot, + arg: 41, + arg1: Some(69), + arg2: None, + arg3: None, + ), + Math( + fun: Max, + arg: 19, + arg1: Some(70), + arg2: None, + arg3: None, + ), + Binary( + op: Multiply, + left: 39, + right: 71, + ), + AccessIndex( + base: 4, + index: 0, + ), + Load( + pointer: 23, + ), + Access( + base: 73, + index: 74, + ), + AccessIndex( + base: 75, + index: 2, + ), + Access( + base: 76, + index: 6, + ), + Load( + pointer: 77, + ), + AccessIndex( + base: 4, + index: 0, + ), + Load( + pointer: 23, + ), + Access( + base: 79, + index: 80, + ), + AccessIndex( + base: 81, + index: 2, + ), + Access( + base: 82, + index: 9, + ), + Load( + pointer: 83, + ), + AccessIndex( + base: 4, + index: 0, + ), + Load( + pointer: 23, + ), + Access( + base: 85, + index: 86, + ), + AccessIndex( + base: 87, + index: 2, + ), + Access( + base: 88, + index: 11, + ), + Load( + pointer: 89, + ), + Compose( + ty: 2, + components: [ + 78, + 84, + 90, + ], + ), + Binary( + op: Multiply, + left: 91, + right: 72, + ), + Binary( + op: Add, + left: 30, + right: 92, + ), + Load( + pointer: 23, + ), + Binary( + op: Add, + left: 94, + right: 12, + ), + Load( + pointer: 21, + ), + Compose( + ty: 4, + components: [ + 96, + 14, + ], + ), + ], + named_expressions: {}, + body: [ + Loop( + body: [ + Emit(( + start: 23, + end: 29, + )), + If( + condition: 29, + accept: [ + Break, + ], + reject: [], + ), + Emit(( + start: 29, + end: 38, + )), + Call( + function: 1, + arguments: [ + 31, + 38, + ], + result: Some(39), + ), + Emit(( + start: 39, + end: 93, + )), + Store( + pointer: 21, + value: 93, + ), + Continue, + ], + continuing: [ + Emit(( + start: 93, + end: 95, + )), + Store( + pointer: 23, + value: 95, + ), + ], + break_if: None, + ), + Emit(( + start: 95, + end: 97, + )), + Store( + pointer: 5, + value: 97, + ), + Return( + value: None, + ), + ], + ), + ], + entry_points: [ + ( + name: "fs_main", + stage: Fragment, + early_depth_test: None, + workgroup_size: (0, 0, 0), + function: ( + name: Some("fs_main_wrap"), + arguments: [ + ( + name: Some("in_normal_fs"), + ty: 2, + binding: Some(Location( + location: 0, + second_blend_source: false, + interpolation: Some(Perspective), + sampling: Some(Center), + )), + ), + ( + name: Some("in_position_fs"), + ty: 4, + binding: Some(Location( + location: 1, + second_blend_source: false, + interpolation: Some(Perspective), + sampling: Some(Center), + )), + ), + ], + result: Some(( + ty: 4, + binding: Some(Location( + location: 0, + second_blend_source: false, + interpolation: None, + sampling: None, + )), + )), + local_variables: [], + expressions: [ + FunctionArgument(0), + GlobalVariable(6), + FunctionArgument(1), + GlobalVariable(5), + GlobalVariable(7), + Load( + pointer: 5, + ), + ], + named_expressions: {}, + body: [ + Store( + pointer: 2, + value: 1, + ), + Store( + pointer: 4, + value: 3, + ), + Call( + function: 2, + arguments: [], + result: None, + ), + Emit(( + start: 5, + end: 6, + )), + Return( + value: Some(6), + ), + ], + ), + ), + ], +) \ No newline at end of file diff --git a/naga/tests/out/ir/shadow.ron b/naga/tests/out/ir/shadow.ron new file mode 100644 index 0000000000..51bd3b264e --- /dev/null +++ b/naga/tests/out/ir/shadow.ron @@ -0,0 +1,1341 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Float, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Bool, + width: 1, + )), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Image( + dim: D2, + arrayed: true, + class: Depth( + multi: false, + ), + ), + ), + ( + name: None, + inner: Sampler( + comparison: false, + ), + ), + ( + name: None, + inner: Scalar(( + kind: Sint, + width: 4, + )), + ), + ( + name: None, + inner: Pointer( + base: 2, + space: Function, + ), + ), + ( + name: None, + inner: Pointer( + base: 3, + space: Function, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: Some("Globals"), + inner: Struct( + members: [ + ( + name: Some("num_lights"), + ty: 12, + binding: None, + offset: 0, + ), + ], + span: 16, + ), + ), + ( + name: None, + inner: Pointer( + base: 13, + space: Uniform, + ), + ), + ( + name: None, + inner: Pointer( + base: 12, + space: Uniform, + ), + ), + ( + name: None, + inner: Pointer( + base: 3, + space: Uniform, + ), + ), + ( + name: None, + inner: Matrix( + columns: Quad, + rows: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: Some("Light"), + inner: Struct( + members: [ + ( + name: Some("proj"), + ty: 17, + binding: None, + offset: 0, + ), + ( + name: Some("pos"), + ty: 4, + binding: None, + offset: 64, + ), + ( + name: Some("color"), + ty: 4, + binding: None, + offset: 80, + ), + ], + span: 96, + ), + ), + ( + name: None, + inner: Array( + base: 18, + size: Dynamic, + stride: 96, + ), + ), + ( + name: Some("Lights"), + inner: Struct( + members: [ + ( + name: Some("data"), + ty: 19, + binding: None, + offset: 0, + ), + ], + span: 96, + ), + ), + ( + name: None, + inner: Pointer( + base: 20, + space: Storage( + access: (""), + ), + ), + ), + ( + name: None, + inner: Pointer( + base: 19, + space: Storage( + access: ("LOAD | STORE"), + ), + ), + ), + ( + name: None, + inner: Pointer( + base: 18, + space: Storage( + access: ("LOAD | STORE"), + ), + ), + ), + ( + name: None, + inner: Pointer( + base: 17, + space: Storage( + access: ("LOAD | STORE"), + ), + ), + ), + ( + name: None, + inner: Pointer( + base: 4, + space: Private, + ), + ), + ( + name: None, + inner: Pointer( + base: 2, + space: Private, + ), + ), + ( + name: None, + inner: Pointer( + base: 4, + space: Storage( + access: ("LOAD | STORE"), + ), + ), + ), + ( + name: None, + inner: Pointer( + base: 1, + space: Storage( + access: ("LOAD | STORE"), + ), + ), + ), + ( + name: None, + inner: Pointer( + base: 1, + space: Private, + ), + ), + ( + name: None, + inner: Sampler( + comparison: true, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + predeclared_types: {}, + ), + constants: [ + ( + name: None, + override: None, + ty: 1, + init: 1, + ), + ( + name: None, + override: None, + ty: 1, + init: 2, + ), + ( + name: None, + override: None, + ty: 1, + init: 3, + ), + ( + name: None, + override: None, + ty: 1, + init: 4, + ), + ( + name: None, + override: None, + ty: 1, + init: 5, + ), + ( + name: None, + override: None, + ty: 2, + init: 9, + ), + ( + name: None, + override: None, + ty: 3, + init: 10, + ), + ( + name: None, + override: None, + ty: 3, + init: 11, + ), + ( + name: None, + override: None, + ty: 3, + init: 12, + ), + ( + name: None, + override: None, + ty: 1, + init: 13, + ), + ( + name: None, + override: None, + ty: 9, + init: 14, + ), + ( + name: None, + override: None, + ty: 9, + init: 15, + ), + ( + name: None, + override: None, + ty: 9, + init: 16, + ), + ( + name: None, + override: None, + ty: 9, + init: 17, + ), + ( + name: None, + override: None, + ty: 9, + init: 18, + ), + ( + name: None, + override: None, + ty: 9, + init: 19, + ), + ( + name: None, + override: None, + ty: 9, + init: 20, + ), + ( + name: None, + override: None, + ty: 9, + init: 21, + ), + ( + name: None, + override: None, + ty: 9, + init: 22, + ), + ( + name: None, + override: None, + ty: 9, + init: 23, + ), + ( + name: None, + override: None, + ty: 9, + init: 24, + ), + ( + name: None, + override: None, + ty: 9, + init: 25, + ), + ( + name: None, + override: None, + ty: 9, + init: 26, + ), + ( + name: None, + override: None, + ty: 9, + init: 27, + ), + ( + name: None, + override: None, + ty: 9, + init: 28, + ), + ( + name: None, + override: None, + ty: 9, + init: 29, + ), + ( + name: None, + override: None, + ty: 9, + init: 30, + ), + ( + name: None, + override: None, + ty: 9, + init: 31, + ), + ( + name: None, + override: None, + ty: 9, + init: 32, + ), + ( + name: None, + override: None, + ty: 9, + init: 33, + ), + ( + name: None, + override: None, + ty: 9, + init: 34, + ), + ( + name: None, + override: None, + ty: 9, + init: 35, + ), + ( + name: None, + override: None, + ty: 9, + init: 36, + ), + ( + name: None, + override: None, + ty: 9, + init: 37, + ), + ( + name: None, + override: None, + ty: 9, + init: 38, + ), + ], + global_variables: [ + ( + name: Some("t_shadow"), + space: Handle, + binding: Some(( + group: 0, + binding: 2, + )), + ty: 7, + init: None, + ), + ( + name: Some("sampler_shadow"), + space: Handle, + binding: Some(( + group: 0, + binding: 3, + )), + ty: 30, + init: None, + ), + ( + name: Some("u_globals"), + space: Uniform, + binding: Some(( + group: 0, + binding: 0, + )), + ty: 13, + init: None, + ), + ( + name: Some("s_lights"), + space: Storage( + access: ("LOAD"), + ), + binding: Some(( + group: 0, + binding: 1, + )), + ty: 20, + init: None, + ), + ( + name: Some("in_position_fs"), + space: Private, + binding: None, + ty: 4, + init: None, + ), + ( + name: Some("in_normal_fs"), + space: Private, + binding: None, + ty: 2, + init: None, + ), + ( + name: Some("out_color_fs"), + space: Private, + binding: None, + ty: 4, + init: None, + ), + ], + const_expressions: [ + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(0.5)), + Literal(F32(-0.5)), + Literal(F32(0.05)), + Constant(5), + Constant(5), + Constant(5), + Compose( + ty: 2, + components: [ + 6, + 7, + 8, + ], + ), + Literal(U32(10)), + Literal(U32(0)), + Literal(U32(1)), + Literal(F32(0.0)), + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(1)), + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(1)), + Literal(I32(1)), + Literal(I32(0)), + Literal(I32(1)), + Literal(I32(2)), + Literal(I32(0)), + Literal(I32(1)), + Literal(I32(2)), + Literal(I32(0)), + Literal(I32(2)), + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(2)), + Literal(I32(1)), + Literal(I32(0)), + Literal(I32(2)), + Literal(I32(2)), + ], + functions: [ + ( + name: None, + arguments: [ + ( + name: None, + ty: 3, + binding: None, + ), + ( + name: None, + ty: 4, + binding: None, + ), + ], + result: Some(( + ty: 1, + binding: None, + )), + local_variables: [], + expressions: [ + GlobalVariable(3), + GlobalVariable(6), + GlobalVariable(5), + GlobalVariable(1), + GlobalVariable(2), + GlobalVariable(4), + GlobalVariable(7), + Constant(16), + Constant(3), + Constant(29), + Constant(27), + Constant(25), + Constant(23), + Constant(21), + Constant(11), + Constant(8), + Constant(19), + Constant(4), + Constant(32), + Constant(30), + Constant(10), + Constant(28), + Constant(26), + Constant(13), + Constant(22), + Constant(35), + Constant(9), + Constant(7), + Constant(5), + Constant(2), + Constant(17), + Constant(31), + Constant(15), + Constant(33), + Constant(14), + Constant(24), + Constant(12), + Constant(20), + Constant(34), + Constant(18), + Constant(6), + Constant(1), + FunctionArgument(0), + FunctionArgument(1), + AccessIndex( + base: 44, + index: 3, + ), + Binary( + op: LessEqual, + left: 45, + right: 42, + ), + AccessIndex( + base: 44, + index: 0, + ), + AccessIndex( + base: 44, + index: 1, + ), + Compose( + ty: 6, + components: [ + 47, + 48, + ], + ), + Compose( + ty: 6, + components: [ + 9, + 18, + ], + ), + Binary( + op: Multiply, + left: 49, + right: 50, + ), + AccessIndex( + base: 44, + index: 3, + ), + Binary( + op: Divide, + left: 30, + right: 52, + ), + Binary( + op: Multiply, + left: 51, + right: 53, + ), + Splat( + size: Bi, + value: 9, + ), + Binary( + op: Add, + left: 54, + right: 55, + ), + AccessIndex( + base: 56, + index: 0, + ), + AccessIndex( + base: 56, + index: 1, + ), + As( + expr: 43, + kind: Sint, + convert: None, + ), + As( + expr: 59, + kind: Float, + convert: Some(4), + ), + Compose( + ty: 2, + components: [ + 57, + 58, + 60, + ], + ), + AccessIndex( + base: 44, + index: 2, + ), + AccessIndex( + base: 44, + index: 3, + ), + Binary( + op: Divide, + left: 30, + right: 63, + ), + Binary( + op: Multiply, + left: 62, + right: 64, + ), + AccessIndex( + base: 61, + index: 0, + ), + AccessIndex( + base: 61, + index: 1, + ), + Compose( + ty: 6, + components: [ + 66, + 67, + ], + ), + AccessIndex( + base: 61, + index: 2, + ), + As( + expr: 69, + kind: Sint, + convert: Some(4), + ), + ImageSample( + image: 4, + sampler: 5, + gather: None, + coordinate: 68, + array_index: Some(70), + offset: None, + level: Zero, + depth_ref: Some(65), + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 44, + end: 46, + )), + If( + condition: 46, + accept: [ + Return( + value: Some(30), + ), + ], + reject: [], + ), + Emit(( + start: 46, + end: 71, + )), + Return( + value: Some(71), + ), + ], + ), + ( + name: Some("fs_main"), + arguments: [], + result: None, + local_variables: [ + ( + name: Some("color"), + ty: 2, + init: Some(43), + ), + ( + name: Some("i"), + ty: 3, + init: Some(45), + ), + ], + expressions: [ + GlobalVariable(3), + GlobalVariable(6), + GlobalVariable(5), + GlobalVariable(1), + GlobalVariable(2), + GlobalVariable(4), + GlobalVariable(7), + Constant(16), + Constant(3), + Constant(29), + Constant(27), + Constant(25), + Constant(23), + Constant(21), + Constant(11), + Constant(8), + Constant(19), + Constant(4), + Constant(32), + Constant(30), + Constant(10), + Constant(28), + Constant(26), + Constant(13), + Constant(22), + Constant(35), + Constant(9), + Constant(7), + Constant(5), + Constant(2), + Constant(17), + Constant(31), + Constant(15), + Constant(33), + Constant(14), + Constant(24), + Constant(12), + Constant(20), + Constant(34), + Constant(18), + Constant(6), + Constant(1), + Constant(6), + LocalVariable(1), + Constant(8), + LocalVariable(2), + Load( + pointer: 46, + ), + AccessIndex( + base: 1, + index: 0, + ), + Access( + base: 48, + index: 37, + ), + Load( + pointer: 49, + ), + Math( + fun: Min, + arg: 50, + arg1: Some(28), + arg2: None, + arg3: None, + ), + Binary( + op: GreaterEqual, + left: 47, + right: 51, + ), + Load( + pointer: 44, + ), + Load( + pointer: 46, + ), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 46, + ), + Access( + base: 55, + index: 56, + ), + AccessIndex( + base: 57, + index: 0, + ), + Load( + pointer: 58, + ), + Load( + pointer: 3, + ), + Binary( + op: Multiply, + left: 59, + right: 60, + ), + CallResult(1), + Load( + pointer: 2, + ), + Math( + fun: Normalize, + arg: 63, + arg1: None, + arg2: None, + arg3: None, + ), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 46, + ), + Access( + base: 65, + index: 66, + ), + AccessIndex( + base: 67, + index: 1, + ), + Access( + base: 68, + index: 31, + ), + Load( + pointer: 69, + ), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 46, + ), + Access( + base: 71, + index: 72, + ), + AccessIndex( + base: 73, + index: 1, + ), + Access( + base: 74, + index: 38, + ), + Load( + pointer: 75, + ), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 46, + ), + Access( + base: 77, + index: 78, + ), + AccessIndex( + base: 79, + index: 1, + ), + Access( + base: 80, + index: 13, + ), + Load( + pointer: 81, + ), + Compose( + ty: 2, + components: [ + 70, + 76, + 82, + ], + ), + Access( + base: 3, + index: 36, + ), + Load( + pointer: 84, + ), + Access( + base: 3, + index: 12, + ), + Load( + pointer: 86, + ), + Access( + base: 3, + index: 23, + ), + Load( + pointer: 88, + ), + Compose( + ty: 2, + components: [ + 85, + 87, + 89, + ], + ), + Binary( + op: Subtract, + left: 83, + right: 90, + ), + Math( + fun: Normalize, + arg: 91, + arg1: None, + arg2: None, + arg3: None, + ), + Math( + fun: Dot, + arg: 64, + arg1: Some(92), + arg2: None, + arg3: None, + ), + Math( + fun: Max, + arg: 42, + arg1: Some(93), + arg2: None, + arg3: None, + ), + Binary( + op: Multiply, + left: 62, + right: 94, + ), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 46, + ), + Access( + base: 96, + index: 97, + ), + AccessIndex( + base: 98, + index: 2, + ), + Access( + base: 99, + index: 10, + ), + Load( + pointer: 100, + ), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 46, + ), + Access( + base: 102, + index: 103, + ), + AccessIndex( + base: 104, + index: 2, + ), + Access( + base: 105, + index: 19, + ), + Load( + pointer: 106, + ), + AccessIndex( + base: 6, + index: 0, + ), + Load( + pointer: 46, + ), + Access( + base: 108, + index: 109, + ), + AccessIndex( + base: 110, + index: 2, + ), + Access( + base: 111, + index: 26, + ), + Load( + pointer: 112, + ), + Compose( + ty: 2, + components: [ + 101, + 107, + 113, + ], + ), + Binary( + op: Multiply, + left: 114, + right: 95, + ), + Binary( + op: Add, + left: 53, + right: 115, + ), + Load( + pointer: 46, + ), + Binary( + op: Add, + left: 117, + right: 27, + ), + Load( + pointer: 44, + ), + Compose( + ty: 4, + components: [ + 119, + 30, + ], + ), + ], + named_expressions: {}, + body: [ + Loop( + body: [ + Emit(( + start: 46, + end: 52, + )), + If( + condition: 52, + accept: [ + Break, + ], + reject: [], + ), + Emit(( + start: 52, + end: 61, + )), + Call( + function: 1, + arguments: [ + 54, + 61, + ], + result: Some(62), + ), + Emit(( + start: 62, + end: 116, + )), + Store( + pointer: 44, + value: 116, + ), + Continue, + ], + continuing: [ + Emit(( + start: 116, + end: 118, + )), + Store( + pointer: 46, + value: 118, + ), + ], + break_if: None, + ), + Emit(( + start: 118, + end: 120, + )), + Store( + pointer: 7, + value: 120, + ), + Return( + value: None, + ), + ], + ), + ], + entry_points: [ + ( + name: "fs_main", + stage: Fragment, + early_depth_test: None, + workgroup_size: (0, 0, 0), + function: ( + name: Some("fs_main_wrap"), + arguments: [ + ( + name: Some("in_normal_fs"), + ty: 2, + binding: Some(Location( + location: 0, + second_blend_source: false, + interpolation: Some(Perspective), + sampling: Some(Center), + )), + ), + ( + name: Some("in_position_fs"), + ty: 4, + binding: Some(Location( + location: 1, + second_blend_source: false, + interpolation: Some(Perspective), + sampling: Some(Center), + )), + ), + ], + result: Some(( + ty: 4, + binding: Some(Location( + location: 0, + second_blend_source: false, + interpolation: None, + sampling: None, + )), + )), + local_variables: [], + expressions: [ + FunctionArgument(0), + GlobalVariable(6), + FunctionArgument(1), + GlobalVariable(5), + GlobalVariable(7), + Load( + pointer: 5, + ), + ], + named_expressions: {}, + body: [ + Store( + pointer: 2, + value: 1, + ), + Store( + pointer: 4, + value: 3, + ), + Call( + function: 2, + arguments: [], + result: None, + ), + Emit(( + start: 5, + end: 6, + )), + Return( + value: Some(6), + ), + ], + ), + ), + ], +) \ No newline at end of file diff --git a/naga/tests/out/msl/abstract-types-const.msl b/naga/tests/out/msl/abstract-types-const.msl new file mode 100644 index 0000000000..af4de25cc6 --- /dev/null +++ b/naga/tests/out/msl/abstract-types-const.msl @@ -0,0 +1,62 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_5 { + float inner[2]; +}; +struct S { + float f; + int i; + uint u; +}; +constant metal::uint2 xvupaiai = metal::uint2(42u, 43u); +constant metal::float2 xvfpaiai = metal::float2(44.0, 45.0); +constant metal::uint2 xvupuai = metal::uint2(42u, 43u); +constant metal::uint2 xvupaiu = metal::uint2(42u, 43u); +constant metal::uint2 xvuuai = metal::uint2(42u, 43u); +constant metal::uint2 xvuaiu = metal::uint2(42u, 43u); +constant metal::float2x2 xmfpaiaiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); +constant metal::float2x2 xmfpafaiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); +constant metal::float2x2 xmfpaiafaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); +constant metal::float2x2 xmfpaiaiafai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); +constant metal::float2x2 xmfpaiaiaiaf = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); +constant metal::float2x2 imfpaiaiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); +constant metal::float2x2 imfpafaiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); +constant metal::float2x2 imfpafafafaf = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); +constant metal::int2 ivispai = metal::int2(1); +constant metal::float2 ivfspaf = metal::float2(1.0); +constant metal::int2 ivis_ai = metal::int2(1); +constant metal::uint2 ivus_ai = metal::uint2(1u); +constant metal::float2 ivfs_ai = metal::float2(1.0); +constant metal::float2 ivfs_af = metal::float2(1.0); +constant type_5 iafafaf = type_5 {1.0, 2.0}; +constant type_5 iafaiai = type_5 {1.0, 2.0}; +constant type_5 iafpafaf = type_5 {1.0, 2.0}; +constant type_5 iafpaiaf = type_5 {1.0, 2.0}; +constant type_5 iafpafai = type_5 {1.0, 2.0}; +constant type_5 xafpafaf = type_5 {1.0, 2.0}; +constant S s_f_i_u = S {1.0, 1, 1u}; +constant S s_f_iai = S {1.0, 1, 1u}; +constant S s_fai_u = S {1.0, 1, 1u}; +constant S s_faiai = S {1.0, 1, 1u}; +constant S saf_i_u = S {1.0, 1, 1u}; +constant S saf_iai = S {1.0, 1, 1u}; +constant S safai_u = S {1.0, 1, 1u}; +constant S safaiai = S {1.0, 1, 1u}; +constant metal::float3 ivfr_f_f = metal::float3(metal::float2(1.0, 2.0), 3.0); +constant metal::float3 ivfr_f_af = metal::float3(metal::float2(1.0, 2.0), 3.0); +constant metal::float3 ivfraf_f = metal::float3(metal::float2(1.0, 2.0), 3.0); +constant metal::float3 ivfraf_af = metal::float3(metal::float2(1.0, 2.0), 3.0); +constant metal::float3 ivf_fr_f = metal::float3(1.0, metal::float2(2.0, 3.0)); +constant metal::float3 ivf_fraf = metal::float3(1.0, metal::float2(2.0, 3.0)); +constant metal::float3 ivf_afr_f = metal::float3(1.0, metal::float2(2.0, 3.0)); +constant metal::float3 ivf_afraf = metal::float3(1.0, metal::float2(2.0, 3.0)); +constant metal::float3 ivfr_f_ai = metal::float3(metal::float2(1.0, 2.0), 3.0); +constant metal::float3 ivfrai_f = metal::float3(metal::float2(1.0, 2.0), 3.0); +constant metal::float3 ivfrai_ai = metal::float3(metal::float2(1.0, 2.0), 3.0); +constant metal::float3 ivf_frai = metal::float3(1.0, metal::float2(2.0, 3.0)); +constant metal::float3 ivf_air_f = metal::float3(1.0, metal::float2(2.0, 3.0)); +constant metal::float3 ivf_airai = metal::float3(1.0, metal::float2(2.0, 3.0)); diff --git a/naga/tests/out/msl/abstract-types-operators.msl b/naga/tests/out/msl/abstract-types-operators.msl new file mode 100644 index 0000000000..f273b06610 --- /dev/null +++ b/naga/tests/out/msl/abstract-types-operators.msl @@ -0,0 +1,93 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_3 { + uint inner[64]; +}; +constant float plus_fafaf_1 = 3.0; +constant float plus_fafai_1 = 3.0; +constant float plus_faf_f_1 = 3.0; +constant float plus_faiaf_1 = 3.0; +constant float plus_faiai_1 = 3.0; +constant float plus_fai_f_1 = 3.0; +constant float plus_f_faf_1 = 3.0; +constant float plus_f_fai_1 = 3.0; +constant float plus_f_f_f_1 = 3.0; +constant int plus_iaiai_1 = 3; +constant int plus_iai_i_1 = 3; +constant int plus_i_iai_1 = 3; +constant int plus_i_i_i_1 = 3; +constant uint plus_uaiai_1 = 3u; +constant uint plus_uai_u_1 = 3u; +constant uint plus_u_uai_1 = 3u; +constant uint plus_u_u_u_1 = 3u; +constant uint bitflip_u_u = 0u; +constant uint bitflip_uai = 0u; +constant int least_i32_ = -2147483648; +constant float least_f32_ = -340282350000000000000000000000000000000.0; +constant int wgpu_4492_ = -2147483648; +constant int wgpu_4492_2_ = -2147483648; + +void runtime_values( +) { + float f = 42.0; + int i = 43; + uint u = 44u; + float plus_fafaf = 3.0; + float plus_fafai = 3.0; + float plus_faf_f = {}; + float plus_faiaf = 3.0; + float plus_faiai = 3.0; + float plus_fai_f = {}; + float plus_f_faf = {}; + float plus_f_fai = {}; + float plus_f_f_f = {}; + int plus_iaiai = 3; + int plus_iai_i = {}; + int plus_i_iai = {}; + int plus_i_i_i = {}; + uint plus_uaiai = 3u; + uint plus_uai_u = {}; + uint plus_u_uai = {}; + uint plus_u_u_u = {}; + float _e8 = f; + plus_faf_f = 1.0 + _e8; + float _e14 = f; + plus_fai_f = 1.0 + _e14; + float _e18 = f; + plus_f_faf = _e18 + 2.0; + float _e22 = f; + plus_f_fai = _e22 + 2.0; + float _e26 = f; + float _e27 = f; + plus_f_f_f = _e26 + _e27; + int _e31 = i; + plus_iai_i = 1 + _e31; + int _e35 = i; + plus_i_iai = _e35 + 2; + int _e39 = i; + int _e40 = i; + plus_i_i_i = _e39 + _e40; + uint _e44 = u; + plus_uai_u = 1u + _e44; + uint _e48 = u; + plus_u_uai = _e48 + 2u; + uint _e52 = u; + uint _e53 = u; + plus_u_u_u = _e52 + _e53; + return; +} + +void wgpu_4445_( +) { + return; +} + +void wgpu_4435_( + threadgroup type_3& a +) { + uint y = a.inner[1 - 1]; +} diff --git a/naga/tests/out/msl/abstract-types-var.msl b/naga/tests/out/msl/abstract-types-var.msl new file mode 100644 index 0000000000..45096f8672 --- /dev/null +++ b/naga/tests/out/msl/abstract-types-var.msl @@ -0,0 +1,117 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_5 { + float inner[2]; +}; +struct type_7 { + int inner[2]; +}; + +void all_constant_arguments( +) { + metal::int2 xvipaiai = metal::int2(42, 43); + metal::uint2 xvupaiai = metal::uint2(44u, 45u); + metal::float2 xvfpaiai = metal::float2(46.0, 47.0); + metal::uint2 xvupuai = metal::uint2(42u, 43u); + metal::uint2 xvupaiu = metal::uint2(42u, 43u); + metal::uint2 xvuuai = metal::uint2(42u, 43u); + metal::uint2 xvuaiu = metal::uint2(42u, 43u); + metal::float2x2 xmfpaiaiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + metal::float2x2 xmfpafaiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + metal::float2x2 xmfpaiafaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + metal::float2x2 xmfpaiaiafai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + metal::float2x2 xmfpaiaiaiaf = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + metal::float2x2 xmfp_faiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + metal::float2x2 xmfpai_faiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + metal::float2x2 xmfpaiai_fai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + metal::float2x2 xmfpaiaiai_f = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + metal::int2 xvispai = metal::int2(1); + metal::float2 xvfspaf = metal::float2(1.0); + metal::int2 xvis_ai = metal::int2(1); + metal::uint2 xvus_ai = metal::uint2(1u); + metal::float2 xvfs_ai = metal::float2(1.0); + metal::float2 xvfs_af = metal::float2(1.0); + type_5 xafafaf = type_5 {1.0, 2.0}; + type_5 xaf_faf = type_5 {1.0, 2.0}; + type_5 xafaf_f = type_5 {1.0, 2.0}; + type_5 xafaiai = type_5 {1.0, 2.0}; + type_7 xai_iai = type_7 {1, 2}; + type_7 xaiai_i = type_7 {1, 2}; + type_7 xaipaiai = type_7 {1, 2}; + type_5 xafpaiai = type_5 {1.0, 2.0}; + type_5 xafpaiaf = type_5 {1.0, 2.0}; + type_5 xafpafai = type_5 {1.0, 2.0}; + type_5 xafpafaf = type_5 {1.0, 2.0}; +} + +void mixed_constant_and_runtime_arguments( +) { + uint u = {}; + int i = {}; + float f = {}; + metal::uint2 xvupuai_1 = {}; + metal::uint2 xvupaiu_1 = {}; + metal::uint2 xvuuai_1 = {}; + metal::uint2 xvuaiu_1 = {}; + metal::float2x2 xmfp_faiaiai_1 = {}; + metal::float2x2 xmfpai_faiai_1 = {}; + metal::float2x2 xmfpaiai_fai_1 = {}; + metal::float2x2 xmfpaiaiai_f_1 = {}; + type_5 xaf_faf_1 = {}; + type_5 xafaf_f_1 = {}; + type_5 xaf_fai = {}; + type_5 xafai_f = {}; + type_7 xai_iai_1 = {}; + type_7 xaiai_i_1 = {}; + type_5 xafp_faf = {}; + type_5 xafpaf_f = {}; + type_5 xafp_fai = {}; + type_5 xafpai_f = {}; + type_7 xaip_iai = {}; + type_7 xaipai_i = {}; + uint _e3 = u; + xvupuai_1 = metal::uint2(_e3, 43u); + uint _e7 = u; + xvupaiu_1 = metal::uint2(42u, _e7); + uint _e11 = u; + xvuuai_1 = metal::uint2(_e11, 43u); + uint _e15 = u; + xvuaiu_1 = metal::uint2(42u, _e15); + float _e19 = f; + xmfp_faiaiai_1 = metal::float2x2(metal::float2(_e19, 2.0), metal::float2(3.0, 4.0)); + float _e27 = f; + xmfpai_faiai_1 = metal::float2x2(metal::float2(1.0, _e27), metal::float2(3.0, 4.0)); + float _e35 = f; + xmfpaiai_fai_1 = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(_e35, 4.0)); + float _e43 = f; + xmfpaiaiai_f_1 = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, _e43)); + float _e51 = f; + xaf_faf_1 = type_5 {_e51, 2.0}; + float _e55 = f; + xafaf_f_1 = type_5 {1.0, _e55}; + float _e59 = f; + xaf_fai = type_5 {_e59, 2.0}; + float _e63 = f; + xafai_f = type_5 {1.0, _e63}; + int _e67 = i; + xai_iai_1 = type_7 {_e67, 2}; + int _e71 = i; + xaiai_i_1 = type_7 {1, _e71}; + float _e75 = f; + xafp_faf = type_5 {_e75, 2.0}; + float _e79 = f; + xafpaf_f = type_5 {1.0, _e79}; + float _e83 = f; + xafp_fai = type_5 {_e83, 2.0}; + float _e87 = f; + xafpai_f = type_5 {1.0, _e87}; + int _e91 = i; + xaip_iai = type_7 {_e91, 2}; + int _e95 = i; + xaipai_i = type_7 {1, _e95}; + return; +} diff --git a/naga/tests/out/msl/access.msl b/naga/tests/out/msl/access.msl new file mode 100644 index 0000000000..908535ea31 --- /dev/null +++ b/naga/tests/out/msl/access.msl @@ -0,0 +1,219 @@ +// language: metal1.2 +#include +#include + +using metal::uint; + +struct _mslBufferSizes { + uint size1; +}; + +struct GlobalConst { + uint a; + char _pad1[12]; + metal::packed_uint3 b; + int c; +}; +struct AlignedWrapper { + int value; +}; +struct type_5 { + metal::float2x2 inner[2]; +}; +struct type_7 { + metal::atomic_int inner[10]; +}; +struct type_9 { + metal::uint2 inner[2]; +}; +typedef AlignedWrapper type_10[1]; +struct Bar { + metal::float4x3 _matrix; + type_5 matrix_array; + metal::atomic_int atom; + type_7 atom_arr; + char _pad4[4]; + type_9 arr; + type_10 data; +}; +struct Baz { + metal::float3x2 m; +}; +struct type_14 { + metal::float4x2 inner[2]; +}; +struct MatCx2InArray { + type_14 am; +}; +struct type_17 { + float inner[10]; +}; +struct type_18 { + type_17 inner[5]; +}; +struct type_20 { + int inner[5]; +}; +struct type_22 { + metal::float4 inner[2]; +}; + +void test_matrix_within_struct_accesses( + constant Baz& baz +) { + int idx = 1; + Baz t = Baz {metal::float3x2(metal::float2(1.0), metal::float2(2.0), metal::float2(3.0))}; + int _e3 = idx; + idx = _e3 - 1; + metal::float3x2 l0_ = baz.m; + metal::float2 l1_ = baz.m[0]; + int _e14 = idx; + metal::float2 l2_ = baz.m[_e14]; + float l3_ = baz.m[0].y; + int _e25 = idx; + float l4_ = baz.m[0][_e25]; + int _e30 = idx; + float l5_ = baz.m[_e30].y; + int _e36 = idx; + int _e38 = idx; + float l6_ = baz.m[_e36][_e38]; + int _e51 = idx; + idx = _e51 + 1; + t.m = metal::float3x2(metal::float2(6.0), metal::float2(5.0), metal::float2(4.0)); + t.m[0] = metal::float2(9.0); + int _e66 = idx; + t.m[_e66] = metal::float2(90.0); + t.m[0].y = 10.0; + int _e76 = idx; + t.m[0][_e76] = 20.0; + int _e80 = idx; + t.m[_e80].y = 30.0; + int _e85 = idx; + int _e87 = idx; + t.m[_e85][_e87] = 40.0; + return; +} + +void test_matrix_within_array_within_struct_accesses( + constant MatCx2InArray& nested_mat_cx2_ +) { + int idx_1 = 1; + MatCx2InArray t_1 = MatCx2InArray {type_14 {}}; + int _e3 = idx_1; + idx_1 = _e3 - 1; + type_14 l0_1 = nested_mat_cx2_.am; + metal::float4x2 l1_1 = nested_mat_cx2_.am.inner[0]; + metal::float2 l2_1 = nested_mat_cx2_.am.inner[0][0]; + int _e20 = idx_1; + metal::float2 l3_1 = nested_mat_cx2_.am.inner[0][_e20]; + float l4_1 = nested_mat_cx2_.am.inner[0][0].y; + int _e33 = idx_1; + float l5_1 = nested_mat_cx2_.am.inner[0][0][_e33]; + int _e39 = idx_1; + float l6_1 = nested_mat_cx2_.am.inner[0][_e39].y; + int _e46 = idx_1; + int _e48 = idx_1; + float l7_ = nested_mat_cx2_.am.inner[0][_e46][_e48]; + int _e55 = idx_1; + idx_1 = _e55 + 1; + t_1.am = type_14 {}; + t_1.am.inner[0] = metal::float4x2(metal::float2(8.0), metal::float2(7.0), metal::float2(6.0), metal::float2(5.0)); + t_1.am.inner[0][0] = metal::float2(9.0); + int _e77 = idx_1; + t_1.am.inner[0][_e77] = metal::float2(90.0); + t_1.am.inner[0][0].y = 10.0; + int _e89 = idx_1; + t_1.am.inner[0][0][_e89] = 20.0; + int _e94 = idx_1; + t_1.am.inner[0][_e94].y = 30.0; + int _e100 = idx_1; + int _e102 = idx_1; + t_1.am.inner[0][_e100][_e102] = 40.0; + return; +} + +float read_from_private( + thread float& foo_1 +) { + float _e1 = foo_1; + return _e1; +} + +float test_arr_as_arg( + type_18 a +) { + return a.inner[4].inner[9]; +} + +void assign_through_ptr_fn( + thread uint& p +) { + p = 42u; + return; +} + +void assign_array_through_ptr_fn( + thread type_22& foo_2 +) { + foo_2 = type_22 {metal::float4(1.0), metal::float4(2.0)}; + return; +} + +struct foo_vertInput { +}; +struct foo_vertOutput { + metal::float4 member [[position]]; +}; +vertex foo_vertOutput foo_vert( + uint vi [[vertex_id]] +, device Bar const& bar [[buffer(0)]] +, constant Baz& baz [[buffer(1)]] +, device metal::int2 const& qux [[buffer(2)]] +, constant MatCx2InArray& nested_mat_cx2_ [[buffer(3)]] +, constant _mslBufferSizes& _buffer_sizes [[buffer(24)]] +) { + float foo = 0.0; + type_20 c2_ = {}; + float baz_1 = foo; + foo = 1.0; + test_matrix_within_struct_accesses(baz); + test_matrix_within_array_within_struct_accesses(nested_mat_cx2_); + metal::float4x3 _matrix = bar._matrix; + type_9 arr_1 = bar.arr; + float b = bar._matrix[3u].x; + int a_1 = bar.data[(1 + (_buffer_sizes.size1 - 160 - 8) / 8) - 2u].value; + metal::int2 c = qux; + float _e33 = read_from_private(foo); + c2_ = type_20 {a_1, static_cast(b), 3, 4, 5}; + c2_.inner[vi + 1u] = 42; + int value = c2_.inner[vi]; + float _e47 = test_arr_as_arg(type_18 {}); + return foo_vertOutput { metal::float4(_matrix * static_cast(metal::int4(value)), 2.0) }; +} + + +struct foo_fragOutput { + metal::float4 member_1 [[color(0)]]; +}; +fragment foo_fragOutput foo_frag( + device Bar& bar [[buffer(0)]] +, device metal::int2& qux [[buffer(2)]] +, constant _mslBufferSizes& _buffer_sizes [[buffer(24)]] +) { + bar._matrix[1].z = 1.0; + bar._matrix = metal::float4x3(metal::float3(0.0), metal::float3(1.0), metal::float3(2.0), metal::float3(3.0)); + bar.arr = type_9 {metal::uint2(0u), metal::uint2(1u)}; + bar.data[1].value = 1; + qux = metal::int2 {}; + return foo_fragOutput { metal::float4(0.0) }; +} + + +kernel void assign_through_ptr( +) { + uint val = 33u; + type_22 arr = type_22 {metal::float4(6.0), metal::float4(7.0)}; + assign_through_ptr_fn(val); + assign_array_through_ptr_fn(arr); + return; +} diff --git a/naga/tests/out/msl/array-in-ctor.msl b/naga/tests/out/msl/array-in-ctor.msl new file mode 100644 index 0000000000..a3bbb2057c --- /dev/null +++ b/naga/tests/out/msl/array-in-ctor.msl @@ -0,0 +1,18 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_1 { + float inner[2]; +}; +struct Ah { + type_1 inner; +}; + +kernel void cs_main( + device Ah const& ah [[user(fake0)]] +) { + Ah ah_1 = ah; +} diff --git a/naga/tests/out/msl/array-in-function-return-type.msl b/naga/tests/out/msl/array-in-function-return-type.msl new file mode 100644 index 0000000000..77399f6424 --- /dev/null +++ b/naga/tests/out/msl/array-in-function-return-type.msl @@ -0,0 +1,23 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_1 { + float inner[2]; +}; + +type_1 ret_array( +) { + return type_1 {1.0, 2.0}; +} + +struct main_Output { + metal::float4 member [[color(0)]]; +}; +fragment main_Output main_( +) { + type_1 _e0 = ret_array(); + return main_Output { metal::float4(_e0.inner[0], _e0.inner[1], 0.0, 1.0) }; +} diff --git a/naga/tests/out/msl/atomicOps.msl b/naga/tests/out/msl/atomicOps.msl new file mode 100644 index 0000000000..4732b4a32d --- /dev/null +++ b/naga/tests/out/msl/atomicOps.msl @@ -0,0 +1,126 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_2 { + metal::atomic_int inner[2]; +}; +struct Struct { + metal::atomic_uint atomic_scalar; + type_2 atomic_arr; +}; + +struct cs_mainInput { +}; +kernel void cs_main( + metal::uint3 id [[thread_position_in_threadgroup]] +, device metal::atomic_uint& storage_atomic_scalar [[user(fake0)]] +, device type_2& storage_atomic_arr [[user(fake0)]] +, device Struct& storage_struct [[user(fake0)]] +, threadgroup metal::atomic_uint& workgroup_atomic_scalar +, threadgroup type_2& workgroup_atomic_arr +, threadgroup Struct& workgroup_struct +) { + if (metal::all(id == metal::uint3(0u))) { + metal::atomic_store_explicit(&workgroup_atomic_scalar, 0, metal::memory_order_relaxed); + for (int __i0 = 0; __i0 < 2; __i0++) { + metal::atomic_store_explicit(&workgroup_atomic_arr.inner[__i0], 0, metal::memory_order_relaxed); + } + metal::atomic_store_explicit(&workgroup_struct.atomic_scalar, 0, metal::memory_order_relaxed); + for (int __i0 = 0; __i0 < 2; __i0++) { + metal::atomic_store_explicit(&workgroup_struct.atomic_arr.inner[__i0], 0, metal::memory_order_relaxed); + } + } + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + metal::atomic_store_explicit(&storage_atomic_scalar, 1u, metal::memory_order_relaxed); + metal::atomic_store_explicit(&storage_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::atomic_store_explicit(&storage_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + metal::atomic_store_explicit(&storage_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::atomic_store_explicit(&workgroup_atomic_scalar, 1u, metal::memory_order_relaxed); + metal::atomic_store_explicit(&workgroup_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::atomic_store_explicit(&workgroup_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + metal::atomic_store_explicit(&workgroup_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + uint l0_ = metal::atomic_load_explicit(&storage_atomic_scalar, metal::memory_order_relaxed); + int l1_ = metal::atomic_load_explicit(&storage_atomic_arr.inner[1], metal::memory_order_relaxed); + uint l2_ = metal::atomic_load_explicit(&storage_struct.atomic_scalar, metal::memory_order_relaxed); + int l3_ = metal::atomic_load_explicit(&storage_struct.atomic_arr.inner[1], metal::memory_order_relaxed); + uint l4_ = metal::atomic_load_explicit(&workgroup_atomic_scalar, metal::memory_order_relaxed); + int l5_ = metal::atomic_load_explicit(&workgroup_atomic_arr.inner[1], metal::memory_order_relaxed); + uint l6_ = metal::atomic_load_explicit(&workgroup_struct.atomic_scalar, metal::memory_order_relaxed); + int l7_ = metal::atomic_load_explicit(&workgroup_struct.atomic_arr.inner[1], metal::memory_order_relaxed); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + uint _e51 = metal::atomic_fetch_add_explicit(&storage_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e55 = metal::atomic_fetch_add_explicit(&storage_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e59 = metal::atomic_fetch_add_explicit(&storage_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e64 = metal::atomic_fetch_add_explicit(&storage_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e67 = metal::atomic_fetch_add_explicit(&workgroup_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e71 = metal::atomic_fetch_add_explicit(&workgroup_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e75 = metal::atomic_fetch_add_explicit(&workgroup_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e80 = metal::atomic_fetch_add_explicit(&workgroup_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + uint _e83 = metal::atomic_fetch_sub_explicit(&storage_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e87 = metal::atomic_fetch_sub_explicit(&storage_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e91 = metal::atomic_fetch_sub_explicit(&storage_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e96 = metal::atomic_fetch_sub_explicit(&storage_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e99 = metal::atomic_fetch_sub_explicit(&workgroup_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e103 = metal::atomic_fetch_sub_explicit(&workgroup_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e107 = metal::atomic_fetch_sub_explicit(&workgroup_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e112 = metal::atomic_fetch_sub_explicit(&workgroup_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + uint _e115 = metal::atomic_fetch_max_explicit(&storage_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e119 = metal::atomic_fetch_max_explicit(&storage_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e123 = metal::atomic_fetch_max_explicit(&storage_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e128 = metal::atomic_fetch_max_explicit(&storage_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e131 = metal::atomic_fetch_max_explicit(&workgroup_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e135 = metal::atomic_fetch_max_explicit(&workgroup_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e139 = metal::atomic_fetch_max_explicit(&workgroup_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e144 = metal::atomic_fetch_max_explicit(&workgroup_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + uint _e147 = metal::atomic_fetch_min_explicit(&storage_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e151 = metal::atomic_fetch_min_explicit(&storage_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e155 = metal::atomic_fetch_min_explicit(&storage_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e160 = metal::atomic_fetch_min_explicit(&storage_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e163 = metal::atomic_fetch_min_explicit(&workgroup_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e167 = metal::atomic_fetch_min_explicit(&workgroup_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e171 = metal::atomic_fetch_min_explicit(&workgroup_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e176 = metal::atomic_fetch_min_explicit(&workgroup_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + uint _e179 = metal::atomic_fetch_and_explicit(&storage_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e183 = metal::atomic_fetch_and_explicit(&storage_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e187 = metal::atomic_fetch_and_explicit(&storage_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e192 = metal::atomic_fetch_and_explicit(&storage_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e195 = metal::atomic_fetch_and_explicit(&workgroup_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e199 = metal::atomic_fetch_and_explicit(&workgroup_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e203 = metal::atomic_fetch_and_explicit(&workgroup_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e208 = metal::atomic_fetch_and_explicit(&workgroup_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + uint _e211 = metal::atomic_fetch_or_explicit(&storage_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e215 = metal::atomic_fetch_or_explicit(&storage_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e219 = metal::atomic_fetch_or_explicit(&storage_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e224 = metal::atomic_fetch_or_explicit(&storage_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e227 = metal::atomic_fetch_or_explicit(&workgroup_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e231 = metal::atomic_fetch_or_explicit(&workgroup_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e235 = metal::atomic_fetch_or_explicit(&workgroup_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e240 = metal::atomic_fetch_or_explicit(&workgroup_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + uint _e243 = metal::atomic_fetch_xor_explicit(&storage_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e247 = metal::atomic_fetch_xor_explicit(&storage_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e251 = metal::atomic_fetch_xor_explicit(&storage_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e256 = metal::atomic_fetch_xor_explicit(&storage_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e259 = metal::atomic_fetch_xor_explicit(&workgroup_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e263 = metal::atomic_fetch_xor_explicit(&workgroup_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e267 = metal::atomic_fetch_xor_explicit(&workgroup_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e272 = metal::atomic_fetch_xor_explicit(&workgroup_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e275 = metal::atomic_exchange_explicit(&storage_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e279 = metal::atomic_exchange_explicit(&storage_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e283 = metal::atomic_exchange_explicit(&storage_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e288 = metal::atomic_exchange_explicit(&storage_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e291 = metal::atomic_exchange_explicit(&workgroup_atomic_scalar, 1u, metal::memory_order_relaxed); + int _e295 = metal::atomic_exchange_explicit(&workgroup_atomic_arr.inner[1], 1, metal::memory_order_relaxed); + uint _e299 = metal::atomic_exchange_explicit(&workgroup_struct.atomic_scalar, 1u, metal::memory_order_relaxed); + int _e304 = metal::atomic_exchange_explicit(&workgroup_struct.atomic_arr.inner[1], 1, metal::memory_order_relaxed); + return; +} diff --git a/naga/tests/out/msl/binding-arrays.msl b/naga/tests/out/msl/binding-arrays.msl new file mode 100644 index 0000000000..f3548c9e79 --- /dev/null +++ b/naga/tests/out/msl/binding-arrays.msl @@ -0,0 +1,170 @@ +// language: metal2.0 +#include +#include + +using metal::uint; +struct DefaultConstructible { + template + operator T() && { + return T {}; + } +}; + +struct UniformIndex { + uint index; +}; +struct FragmentIn { + uint index; +}; + +struct main_Input { + uint index [[user(loc0), flat]]; +}; +struct main_Output { + metal::float4 member [[color(0)]]; +}; +fragment main_Output main_( + main_Input varyings [[stage_in]] +, metal::array, 10> texture_array_unbounded [[texture(0)]] +, metal::array, 5> texture_array_bounded [[user(fake0)]] +, metal::array, 5> texture_array_2darray [[user(fake0)]] +, metal::array, 5> texture_array_multisampled [[user(fake0)]] +, metal::array, 5> texture_array_depth [[user(fake0)]] +, metal::array, 5> texture_array_storage [[user(fake0)]] +, metal::array samp [[user(fake0)]] +, metal::array samp_comp [[user(fake0)]] +, constant UniformIndex& uni [[user(fake0)]] +) { + const FragmentIn fragment_in = { varyings.index }; + uint u1_ = 0u; + metal::uint2 u2_ = metal::uint2(0u); + float v1_ = 0.0; + metal::float4 v4_ = metal::float4(0.0); + uint uniform_index = uni.index; + uint non_uniform_index = fragment_in.index; + metal::float2 uv = metal::float2(0.0); + metal::int2 pix = metal::int2(0); + metal::uint2 _e22 = u2_; + u2_ = _e22 + metal::uint2(texture_array_unbounded[0].get_width(), texture_array_unbounded[0].get_height()); + metal::uint2 _e27 = u2_; + u2_ = _e27 + metal::uint2(texture_array_unbounded[uniform_index].get_width(), texture_array_unbounded[uniform_index].get_height()); + metal::uint2 _e32 = u2_; + u2_ = _e32 + metal::uint2(texture_array_unbounded[non_uniform_index].get_width(), texture_array_unbounded[non_uniform_index].get_height()); + metal::float4 _e38 = texture_array_bounded[0].gather(samp[0], uv); + metal::float4 _e39 = v4_; + v4_ = _e39 + _e38; + metal::float4 _e45 = texture_array_bounded[uniform_index].gather(samp[uniform_index], uv); + metal::float4 _e46 = v4_; + v4_ = _e46 + _e45; + metal::float4 _e52 = texture_array_bounded[non_uniform_index].gather(samp[non_uniform_index], uv); + metal::float4 _e53 = v4_; + v4_ = _e53 + _e52; + metal::float4 _e60 = texture_array_depth[0].gather_compare(samp_comp[0], uv, 0.0); + metal::float4 _e61 = v4_; + v4_ = _e61 + _e60; + metal::float4 _e68 = texture_array_depth[uniform_index].gather_compare(samp_comp[uniform_index], uv, 0.0); + metal::float4 _e69 = v4_; + v4_ = _e69 + _e68; + metal::float4 _e76 = texture_array_depth[non_uniform_index].gather_compare(samp_comp[non_uniform_index], uv, 0.0); + metal::float4 _e77 = v4_; + v4_ = _e77 + _e76; + metal::float4 _e82 = (uint(0) < texture_array_unbounded[0].get_num_mip_levels() && metal::all(metal::uint2(pix) < metal::uint2(texture_array_unbounded[0].get_width(0), texture_array_unbounded[0].get_height(0))) ? texture_array_unbounded[0].read(metal::uint2(pix), 0): DefaultConstructible()); + metal::float4 _e83 = v4_; + v4_ = _e83 + _e82; + metal::float4 _e88 = (uint(0) < texture_array_unbounded[uniform_index].get_num_mip_levels() && metal::all(metal::uint2(pix) < metal::uint2(texture_array_unbounded[uniform_index].get_width(0), texture_array_unbounded[uniform_index].get_height(0))) ? texture_array_unbounded[uniform_index].read(metal::uint2(pix), 0): DefaultConstructible()); + metal::float4 _e89 = v4_; + v4_ = _e89 + _e88; + metal::float4 _e94 = (uint(0) < texture_array_unbounded[non_uniform_index].get_num_mip_levels() && metal::all(metal::uint2(pix) < metal::uint2(texture_array_unbounded[non_uniform_index].get_width(0), texture_array_unbounded[non_uniform_index].get_height(0))) ? texture_array_unbounded[non_uniform_index].read(metal::uint2(pix), 0): DefaultConstructible()); + metal::float4 _e95 = v4_; + v4_ = _e95 + _e94; + uint _e100 = u1_; + u1_ = _e100 + texture_array_2darray[0].get_array_size(); + uint _e105 = u1_; + u1_ = _e105 + texture_array_2darray[uniform_index].get_array_size(); + uint _e110 = u1_; + u1_ = _e110 + texture_array_2darray[non_uniform_index].get_array_size(); + uint _e115 = u1_; + u1_ = _e115 + texture_array_bounded[0].get_num_mip_levels(); + uint _e120 = u1_; + u1_ = _e120 + texture_array_bounded[uniform_index].get_num_mip_levels(); + uint _e125 = u1_; + u1_ = _e125 + texture_array_bounded[non_uniform_index].get_num_mip_levels(); + uint _e130 = u1_; + u1_ = _e130 + texture_array_multisampled[0].get_num_samples(); + uint _e135 = u1_; + u1_ = _e135 + texture_array_multisampled[uniform_index].get_num_samples(); + uint _e140 = u1_; + u1_ = _e140 + texture_array_multisampled[non_uniform_index].get_num_samples(); + metal::float4 _e146 = texture_array_bounded[0].sample(samp[0], uv); + metal::float4 _e147 = v4_; + v4_ = _e147 + _e146; + metal::float4 _e153 = texture_array_bounded[uniform_index].sample(samp[uniform_index], uv); + metal::float4 _e154 = v4_; + v4_ = _e154 + _e153; + metal::float4 _e160 = texture_array_bounded[non_uniform_index].sample(samp[non_uniform_index], uv); + metal::float4 _e161 = v4_; + v4_ = _e161 + _e160; + metal::float4 _e168 = texture_array_bounded[0].sample(samp[0], uv, metal::bias(0.0)); + metal::float4 _e169 = v4_; + v4_ = _e169 + _e168; + metal::float4 _e176 = texture_array_bounded[uniform_index].sample(samp[uniform_index], uv, metal::bias(0.0)); + metal::float4 _e177 = v4_; + v4_ = _e177 + _e176; + metal::float4 _e184 = texture_array_bounded[non_uniform_index].sample(samp[non_uniform_index], uv, metal::bias(0.0)); + metal::float4 _e185 = v4_; + v4_ = _e185 + _e184; + float _e192 = texture_array_depth[0].sample_compare(samp_comp[0], uv, 0.0); + float _e193 = v1_; + v1_ = _e193 + _e192; + float _e200 = texture_array_depth[uniform_index].sample_compare(samp_comp[uniform_index], uv, 0.0); + float _e201 = v1_; + v1_ = _e201 + _e200; + float _e208 = texture_array_depth[non_uniform_index].sample_compare(samp_comp[non_uniform_index], uv, 0.0); + float _e209 = v1_; + v1_ = _e209 + _e208; + float _e216 = texture_array_depth[0].sample_compare(samp_comp[0], uv, 0.0); + float _e217 = v1_; + v1_ = _e217 + _e216; + float _e224 = texture_array_depth[uniform_index].sample_compare(samp_comp[uniform_index], uv, 0.0); + float _e225 = v1_; + v1_ = _e225 + _e224; + float _e232 = texture_array_depth[non_uniform_index].sample_compare(samp_comp[non_uniform_index], uv, 0.0); + float _e233 = v1_; + v1_ = _e233 + _e232; + metal::float4 _e239 = texture_array_bounded[0].sample(samp[0], uv, metal::gradient2d(uv, uv)); + metal::float4 _e240 = v4_; + v4_ = _e240 + _e239; + metal::float4 _e246 = texture_array_bounded[uniform_index].sample(samp[uniform_index], uv, metal::gradient2d(uv, uv)); + metal::float4 _e247 = v4_; + v4_ = _e247 + _e246; + metal::float4 _e253 = texture_array_bounded[non_uniform_index].sample(samp[non_uniform_index], uv, metal::gradient2d(uv, uv)); + metal::float4 _e254 = v4_; + v4_ = _e254 + _e253; + metal::float4 _e261 = texture_array_bounded[0].sample(samp[0], uv, metal::level(0.0)); + metal::float4 _e262 = v4_; + v4_ = _e262 + _e261; + metal::float4 _e269 = texture_array_bounded[uniform_index].sample(samp[uniform_index], uv, metal::level(0.0)); + metal::float4 _e270 = v4_; + v4_ = _e270 + _e269; + metal::float4 _e277 = texture_array_bounded[non_uniform_index].sample(samp[non_uniform_index], uv, metal::level(0.0)); + metal::float4 _e278 = v4_; + v4_ = _e278 + _e277; + metal::float4 _e282 = v4_; + if (metal::all(metal::uint2(pix) < metal::uint2(texture_array_storage[0].get_width(), texture_array_storage[0].get_height()))) { + texture_array_storage[0].write(_e282, metal::uint2(pix)); + } + metal::float4 _e285 = v4_; + if (metal::all(metal::uint2(pix) < metal::uint2(texture_array_storage[uniform_index].get_width(), texture_array_storage[uniform_index].get_height()))) { + texture_array_storage[uniform_index].write(_e285, metal::uint2(pix)); + } + metal::float4 _e288 = v4_; + if (metal::all(metal::uint2(pix) < metal::uint2(texture_array_storage[non_uniform_index].get_width(), texture_array_storage[non_uniform_index].get_height()))) { + texture_array_storage[non_uniform_index].write(_e288, metal::uint2(pix)); + } + metal::uint2 _e289 = u2_; + uint _e290 = u1_; + metal::float2 v2_ = static_cast(_e289 + metal::uint2(_e290)); + metal::float4 _e294 = v4_; + float _e301 = v1_; + return main_Output { (_e294 + metal::float4(v2_.x, v2_.y, v2_.x, v2_.y)) + metal::float4(_e301) }; +} diff --git a/naga/tests/out/msl/bitcast.msl b/naga/tests/out/msl/bitcast.msl new file mode 100644 index 0000000000..20f4b850e3 --- /dev/null +++ b/naga/tests/out/msl/bitcast.msl @@ -0,0 +1,38 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +kernel void main_( +) { + metal::int2 i2_ = metal::int2(0); + metal::int3 i3_ = metal::int3(0); + metal::int4 i4_ = metal::int4(0); + metal::uint2 u2_ = metal::uint2(0u); + metal::uint3 u3_ = metal::uint3(0u); + metal::uint4 u4_ = metal::uint4(0u); + metal::float2 f2_ = metal::float2(0.0); + metal::float3 f3_ = metal::float3(0.0); + metal::float4 f4_ = metal::float4(0.0); + metal::int2 _e27 = i2_; + u2_ = as_type(_e27); + metal::int3 _e29 = i3_; + u3_ = as_type(_e29); + metal::int4 _e31 = i4_; + u4_ = as_type(_e31); + metal::uint2 _e33 = u2_; + i2_ = as_type(_e33); + metal::uint3 _e35 = u3_; + i3_ = as_type(_e35); + metal::uint4 _e37 = u4_; + i4_ = as_type(_e37); + metal::int2 _e39 = i2_; + f2_ = as_type(_e39); + metal::int3 _e41 = i3_; + f3_ = as_type(_e41); + metal::int4 _e43 = i4_; + f4_ = as_type(_e43); + return; +} diff --git a/naga/tests/out/msl/bits.msl b/naga/tests/out/msl/bits.msl new file mode 100644 index 0000000000..7d73568b7f --- /dev/null +++ b/naga/tests/out/msl/bits.msl @@ -0,0 +1,125 @@ +// language: metal1.2 +#include +#include + +using metal::uint; + + +kernel void main_( +) { + int i = 0; + metal::int2 i2_ = metal::int2(0); + metal::int3 i3_ = metal::int3(0); + metal::int4 i4_ = metal::int4(0); + uint u = 0u; + metal::uint2 u2_ = metal::uint2(0u); + metal::uint3 u3_ = metal::uint3(0u); + metal::uint4 u4_ = metal::uint4(0u); + metal::float2 f2_ = metal::float2(0.0); + metal::float4 f4_ = metal::float4(0.0); + metal::float4 _e28 = f4_; + u = metal::pack_float_to_snorm4x8(_e28); + metal::float4 _e30 = f4_; + u = metal::pack_float_to_unorm4x8(_e30); + metal::float2 _e32 = f2_; + u = metal::pack_float_to_snorm2x16(_e32); + metal::float2 _e34 = f2_; + u = metal::pack_float_to_unorm2x16(_e34); + metal::float2 _e36 = f2_; + u = as_type(half2(_e36)); + uint _e38 = u; + f4_ = metal::unpack_snorm4x8_to_float(_e38); + uint _e40 = u; + f4_ = metal::unpack_unorm4x8_to_float(_e40); + uint _e42 = u; + f2_ = metal::unpack_snorm2x16_to_float(_e42); + uint _e44 = u; + f2_ = metal::unpack_unorm2x16_to_float(_e44); + uint _e46 = u; + f2_ = float2(as_type(_e46)); + int _e48 = i; + int _e49 = i; + i = metal::insert_bits(_e48, _e49, 5u, 10u); + metal::int2 _e53 = i2_; + metal::int2 _e54 = i2_; + i2_ = metal::insert_bits(_e53, _e54, 5u, 10u); + metal::int3 _e58 = i3_; + metal::int3 _e59 = i3_; + i3_ = metal::insert_bits(_e58, _e59, 5u, 10u); + metal::int4 _e63 = i4_; + metal::int4 _e64 = i4_; + i4_ = metal::insert_bits(_e63, _e64, 5u, 10u); + uint _e68 = u; + uint _e69 = u; + u = metal::insert_bits(_e68, _e69, 5u, 10u); + metal::uint2 _e73 = u2_; + metal::uint2 _e74 = u2_; + u2_ = metal::insert_bits(_e73, _e74, 5u, 10u); + metal::uint3 _e78 = u3_; + metal::uint3 _e79 = u3_; + u3_ = metal::insert_bits(_e78, _e79, 5u, 10u); + metal::uint4 _e83 = u4_; + metal::uint4 _e84 = u4_; + u4_ = metal::insert_bits(_e83, _e84, 5u, 10u); + int _e88 = i; + i = metal::extract_bits(_e88, 5u, 10u); + metal::int2 _e92 = i2_; + i2_ = metal::extract_bits(_e92, 5u, 10u); + metal::int3 _e96 = i3_; + i3_ = metal::extract_bits(_e96, 5u, 10u); + metal::int4 _e100 = i4_; + i4_ = metal::extract_bits(_e100, 5u, 10u); + uint _e104 = u; + u = metal::extract_bits(_e104, 5u, 10u); + metal::uint2 _e108 = u2_; + u2_ = metal::extract_bits(_e108, 5u, 10u); + metal::uint3 _e112 = u3_; + u3_ = metal::extract_bits(_e112, 5u, 10u); + metal::uint4 _e116 = u4_; + u4_ = metal::extract_bits(_e116, 5u, 10u); + int _e120 = i; + i = (((metal::ctz(_e120) + 1) % 33) - 1); + metal::uint2 _e122 = u2_; + u2_ = (((metal::ctz(_e122) + 1) % 33) - 1); + metal::int3 _e124 = i3_; + i3_ = metal::select(31 - metal::clz(metal::select(_e124, ~_e124, _e124 < 0)), int3(-1), _e124 == 0 || _e124 == -1); + metal::uint3 _e126 = u3_; + u3_ = metal::select(31 - metal::clz(_e126), uint3(-1), _e126 == 0 || _e126 == -1); + int _e128 = i; + i = metal::select(31 - metal::clz(metal::select(_e128, ~_e128, _e128 < 0)), int(-1), _e128 == 0 || _e128 == -1); + uint _e130 = u; + u = metal::select(31 - metal::clz(_e130), uint(-1), _e130 == 0 || _e130 == -1); + int _e132 = i; + i = metal::popcount(_e132); + metal::int2 _e134 = i2_; + i2_ = metal::popcount(_e134); + metal::int3 _e136 = i3_; + i3_ = metal::popcount(_e136); + metal::int4 _e138 = i4_; + i4_ = metal::popcount(_e138); + uint _e140 = u; + u = metal::popcount(_e140); + metal::uint2 _e142 = u2_; + u2_ = metal::popcount(_e142); + metal::uint3 _e144 = u3_; + u3_ = metal::popcount(_e144); + metal::uint4 _e146 = u4_; + u4_ = metal::popcount(_e146); + int _e148 = i; + i = metal::reverse_bits(_e148); + metal::int2 _e150 = i2_; + i2_ = metal::reverse_bits(_e150); + metal::int3 _e152 = i3_; + i3_ = metal::reverse_bits(_e152); + metal::int4 _e154 = i4_; + i4_ = metal::reverse_bits(_e154); + uint _e156 = u; + u = metal::reverse_bits(_e156); + metal::uint2 _e158 = u2_; + u2_ = metal::reverse_bits(_e158); + metal::uint3 _e160 = u3_; + u3_ = metal::reverse_bits(_e160); + metal::uint4 _e162 = u4_; + u4_ = metal::reverse_bits(_e162); + return; +} diff --git a/naga/tests/out/msl/boids.msl b/naga/tests/out/msl/boids.msl new file mode 100644 index 0000000000..ce1ccc7cc2 --- /dev/null +++ b/naga/tests/out/msl/boids.msl @@ -0,0 +1,158 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct _mslBufferSizes { + uint size1; + uint size2; +}; + +struct Particle { + metal::float2 pos; + metal::float2 vel; +}; +struct SimParams { + float deltaT; + float rule1Distance; + float rule2Distance; + float rule3Distance; + float rule1Scale; + float rule2Scale; + float rule3Scale; +}; +typedef Particle type_3[1]; +struct Particles { + type_3 particles; +}; +constant uint NUM_PARTICLES = 1500u; + +struct main_Input { +}; +kernel void main_( + metal::uint3 global_invocation_id [[thread_position_in_grid]] +, constant SimParams& params [[buffer(0)]] +, device Particles const& particlesSrc [[buffer(1)]] +, device Particles& particlesDst [[buffer(2)]] +, constant _mslBufferSizes& _buffer_sizes [[buffer(3)]] +) { + metal::float2 vPos = {}; + metal::float2 vVel = {}; + metal::float2 cMass = metal::float2(0.0, 0.0); + metal::float2 cVel = metal::float2(0.0, 0.0); + metal::float2 colVel = metal::float2(0.0, 0.0); + int cMassCount = 0; + int cVelCount = 0; + metal::float2 pos = {}; + metal::float2 vel = {}; + uint i = 0u; + uint index = global_invocation_id.x; + if (index >= NUM_PARTICLES) { + return; + } + metal::float2 _e8 = particlesSrc.particles[index].pos; + vPos = _e8; + metal::float2 _e14 = particlesSrc.particles[index].vel; + vVel = _e14; + bool loop_init = true; + while(true) { + if (!loop_init) { + uint _e91 = i; + i = _e91 + 1u; + } + loop_init = false; + uint _e36 = i; + if (_e36 >= NUM_PARTICLES) { + break; + } + uint _e39 = i; + if (_e39 == index) { + continue; + } + uint _e43 = i; + metal::float2 _e46 = particlesSrc.particles[_e43].pos; + pos = _e46; + uint _e49 = i; + metal::float2 _e52 = particlesSrc.particles[_e49].vel; + vel = _e52; + metal::float2 _e53 = pos; + metal::float2 _e54 = vPos; + float _e58 = params.rule1Distance; + if (metal::distance(_e53, _e54) < _e58) { + metal::float2 _e60 = cMass; + metal::float2 _e61 = pos; + cMass = _e60 + _e61; + int _e63 = cMassCount; + cMassCount = _e63 + 1; + } + metal::float2 _e66 = pos; + metal::float2 _e67 = vPos; + float _e71 = params.rule2Distance; + if (metal::distance(_e66, _e67) < _e71) { + metal::float2 _e73 = colVel; + metal::float2 _e74 = pos; + metal::float2 _e75 = vPos; + colVel = _e73 - (_e74 - _e75); + } + metal::float2 _e78 = pos; + metal::float2 _e79 = vPos; + float _e83 = params.rule3Distance; + if (metal::distance(_e78, _e79) < _e83) { + metal::float2 _e85 = cVel; + metal::float2 _e86 = vel; + cVel = _e85 + _e86; + int _e88 = cVelCount; + cVelCount = _e88 + 1; + } + } + int _e94 = cMassCount; + if (_e94 > 0) { + metal::float2 _e97 = cMass; + int _e98 = cMassCount; + metal::float2 _e102 = vPos; + cMass = (_e97 / metal::float2(static_cast(_e98))) - _e102; + } + int _e104 = cVelCount; + if (_e104 > 0) { + metal::float2 _e107 = cVel; + int _e108 = cVelCount; + cVel = _e107 / metal::float2(static_cast(_e108)); + } + metal::float2 _e112 = vVel; + metal::float2 _e113 = cMass; + float _e116 = params.rule1Scale; + metal::float2 _e119 = colVel; + float _e122 = params.rule2Scale; + metal::float2 _e125 = cVel; + float _e128 = params.rule3Scale; + vVel = ((_e112 + (_e113 * _e116)) + (_e119 * _e122)) + (_e125 * _e128); + metal::float2 _e131 = vVel; + metal::float2 _e133 = vVel; + vVel = metal::normalize(_e131) * metal::clamp(metal::length(_e133), 0.0, 0.1); + metal::float2 _e139 = vPos; + metal::float2 _e140 = vVel; + float _e143 = params.deltaT; + vPos = _e139 + (_e140 * _e143); + float _e147 = vPos.x; + if (_e147 < -1.0) { + vPos.x = 1.0; + } + float _e153 = vPos.x; + if (_e153 > 1.0) { + vPos.x = -1.0; + } + float _e159 = vPos.y; + if (_e159 < -1.0) { + vPos.y = 1.0; + } + float _e165 = vPos.y; + if (_e165 > 1.0) { + vPos.y = -1.0; + } + metal::float2 _e174 = vPos; + particlesDst.particles[index].pos = _e174; + metal::float2 _e179 = vVel; + particlesDst.particles[index].vel = _e179; + return; +} diff --git a/naga/tests/out/msl/bounds-check-image-restrict.msl b/naga/tests/out/msl/bounds-check-image-restrict.msl new file mode 100644 index 0000000000..6a3c43f0ce --- /dev/null +++ b/naga/tests/out/msl/bounds-check-image-restrict.msl @@ -0,0 +1,182 @@ +// language: metal1.2 +#include +#include + +using metal::uint; + + +metal::float4 test_textureLoad_1d( + int coords, + int level, + metal::texture1d image_1d +) { + metal::float4 _e3 = image_1d.read(metal::min(uint(coords), image_1d.get_width() - 1)); + return _e3; +} + +metal::float4 test_textureLoad_2d( + metal::int2 coords_1, + int level_1, + metal::texture2d image_2d +) { + uint clamped_lod_e3 = metal::min(uint(level_1), image_2d.get_num_mip_levels() - 1); + metal::float4 _e3 = image_2d.read(metal::min(metal::uint2(coords_1), metal::uint2(image_2d.get_width(clamped_lod_e3), image_2d.get_height(clamped_lod_e3)) - 1), clamped_lod_e3); + return _e3; +} + +metal::float4 test_textureLoad_2d_array_u( + metal::int2 coords_2, + uint index, + int level_2, + metal::texture2d_array image_2d_array +) { + uint clamped_lod_e4 = metal::min(uint(level_2), image_2d_array.get_num_mip_levels() - 1); + metal::float4 _e4 = image_2d_array.read(metal::min(metal::uint2(coords_2), metal::uint2(image_2d_array.get_width(clamped_lod_e4), image_2d_array.get_height(clamped_lod_e4)) - 1), metal::min(uint(index), image_2d_array.get_array_size() - 1), clamped_lod_e4); + return _e4; +} + +metal::float4 test_textureLoad_2d_array_s( + metal::int2 coords_3, + int index_1, + int level_3, + metal::texture2d_array image_2d_array +) { + uint clamped_lod_e4 = metal::min(uint(level_3), image_2d_array.get_num_mip_levels() - 1); + metal::float4 _e4 = image_2d_array.read(metal::min(metal::uint2(coords_3), metal::uint2(image_2d_array.get_width(clamped_lod_e4), image_2d_array.get_height(clamped_lod_e4)) - 1), metal::min(uint(index_1), image_2d_array.get_array_size() - 1), clamped_lod_e4); + return _e4; +} + +metal::float4 test_textureLoad_3d( + metal::int3 coords_4, + int level_4, + metal::texture3d image_3d +) { + uint clamped_lod_e3 = metal::min(uint(level_4), image_3d.get_num_mip_levels() - 1); + metal::float4 _e3 = image_3d.read(metal::min(metal::uint3(coords_4), metal::uint3(image_3d.get_width(clamped_lod_e3), image_3d.get_height(clamped_lod_e3), image_3d.get_depth(clamped_lod_e3)) - 1), clamped_lod_e3); + return _e3; +} + +metal::float4 test_textureLoad_multisampled_2d( + metal::int2 coords_5, + int _sample, + metal::texture2d_ms image_multisampled_2d +) { + metal::float4 _e3 = image_multisampled_2d.read(metal::min(metal::uint2(coords_5), metal::uint2(image_multisampled_2d.get_width(), image_multisampled_2d.get_height()) - 1), metal::min(uint(_sample), image_multisampled_2d.get_num_samples() - 1)); + return _e3; +} + +float test_textureLoad_depth_2d( + metal::int2 coords_6, + int level_5, + metal::depth2d image_depth_2d +) { + uint clamped_lod_e3 = metal::min(uint(level_5), image_depth_2d.get_num_mip_levels() - 1); + float _e3 = image_depth_2d.read(metal::min(metal::uint2(coords_6), metal::uint2(image_depth_2d.get_width(clamped_lod_e3), image_depth_2d.get_height(clamped_lod_e3)) - 1), clamped_lod_e3); + return _e3; +} + +float test_textureLoad_depth_2d_array_u( + metal::int2 coords_7, + uint index_2, + int level_6, + metal::depth2d_array image_depth_2d_array +) { + uint clamped_lod_e4 = metal::min(uint(level_6), image_depth_2d_array.get_num_mip_levels() - 1); + float _e4 = image_depth_2d_array.read(metal::min(metal::uint2(coords_7), metal::uint2(image_depth_2d_array.get_width(clamped_lod_e4), image_depth_2d_array.get_height(clamped_lod_e4)) - 1), metal::min(uint(index_2), image_depth_2d_array.get_array_size() - 1), clamped_lod_e4); + return _e4; +} + +float test_textureLoad_depth_2d_array_s( + metal::int2 coords_8, + int index_3, + int level_7, + metal::depth2d_array image_depth_2d_array +) { + uint clamped_lod_e4 = metal::min(uint(level_7), image_depth_2d_array.get_num_mip_levels() - 1); + float _e4 = image_depth_2d_array.read(metal::min(metal::uint2(coords_8), metal::uint2(image_depth_2d_array.get_width(clamped_lod_e4), image_depth_2d_array.get_height(clamped_lod_e4)) - 1), metal::min(uint(index_3), image_depth_2d_array.get_array_size() - 1), clamped_lod_e4); + return _e4; +} + +float test_textureLoad_depth_multisampled_2d( + metal::int2 coords_9, + int _sample_1, + metal::depth2d_ms image_depth_multisampled_2d +) { + float _e3 = image_depth_multisampled_2d.read(metal::min(metal::uint2(coords_9), metal::uint2(image_depth_multisampled_2d.get_width(), image_depth_multisampled_2d.get_height()) - 1), metal::min(uint(_sample_1), image_depth_multisampled_2d.get_num_samples() - 1)); + return _e3; +} + +void test_textureStore_1d( + int coords_10, + metal::float4 value, + metal::texture1d image_storage_1d +) { + image_storage_1d.write(value, metal::min(uint(coords_10), image_storage_1d.get_width() - 1)); + return; +} + +void test_textureStore_2d( + metal::int2 coords_11, + metal::float4 value_1, + metal::texture2d image_storage_2d +) { + image_storage_2d.write(value_1, metal::min(metal::uint2(coords_11), metal::uint2(image_storage_2d.get_width(), image_storage_2d.get_height()) - 1)); + return; +} + +void test_textureStore_2d_array_u( + metal::int2 coords_12, + uint array_index, + metal::float4 value_2, + metal::texture2d_array image_storage_2d_array +) { + image_storage_2d_array.write(value_2, metal::min(metal::uint2(coords_12), metal::uint2(image_storage_2d_array.get_width(), image_storage_2d_array.get_height()) - 1), metal::min(uint(array_index), image_storage_2d_array.get_array_size() - 1)); + return; +} + +void test_textureStore_2d_array_s( + metal::int2 coords_13, + int array_index_1, + metal::float4 value_3, + metal::texture2d_array image_storage_2d_array +) { + image_storage_2d_array.write(value_3, metal::min(metal::uint2(coords_13), metal::uint2(image_storage_2d_array.get_width(), image_storage_2d_array.get_height()) - 1), metal::min(uint(array_index_1), image_storage_2d_array.get_array_size() - 1)); + return; +} + +void test_textureStore_3d( + metal::int3 coords_14, + metal::float4 value_4, + metal::texture3d image_storage_3d +) { + image_storage_3d.write(value_4, metal::min(metal::uint3(coords_14), metal::uint3(image_storage_3d.get_width(), image_storage_3d.get_height(), image_storage_3d.get_depth()) - 1)); + return; +} + +struct fragment_shaderOutput { + metal::float4 member [[color(0)]]; +}; +fragment fragment_shaderOutput fragment_shader( + metal::texture1d image_1d [[user(fake0)]] +, metal::texture2d image_2d [[user(fake0)]] +, metal::texture2d_array image_2d_array [[user(fake0)]] +, metal::texture3d image_3d [[user(fake0)]] +, metal::texture2d_ms image_multisampled_2d [[user(fake0)]] +, metal::texture1d image_storage_1d [[user(fake0)]] +, metal::texture2d image_storage_2d [[user(fake0)]] +, metal::texture2d_array image_storage_2d_array [[user(fake0)]] +, metal::texture3d image_storage_3d [[user(fake0)]] +) { + metal::float4 _e2 = test_textureLoad_1d(0, 0, image_1d); + metal::float4 _e5 = test_textureLoad_2d(metal::int2 {}, 0, image_2d); + metal::float4 _e9 = test_textureLoad_2d_array_u(metal::int2 {}, 0u, 0, image_2d_array); + metal::float4 _e13 = test_textureLoad_2d_array_s(metal::int2 {}, 0, 0, image_2d_array); + metal::float4 _e16 = test_textureLoad_3d(metal::int3 {}, 0, image_3d); + metal::float4 _e19 = test_textureLoad_multisampled_2d(metal::int2 {}, 0, image_multisampled_2d); + test_textureStore_1d(0, metal::float4 {}, image_storage_1d); + test_textureStore_2d(metal::int2 {}, metal::float4 {}, image_storage_2d); + test_textureStore_2d_array_u(metal::int2 {}, 0u, metal::float4 {}, image_storage_2d_array); + test_textureStore_2d_array_s(metal::int2 {}, 0, metal::float4 {}, image_storage_2d_array); + test_textureStore_3d(metal::int3 {}, metal::float4 {}, image_storage_3d); + return fragment_shaderOutput { metal::float4(0.0, 0.0, 0.0, 0.0) }; +} diff --git a/naga/tests/out/msl/bounds-check-image-rzsw.msl b/naga/tests/out/msl/bounds-check-image-rzsw.msl new file mode 100644 index 0000000000..5db0c9df94 --- /dev/null +++ b/naga/tests/out/msl/bounds-check-image-rzsw.msl @@ -0,0 +1,191 @@ +// language: metal1.2 +#include +#include + +using metal::uint; +struct DefaultConstructible { + template + operator T() && { + return T {}; + } +}; + + +metal::float4 test_textureLoad_1d( + int coords, + int level, + metal::texture1d image_1d +) { + metal::float4 _e3 = (uint(level) < image_1d.get_num_mip_levels() && uint(coords) < image_1d.get_width() ? image_1d.read(uint(coords)): DefaultConstructible()); + return _e3; +} + +metal::float4 test_textureLoad_2d( + metal::int2 coords_1, + int level_1, + metal::texture2d image_2d +) { + metal::float4 _e3 = (uint(level_1) < image_2d.get_num_mip_levels() && metal::all(metal::uint2(coords_1) < metal::uint2(image_2d.get_width(level_1), image_2d.get_height(level_1))) ? image_2d.read(metal::uint2(coords_1), level_1): DefaultConstructible()); + return _e3; +} + +metal::float4 test_textureLoad_2d_array_u( + metal::int2 coords_2, + uint index, + int level_2, + metal::texture2d_array image_2d_array +) { + metal::float4 _e4 = (uint(level_2) < image_2d_array.get_num_mip_levels() && uint(index) < image_2d_array.get_array_size() && metal::all(metal::uint2(coords_2) < metal::uint2(image_2d_array.get_width(level_2), image_2d_array.get_height(level_2))) ? image_2d_array.read(metal::uint2(coords_2), index, level_2): DefaultConstructible()); + return _e4; +} + +metal::float4 test_textureLoad_2d_array_s( + metal::int2 coords_3, + int index_1, + int level_3, + metal::texture2d_array image_2d_array +) { + metal::float4 _e4 = (uint(level_3) < image_2d_array.get_num_mip_levels() && uint(index_1) < image_2d_array.get_array_size() && metal::all(metal::uint2(coords_3) < metal::uint2(image_2d_array.get_width(level_3), image_2d_array.get_height(level_3))) ? image_2d_array.read(metal::uint2(coords_3), index_1, level_3): DefaultConstructible()); + return _e4; +} + +metal::float4 test_textureLoad_3d( + metal::int3 coords_4, + int level_4, + metal::texture3d image_3d +) { + metal::float4 _e3 = (uint(level_4) < image_3d.get_num_mip_levels() && metal::all(metal::uint3(coords_4) < metal::uint3(image_3d.get_width(level_4), image_3d.get_height(level_4), image_3d.get_depth(level_4))) ? image_3d.read(metal::uint3(coords_4), level_4): DefaultConstructible()); + return _e3; +} + +metal::float4 test_textureLoad_multisampled_2d( + metal::int2 coords_5, + int _sample, + metal::texture2d_ms image_multisampled_2d +) { + metal::float4 _e3 = (uint(_sample) < image_multisampled_2d.get_num_samples() && metal::all(metal::uint2(coords_5) < metal::uint2(image_multisampled_2d.get_width(), image_multisampled_2d.get_height())) ? image_multisampled_2d.read(metal::uint2(coords_5), _sample): DefaultConstructible()); + return _e3; +} + +float test_textureLoad_depth_2d( + metal::int2 coords_6, + int level_5, + metal::depth2d image_depth_2d +) { + float _e3 = (uint(level_5) < image_depth_2d.get_num_mip_levels() && metal::all(metal::uint2(coords_6) < metal::uint2(image_depth_2d.get_width(level_5), image_depth_2d.get_height(level_5))) ? image_depth_2d.read(metal::uint2(coords_6), level_5): DefaultConstructible()); + return _e3; +} + +float test_textureLoad_depth_2d_array_u( + metal::int2 coords_7, + uint index_2, + int level_6, + metal::depth2d_array image_depth_2d_array +) { + float _e4 = (uint(level_6) < image_depth_2d_array.get_num_mip_levels() && uint(index_2) < image_depth_2d_array.get_array_size() && metal::all(metal::uint2(coords_7) < metal::uint2(image_depth_2d_array.get_width(level_6), image_depth_2d_array.get_height(level_6))) ? image_depth_2d_array.read(metal::uint2(coords_7), index_2, level_6): DefaultConstructible()); + return _e4; +} + +float test_textureLoad_depth_2d_array_s( + metal::int2 coords_8, + int index_3, + int level_7, + metal::depth2d_array image_depth_2d_array +) { + float _e4 = (uint(level_7) < image_depth_2d_array.get_num_mip_levels() && uint(index_3) < image_depth_2d_array.get_array_size() && metal::all(metal::uint2(coords_8) < metal::uint2(image_depth_2d_array.get_width(level_7), image_depth_2d_array.get_height(level_7))) ? image_depth_2d_array.read(metal::uint2(coords_8), index_3, level_7): DefaultConstructible()); + return _e4; +} + +float test_textureLoad_depth_multisampled_2d( + metal::int2 coords_9, + int _sample_1, + metal::depth2d_ms image_depth_multisampled_2d +) { + float _e3 = (uint(_sample_1) < image_depth_multisampled_2d.get_num_samples() && metal::all(metal::uint2(coords_9) < metal::uint2(image_depth_multisampled_2d.get_width(), image_depth_multisampled_2d.get_height())) ? image_depth_multisampled_2d.read(metal::uint2(coords_9), _sample_1): DefaultConstructible()); + return _e3; +} + +void test_textureStore_1d( + int coords_10, + metal::float4 value, + metal::texture1d image_storage_1d +) { + if (uint(coords_10) < image_storage_1d.get_width()) { + image_storage_1d.write(value, uint(coords_10)); + } + return; +} + +void test_textureStore_2d( + metal::int2 coords_11, + metal::float4 value_1, + metal::texture2d image_storage_2d +) { + if (metal::all(metal::uint2(coords_11) < metal::uint2(image_storage_2d.get_width(), image_storage_2d.get_height()))) { + image_storage_2d.write(value_1, metal::uint2(coords_11)); + } + return; +} + +void test_textureStore_2d_array_u( + metal::int2 coords_12, + uint array_index, + metal::float4 value_2, + metal::texture2d_array image_storage_2d_array +) { + if (uint(array_index) < image_storage_2d_array.get_array_size() && metal::all(metal::uint2(coords_12) < metal::uint2(image_storage_2d_array.get_width(), image_storage_2d_array.get_height()))) { + image_storage_2d_array.write(value_2, metal::uint2(coords_12), array_index); + } + return; +} + +void test_textureStore_2d_array_s( + metal::int2 coords_13, + int array_index_1, + metal::float4 value_3, + metal::texture2d_array image_storage_2d_array +) { + if (uint(array_index_1) < image_storage_2d_array.get_array_size() && metal::all(metal::uint2(coords_13) < metal::uint2(image_storage_2d_array.get_width(), image_storage_2d_array.get_height()))) { + image_storage_2d_array.write(value_3, metal::uint2(coords_13), array_index_1); + } + return; +} + +void test_textureStore_3d( + metal::int3 coords_14, + metal::float4 value_4, + metal::texture3d image_storage_3d +) { + if (metal::all(metal::uint3(coords_14) < metal::uint3(image_storage_3d.get_width(), image_storage_3d.get_height(), image_storage_3d.get_depth()))) { + image_storage_3d.write(value_4, metal::uint3(coords_14)); + } + return; +} + +struct fragment_shaderOutput { + metal::float4 member [[color(0)]]; +}; +fragment fragment_shaderOutput fragment_shader( + metal::texture1d image_1d [[user(fake0)]] +, metal::texture2d image_2d [[user(fake0)]] +, metal::texture2d_array image_2d_array [[user(fake0)]] +, metal::texture3d image_3d [[user(fake0)]] +, metal::texture2d_ms image_multisampled_2d [[user(fake0)]] +, metal::texture1d image_storage_1d [[user(fake0)]] +, metal::texture2d image_storage_2d [[user(fake0)]] +, metal::texture2d_array image_storage_2d_array [[user(fake0)]] +, metal::texture3d image_storage_3d [[user(fake0)]] +) { + metal::float4 _e2 = test_textureLoad_1d(0, 0, image_1d); + metal::float4 _e5 = test_textureLoad_2d(metal::int2 {}, 0, image_2d); + metal::float4 _e9 = test_textureLoad_2d_array_u(metal::int2 {}, 0u, 0, image_2d_array); + metal::float4 _e13 = test_textureLoad_2d_array_s(metal::int2 {}, 0, 0, image_2d_array); + metal::float4 _e16 = test_textureLoad_3d(metal::int3 {}, 0, image_3d); + metal::float4 _e19 = test_textureLoad_multisampled_2d(metal::int2 {}, 0, image_multisampled_2d); + test_textureStore_1d(0, metal::float4 {}, image_storage_1d); + test_textureStore_2d(metal::int2 {}, metal::float4 {}, image_storage_2d); + test_textureStore_2d_array_u(metal::int2 {}, 0u, metal::float4 {}, image_storage_2d_array); + test_textureStore_2d_array_s(metal::int2 {}, 0, metal::float4 {}, image_storage_2d_array); + test_textureStore_3d(metal::int3 {}, metal::float4 {}, image_storage_3d); + return fragment_shaderOutput { metal::float4(0.0, 0.0, 0.0, 0.0) }; +} diff --git a/naga/tests/out/msl/bounds-check-restrict.msl b/naga/tests/out/msl/bounds-check-restrict.msl new file mode 100644 index 0000000000..0d41436534 --- /dev/null +++ b/naga/tests/out/msl/bounds-check-restrict.msl @@ -0,0 +1,165 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct _mslBufferSizes { + uint size0; +}; + +struct type_1 { + float inner[10]; +}; +typedef float type_4[1]; +struct Globals { + type_1 a; + char _pad1[8]; + metal::float4 v; + metal::float3x4 m; + type_4 d; +}; + +float index_array( + int i, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e4 = globals.a.inner[metal::min(unsigned(i), 9u)]; + return _e4; +} + +float index_dynamic_array( + int i_1, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e4 = globals.d[metal::min(unsigned(i_1), (_buffer_sizes.size0 - 112 - 4) / 4)]; + return _e4; +} + +float index_vector( + int i_2, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e4 = globals.v[metal::min(unsigned(i_2), 3u)]; + return _e4; +} + +float index_vector_by_value( + metal::float4 v, + int i_3 +) { + return v[metal::min(unsigned(i_3), 3u)]; +} + +metal::float4 index_matrix( + int i_4, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + metal::float4 _e4 = globals.m[metal::min(unsigned(i_4), 2u)]; + return _e4; +} + +float index_twice( + int i_5, + int j, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e6 = globals.m[metal::min(unsigned(i_5), 2u)][metal::min(unsigned(j), 3u)]; + return _e6; +} + +float index_expensive( + int i_6, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e11 = globals.a.inner[metal::min(unsigned(static_cast(metal::sin(static_cast(i_6) / 100.0) * 100.0)), 9u)]; + return _e11; +} + +float index_in_bounds( + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e3 = globals.a.inner[9]; + float _e7 = globals.v.w; + float _e13 = globals.m[2].w; + return (_e3 + _e7) + _e13; +} + +void set_array( + int i_7, + float v_1, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + globals.a.inner[metal::min(unsigned(i_7), 9u)] = v_1; + return; +} + +void set_dynamic_array( + int i_8, + float v_2, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + globals.d[metal::min(unsigned(i_8), (_buffer_sizes.size0 - 112 - 4) / 4)] = v_2; + return; +} + +void set_vector( + int i_9, + float v_3, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + globals.v[metal::min(unsigned(i_9), 3u)] = v_3; + return; +} + +void set_matrix( + int i_10, + metal::float4 v_4, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + globals.m[metal::min(unsigned(i_10), 2u)] = v_4; + return; +} + +void set_index_twice( + int i_11, + int j_1, + float v_5, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + globals.m[metal::min(unsigned(i_11), 2u)][metal::min(unsigned(j_1), 3u)] = v_5; + return; +} + +void set_expensive( + int i_12, + float v_6, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + globals.a.inner[metal::min(unsigned(static_cast(metal::sin(static_cast(i_12) / 100.0) * 100.0)), 9u)] = v_6; + return; +} + +void set_in_bounds( + float v_7, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + globals.a.inner[9] = v_7; + globals.v.w = v_7; + globals.m[2].w = v_7; + return; +} diff --git a/naga/tests/out/msl/bounds-check-zero-atomic.msl b/naga/tests/out/msl/bounds-check-zero-atomic.msl new file mode 100644 index 0000000000..4a2f0b07dc --- /dev/null +++ b/naga/tests/out/msl/bounds-check-zero-atomic.msl @@ -0,0 +1,77 @@ +// language: metal1.0 +#include +#include + +using metal::uint; +struct DefaultConstructible { + template + operator T() && { + return T {}; + } +}; + +struct _mslBufferSizes { + uint size0; +}; + +struct type_1 { + metal::atomic_uint inner[10]; +}; +typedef metal::atomic_uint type_2[1]; +struct Globals { + metal::atomic_uint a; + type_1 b; + type_2 c; +}; + +uint fetch_add_atomic( + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + uint _e3 = metal::atomic_fetch_add_explicit(&globals.a, 1u, metal::memory_order_relaxed); + return _e3; +} + +uint fetch_add_atomic_static_sized_array( + int i, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + uint _e5 = uint(i) < 10 ? metal::atomic_fetch_add_explicit(&globals.b.inner[i], 1u, metal::memory_order_relaxed) : DefaultConstructible(); + return _e5; +} + +uint fetch_add_atomic_dynamic_sized_array( + int i_1, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + uint _e5 = uint(i_1) < 1 + (_buffer_sizes.size0 - 44 - 4) / 4 ? metal::atomic_fetch_add_explicit(&globals.c[i_1], 1u, metal::memory_order_relaxed) : DefaultConstructible(); + return _e5; +} + +uint exchange_atomic( + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + uint _e3 = metal::atomic_exchange_explicit(&globals.a, 1u, metal::memory_order_relaxed); + return _e3; +} + +uint exchange_atomic_static_sized_array( + int i_2, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + uint _e5 = uint(i_2) < 10 ? metal::atomic_exchange_explicit(&globals.b.inner[i_2], 1u, metal::memory_order_relaxed) : DefaultConstructible(); + return _e5; +} + +uint exchange_atomic_dynamic_sized_array( + int i_3, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + uint _e5 = uint(i_3) < 1 + (_buffer_sizes.size0 - 44 - 4) / 4 ? metal::atomic_exchange_explicit(&globals.c[i_3], 1u, metal::memory_order_relaxed) : DefaultConstructible(); + return _e5; +} diff --git a/naga/tests/out/msl/bounds-check-zero.msl b/naga/tests/out/msl/bounds-check-zero.msl new file mode 100644 index 0000000000..7bbdd50d1b --- /dev/null +++ b/naga/tests/out/msl/bounds-check-zero.msl @@ -0,0 +1,185 @@ +// language: metal1.0 +#include +#include + +using metal::uint; +struct DefaultConstructible { + template + operator T() && { + return T {}; + } +}; + +struct _mslBufferSizes { + uint size0; +}; + +struct type_1 { + float inner[10]; +}; +typedef float type_4[1]; +struct Globals { + type_1 a; + char _pad1[8]; + metal::float4 v; + metal::float3x4 m; + type_4 d; +}; + +float index_array( + int i, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e4 = uint(i) < 10 ? globals.a.inner[i] : DefaultConstructible(); + return _e4; +} + +float index_dynamic_array( + int i_1, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e4 = uint(i_1) < 1 + (_buffer_sizes.size0 - 112 - 4) / 4 ? globals.d[i_1] : DefaultConstructible(); + return _e4; +} + +float index_vector( + int i_2, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e4 = uint(i_2) < 4 ? globals.v[i_2] : DefaultConstructible(); + return _e4; +} + +float index_vector_by_value( + metal::float4 v, + int i_3 +) { + return uint(i_3) < 4 ? v[i_3] : DefaultConstructible(); +} + +metal::float4 index_matrix( + int i_4, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + metal::float4 _e4 = uint(i_4) < 3 ? globals.m[i_4] : DefaultConstructible(); + return _e4; +} + +float index_twice( + int i_5, + int j, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e6 = uint(j) < 4 && uint(i_5) < 3 ? globals.m[i_5][j] : DefaultConstructible(); + return _e6; +} + +float index_expensive( + int i_6, + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + int _e9 = static_cast(metal::sin(static_cast(i_6) / 100.0) * 100.0); + float _e11 = uint(_e9) < 10 ? globals.a.inner[_e9] : DefaultConstructible(); + return _e11; +} + +float index_in_bounds( + device Globals const& globals, + constant _mslBufferSizes& _buffer_sizes +) { + float _e3 = globals.a.inner[9]; + float _e7 = globals.v.w; + float _e13 = globals.m[2].w; + return (_e3 + _e7) + _e13; +} + +void set_array( + int i_7, + float v_1, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + if (uint(i_7) < 10) { + globals.a.inner[i_7] = v_1; + } + return; +} + +void set_dynamic_array( + int i_8, + float v_2, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + if (uint(i_8) < 1 + (_buffer_sizes.size0 - 112 - 4) / 4) { + globals.d[i_8] = v_2; + } + return; +} + +void set_vector( + int i_9, + float v_3, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + if (uint(i_9) < 4) { + globals.v[i_9] = v_3; + } + return; +} + +void set_matrix( + int i_10, + metal::float4 v_4, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + if (uint(i_10) < 3) { + globals.m[i_10] = v_4; + } + return; +} + +void set_index_twice( + int i_11, + int j_1, + float v_5, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + if (uint(j_1) < 4 && uint(i_11) < 3) { + globals.m[i_11][j_1] = v_5; + } + return; +} + +void set_expensive( + int i_12, + float v_6, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + int _e10 = static_cast(metal::sin(static_cast(i_12) / 100.0) * 100.0); + if (uint(_e10) < 10) { + globals.a.inner[_e10] = v_6; + } + return; +} + +void set_in_bounds( + float v_7, + device Globals& globals, + constant _mslBufferSizes& _buffer_sizes +) { + globals.a.inner[9] = v_7; + globals.v.w = v_7; + globals.m[2].w = v_7; + return; +} diff --git a/naga/tests/out/msl/break-if.msl b/naga/tests/out/msl/break-if.msl new file mode 100644 index 0000000000..8c0d9343b9 --- /dev/null +++ b/naga/tests/out/msl/break-if.msl @@ -0,0 +1,85 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +void breakIfEmpty( +) { + bool loop_init = true; + while(true) { + if (!loop_init) { + if (true) { + break; + } + } + loop_init = false; + } + return; +} + +void breakIfEmptyBody( + bool a +) { + bool b = {}; + bool c = {}; + bool loop_init_1 = true; + while(true) { + if (!loop_init_1) { + b = a; + bool _e2 = b; + c = a != _e2; + bool _e5 = c; + if (a == c) { + break; + } + } + loop_init_1 = false; + } + return; +} + +void breakIf( + bool a_1 +) { + bool d = {}; + bool e = {}; + bool loop_init_2 = true; + while(true) { + if (!loop_init_2) { + bool _e5 = e; + if (a_1 == e) { + break; + } + } + loop_init_2 = false; + d = a_1; + bool _e2 = d; + e = a_1 != _e2; + } + return; +} + +void breakIfSeparateVariable( +) { + uint counter = 0u; + bool loop_init_3 = true; + while(true) { + if (!loop_init_3) { + uint _e5 = counter; + if (counter == 5u) { + break; + } + } + loop_init_3 = false; + uint _e3 = counter; + counter = _e3 + 1u; + } + return; +} + +kernel void main_( +) { + return; +} diff --git a/naga/tests/out/msl/collatz.msl b/naga/tests/out/msl/collatz.msl new file mode 100644 index 0000000000..e283741459 --- /dev/null +++ b/naga/tests/out/msl/collatz.msl @@ -0,0 +1,56 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct _mslBufferSizes { + uint size0; +}; + +typedef uint type_1[1]; +struct PrimeIndices { + type_1 data; +}; + +uint collatz_iterations( + uint n_base +) { + uint n = {}; + uint i = 0u; + n = n_base; + while(true) { + uint _e4 = n; + if (_e4 > 1u) { + } else { + break; + } + { + uint _e7 = n; + if ((_e7 % 2u) == 0u) { + uint _e12 = n; + n = _e12 / 2u; + } else { + uint _e16 = n; + n = (3u * _e16) + 1u; + } + uint _e20 = i; + i = _e20 + 1u; + } + } + uint _e23 = i; + return _e23; +} + +struct main_Input { +}; +kernel void main_( + metal::uint3 global_id [[thread_position_in_grid]] +, device PrimeIndices& v_indices [[user(fake0)]] +, constant _mslBufferSizes& _buffer_sizes [[user(fake0)]] +) { + uint _e9 = v_indices.data[global_id.x]; + uint _e10 = collatz_iterations(_e9); + v_indices.data[global_id.x] = _e10; + return; +} diff --git a/naga/tests/out/msl/const-exprs.msl b/naga/tests/out/msl/const-exprs.msl new file mode 100644 index 0000000000..7798ae62b3 --- /dev/null +++ b/naga/tests/out/msl/const-exprs.msl @@ -0,0 +1,100 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +constant uint TWO = 2u; +constant int THREE = 3; +constant int FOUR = 4; +constant int FOUR_ALIAS = 4; +constant int TEST_CONSTANT_ADDITION = 8; +constant int TEST_CONSTANT_ALIAS_ADDITION = 8; +constant float PI = 3.141; +constant float phi_sun = 6.282; +constant metal::float4 DIV = metal::float4(0.44444445, 0.0, 0.0, 0.0); +constant int TEXTURE_KIND_REGULAR = 0; +constant int TEXTURE_KIND_WARP = 1; +constant int TEXTURE_KIND_SKY = 2; +constant metal::float2 add_vec = metal::float2(4.0, 5.0); +constant metal::bool2 compare_vec = metal::bool2(true, false); + +void swizzle_of_compose( +) { + metal::int4 out = metal::int4(4, 3, 2, 1); +} + +void index_of_compose( +) { + int out_1 = 2; +} + +void compose_three_deep( +) { + int out_2 = 6; +} + +void non_constant_initializers( +) { + int w = 30; + int x = {}; + int y = {}; + int z = 70; + metal::int4 out_3 = {}; + int _e2 = w; + x = _e2; + int _e4 = x; + y = _e4; + int _e8 = w; + int _e9 = x; + int _e10 = y; + int _e11 = z; + out_3 = metal::int4(_e8, _e9, _e10, _e11); + return; +} + +void splat_of_constant( +) { + metal::int4 out_4 = metal::int4(-4, -4, -4, -4); +} + +void compose_of_constant( +) { + metal::int4 out_5 = metal::int4(-4, -4, -4, -4); +} + +void compose_of_splat( +) { + metal::float4 x_1 = metal::float4(2.0, 1.0, 1.0, 1.0); +} + +uint map_texture_kind( + int texture_kind +) { + switch(texture_kind) { + case 0: { + return 10u; + } + case 1: { + return 20u; + } + case 2: { + return 30u; + } + default: { + return 0u; + } + } +} + +kernel void main_( +) { + swizzle_of_compose(); + index_of_compose(); + compose_three_deep(); + non_constant_initializers(); + splat_of_constant(); + compose_of_constant(); + compose_of_splat(); + return; +} diff --git a/naga/tests/out/msl/constructors.msl b/naga/tests/out/msl/constructors.msl new file mode 100644 index 0000000000..b29e2468b0 --- /dev/null +++ b/naga/tests/out/msl/constructors.msl @@ -0,0 +1,45 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct Foo { + metal::float4 a; + int b; +}; +struct type_5 { + metal::float2x2 inner[1]; +}; +struct type_10 { + Foo inner[3]; +}; +struct type_11 { + int inner[4]; +}; +constant metal::float3 const2_ = metal::float3(0.0, 1.0, 2.0); +constant metal::float2x2 const3_ = metal::float2x2(metal::float2(0.0, 1.0), metal::float2(2.0, 3.0)); +constant type_5 const4_ = type_5 {metal::float2x2(metal::float2(0.0, 1.0), metal::float2(2.0, 3.0))}; +constant bool cz0_ = bool {}; +constant int cz1_ = int {}; +constant uint cz2_ = uint {}; +constant float cz3_ = float {}; +constant metal::uint2 cz4_ = metal::uint2 {}; +constant metal::float2x2 cz5_ = metal::float2x2 {}; +constant type_10 cz6_ = type_10 {}; +constant Foo cz7_ = Foo {}; +constant type_11 cp3_ = type_11 {0, 1, 2, 3}; + +kernel void main_( +) { + Foo foo = {}; + foo = Foo {metal::float4(1.0), 1}; + metal::float2x2 m0_ = metal::float2x2(metal::float2(1.0, 0.0), metal::float2(0.0, 1.0)); + metal::float4x4 m1_ = metal::float4x4(metal::float4(1.0, 0.0, 0.0, 0.0), metal::float4(0.0, 1.0, 0.0, 0.0), metal::float4(0.0, 0.0, 1.0, 0.0), metal::float4(0.0, 0.0, 0.0, 1.0)); + metal::uint2 cit0_ = metal::uint2(0u); + metal::float2x2 cit1_ = metal::float2x2(metal::float2(0.0), metal::float2(0.0)); + type_11 cit2_ = type_11 {0, 1, 2, 3}; + bool ic0_ = static_cast(bool {}); + metal::uint2 ic4_ = metal::uint2(0u, 0u); + metal::float2x3 ic5_ = metal::float2x3(metal::float3(0.0, 0.0, 0.0), metal::float3(0.0, 0.0, 0.0)); +} diff --git a/naga/tests/out/msl/control-flow.msl b/naga/tests/out/msl/control-flow.msl new file mode 100644 index 0000000000..0d0e082e41 --- /dev/null +++ b/naga/tests/out/msl/control-flow.msl @@ -0,0 +1,116 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +void switch_default_break( + int i +) { + switch(i) { + default: { + break; + } + } +} + +void switch_case_break( +) { + switch(0) { + case 0: { + break; + } + default: { + break; + } + } + return; +} + +void loop_switch_continue( + int x +) { + while(true) { + switch(x) { + case 1: { + continue; + } + default: { + break; + } + } + } + return; +} + +struct main_Input { +}; +kernel void main_( + metal::uint3 global_id [[thread_position_in_grid]] +) { + int pos = {}; + metal::threadgroup_barrier(metal::mem_flags::mem_device); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + switch(1) { + default: { + pos = 1; + break; + } + } + int _e4 = pos; + switch(_e4) { + case 1: { + pos = 0; + break; + } + case 2: { + pos = 1; + break; + } + case 3: + case 4: { + pos = 2; + break; + } + case 5: { + pos = 3; + break; + } + default: + case 6: { + pos = 4; + break; + } + } + switch(0u) { + case 0u: { + break; + } + default: { + break; + } + } + int _e11 = pos; + switch(_e11) { + case 1: { + pos = 0; + break; + } + case 2: { + pos = 1; + return; + } + case 3: { + pos = 2; + return; + } + case 4: { + return; + } + default: { + pos = 3; + return; + } + } +} diff --git a/naga/tests/out/msl/do-while.msl b/naga/tests/out/msl/do-while.msl new file mode 100644 index 0000000000..c1b4d08b0e --- /dev/null +++ b/naga/tests/out/msl/do-while.msl @@ -0,0 +1,36 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +void fb1_( + thread bool& cond +) { + bool loop_init = true; + while(true) { + if (!loop_init) { + bool _e1 = cond; + if (!(cond)) { + break; + } + } + loop_init = false; + continue; + } + return; +} + +void main_1( +) { + bool param = {}; + param = false; + fb1_(param); + return; +} + +fragment void main_( +) { + main_1(); +} diff --git a/naga/tests/out/msl/dualsource.msl b/naga/tests/out/msl/dualsource.msl new file mode 100644 index 0000000000..439e3c0d8c --- /dev/null +++ b/naga/tests/out/msl/dualsource.msl @@ -0,0 +1,27 @@ +// language: metal1.2 +#include +#include + +using metal::uint; + +struct FragmentOutput { + metal::float4 color; + metal::float4 mask; +}; + +struct main_Input { +}; +struct main_Output { + metal::float4 color [[color(0)]]; + metal::float4 mask [[color(0) index(1)]]; +}; +fragment main_Output main_( + metal::float4 position [[position]] +) { + metal::float4 color = metal::float4(0.4, 0.3, 0.2, 0.1); + metal::float4 mask = metal::float4(0.9, 0.8, 0.7, 0.6); + metal::float4 _e13 = color; + metal::float4 _e14 = mask; + const auto _tmp = FragmentOutput {_e13, _e14}; + return main_Output { _tmp.color, _tmp.mask }; +} diff --git a/naga/tests/out/msl/empty-global-name.msl b/naga/tests/out/msl/empty-global-name.msl new file mode 100644 index 0000000000..01cac3f6e0 --- /dev/null +++ b/naga/tests/out/msl/empty-global-name.msl @@ -0,0 +1,23 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_1 { + int member; +}; + +void function( + device type_1& unnamed +) { + int _e3 = unnamed.member; + unnamed.member = _e3 + 1; + return; +} + +kernel void main_( + device type_1& unnamed [[user(fake0)]] +) { + function(unnamed); +} diff --git a/naga/tests/out/msl/empty.msl b/naga/tests/out/msl/empty.msl new file mode 100644 index 0000000000..414cd22012 --- /dev/null +++ b/naga/tests/out/msl/empty.msl @@ -0,0 +1,11 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +kernel void main_( +) { + return; +} diff --git a/naga/tests/out/msl/extra.msl b/naga/tests/out/msl/extra.msl new file mode 100644 index 0000000000..8288dfad92 --- /dev/null +++ b/naga/tests/out/msl/extra.msl @@ -0,0 +1,35 @@ +// language: metal2.2 +#include +#include + +using metal::uint; + +struct PushConstants { + uint index; + char _pad1[12]; + metal::float2 double_; +}; +struct FragmentIn { + metal::float4 color; + uint primitive_index; +}; + +struct main_Input { + metal::float4 color [[user(loc0), center_perspective]]; +}; +struct main_Output { + metal::float4 member [[color(0)]]; +}; +fragment main_Output main_( + main_Input varyings [[stage_in]] +, uint primitive_index [[primitive_id]] +, constant PushConstants& pc [[buffer(1)]] +) { + const FragmentIn in = { varyings.color, primitive_index }; + uint _e4 = pc.index; + if (in.primitive_index == _e4) { + return main_Output { in.color }; + } else { + return main_Output { metal::float4(metal::float3(1.0) - in.color.xyz, in.color.w) }; + } +} diff --git a/naga/tests/out/msl/fragment-output.msl b/naga/tests/out/msl/fragment-output.msl new file mode 100644 index 0000000000..c886fc885e --- /dev/null +++ b/naga/tests/out/msl/fragment-output.msl @@ -0,0 +1,67 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct FragmentOutputVec4Vec3_ { + metal::float4 vec4f; + metal::int4 vec4i; + metal::uint4 vec4u; + metal::float3 vec3f; + metal::int3 vec3i; + metal::uint3 vec3u; +}; +struct FragmentOutputVec2Scalar { + metal::float2 vec2f; + metal::int2 vec2i; + metal::uint2 vec2u; + float scalarf; + int scalari; + uint scalaru; +}; + +struct main_vec4vec3_Output { + metal::float4 vec4f [[color(0)]]; + metal::int4 vec4i [[color(1)]]; + metal::uint4 vec4u [[color(2)]]; + metal::float3 vec3f [[color(3)]]; + metal::int3 vec3i [[color(4)]]; + metal::uint3 vec3u [[color(5)]]; +}; +fragment main_vec4vec3_Output main_vec4vec3_( +) { + FragmentOutputVec4Vec3_ output = {}; + output.vec4f = metal::float4(0.0); + output.vec4i = metal::int4(0); + output.vec4u = metal::uint4(0u); + output.vec3f = metal::float3(0.0); + output.vec3i = metal::int3(0); + output.vec3u = metal::uint3(0u); + FragmentOutputVec4Vec3_ _e19 = output; + const auto _tmp = _e19; + return main_vec4vec3_Output { _tmp.vec4f, _tmp.vec4i, _tmp.vec4u, _tmp.vec3f, _tmp.vec3i, _tmp.vec3u }; +} + + +struct main_vec2scalarOutput { + metal::float2 vec2f [[color(0)]]; + metal::int2 vec2i [[color(1)]]; + metal::uint2 vec2u [[color(2)]]; + float scalarf [[color(3)]]; + int scalari [[color(4)]]; + uint scalaru [[color(5)]]; +}; +fragment main_vec2scalarOutput main_vec2scalar( +) { + FragmentOutputVec2Scalar output_1 = {}; + output_1.vec2f = metal::float2(0.0); + output_1.vec2i = metal::int2(0); + output_1.vec2u = metal::uint2(0u); + output_1.scalarf = 0.0; + output_1.scalari = 0; + output_1.scalaru = 0u; + FragmentOutputVec2Scalar _e16 = output_1; + const auto _tmp = _e16; + return main_vec2scalarOutput { _tmp.vec2f, _tmp.vec2i, _tmp.vec2u, _tmp.scalarf, _tmp.scalari, _tmp.scalaru }; +} diff --git a/naga/tests/out/msl/functions.msl b/naga/tests/out/msl/functions.msl new file mode 100644 index 0000000000..42632f99be --- /dev/null +++ b/naga/tests/out/msl/functions.msl @@ -0,0 +1,35 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +metal::float2 test_fma( +) { + metal::float2 a = metal::float2(2.0, 2.0); + metal::float2 b = metal::float2(0.5, 0.5); + metal::float2 c = metal::float2(0.5, 0.5); + return metal::fma(a, b, c); +} + +int test_integer_dot_product( +) { + metal::int2 a_2_ = metal::int2(1); + metal::int2 b_2_ = metal::int2(1); + int c_2_ = ( + a_2_.x * b_2_.x + a_2_.y * b_2_.y); + metal::uint3 a_3_ = metal::uint3(1u); + metal::uint3 b_3_ = metal::uint3(1u); + uint c_3_ = ( + a_3_.x * b_3_.x + a_3_.y * b_3_.y + a_3_.z * b_3_.z); + metal::int4 _e11 = metal::int4(4); + metal::int4 _e13 = metal::int4(2); + int c_4_ = ( + _e11.x * _e13.x + _e11.y * _e13.y + _e11.z * _e13.z + _e11.w * _e13.w); + return c_4_; +} + +kernel void main_( +) { + metal::float2 _e0 = test_fma(); + int _e1 = test_integer_dot_product(); + return; +} diff --git a/naga/tests/out/msl/globals.msl b/naga/tests/out/msl/globals.msl new file mode 100644 index 0000000000..d2ed89ed46 --- /dev/null +++ b/naga/tests/out/msl/globals.msl @@ -0,0 +1,100 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct _mslBufferSizes { + uint size3; +}; + +struct type_2 { + float inner[10]; +}; +struct FooStruct { + metal::packed_float3 v3_; + float v1_; +}; +typedef metal::float2 type_6[1]; +struct type_8 { + metal::float4 inner[20]; +}; +struct type_11 { + metal::float2x4 inner[2]; +}; +struct type_12 { + type_11 inner[2]; +}; +struct type_14 { + metal::float4x2 inner[2]; +}; +struct type_15 { + type_14 inner[2]; +}; +constant bool Foo_1 = true; + +void test_msl_packed_vec3_as_arg( + metal::float3 arg +) { + return; +} + +void test_msl_packed_vec3_( + device FooStruct& alignment +) { + int idx = 1; + alignment.v3_ = metal::float3(1.0); + alignment.v3_[0] = 1.0; + alignment.v3_[0] = 2.0; + int _e16 = idx; + alignment.v3_[_e16] = 3.0; + FooStruct data = alignment; + metal::float3 l0_ = data.v3_; + metal::float2 l1_ = metal::float3(data.v3_).zx; + test_msl_packed_vec3_as_arg(data.v3_); + metal::float3 mvm0_ = metal::float3(data.v3_) * metal::float3x3 {}; + metal::float3 mvm1_ = metal::float3x3 {} * metal::float3(data.v3_); + metal::float3 svm0_ = data.v3_ * 2.0; + metal::float3 svm1_ = 2.0 * data.v3_; +} + +kernel void main_( + metal::uint3 __local_invocation_id [[thread_position_in_threadgroup]] +, threadgroup type_2& wg +, threadgroup metal::atomic_uint& at_1 +, device FooStruct& alignment [[user(fake0)]] +, device type_6 const& dummy [[user(fake0)]] +, constant type_8& float_vecs [[user(fake0)]] +, constant metal::float3& global_vec [[user(fake0)]] +, constant metal::float3x2& global_mat [[user(fake0)]] +, constant type_12& global_nested_arrays_of_matrices_2x4_ [[user(fake0)]] +, constant type_15& global_nested_arrays_of_matrices_4x2_ [[user(fake0)]] +, constant _mslBufferSizes& _buffer_sizes [[user(fake0)]] +) { + if (metal::all(__local_invocation_id == metal::uint3(0u))) { + wg = {}; + metal::atomic_store_explicit(&at_1, 0, metal::memory_order_relaxed); + } + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + float Foo = 1.0; + bool at = true; + test_msl_packed_vec3_(alignment); + metal::float4x2 _e5 = global_nested_arrays_of_matrices_4x2_.inner[0].inner[0]; + metal::float4 _e10 = global_nested_arrays_of_matrices_2x4_.inner[0].inner[0][0]; + wg.inner[7] = (_e5 * _e10).x; + metal::float3x2 _e16 = global_mat; + metal::float3 _e18 = global_vec; + wg.inner[6] = (_e16 * _e18).x; + float _e26 = dummy[1].y; + wg.inner[5] = _e26; + float _e32 = float_vecs.inner[0].w; + wg.inner[4] = _e32; + float _e37 = alignment.v1_; + wg.inner[3] = _e37; + float _e43 = alignment.v3_[0]; + wg.inner[2] = _e43; + alignment.v1_ = 4.0; + wg.inner[1] = static_cast(1 + (_buffer_sizes.size3 - 0 - 8) / 8); + metal::atomic_store_explicit(&at_1, 2u, metal::memory_order_relaxed); + return; +} diff --git a/naga/tests/out/msl/image.msl b/naga/tests/out/msl/image.msl new file mode 100644 index 0000000000..40d6e809ee --- /dev/null +++ b/naga/tests/out/msl/image.msl @@ -0,0 +1,268 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +struct main_Input { +}; +kernel void main_( + metal::uint3 local_id [[thread_position_in_threadgroup]] +, metal::texture2d image_mipmapped_src [[user(fake0)]] +, metal::texture2d_ms image_multisampled_src [[user(fake0)]] +, metal::texture2d image_storage_src [[user(fake0)]] +, metal::texture2d_array image_array_src [[user(fake0)]] +, metal::texture1d image_1d_src [[user(fake0)]] +, metal::texture1d image_dst [[user(fake0)]] +) { + metal::uint2 dim = metal::uint2(image_storage_src.get_width(), image_storage_src.get_height()); + metal::int2 itc = static_cast(dim * local_id.xy) % metal::int2(10, 20); + metal::uint4 value1_ = image_mipmapped_src.read(metal::uint2(itc), static_cast(local_id.z)); + metal::uint4 value2_ = image_multisampled_src.read(metal::uint2(itc), static_cast(local_id.z)); + metal::uint4 value4_ = image_storage_src.read(metal::uint2(itc)); + metal::uint4 value5_ = image_array_src.read(metal::uint2(itc), local_id.z, static_cast(local_id.z) + 1); + metal::uint4 value6_ = image_array_src.read(metal::uint2(itc), static_cast(local_id.z), static_cast(local_id.z) + 1); + metal::uint4 value7_ = image_1d_src.read(uint(static_cast(local_id.x))); + metal::uint4 value1u = image_mipmapped_src.read(metal::uint2(static_cast(itc)), static_cast(local_id.z)); + metal::uint4 value2u = image_multisampled_src.read(metal::uint2(static_cast(itc)), static_cast(local_id.z)); + metal::uint4 value4u = image_storage_src.read(metal::uint2(static_cast(itc))); + metal::uint4 value5u = image_array_src.read(metal::uint2(static_cast(itc)), local_id.z, static_cast(local_id.z) + 1); + metal::uint4 value6u = image_array_src.read(metal::uint2(static_cast(itc)), static_cast(local_id.z), static_cast(local_id.z) + 1); + metal::uint4 value7u = image_1d_src.read(uint(static_cast(local_id.x))); + image_dst.write((((value1_ + value2_) + value4_) + value5_) + value6_, uint(itc.x)); + image_dst.write((((value1u + value2u) + value4u) + value5u) + value6u, uint(static_cast(itc.x))); + return; +} + + +struct depth_loadInput { +}; +kernel void depth_load( + metal::uint3 local_id_1 [[thread_position_in_threadgroup]] +, metal::depth2d_ms image_depth_multisampled_src [[user(fake0)]] +, metal::texture2d image_storage_src [[user(fake0)]] +, metal::texture1d image_dst [[user(fake0)]] +) { + metal::uint2 dim_1 = metal::uint2(image_storage_src.get_width(), image_storage_src.get_height()); + metal::int2 itc_1 = static_cast(dim_1 * local_id_1.xy) % metal::int2(10, 20); + float val = image_depth_multisampled_src.read(metal::uint2(itc_1), static_cast(local_id_1.z)); + image_dst.write(metal::uint4(static_cast(val)), uint(itc_1.x)); + return; +} + + +struct queriesOutput { + metal::float4 member_2 [[position]]; +}; +vertex queriesOutput queries( + metal::texture1d image_1d [[user(fake0)]] +, metal::texture2d image_2d [[user(fake0)]] +, metal::texture2d_array image_2d_array [[user(fake0)]] +, metal::texturecube image_cube [[user(fake0)]] +, metal::texturecube_array image_cube_array [[user(fake0)]] +, metal::texture3d image_3d [[user(fake0)]] +, metal::texture2d_ms image_aa [[user(fake0)]] +) { + uint dim_1d = image_1d.get_width(); + uint dim_1d_lod = image_1d.get_width(); + metal::uint2 dim_2d = metal::uint2(image_2d.get_width(), image_2d.get_height()); + metal::uint2 dim_2d_lod = metal::uint2(image_2d.get_width(1), image_2d.get_height(1)); + metal::uint2 dim_2d_array = metal::uint2(image_2d_array.get_width(), image_2d_array.get_height()); + metal::uint2 dim_2d_array_lod = metal::uint2(image_2d_array.get_width(1), image_2d_array.get_height(1)); + metal::uint2 dim_cube = metal::uint2(image_cube.get_width()); + metal::uint2 dim_cube_lod = metal::uint2(image_cube.get_width(1)); + metal::uint2 dim_cube_array = metal::uint2(image_cube_array.get_width()); + metal::uint2 dim_cube_array_lod = metal::uint2(image_cube_array.get_width(1)); + metal::uint3 dim_3d = metal::uint3(image_3d.get_width(), image_3d.get_height(), image_3d.get_depth()); + metal::uint3 dim_3d_lod = metal::uint3(image_3d.get_width(1), image_3d.get_height(1), image_3d.get_depth(1)); + metal::uint2 dim_2s_ms = metal::uint2(image_aa.get_width(), image_aa.get_height()); + uint sum = (((((((((dim_1d + dim_2d.y) + dim_2d_lod.y) + dim_2d_array.y) + dim_2d_array_lod.y) + dim_cube.y) + dim_cube_lod.y) + dim_cube_array.y) + dim_cube_array_lod.y) + dim_3d.z) + dim_3d_lod.z; + return queriesOutput { metal::float4(static_cast(sum)) }; +} + + +struct levels_queriesOutput { + metal::float4 member_3 [[position]]; +}; +vertex levels_queriesOutput levels_queries( + metal::texture2d image_2d [[user(fake0)]] +, metal::texture2d_array image_2d_array [[user(fake0)]] +, metal::texturecube image_cube [[user(fake0)]] +, metal::texturecube_array image_cube_array [[user(fake0)]] +, metal::texture3d image_3d [[user(fake0)]] +, metal::texture2d_ms image_aa [[user(fake0)]] +) { + uint num_levels_2d = image_2d.get_num_mip_levels(); + uint num_levels_2d_array = image_2d_array.get_num_mip_levels(); + uint num_layers_2d = image_2d_array.get_array_size(); + uint num_levels_cube = image_cube.get_num_mip_levels(); + uint num_levels_cube_array = image_cube_array.get_num_mip_levels(); + uint num_layers_cube = image_cube_array.get_array_size(); + uint num_levels_3d = image_3d.get_num_mip_levels(); + uint num_samples_aa = image_aa.get_num_samples(); + uint sum_1 = ((((((num_layers_2d + num_layers_cube) + num_samples_aa) + num_levels_2d) + num_levels_2d_array) + num_levels_3d) + num_levels_cube) + num_levels_cube_array; + return levels_queriesOutput { metal::float4(static_cast(sum_1)) }; +} + + +struct texture_sampleOutput { + metal::float4 member_4 [[color(0)]]; +}; +fragment texture_sampleOutput texture_sample( + metal::texture1d image_1d [[user(fake0)]] +, metal::texture2d image_2d [[user(fake0)]] +, metal::texture2d_array image_2d_array [[user(fake0)]] +, metal::texturecube_array image_cube_array [[user(fake0)]] +, metal::sampler sampler_reg [[user(fake0)]] +) { + metal::float4 a = {}; + metal::float2 tc = metal::float2(0.5); + metal::float3 tc3_ = metal::float3(0.5); + metal::float4 _e9 = image_1d.sample(sampler_reg, tc.x); + metal::float4 _e10 = a; + a = _e10 + _e9; + metal::float4 _e14 = image_2d.sample(sampler_reg, tc); + metal::float4 _e15 = a; + a = _e15 + _e14; + metal::float4 _e19 = image_2d.sample(sampler_reg, tc, metal::int2(3, 1)); + metal::float4 _e20 = a; + a = _e20 + _e19; + metal::float4 _e24 = image_2d.sample(sampler_reg, tc, metal::level(2.3)); + metal::float4 _e25 = a; + a = _e25 + _e24; + metal::float4 _e29 = image_2d.sample(sampler_reg, tc, metal::level(2.3), metal::int2(3, 1)); + metal::float4 _e30 = a; + a = _e30 + _e29; + metal::float4 _e35 = image_2d.sample(sampler_reg, tc, metal::bias(2.0), metal::int2(3, 1)); + metal::float4 _e36 = a; + a = _e36 + _e35; + metal::float4 _e41 = image_2d_array.sample(sampler_reg, tc, 0u); + metal::float4 _e42 = a; + a = _e42 + _e41; + metal::float4 _e47 = image_2d_array.sample(sampler_reg, tc, 0u, metal::int2(3, 1)); + metal::float4 _e48 = a; + a = _e48 + _e47; + metal::float4 _e53 = image_2d_array.sample(sampler_reg, tc, 0u, metal::level(2.3)); + metal::float4 _e54 = a; + a = _e54 + _e53; + metal::float4 _e59 = image_2d_array.sample(sampler_reg, tc, 0u, metal::level(2.3), metal::int2(3, 1)); + metal::float4 _e60 = a; + a = _e60 + _e59; + metal::float4 _e66 = image_2d_array.sample(sampler_reg, tc, 0u, metal::bias(2.0), metal::int2(3, 1)); + metal::float4 _e67 = a; + a = _e67 + _e66; + metal::float4 _e72 = image_2d_array.sample(sampler_reg, tc, 0); + metal::float4 _e73 = a; + a = _e73 + _e72; + metal::float4 _e78 = image_2d_array.sample(sampler_reg, tc, 0, metal::int2(3, 1)); + metal::float4 _e79 = a; + a = _e79 + _e78; + metal::float4 _e84 = image_2d_array.sample(sampler_reg, tc, 0, metal::level(2.3)); + metal::float4 _e85 = a; + a = _e85 + _e84; + metal::float4 _e90 = image_2d_array.sample(sampler_reg, tc, 0, metal::level(2.3), metal::int2(3, 1)); + metal::float4 _e91 = a; + a = _e91 + _e90; + metal::float4 _e97 = image_2d_array.sample(sampler_reg, tc, 0, metal::bias(2.0), metal::int2(3, 1)); + metal::float4 _e98 = a; + a = _e98 + _e97; + metal::float4 _e103 = image_cube_array.sample(sampler_reg, tc3_, 0u); + metal::float4 _e104 = a; + a = _e104 + _e103; + metal::float4 _e109 = image_cube_array.sample(sampler_reg, tc3_, 0u, metal::level(2.3)); + metal::float4 _e110 = a; + a = _e110 + _e109; + metal::float4 _e116 = image_cube_array.sample(sampler_reg, tc3_, 0u, metal::bias(2.0)); + metal::float4 _e117 = a; + a = _e117 + _e116; + metal::float4 _e122 = image_cube_array.sample(sampler_reg, tc3_, 0); + metal::float4 _e123 = a; + a = _e123 + _e122; + metal::float4 _e128 = image_cube_array.sample(sampler_reg, tc3_, 0, metal::level(2.3)); + metal::float4 _e129 = a; + a = _e129 + _e128; + metal::float4 _e135 = image_cube_array.sample(sampler_reg, tc3_, 0, metal::bias(2.0)); + metal::float4 _e136 = a; + a = _e136 + _e135; + metal::float4 _e138 = a; + return texture_sampleOutput { _e138 }; +} + + +struct texture_sample_comparisonOutput { + float member_5 [[color(0)]]; +}; +fragment texture_sample_comparisonOutput texture_sample_comparison( + metal::sampler sampler_cmp [[user(fake0)]] +, metal::depth2d image_2d_depth [[user(fake0)]] +, metal::depth2d_array image_2d_array_depth [[user(fake0)]] +, metal::depthcube image_cube_depth [[user(fake0)]] +) { + float a_1 = {}; + metal::float2 tc_1 = metal::float2(0.5); + metal::float3 tc3_1 = metal::float3(0.5); + float _e8 = image_2d_depth.sample_compare(sampler_cmp, tc_1, 0.5); + float _e9 = a_1; + a_1 = _e9 + _e8; + float _e14 = image_2d_array_depth.sample_compare(sampler_cmp, tc_1, 0u, 0.5); + float _e15 = a_1; + a_1 = _e15 + _e14; + float _e20 = image_2d_array_depth.sample_compare(sampler_cmp, tc_1, 0, 0.5); + float _e21 = a_1; + a_1 = _e21 + _e20; + float _e25 = image_cube_depth.sample_compare(sampler_cmp, tc3_1, 0.5); + float _e26 = a_1; + a_1 = _e26 + _e25; + float _e30 = image_2d_depth.sample_compare(sampler_cmp, tc_1, 0.5); + float _e31 = a_1; + a_1 = _e31 + _e30; + float _e36 = image_2d_array_depth.sample_compare(sampler_cmp, tc_1, 0u, 0.5); + float _e37 = a_1; + a_1 = _e37 + _e36; + float _e42 = image_2d_array_depth.sample_compare(sampler_cmp, tc_1, 0, 0.5); + float _e43 = a_1; + a_1 = _e43 + _e42; + float _e47 = image_cube_depth.sample_compare(sampler_cmp, tc3_1, 0.5); + float _e48 = a_1; + a_1 = _e48 + _e47; + float _e50 = a_1; + return texture_sample_comparisonOutput { _e50 }; +} + + +struct gatherOutput { + metal::float4 member_6 [[color(0)]]; +}; +fragment gatherOutput gather( + metal::texture2d image_2d [[user(fake0)]] +, metal::texture2d image_2d_u32_ [[user(fake0)]] +, metal::texture2d image_2d_i32_ [[user(fake0)]] +, metal::sampler sampler_reg [[user(fake0)]] +, metal::sampler sampler_cmp [[user(fake0)]] +, metal::depth2d image_2d_depth [[user(fake0)]] +) { + metal::float2 tc_2 = metal::float2(0.5); + metal::float4 s2d = image_2d.gather(sampler_reg, tc_2, metal::int2(0), metal::component::y); + metal::float4 s2d_offset = image_2d.gather(sampler_reg, tc_2, metal::int2(3, 1), metal::component::w); + metal::float4 s2d_depth = image_2d_depth.gather_compare(sampler_cmp, tc_2, 0.5); + metal::float4 s2d_depth_offset = image_2d_depth.gather_compare(sampler_cmp, tc_2, 0.5, metal::int2(3, 1)); + metal::uint4 u = image_2d_u32_.gather(sampler_reg, tc_2); + metal::int4 i = image_2d_i32_.gather(sampler_reg, tc_2); + metal::float4 f = static_cast(u) + static_cast(i); + return gatherOutput { (((s2d + s2d_offset) + s2d_depth) + s2d_depth_offset) + f }; +} + + +struct depth_no_comparisonOutput { + metal::float4 member_7 [[color(0)]]; +}; +fragment depth_no_comparisonOutput depth_no_comparison( + metal::sampler sampler_reg [[user(fake0)]] +, metal::depth2d image_2d_depth [[user(fake0)]] +) { + metal::float2 tc_3 = metal::float2(0.5); + float s2d_1 = image_2d_depth.sample(sampler_reg, tc_3); + metal::float4 s2d_gather = image_2d_depth.gather(sampler_reg, tc_3); + return depth_no_comparisonOutput { metal::float4(s2d_1) + s2d_gather }; +} diff --git a/naga/tests/out/msl/interface.msl b/naga/tests/out/msl/interface.msl new file mode 100644 index 0000000000..047873da9f --- /dev/null +++ b/naga/tests/out/msl/interface.msl @@ -0,0 +1,103 @@ +// language: metal2.1 +#include +#include + +using metal::uint; + +struct VertexOutput { + metal::float4 position; + float _varying; +}; +struct FragmentOutput { + float depth; + uint sample_mask; + float color; +}; +struct type_4 { + uint inner[1]; +}; +struct Input1_ { + uint index; +}; +struct Input2_ { + uint index; +}; + +struct vertex_Input { + uint color [[attribute(10)]]; +}; +struct vertex_Output { + metal::float4 position [[position, invariant]]; + float _varying [[user(loc1), center_perspective]]; + float _point_size [[point_size]]; +}; +vertex vertex_Output vertex_( + vertex_Input varyings [[stage_in]] +, uint vertex_index [[vertex_id]] +, uint instance_index [[instance_id]] +) { + const auto color = varyings.color; + uint tmp = (vertex_index + instance_index) + color; + const auto _tmp = VertexOutput {metal::float4(1.0), static_cast(tmp)}; + return vertex_Output { _tmp.position, _tmp._varying, 1.0 }; +} + + +struct fragment_Input { + float _varying [[user(loc1), center_perspective]]; +}; +struct fragment_Output { + float depth [[depth(any)]]; + uint sample_mask [[sample_mask]]; + float color [[color(0)]]; +}; +fragment fragment_Output fragment_( + fragment_Input varyings_1 [[stage_in]] +, metal::float4 position [[position]] +, bool front_facing [[front_facing]] +, uint sample_index [[sample_id]] +, uint sample_mask [[sample_mask]] +) { + const VertexOutput in = { position, varyings_1._varying }; + uint mask = sample_mask & (1u << sample_index); + float color_1 = front_facing ? 1.0 : 0.0; + const auto _tmp = FragmentOutput {in._varying, mask, color_1}; + return fragment_Output { _tmp.depth, _tmp.sample_mask, _tmp.color }; +} + + +struct computeInput { +}; +kernel void compute( + metal::uint3 global_id [[thread_position_in_grid]] +, metal::uint3 local_id [[thread_position_in_threadgroup]] +, uint local_index [[thread_index_in_threadgroup]] +, metal::uint3 wg_id [[threadgroup_position_in_grid]] +, metal::uint3 num_wgs [[threadgroups_per_grid]] +, threadgroup type_4& output +) { + if (metal::all(local_id == metal::uint3(0u))) { + output = {}; + } + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + output.inner[0] = (((global_id.x + local_id.x) + local_index) + wg_id.x) + num_wgs.x; + return; +} + + +struct vertex_two_structsInput { +}; +struct vertex_two_structsOutput { + metal::float4 member_3 [[position, invariant]]; + float _point_size [[point_size]]; +}; +vertex vertex_two_structsOutput vertex_two_structs( + uint index_1 [[vertex_id]] +, uint index_2 [[instance_id]] +) { + const Input1_ in1_ = { index_1 }; + const Input2_ in2_ = { index_2 }; + uint index = 2u; + uint _e8 = index; + return vertex_two_structsOutput { metal::float4(static_cast(in1_.index), static_cast(in2_.index), static_cast(_e8), 0.0), 1.0 }; +} diff --git a/naga/tests/out/msl/interpolate.msl b/naga/tests/out/msl/interpolate.msl new file mode 100644 index 0000000000..616291253f --- /dev/null +++ b/naga/tests/out/msl/interpolate.msl @@ -0,0 +1,60 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct FragmentInput { + metal::float4 position; + uint _flat; + float _linear; + metal::float2 linear_centroid; + metal::float3 linear_sample; + metal::float4 perspective; + float perspective_centroid; + float perspective_sample; +}; + +struct vert_mainOutput { + metal::float4 position [[position]]; + uint _flat [[user(loc0), flat]]; + float _linear [[user(loc1), center_no_perspective]]; + metal::float2 linear_centroid [[user(loc2), centroid_no_perspective]]; + metal::float3 linear_sample [[user(loc3), sample_no_perspective]]; + metal::float4 perspective [[user(loc4), center_perspective]]; + float perspective_centroid [[user(loc5), centroid_perspective]]; + float perspective_sample [[user(loc6), sample_perspective]]; +}; +vertex vert_mainOutput vert_main( +) { + FragmentInput out = {}; + out.position = metal::float4(2.0, 4.0, 5.0, 6.0); + out._flat = 8u; + out._linear = 27.0; + out.linear_centroid = metal::float2(64.0, 125.0); + out.linear_sample = metal::float3(216.0, 343.0, 512.0); + out.perspective = metal::float4(729.0, 1000.0, 1331.0, 1728.0); + out.perspective_centroid = 2197.0; + out.perspective_sample = 2744.0; + FragmentInput _e30 = out; + const auto _tmp = _e30; + return vert_mainOutput { _tmp.position, _tmp._flat, _tmp._linear, _tmp.linear_centroid, _tmp.linear_sample, _tmp.perspective, _tmp.perspective_centroid, _tmp.perspective_sample }; +} + + +struct frag_mainInput { + uint _flat [[user(loc0), flat]]; + float _linear [[user(loc1), center_no_perspective]]; + metal::float2 linear_centroid [[user(loc2), centroid_no_perspective]]; + metal::float3 linear_sample [[user(loc3), sample_no_perspective]]; + metal::float4 perspective [[user(loc4), center_perspective]]; + float perspective_centroid [[user(loc5), centroid_perspective]]; + float perspective_sample [[user(loc6), sample_perspective]]; +}; +fragment void frag_main( + frag_mainInput varyings_1 [[stage_in]] +, metal::float4 position [[position]] +) { + const FragmentInput val = { position, varyings_1._flat, varyings_1._linear, varyings_1.linear_centroid, varyings_1.linear_sample, varyings_1.perspective, varyings_1.perspective_centroid, varyings_1.perspective_sample }; + return; +} diff --git a/naga/tests/out/msl/math-functions.msl b/naga/tests/out/msl/math-functions.msl new file mode 100644 index 0000000000..d93e502dc6 --- /dev/null +++ b/naga/tests/out/msl/math-functions.msl @@ -0,0 +1,108 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct _modf_result_f32_ { + float fract; + float whole; +}; +struct _modf_result_vec2_f32_ { + metal::float2 fract; + metal::float2 whole; +}; +struct _modf_result_vec4_f32_ { + metal::float4 fract; + metal::float4 whole; +}; +struct _frexp_result_f32_ { + float fract; + int exp; +}; +struct _frexp_result_vec4_f32_ { + metal::float4 fract; + metal::int4 exp; +}; + +_modf_result_f32_ naga_modf(float arg) { + float other; + float fract = metal::modf(arg, other); + return _modf_result_f32_{ fract, other }; +} + +_modf_result_vec2_f32_ naga_modf(metal::float2 arg) { + metal::float2 other; + metal::float2 fract = metal::modf(arg, other); + return _modf_result_vec2_f32_{ fract, other }; +} + +_modf_result_vec4_f32_ naga_modf(metal::float4 arg) { + metal::float4 other; + metal::float4 fract = metal::modf(arg, other); + return _modf_result_vec4_f32_{ fract, other }; +} + +_frexp_result_f32_ naga_frexp(float arg) { + int other; + float fract = metal::frexp(arg, other); + return _frexp_result_f32_{ fract, other }; +} + +_frexp_result_vec4_f32_ naga_frexp(metal::float4 arg) { + int4 other; + metal::float4 fract = metal::frexp(arg, other); + return _frexp_result_vec4_f32_{ fract, other }; +} + +fragment void main_( +) { + metal::float4 v = metal::float4(0.0); + float a = ((1.0) * 57.295779513082322865); + float b = ((1.0) * 0.017453292519943295474); + metal::float4 c = ((v) * 57.295779513082322865); + metal::float4 d = ((v) * 0.017453292519943295474); + metal::float4 e = metal::saturate(v); + metal::float4 g = metal::refract(v, v, 1.0); + int sign_a = metal::select(metal::select(-1, 1, (-1 > 0)), 0, (-1 == 0)); + metal::int4 _e12 = metal::int4(-1); + metal::int4 sign_b = metal::select(metal::select(int4(-1), int4(1), (_e12 > 0)), 0, (_e12 == 0)); + float sign_c = metal::sign(-1.0); + metal::float4 sign_d = metal::sign(metal::float4(-1.0)); + int const_dot = ( + metal::int2 {}.x * metal::int2 {}.x + metal::int2 {}.y * metal::int2 {}.y); + uint _e23 = metal::abs(0u); + uint first_leading_bit_abs = metal::select(31 - metal::clz(_e23), uint(-1), _e23 == 0 || _e23 == -1); + int flb_a = metal::select(31 - metal::clz(metal::select(-1, ~-1, -1 < 0)), int(-1), -1 == 0 || -1 == -1); + metal::int2 _e28 = metal::int2(-1); + metal::int2 flb_b = metal::select(31 - metal::clz(metal::select(_e28, ~_e28, _e28 < 0)), int2(-1), _e28 == 0 || _e28 == -1); + metal::uint2 _e31 = metal::uint2(1u); + metal::uint2 flb_c = metal::select(31 - metal::clz(_e31), uint2(-1), _e31 == 0 || _e31 == -1); + int ftb_a = (((metal::ctz(-1) + 1) % 33) - 1); + uint ftb_b = (((metal::ctz(1u) + 1) % 33) - 1); + metal::int2 ftb_c = (((metal::ctz(metal::int2(-1)) + 1) % 33) - 1); + metal::uint2 ftb_d = (((metal::ctz(metal::uint2(1u)) + 1) % 33) - 1); + uint ctz_a = metal::ctz(0u); + int ctz_b = metal::ctz(0); + uint ctz_c = metal::ctz(4294967295u); + int ctz_d = metal::ctz(-1); + metal::uint2 ctz_e = metal::ctz(metal::uint2(0u)); + metal::int2 ctz_f = metal::ctz(metal::int2(0)); + metal::uint2 ctz_g = metal::ctz(metal::uint2(1u)); + metal::int2 ctz_h = metal::ctz(metal::int2(1)); + int clz_a = metal::clz(-1); + uint clz_b = metal::clz(1u); + metal::int2 clz_c = metal::clz(metal::int2(-1)); + metal::uint2 clz_d = metal::clz(metal::uint2(1u)); + float lde_a = metal::ldexp(1.0, 2); + metal::float2 lde_b = metal::ldexp(metal::float2(1.0, 2.0), metal::int2(3, 4)); + _modf_result_f32_ modf_a = naga_modf(1.5); + float modf_b = naga_modf(1.5).fract; + float modf_c = naga_modf(1.5).whole; + _modf_result_vec2_f32_ modf_d = naga_modf(metal::float2(1.5, 1.5)); + float modf_e = naga_modf(metal::float4(1.5, 1.5, 1.5, 1.5)).whole.x; + float modf_f = naga_modf(metal::float2(1.5, 1.5)).fract.y; + _frexp_result_f32_ frexp_a = naga_frexp(1.5); + float frexp_b = naga_frexp(1.5).fract; + int frexp_c = naga_frexp(1.5).exp; + int frexp_d = naga_frexp(metal::float4(1.5, 1.5, 1.5, 1.5)).exp.x; +} diff --git a/naga/tests/out/msl/msl-varyings.msl b/naga/tests/out/msl/msl-varyings.msl new file mode 100644 index 0000000000..5e5788c8c5 --- /dev/null +++ b/naga/tests/out/msl/msl-varyings.msl @@ -0,0 +1,50 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct Vertex { + metal::float2 position; +}; +struct NoteInstance { + metal::float2 position; +}; +struct VertexOutput { + metal::float4 position; +}; + +struct vs_mainInput { + metal::float2 position [[attribute(0)]]; + metal::float2 position_1 [[attribute(1)]]; +}; +struct vs_mainOutput { + metal::float4 position [[position]]; +}; +vertex vs_mainOutput vs_main( + vs_mainInput varyings [[stage_in]] +) { + const Vertex vertex_ = { varyings.position }; + const NoteInstance note = { varyings.position_1 }; + VertexOutput out = {}; + VertexOutput _e3 = out; + const auto _tmp = _e3; + return vs_mainOutput { _tmp.position }; +} + + +struct fs_mainInput { + metal::float2 position [[user(loc1), center_perspective]]; +}; +struct fs_mainOutput { + metal::float4 member_1 [[color(0)]]; +}; +fragment fs_mainOutput fs_main( + fs_mainInput varyings_1 [[stage_in]] +, metal::float4 position [[position]] +) { + const VertexOutput in = { position }; + const NoteInstance note_1 = { varyings_1.position }; + metal::float3 position_1 = metal::float3(1.0); + return fs_mainOutput { in.position }; +} diff --git a/naga/tests/out/msl/operators.msl b/naga/tests/out/msl/operators.msl new file mode 100644 index 0000000000..85fba28c33 --- /dev/null +++ b/naga/tests/out/msl/operators.msl @@ -0,0 +1,274 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +constant metal::float4 v_f32_one = metal::float4(1.0, 1.0, 1.0, 1.0); +constant metal::float4 v_f32_zero = metal::float4(0.0, 0.0, 0.0, 0.0); +constant metal::float4 v_f32_half = metal::float4(0.5, 0.5, 0.5, 0.5); +constant metal::int4 v_i32_one = metal::int4(1, 1, 1, 1); + +metal::float4 builtins( +) { + int s1_ = true ? 1 : 0; + metal::float4 s2_ = true ? v_f32_one : v_f32_zero; + metal::float4 s3_ = metal::select(v_f32_one, v_f32_zero, metal::bool4(false, false, false, false)); + metal::float4 m1_ = metal::mix(v_f32_zero, v_f32_one, v_f32_half); + metal::float4 m2_ = metal::mix(v_f32_zero, v_f32_one, 0.1); + float b1_ = as_type(1); + metal::float4 b2_ = as_type(v_i32_one); + metal::int4 v_i32_zero = metal::int4(0, 0, 0, 0); + return ((((static_cast(metal::int4(s1_) + v_i32_zero) + s2_) + m1_) + m2_) + metal::float4(b1_)) + b2_; +} + +metal::float4 splat( + float m, + int n +) { + metal::float2 a_2 = ((metal::float2(2.0) + metal::float2(m)) - metal::float2(4.0)) / metal::float2(8.0); + metal::int4 b = metal::int4(n) % metal::int4(2); + return a_2.xyxy + static_cast(b); +} + +metal::float2 splat_assignment( +) { + metal::float2 a = metal::float2(2.0); + metal::float2 _e4 = a; + a = _e4 + metal::float2(1.0); + metal::float2 _e8 = a; + a = _e8 - metal::float2(3.0); + metal::float2 _e12 = a; + a = _e12 / metal::float2(4.0); + metal::float2 _e15 = a; + return _e15; +} + +metal::float3 bool_cast( + metal::float3 x +) { + metal::bool3 y = static_cast(x); + return static_cast(y); +} + +void logical( +) { + bool neg0_ = !(true); + metal::bool2 neg1_ = !(metal::bool2(true)); + bool or_ = true || false; + bool and_ = true && false; + bool bitwise_or0_ = true | false; + metal::bool3 bitwise_or1_ = metal::bool3(true) | metal::bool3(false); + bool bitwise_and0_ = true & false; + metal::bool4 bitwise_and1_ = metal::bool4(true) & metal::bool4(false); +} + +void arithmetic( +) { + float neg0_1 = -(1.0); + metal::int2 neg1_1 = -(metal::int2(1)); + metal::float2 neg2_ = -(metal::float2(1.0)); + int add0_ = 2 + 1; + uint add1_ = 2u + 1u; + float add2_ = 2.0 + 1.0; + metal::int2 add3_ = metal::int2(2) + metal::int2(1); + metal::uint3 add4_ = metal::uint3(2u) + metal::uint3(1u); + metal::float4 add5_ = metal::float4(2.0) + metal::float4(1.0); + int sub0_ = 2 - 1; + uint sub1_ = 2u - 1u; + float sub2_ = 2.0 - 1.0; + metal::int2 sub3_ = metal::int2(2) - metal::int2(1); + metal::uint3 sub4_ = metal::uint3(2u) - metal::uint3(1u); + metal::float4 sub5_ = metal::float4(2.0) - metal::float4(1.0); + int mul0_ = 2 * 1; + uint mul1_ = 2u * 1u; + float mul2_ = 2.0 * 1.0; + metal::int2 mul3_ = metal::int2(2) * metal::int2(1); + metal::uint3 mul4_ = metal::uint3(2u) * metal::uint3(1u); + metal::float4 mul5_ = metal::float4(2.0) * metal::float4(1.0); + int div0_ = 2 / 1; + uint div1_ = 2u / 1u; + float div2_ = 2.0 / 1.0; + metal::int2 div3_ = metal::int2(2) / metal::int2(1); + metal::uint3 div4_ = metal::uint3(2u) / metal::uint3(1u); + metal::float4 div5_ = metal::float4(2.0) / metal::float4(1.0); + int rem0_ = 2 % 1; + uint rem1_ = 2u % 1u; + float rem2_ = metal::fmod(2.0, 1.0); + metal::int2 rem3_ = metal::int2(2) % metal::int2(1); + metal::uint3 rem4_ = metal::uint3(2u) % metal::uint3(1u); + metal::float4 rem5_ = metal::fmod(metal::float4(2.0), metal::float4(1.0)); + { + metal::int2 add0_1 = metal::int2(2) + metal::int2(1); + metal::int2 add1_1 = metal::int2(2) + metal::int2(1); + metal::uint2 add2_1 = metal::uint2(2u) + metal::uint2(1u); + metal::uint2 add3_1 = metal::uint2(2u) + metal::uint2(1u); + metal::float2 add4_1 = metal::float2(2.0) + metal::float2(1.0); + metal::float2 add5_1 = metal::float2(2.0) + metal::float2(1.0); + metal::int2 sub0_1 = metal::int2(2) - metal::int2(1); + metal::int2 sub1_1 = metal::int2(2) - metal::int2(1); + metal::uint2 sub2_1 = metal::uint2(2u) - metal::uint2(1u); + metal::uint2 sub3_1 = metal::uint2(2u) - metal::uint2(1u); + metal::float2 sub4_1 = metal::float2(2.0) - metal::float2(1.0); + metal::float2 sub5_1 = metal::float2(2.0) - metal::float2(1.0); + metal::int2 mul0_1 = metal::int2(2) * 1; + metal::int2 mul1_1 = 2 * metal::int2(1); + metal::uint2 mul2_1 = metal::uint2(2u) * 1u; + metal::uint2 mul3_1 = 2u * metal::uint2(1u); + metal::float2 mul4_1 = metal::float2(2.0) * 1.0; + metal::float2 mul5_1 = 2.0 * metal::float2(1.0); + metal::int2 div0_1 = metal::int2(2) / metal::int2(1); + metal::int2 div1_1 = metal::int2(2) / metal::int2(1); + metal::uint2 div2_1 = metal::uint2(2u) / metal::uint2(1u); + metal::uint2 div3_1 = metal::uint2(2u) / metal::uint2(1u); + metal::float2 div4_1 = metal::float2(2.0) / metal::float2(1.0); + metal::float2 div5_1 = metal::float2(2.0) / metal::float2(1.0); + metal::int2 rem0_1 = metal::int2(2) % metal::int2(1); + metal::int2 rem1_1 = metal::int2(2) % metal::int2(1); + metal::uint2 rem2_1 = metal::uint2(2u) % metal::uint2(1u); + metal::uint2 rem3_1 = metal::uint2(2u) % metal::uint2(1u); + metal::float2 rem4_1 = metal::fmod(metal::float2(2.0), metal::float2(1.0)); + metal::float2 rem5_1 = metal::fmod(metal::float2(2.0), metal::float2(1.0)); + } + metal::float3x3 add = metal::float3x3 {} + metal::float3x3 {}; + metal::float3x3 sub = metal::float3x3 {} - metal::float3x3 {}; + metal::float3x3 mul_scalar0_ = metal::float3x3 {} * 1.0; + metal::float3x3 mul_scalar1_ = 2.0 * metal::float3x3 {}; + metal::float3 mul_vector0_ = metal::float4x3 {} * metal::float4(1.0); + metal::float4 mul_vector1_ = metal::float3(2.0) * metal::float4x3 {}; + metal::float3x3 mul = metal::float4x3 {} * metal::float3x4 {}; +} + +void bit( +) { + int flip0_ = ~(1); + uint flip1_ = ~(1u); + metal::int2 flip2_ = ~(metal::int2(1)); + metal::uint3 flip3_ = ~(metal::uint3(1u)); + int or0_ = 2 | 1; + uint or1_ = 2u | 1u; + metal::int2 or2_ = metal::int2(2) | metal::int2(1); + metal::uint3 or3_ = metal::uint3(2u) | metal::uint3(1u); + int and0_ = 2 & 1; + uint and1_ = 2u & 1u; + metal::int2 and2_ = metal::int2(2) & metal::int2(1); + metal::uint3 and3_ = metal::uint3(2u) & metal::uint3(1u); + int xor0_ = 2 ^ 1; + uint xor1_ = 2u ^ 1u; + metal::int2 xor2_ = metal::int2(2) ^ metal::int2(1); + metal::uint3 xor3_ = metal::uint3(2u) ^ metal::uint3(1u); + int shl0_ = 2 << 1u; + uint shl1_ = 2u << 1u; + metal::int2 shl2_ = metal::int2(2) << metal::uint2(1u); + metal::uint3 shl3_ = metal::uint3(2u) << metal::uint3(1u); + int shr0_ = 2 >> 1u; + uint shr1_ = 2u >> 1u; + metal::int2 shr2_ = metal::int2(2) >> metal::uint2(1u); + metal::uint3 shr3_ = metal::uint3(2u) >> metal::uint3(1u); +} + +void comparison( +) { + bool eq0_ = 2 == 1; + bool eq1_ = 2u == 1u; + bool eq2_ = 2.0 == 1.0; + metal::bool2 eq3_ = metal::int2(2) == metal::int2(1); + metal::bool3 eq4_ = metal::uint3(2u) == metal::uint3(1u); + metal::bool4 eq5_ = metal::float4(2.0) == metal::float4(1.0); + bool neq0_ = 2 != 1; + bool neq1_ = 2u != 1u; + bool neq2_ = 2.0 != 1.0; + metal::bool2 neq3_ = metal::int2(2) != metal::int2(1); + metal::bool3 neq4_ = metal::uint3(2u) != metal::uint3(1u); + metal::bool4 neq5_ = metal::float4(2.0) != metal::float4(1.0); + bool lt0_ = 2 < 1; + bool lt1_ = 2u < 1u; + bool lt2_ = 2.0 < 1.0; + metal::bool2 lt3_ = metal::int2(2) < metal::int2(1); + metal::bool3 lt4_ = metal::uint3(2u) < metal::uint3(1u); + metal::bool4 lt5_ = metal::float4(2.0) < metal::float4(1.0); + bool lte0_ = 2 <= 1; + bool lte1_ = 2u <= 1u; + bool lte2_ = 2.0 <= 1.0; + metal::bool2 lte3_ = metal::int2(2) <= metal::int2(1); + metal::bool3 lte4_ = metal::uint3(2u) <= metal::uint3(1u); + metal::bool4 lte5_ = metal::float4(2.0) <= metal::float4(1.0); + bool gt0_ = 2 > 1; + bool gt1_ = 2u > 1u; + bool gt2_ = 2.0 > 1.0; + metal::bool2 gt3_ = metal::int2(2) > metal::int2(1); + metal::bool3 gt4_ = metal::uint3(2u) > metal::uint3(1u); + metal::bool4 gt5_ = metal::float4(2.0) > metal::float4(1.0); + bool gte0_ = 2 >= 1; + bool gte1_ = 2u >= 1u; + bool gte2_ = 2.0 >= 1.0; + metal::bool2 gte3_ = metal::int2(2) >= metal::int2(1); + metal::bool3 gte4_ = metal::uint3(2u) >= metal::uint3(1u); + metal::bool4 gte5_ = metal::float4(2.0) >= metal::float4(1.0); +} + +void assignment( +) { + int a_1 = {}; + metal::int3 vec0_ = metal::int3 {}; + a_1 = 1; + int _e5 = a_1; + a_1 = _e5 + 1; + int _e7 = a_1; + a_1 = _e7 - 1; + int _e9 = a_1; + int _e10 = a_1; + a_1 = _e10 * _e9; + int _e12 = a_1; + int _e13 = a_1; + a_1 = _e13 / _e12; + int _e15 = a_1; + a_1 = _e15 % 1; + int _e17 = a_1; + a_1 = _e17 & 0; + int _e19 = a_1; + a_1 = _e19 | 0; + int _e21 = a_1; + a_1 = _e21 ^ 0; + int _e23 = a_1; + a_1 = _e23 << 2u; + int _e25 = a_1; + a_1 = _e25 >> 1u; + int _e28 = a_1; + a_1 = _e28 + 1; + int _e31 = a_1; + a_1 = _e31 - 1; + int _e37 = vec0_[1]; + vec0_[1] = _e37 + 1; + int _e41 = vec0_[1]; + vec0_[1] = _e41 - 1; + return; +} + +void negation_avoids_prefix_decrement( +) { + int p0_ = -(1); + int p1_ = -(-(1)); + int p2_ = -(-(1)); + int p3_ = -(-(1)); + int p4_ = -(-(-(1))); + int p5_ = -(-(-(-(1)))); + int p6_ = -(-(-(-(-(1))))); + int p7_ = -(-(-(-(-(1))))); +} + +struct main_Input { +}; +kernel void main_( + metal::uint3 id [[threadgroup_position_in_grid]] +) { + metal::float4 _e1 = builtins(); + metal::float4 _e6 = splat(static_cast(id.x), static_cast(id.y)); + metal::float3 _e11 = bool_cast(metal::float3(1.0, 1.0, 1.0)); + logical(); + arithmetic(); + bit(); + comparison(); + assignment(); + return; +} diff --git a/naga/tests/out/msl/padding.msl b/naga/tests/out/msl/padding.msl new file mode 100644 index 0000000000..ae11b7d168 --- /dev/null +++ b/naga/tests/out/msl/padding.msl @@ -0,0 +1,38 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct S { + metal::float3 a; +}; +struct Test { + S a; + float b; +}; +struct type_2 { + metal::float3 inner[2]; +}; +struct Test2_ { + type_2 a; + float b; +}; +struct Test3_ { + metal::float4x3 a; + float b; +}; + +struct vertex_Output { + metal::float4 member [[position]]; +}; +vertex vertex_Output vertex_( + constant Test& input1_ [[buffer(0)]] +, constant Test2_& input2_ [[buffer(1)]] +, constant Test3_& input3_ [[buffer(2)]] +) { + float _e4 = input1_.b; + float _e8 = input2_.b; + float _e12 = input3_.b; + return vertex_Output { ((metal::float4(1.0) * _e4) * _e8) * _e12 }; +} diff --git a/naga/tests/out/msl/policy-mix.msl b/naga/tests/out/msl/policy-mix.msl new file mode 100644 index 0000000000..24a40179a8 --- /dev/null +++ b/naga/tests/out/msl/policy-mix.msl @@ -0,0 +1,53 @@ +// language: metal1.0 +#include +#include + +using metal::uint; +struct DefaultConstructible { + template + operator T() && { + return T {}; + } +}; + +struct type_1 { + metal::float4 inner[10]; +}; +struct InStorage { + type_1 a; +}; +struct type_2 { + metal::float4 inner[20]; +}; +struct InUniform { + type_2 a; +}; +struct type_5 { + float inner[30]; +}; +struct type_6 { + float inner[40]; +}; +struct type_9 { + metal::float4 inner[2]; +}; + +metal::float4 mock_function( + metal::int2 c, + int i, + int l, + device InStorage const& in_storage, + constant InUniform& in_uniform, + metal::texture2d_array image_2d_array, + threadgroup type_5& in_workgroup, + thread type_6& in_private +) { + type_9 in_function = type_9 {metal::float4(0.707, 0.0, 0.0, 1.0), metal::float4(0.0, 0.707, 0.0, 1.0)}; + metal::float4 _e18 = in_storage.a.inner[i]; + metal::float4 _e22 = in_uniform.a.inner[i]; + metal::float4 _e25 = (uint(l) < image_2d_array.get_num_mip_levels() && uint(i) < image_2d_array.get_array_size() && metal::all(metal::uint2(c) < metal::uint2(image_2d_array.get_width(l), image_2d_array.get_height(l))) ? image_2d_array.read(metal::uint2(c), i, l): DefaultConstructible()); + float _e29 = in_workgroup.inner[metal::min(unsigned(i), 29u)]; + float _e34 = in_private.inner[metal::min(unsigned(i), 39u)]; + metal::float4 _e38 = in_function.inner[metal::min(unsigned(i), 1u)]; + return ((((_e18 + _e22) + _e25) + metal::float4(_e29)) + metal::float4(_e34)) + _e38; +} diff --git a/naga/tests/out/msl/quad-vert.msl b/naga/tests/out/msl/quad-vert.msl new file mode 100644 index 0000000000..24b6cdd095 --- /dev/null +++ b/naga/tests/out/msl/quad-vert.msl @@ -0,0 +1,58 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_3 { + float inner[1]; +}; +struct gl_PerVertex { + metal::float4 gl_Position; + float gl_PointSize; + type_3 gl_ClipDistance; + type_3 gl_CullDistance; +}; +struct type_4 { + metal::float2 member; + metal::float4 gl_Position; +}; + +void main_1( + thread metal::float2& v_uv, + thread metal::float2& a_uv_1, + thread gl_PerVertex& perVertexStruct, + thread metal::float2& a_pos_1 +) { + metal::float2 _e6 = a_uv_1; + v_uv = _e6; + metal::float2 _e7 = a_pos_1; + perVertexStruct.gl_Position = metal::float4(_e7.x, _e7.y, 0.0, 1.0); + return; +} + +struct main_Input { + metal::float2 a_uv [[attribute(1)]]; + metal::float2 a_pos [[attribute(0)]]; +}; +struct main_Output { + metal::float2 member [[user(loc0), center_perspective]]; + metal::float4 gl_Position [[position]]; +}; +vertex main_Output main_( + main_Input varyings [[stage_in]] +) { + metal::float2 v_uv = {}; + metal::float2 a_uv_1 = {}; + gl_PerVertex perVertexStruct = gl_PerVertex {metal::float4(0.0, 0.0, 0.0, 1.0), 1.0, type_3 {}, type_3 {}}; + metal::float2 a_pos_1 = {}; + const auto a_uv = varyings.a_uv; + const auto a_pos = varyings.a_pos; + a_uv_1 = a_uv; + a_pos_1 = a_pos; + main_1(v_uv, a_uv_1, perVertexStruct, a_pos_1); + metal::float2 _e7 = v_uv; + metal::float4 _e8 = perVertexStruct.gl_Position; + const auto _tmp = type_4 {_e7, _e8}; + return main_Output { _tmp.member, _tmp.gl_Position }; +} diff --git a/naga/tests/out/msl/quad.msl b/naga/tests/out/msl/quad.msl new file mode 100644 index 0000000000..9083991b17 --- /dev/null +++ b/naga/tests/out/msl/quad.msl @@ -0,0 +1,59 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct VertexOutput { + metal::float2 uv; + char _pad1[8]; + metal::float4 position; +}; +constant float c_scale = 1.2; + +struct vert_mainInput { + metal::float2 pos [[attribute(0)]]; + metal::float2 uv [[attribute(1)]]; +}; +struct vert_mainOutput { + metal::float2 uv [[user(loc0), center_perspective]]; + metal::float4 position [[position]]; +}; +vertex vert_mainOutput vert_main( + vert_mainInput varyings [[stage_in]] +) { + const auto pos = varyings.pos; + const auto uv = varyings.uv; + const auto _tmp = VertexOutput {uv, {}, metal::float4(c_scale * pos, 0.0, 1.0)}; + return vert_mainOutput { _tmp.uv, _tmp.position }; +} + + +struct frag_mainInput { + metal::float2 uv_1 [[user(loc0), center_perspective]]; +}; +struct frag_mainOutput { + metal::float4 member_1 [[color(0)]]; +}; +fragment frag_mainOutput frag_main( + frag_mainInput varyings_1 [[stage_in]] +, metal::texture2d u_texture [[user(fake0)]] +, metal::sampler u_sampler [[user(fake0)]] +) { + const auto uv_1 = varyings_1.uv_1; + metal::float4 color = u_texture.sample(u_sampler, uv_1); + if (color.w == 0.0) { + metal::discard_fragment(); + } + metal::float4 premultiplied = color.w * color; + return frag_mainOutput { premultiplied }; +} + + +struct fs_extraOutput { + metal::float4 member_2 [[color(0)]]; +}; +fragment fs_extraOutput fs_extra( +) { + return fs_extraOutput { metal::float4(0.0, 0.5, 0.0, 0.5) }; +} diff --git a/naga/tests/out/msl/ray-query.msl b/naga/tests/out/msl/ray-query.msl new file mode 100644 index 0000000000..17b856427f --- /dev/null +++ b/naga/tests/out/msl/ray-query.msl @@ -0,0 +1,79 @@ +// language: metal2.4 +#include +#include + +using metal::uint; +struct _RayQuery { + metal::raytracing::intersector intersector; + metal::raytracing::intersector::result_type intersection; + bool ready = false; +}; +constexpr metal::uint _map_intersection_type(const metal::raytracing::intersection_type ty) { + return ty==metal::raytracing::intersection_type::triangle ? 1 : + ty==metal::raytracing::intersection_type::bounding_box ? 4 : 0; +} + +struct Output { + uint visible; + char _pad1[12]; + metal::float3 normal; +}; +struct RayIntersection { + uint kind; + float t; + uint instance_custom_index; + uint instance_id; + uint sbt_record_offset; + uint geometry_index; + uint primitive_index; + metal::float2 barycentrics; + bool front_face; + char _pad9[11]; + metal::float4x3 object_to_world; + metal::float4x3 world_to_object; +}; +struct RayDesc { + uint flags; + uint cull_mask; + float tmin; + float tmax; + metal::float3 origin; + metal::float3 dir; +}; + +metal::float3 get_torus_normal( + metal::float3 world_point, + RayIntersection intersection +) { + metal::float3 local_point = intersection.world_to_object * metal::float4(world_point, 1.0); + metal::float2 point_on_guiding_line = metal::normalize(local_point.xy) * 2.4; + metal::float3 world_point_on_guiding_line = intersection.object_to_world * metal::float4(point_on_guiding_line, 0.0, 1.0); + return metal::normalize(world_point - world_point_on_guiding_line); +} + +kernel void main_( + metal::raytracing::instance_acceleration_structure acc_struct [[user(fake0)]] +, device Output& output [[user(fake0)]] +) { + _RayQuery rq = {}; + metal::float3 dir = metal::float3(0.0, 1.0, 0.0); + RayDesc _e12 = RayDesc {4u, 255u, 0.1, 100.0, metal::float3(0.0), dir}; + rq.intersector.assume_geometry_type(metal::raytracing::geometry_type::triangle); + rq.intersector.set_opacity_cull_mode((_e12.flags & 64) != 0 ? metal::raytracing::opacity_cull_mode::opaque : (_e12.flags & 128) != 0 ? metal::raytracing::opacity_cull_mode::non_opaque : metal::raytracing::opacity_cull_mode::none); + rq.intersector.force_opacity((_e12.flags & 1) != 0 ? metal::raytracing::forced_opacity::opaque : (_e12.flags & 2) != 0 ? metal::raytracing::forced_opacity::non_opaque : metal::raytracing::forced_opacity::none); + rq.intersector.accept_any_intersection((_e12.flags & 4) != 0); + rq.intersection = rq.intersector.intersect(metal::raytracing::ray(_e12.origin, _e12.dir, _e12.tmin, _e12.tmax), acc_struct, _e12.cull_mask); rq.ready = true; + while(true) { + bool _e13 = rq.ready; + rq.ready = false; + if (_e13) { + } else { + break; + } + } + RayIntersection intersection_1 = RayIntersection {_map_intersection_type(rq.intersection.type), rq.intersection.distance, rq.intersection.user_instance_id, rq.intersection.instance_id, {}, rq.intersection.geometry_id, rq.intersection.primitive_id, rq.intersection.triangle_barycentric_coord, rq.intersection.triangle_front_facing, {}, rq.intersection.object_to_world_transform, rq.intersection.world_to_object_transform}; + output.visible = static_cast(intersection_1.kind == 0u); + metal::float3 _e25 = get_torus_normal(dir * intersection_1.t, intersection_1); + output.normal = _e25; + return; +} diff --git a/naga/tests/out/msl/resource-binding-map.msl b/naga/tests/out/msl/resource-binding-map.msl new file mode 100644 index 0000000000..56fcea0cce --- /dev/null +++ b/naga/tests/out/msl/resource-binding-map.msl @@ -0,0 +1,74 @@ +// language: metal1.0 +#include +#include + +using metal::uint; +struct DefaultConstructible { + template + operator T() && { + return T {}; + } +}; + + +struct entry_point_oneInput { +}; +struct entry_point_oneOutput { + metal::float4 member [[color(0)]]; +}; +fragment entry_point_oneOutput entry_point_one( + metal::float4 pos [[position]] +, metal::texture2d t1_ [[texture(0)]] +) { + constexpr metal::sampler s1_( + metal::s_address::clamp_to_edge, + metal::t_address::clamp_to_edge, + metal::r_address::clamp_to_edge, + metal::mag_filter::linear, + metal::min_filter::linear, + metal::coord::normalized + ); + metal::float4 _e4 = t1_.sample(s1_, pos.xy); + return entry_point_oneOutput { _e4 }; +} + + +struct entry_point_twoOutput { + metal::float4 member_1 [[color(0)]]; +}; +fragment entry_point_twoOutput entry_point_two( + metal::texture2d t1_ [[texture(0)]] +, metal::sampler s1_ [[sampler(0)]] +, constant metal::float2& uniformOne [[buffer(0)]] +) { + metal::float2 _e3 = uniformOne; + metal::float4 _e4 = t1_.sample(s1_, _e3); + return entry_point_twoOutput { _e4 }; +} + + +struct entry_point_threeOutput { + metal::float4 member_2 [[color(0)]]; +}; +fragment entry_point_threeOutput entry_point_three( + metal::texture2d t1_ [[texture(0)]] +, metal::texture2d t2_ [[texture(1)]] +, metal::sampler s2_ [[sampler(1)]] +, constant metal::float2& uniformOne [[buffer(0)]] +, constant metal::float2& uniformTwo [[buffer(1)]] +) { + constexpr metal::sampler s1_( + metal::s_address::clamp_to_edge, + metal::t_address::clamp_to_edge, + metal::r_address::clamp_to_edge, + metal::mag_filter::linear, + metal::min_filter::linear, + metal::coord::normalized + ); + metal::float2 _e3 = uniformTwo; + metal::float2 _e5 = uniformOne; + metal::float4 _e7 = t1_.sample(s1_, _e3 + _e5); + metal::float2 _e11 = uniformOne; + metal::float4 _e12 = t2_.sample(s2_, _e11); + return entry_point_threeOutput { _e7 + _e12 }; +} diff --git a/naga/tests/out/msl/shadow.msl b/naga/tests/out/msl/shadow.msl new file mode 100644 index 0000000000..2443f002f2 --- /dev/null +++ b/naga/tests/out/msl/shadow.msl @@ -0,0 +1,180 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct _mslBufferSizes { + uint size2; +}; + +struct Globals { + metal::float4x4 view_proj; + metal::uint4 num_lights; +}; +struct Entity { + metal::float4x4 world; + metal::float4 color; +}; +struct VertexOutput { + metal::float4 proj_position; + metal::float3 world_normal; + metal::float4 world_position; +}; +struct Light { + metal::float4x4 proj; + metal::float4 pos; + metal::float4 color; +}; +typedef Light type_6[1]; +struct type_7 { + Light inner[10]; +}; +constant metal::float3 c_ambient = metal::float3(0.05, 0.05, 0.05); +constant uint c_max_lights = 10u; + +float fetch_shadow( + uint light_id, + metal::float4 homogeneous_coords, + metal::depth2d_array t_shadow, + metal::sampler sampler_shadow +) { + if (homogeneous_coords.w <= 0.0) { + return 1.0; + } + metal::float2 flip_correction = metal::float2(0.5, -0.5); + float proj_correction = 1.0 / homogeneous_coords.w; + metal::float2 light_local = ((homogeneous_coords.xy * flip_correction) * proj_correction) + metal::float2(0.5, 0.5); + float _e24 = t_shadow.sample_compare(sampler_shadow, light_local, static_cast(light_id), homogeneous_coords.z * proj_correction); + return _e24; +} + +struct vs_mainInput { + metal::int4 position [[attribute(0)]]; + metal::int4 normal [[attribute(1)]]; +}; +struct vs_mainOutput { + metal::float4 proj_position [[position]]; + metal::float3 world_normal [[user(loc0), center_perspective]]; + metal::float4 world_position [[user(loc1), center_perspective]]; +}; +vertex vs_mainOutput vs_main( + vs_mainInput varyings [[stage_in]] +, constant Globals& u_globals [[user(fake0)]] +, constant Entity& u_entity [[user(fake0)]] +) { + const auto position = varyings.position; + const auto normal = varyings.normal; + VertexOutput out = {}; + metal::float4x4 w = u_entity.world; + metal::float4x4 _e7 = u_entity.world; + metal::float4 world_pos = _e7 * static_cast(position); + out.world_normal = metal::float3x3(w[0].xyz, w[1].xyz, w[2].xyz) * static_cast(normal.xyz); + out.world_position = world_pos; + metal::float4x4 _e26 = u_globals.view_proj; + out.proj_position = _e26 * world_pos; + VertexOutput _e28 = out; + const auto _tmp = _e28; + return vs_mainOutput { _tmp.proj_position, _tmp.world_normal, _tmp.world_position }; +} + + +struct fs_mainInput { + metal::float3 world_normal [[user(loc0), center_perspective]]; + metal::float4 world_position [[user(loc1), center_perspective]]; +}; +struct fs_mainOutput { + metal::float4 member_1 [[color(0)]]; +}; +fragment fs_mainOutput fs_main( + fs_mainInput varyings_1 [[stage_in]] +, metal::float4 proj_position [[position]] +, constant Globals& u_globals [[user(fake0)]] +, constant Entity& u_entity [[user(fake0)]] +, device type_6 const& s_lights [[user(fake0)]] +, metal::depth2d_array t_shadow [[user(fake0)]] +, metal::sampler sampler_shadow [[user(fake0)]] +, constant _mslBufferSizes& _buffer_sizes [[user(fake0)]] +) { + const VertexOutput in = { proj_position, varyings_1.world_normal, varyings_1.world_position }; + metal::float3 color = c_ambient; + uint i = 0u; + metal::float3 normal_1 = metal::normalize(in.world_normal); + bool loop_init = true; + while(true) { + if (!loop_init) { + uint _e40 = i; + i = _e40 + 1u; + } + loop_init = false; + uint _e7 = i; + uint _e11 = u_globals.num_lights.x; + if (_e7 < metal::min(_e11, c_max_lights)) { + } else { + break; + } + { + uint _e16 = i; + Light light = s_lights[_e16]; + uint _e19 = i; + float _e23 = fetch_shadow(_e19, light.proj * in.world_position, t_shadow, sampler_shadow); + metal::float3 light_dir = metal::normalize(light.pos.xyz - in.world_position.xyz); + float diffuse = metal::max(0.0, metal::dot(normal_1, light_dir)); + metal::float3 _e37 = color; + color = _e37 + ((_e23 * diffuse) * light.color.xyz); + } + } + metal::float3 _e42 = color; + metal::float4 _e47 = u_entity.color; + return fs_mainOutput { metal::float4(_e42, 1.0) * _e47 }; +} + + +struct fs_main_without_storageInput { + metal::float3 world_normal [[user(loc0), center_perspective]]; + metal::float4 world_position [[user(loc1), center_perspective]]; +}; +struct fs_main_without_storageOutput { + metal::float4 member_2 [[color(0)]]; +}; +fragment fs_main_without_storageOutput fs_main_without_storage( + fs_main_without_storageInput varyings_2 [[stage_in]] +, metal::float4 proj_position_1 [[position]] +, constant Globals& u_globals [[user(fake0)]] +, constant Entity& u_entity [[user(fake0)]] +, constant type_7& u_lights [[user(fake0)]] +, metal::depth2d_array t_shadow [[user(fake0)]] +, metal::sampler sampler_shadow [[user(fake0)]] +) { + const VertexOutput in_1 = { proj_position_1, varyings_2.world_normal, varyings_2.world_position }; + metal::float3 color_1 = c_ambient; + uint i_1 = 0u; + metal::float3 normal_2 = metal::normalize(in_1.world_normal); + bool loop_init_1 = true; + while(true) { + if (!loop_init_1) { + uint _e40 = i_1; + i_1 = _e40 + 1u; + } + loop_init_1 = false; + uint _e7 = i_1; + uint _e11 = u_globals.num_lights.x; + if (_e7 < metal::min(_e11, c_max_lights)) { + } else { + break; + } + { + uint _e16 = i_1; + Light light_1 = u_lights.inner[_e16]; + uint _e19 = i_1; + float _e23 = fetch_shadow(_e19, light_1.proj * in_1.world_position, t_shadow, sampler_shadow); + metal::float3 light_dir_1 = metal::normalize(light_1.pos.xyz - in_1.world_position.xyz); + float diffuse_1 = metal::max(0.0, metal::dot(normal_2, light_dir_1)); + metal::float3 _e37 = color_1; + color_1 = _e37 + ((_e23 * diffuse_1) * light_1.color.xyz); + } + } + metal::float3 _e42 = color_1; + metal::float4 _e47 = u_entity.color; + return fs_main_without_storageOutput { metal::float4(_e42, 1.0) * _e47 }; +} diff --git a/naga/tests/out/msl/skybox.msl b/naga/tests/out/msl/skybox.msl new file mode 100644 index 0000000000..7b10ea23e7 --- /dev/null +++ b/naga/tests/out/msl/skybox.msl @@ -0,0 +1,66 @@ +// language: metal2.1 +#include +#include + +using metal::uint; + +struct VertexOutput { + metal::float4 position; + metal::float3 uv; +}; +struct Data { + metal::float4x4 proj_inv; + metal::float4x4 view; +}; + +struct vs_mainInput { +}; +struct vs_mainOutput { + metal::float4 position [[position]]; + metal::float3 uv [[user(loc0), center_perspective]]; +}; +vertex vs_mainOutput vs_main( + uint vertex_index [[vertex_id]] +, constant Data& r_data [[buffer(0)]] +) { + int tmp1_ = {}; + int tmp2_ = {}; + tmp1_ = static_cast(vertex_index) / 2; + tmp2_ = static_cast(vertex_index) & 1; + int _e9 = tmp1_; + int _e15 = tmp2_; + metal::float4 pos = metal::float4((static_cast(_e9) * 4.0) - 1.0, (static_cast(_e15) * 4.0) - 1.0, 0.0, 1.0); + metal::float4 _e27 = r_data.view[0]; + metal::float4 _e32 = r_data.view[1]; + metal::float4 _e37 = r_data.view[2]; + metal::float3x3 inv_model_view = metal::transpose(metal::float3x3(_e27.xyz, _e32.xyz, _e37.xyz)); + metal::float4x4 _e43 = r_data.proj_inv; + metal::float4 unprojected = _e43 * pos; + const auto _tmp = VertexOutput {pos, inv_model_view * unprojected.xyz}; + return vs_mainOutput { _tmp.position, _tmp.uv }; +} + + +struct fs_mainInput { + metal::float3 uv [[user(loc0), center_perspective]]; +}; +struct fs_mainOutput { + metal::float4 member_1 [[color(0)]]; +}; +fragment fs_mainOutput fs_main( + fs_mainInput varyings_1 [[stage_in]] +, metal::float4 position [[position]] +, metal::texturecube r_texture [[texture(0)]] +) { + constexpr metal::sampler r_sampler( + metal::s_address::clamp_to_edge, + metal::t_address::clamp_to_edge, + metal::r_address::clamp_to_edge, + metal::mag_filter::linear, + metal::min_filter::linear, + metal::coord::normalized + ); + const VertexOutput in = { position, varyings_1.uv }; + metal::float4 _e4 = r_texture.sample(r_sampler, in.uv); + return fs_mainOutput { _e4 }; +} diff --git a/naga/tests/out/msl/standard.msl b/naga/tests/out/msl/standard.msl new file mode 100644 index 0000000000..e02ef7f892 --- /dev/null +++ b/naga/tests/out/msl/standard.msl @@ -0,0 +1,47 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +bool test_any_and_all_for_bool( +) { + return true; +} + +struct derivativesInput { +}; +struct derivativesOutput { + metal::float4 member [[color(0)]]; +}; +fragment derivativesOutput derivatives( + metal::float4 foo [[position]] +) { + metal::float4 x = {}; + metal::float4 y = {}; + metal::float4 z = {}; + metal::float4 _e1 = metal::dfdx(foo); + x = _e1; + metal::float4 _e3 = metal::dfdy(foo); + y = _e3; + metal::float4 _e5 = metal::fwidth(foo); + z = _e5; + metal::float4 _e7 = metal::dfdx(foo); + x = _e7; + metal::float4 _e8 = metal::dfdy(foo); + y = _e8; + metal::float4 _e9 = metal::fwidth(foo); + z = _e9; + metal::float4 _e10 = metal::dfdx(foo); + x = _e10; + metal::float4 _e11 = metal::dfdy(foo); + y = _e11; + metal::float4 _e12 = metal::fwidth(foo); + z = _e12; + bool _e13 = test_any_and_all_for_bool(); + metal::float4 _e14 = x; + metal::float4 _e15 = y; + metal::float4 _e17 = z; + return derivativesOutput { (_e14 + _e15) * _e17 }; +} diff --git a/naga/tests/out/msl/struct-layout.msl b/naga/tests/out/msl/struct-layout.msl new file mode 100644 index 0000000000..e1f266435b --- /dev/null +++ b/naga/tests/out/msl/struct-layout.msl @@ -0,0 +1,103 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct NoPadding { + metal::packed_float3 v3_; + float f3_; +}; +struct NeedsPadding { + float f3_forces_padding; + char _pad1[12]; + metal::packed_float3 v3_needs_padding; + float f3_; +}; + +struct no_padding_fragInput { + metal::float3 v3_ [[user(loc0), center_perspective]]; + float f3_ [[user(loc1), center_perspective]]; +}; +struct no_padding_fragOutput { + metal::float4 member [[color(0)]]; +}; +fragment no_padding_fragOutput no_padding_frag( + no_padding_fragInput varyings [[stage_in]] +) { + const NoPadding input = { varyings.v3_, varyings.f3_ }; + return no_padding_fragOutput { metal::float4(0.0) }; +} + + +struct no_padding_vertInput { + metal::float3 v3_ [[attribute(0)]]; + float f3_ [[attribute(1)]]; +}; +struct no_padding_vertOutput { + metal::float4 member_1 [[position]]; +}; +vertex no_padding_vertOutput no_padding_vert( + no_padding_vertInput varyings_1 [[stage_in]] +) { + const NoPadding input_1 = { varyings_1.v3_, varyings_1.f3_ }; + return no_padding_vertOutput { metal::float4(0.0) }; +} + + +kernel void no_padding_comp( + constant NoPadding& no_padding_uniform [[user(fake0)]] +, device NoPadding const& no_padding_storage [[user(fake0)]] +) { + NoPadding x = {}; + NoPadding _e2 = no_padding_uniform; + x = _e2; + NoPadding _e4 = no_padding_storage; + x = _e4; + return; +} + + +struct needs_padding_fragInput { + float f3_forces_padding [[user(loc0), center_perspective]]; + metal::float3 v3_needs_padding [[user(loc1), center_perspective]]; + float f3_ [[user(loc2), center_perspective]]; +}; +struct needs_padding_fragOutput { + metal::float4 member_3 [[color(0)]]; +}; +fragment needs_padding_fragOutput needs_padding_frag( + needs_padding_fragInput varyings_3 [[stage_in]] +) { + const NeedsPadding input_2 = { varyings_3.f3_forces_padding, {}, varyings_3.v3_needs_padding, varyings_3.f3_ }; + return needs_padding_fragOutput { metal::float4(0.0) }; +} + + +struct needs_padding_vertInput { + float f3_forces_padding [[attribute(0)]]; + metal::float3 v3_needs_padding [[attribute(1)]]; + float f3_ [[attribute(2)]]; +}; +struct needs_padding_vertOutput { + metal::float4 member_4 [[position]]; +}; +vertex needs_padding_vertOutput needs_padding_vert( + needs_padding_vertInput varyings_4 [[stage_in]] +) { + const NeedsPadding input_3 = { varyings_4.f3_forces_padding, {}, varyings_4.v3_needs_padding, varyings_4.f3_ }; + return needs_padding_vertOutput { metal::float4(0.0) }; +} + + +kernel void needs_padding_comp( + constant NeedsPadding& needs_padding_uniform [[user(fake0)]] +, device NeedsPadding const& needs_padding_storage [[user(fake0)]] +) { + NeedsPadding x_1 = {}; + NeedsPadding _e2 = needs_padding_uniform; + x_1 = _e2; + NeedsPadding _e4 = needs_padding_storage; + x_1 = _e4; + return; +} diff --git a/naga/tests/out/msl/texture-arg.msl b/naga/tests/out/msl/texture-arg.msl new file mode 100644 index 0000000000..4c173fce06 --- /dev/null +++ b/naga/tests/out/msl/texture-arg.msl @@ -0,0 +1,25 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +metal::float4 test( + metal::texture2d Passed_Texture, + metal::sampler Passed_Sampler +) { + metal::float4 _e5 = Passed_Texture.sample(Passed_Sampler, metal::float2(0.0, 0.0)); + return _e5; +} + +struct main_Output { + metal::float4 member [[color(0)]]; +}; +fragment main_Output main_( + metal::texture2d Texture [[user(fake0)]] +, metal::sampler Sampler [[user(fake0)]] +) { + metal::float4 _e2 = test(Texture, Sampler); + return main_Output { _e2 }; +} diff --git a/naga/tests/out/msl/workgroup-uniform-load.msl b/naga/tests/out/msl/workgroup-uniform-load.msl new file mode 100644 index 0000000000..32495c198a --- /dev/null +++ b/naga/tests/out/msl/workgroup-uniform-load.msl @@ -0,0 +1,32 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_2 { + int inner[128]; +}; +constant uint SIZE = 128u; + +struct test_workgroupUniformLoadInput { +}; +kernel void test_workgroupUniformLoad( + metal::uint3 workgroup_id [[threadgroup_position_in_grid]] +, metal::uint3 __local_invocation_id [[thread_position_in_threadgroup]] +, threadgroup type_2& arr_i32_ +) { + if (metal::all(__local_invocation_id == metal::uint3(0u))) { + arr_i32_ = {}; + } + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + int unnamed = arr_i32_.inner[workgroup_id.x]; + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + if (unnamed > 10) { + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + return; + } else { + return; + } +} diff --git a/naga/tests/out/msl/workgroup-var-init.msl b/naga/tests/out/msl/workgroup-var-init.msl new file mode 100644 index 0000000000..991c8b014b --- /dev/null +++ b/naga/tests/out/msl/workgroup-var-init.msl @@ -0,0 +1,40 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct type_1 { + uint inner[512]; +}; +struct type_3 { + metal::atomic_int inner[8]; +}; +struct type_4 { + type_3 inner[8]; +}; +struct WStruct { + type_1 arr; + metal::atomic_int atom; + type_4 atom_arr; +}; + +kernel void main_( + metal::uint3 __local_invocation_id [[thread_position_in_threadgroup]] +, threadgroup WStruct& w_mem +, device type_1& output [[buffer(0)]] +) { + if (metal::all(__local_invocation_id == metal::uint3(0u))) { + w_mem.arr = {}; + metal::atomic_store_explicit(&w_mem.atom, 0, metal::memory_order_relaxed); + for (int __i0 = 0; __i0 < 8; __i0++) { + for (int __i1 = 0; __i1 < 8; __i1++) { + metal::atomic_store_explicit(&w_mem.atom_arr.inner[__i0].inner[__i1], 0, metal::memory_order_relaxed); + } + } + } + metal::threadgroup_barrier(metal::mem_flags::mem_threadgroup); + type_1 _e3 = w_mem.arr; + output = _e3; + return; +} diff --git a/naga/tests/out/spv/abstract-types-const.spvasm b/naga/tests/out/spv/abstract-types-const.spvasm new file mode 100644 index 0000000000..207a04f564 --- /dev/null +++ b/naga/tests/out/spv/abstract-types-const.spvasm @@ -0,0 +1,46 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 36 +OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpDecorate %10 ArrayStride 4 +OpMemberDecorate %12 0 Offset 0 +OpMemberDecorate %12 1 Offset 4 +OpMemberDecorate %12 2 Offset 8 +%2 = OpTypeVoid +%4 = OpTypeInt 32 0 +%3 = OpTypeVector %4 2 +%6 = OpTypeFloat 32 +%5 = OpTypeVector %6 2 +%7 = OpTypeMatrix %5 2 +%9 = OpTypeInt 32 1 +%8 = OpTypeVector %9 2 +%11 = OpConstant %4 2 +%10 = OpTypeArray %6 %11 +%12 = OpTypeStruct %6 %9 %4 +%13 = OpTypeVector %6 3 +%14 = OpConstant %4 42 +%15 = OpConstant %4 43 +%16 = OpConstantComposite %3 %14 %15 +%17 = OpConstant %6 44.0 +%18 = OpConstant %6 45.0 +%19 = OpConstantComposite %5 %17 %18 +%20 = OpConstant %6 1.0 +%21 = OpConstant %6 2.0 +%22 = OpConstantComposite %5 %20 %21 +%23 = OpConstant %6 3.0 +%24 = OpConstant %6 4.0 +%25 = OpConstantComposite %5 %23 %24 +%26 = OpConstantComposite %7 %22 %25 +%27 = OpConstant %9 1 +%28 = OpConstantComposite %8 %27 %27 +%29 = OpConstantComposite %5 %20 %20 +%30 = OpConstant %4 1 +%31 = OpConstantComposite %3 %30 %30 +%32 = OpConstantComposite %10 %20 %21 +%33 = OpConstantComposite %12 %20 %27 %30 +%34 = OpConstantComposite %13 %20 %21 %23 +%35 = OpConstantComposite %5 %21 %23 \ No newline at end of file diff --git a/naga/tests/out/spv/abstract-types-operators.spvasm b/naga/tests/out/spv/abstract-types-operators.spvasm new file mode 100644 index 0000000000..d1ecc56397 --- /dev/null +++ b/naga/tests/out/spv/abstract-types-operators.spvasm @@ -0,0 +1,127 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 100 +OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpDecorate %6 ArrayStride 4 +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%4 = OpTypeInt 32 1 +%5 = OpTypeInt 32 0 +%7 = OpConstant %5 64 +%6 = OpTypeArray %5 %7 +%8 = OpConstant %3 3.0 +%9 = OpConstant %4 3 +%10 = OpConstant %5 3 +%11 = OpConstant %5 0 +%12 = OpConstant %4 -2147483648 +%13 = OpConstant %3 -3.4028235e38 +%15 = OpTypePointer Workgroup %6 +%14 = OpVariable %15 Workgroup +%18 = OpTypeFunction %2 +%19 = OpConstant %3 42.0 +%20 = OpConstant %4 43 +%21 = OpConstant %5 44 +%22 = OpConstant %3 1.0 +%23 = OpConstant %3 2.0 +%24 = OpConstant %4 1 +%25 = OpConstant %4 2 +%26 = OpConstant %5 1 +%27 = OpConstant %5 2 +%29 = OpTypePointer Function %3 +%31 = OpTypePointer Function %4 +%33 = OpTypePointer Function %5 +%37 = OpConstantNull %3 +%41 = OpConstantNull %3 +%43 = OpConstantNull %3 +%45 = OpConstantNull %3 +%47 = OpConstantNull %3 +%50 = OpConstantNull %4 +%52 = OpConstantNull %4 +%54 = OpConstantNull %4 +%57 = OpConstantNull %5 +%59 = OpConstantNull %5 +%61 = OpConstantNull %5 +%90 = OpConstant %3 5.0 +%91 = OpConstant %3 7.0 +%97 = OpTypePointer Workgroup %5 +%17 = OpFunction %2 None %18 +%16 = OpLabel +%60 = OpVariable %33 Function %61 +%55 = OpVariable %33 Function %10 +%49 = OpVariable %31 Function %50 +%44 = OpVariable %29 Function %45 +%39 = OpVariable %29 Function %8 +%35 = OpVariable %29 Function %8 +%30 = OpVariable %31 Function %20 +%58 = OpVariable %33 Function %59 +%53 = OpVariable %31 Function %54 +%48 = OpVariable %31 Function %9 +%42 = OpVariable %29 Function %43 +%38 = OpVariable %29 Function %8 +%34 = OpVariable %29 Function %8 +%28 = OpVariable %29 Function %19 +%56 = OpVariable %33 Function %57 +%51 = OpVariable %31 Function %52 +%46 = OpVariable %29 Function %47 +%40 = OpVariable %29 Function %41 +%36 = OpVariable %29 Function %37 +%32 = OpVariable %33 Function %21 +OpBranch %62 +%62 = OpLabel +%63 = OpLoad %3 %28 +%64 = OpFAdd %3 %22 %63 +OpStore %36 %64 +%65 = OpLoad %3 %28 +%66 = OpFAdd %3 %22 %65 +OpStore %40 %66 +%67 = OpLoad %3 %28 +%68 = OpFAdd %3 %67 %23 +OpStore %42 %68 +%69 = OpLoad %3 %28 +%70 = OpFAdd %3 %69 %23 +OpStore %44 %70 +%71 = OpLoad %3 %28 +%72 = OpLoad %3 %28 +%73 = OpFAdd %3 %71 %72 +OpStore %46 %73 +%74 = OpLoad %4 %30 +%75 = OpIAdd %4 %24 %74 +OpStore %49 %75 +%76 = OpLoad %4 %30 +%77 = OpIAdd %4 %76 %25 +OpStore %51 %77 +%78 = OpLoad %4 %30 +%79 = OpLoad %4 %30 +%80 = OpIAdd %4 %78 %79 +OpStore %53 %80 +%81 = OpLoad %5 %32 +%82 = OpIAdd %5 %26 %81 +OpStore %56 %82 +%83 = OpLoad %5 %32 +%84 = OpIAdd %5 %83 %27 +OpStore %58 %84 +%85 = OpLoad %5 %32 +%86 = OpLoad %5 %32 +%87 = OpIAdd %5 %85 %86 +OpStore %60 %87 +OpReturn +OpFunctionEnd +%89 = OpFunction %2 None %18 +%88 = OpLabel +OpBranch %92 +%92 = OpLabel +OpReturn +OpFunctionEnd +%94 = OpFunction %2 None %18 +%93 = OpLabel +OpBranch %95 +%95 = OpLabel +%96 = OpISub %4 %24 %24 +%98 = OpAccessChain %97 %14 %96 +%99 = OpLoad %5 %98 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/abstract-types-var.spvasm b/naga/tests/out/spv/abstract-types-var.spvasm new file mode 100644 index 0000000000..1b4b0664b4 --- /dev/null +++ b/naga/tests/out/spv/abstract-types-var.spvasm @@ -0,0 +1,243 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 209 +OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpDecorate %10 ArrayStride 4 +OpDecorate %12 ArrayStride 4 +%2 = OpTypeVoid +%4 = OpTypeInt 32 1 +%3 = OpTypeVector %4 2 +%6 = OpTypeInt 32 0 +%5 = OpTypeVector %6 2 +%8 = OpTypeFloat 32 +%7 = OpTypeVector %8 2 +%9 = OpTypeMatrix %7 2 +%11 = OpConstant %6 2 +%10 = OpTypeArray %8 %11 +%12 = OpTypeArray %4 %11 +%13 = OpConstant %4 42 +%14 = OpConstant %4 43 +%15 = OpConstantComposite %3 %13 %14 +%16 = OpConstant %6 44 +%17 = OpConstant %6 45 +%18 = OpConstantComposite %5 %16 %17 +%19 = OpConstant %8 46.0 +%20 = OpConstant %8 47.0 +%21 = OpConstantComposite %7 %19 %20 +%22 = OpConstant %6 42 +%23 = OpConstant %6 43 +%24 = OpConstantComposite %5 %22 %23 +%25 = OpConstant %8 1.0 +%26 = OpConstant %8 2.0 +%27 = OpConstantComposite %7 %25 %26 +%28 = OpConstant %8 3.0 +%29 = OpConstant %8 4.0 +%30 = OpConstantComposite %7 %28 %29 +%31 = OpConstantComposite %9 %27 %30 +%32 = OpConstant %4 1 +%33 = OpConstantComposite %3 %32 %32 +%34 = OpConstantComposite %7 %25 %25 +%35 = OpConstant %6 1 +%36 = OpConstantComposite %5 %35 %35 +%37 = OpConstantComposite %10 %25 %26 +%38 = OpConstant %4 2 +%39 = OpConstantComposite %12 %32 %38 +%41 = OpTypePointer Private %3 +%40 = OpVariable %41 Private %15 +%43 = OpTypePointer Private %5 +%42 = OpVariable %43 Private %18 +%45 = OpTypePointer Private %7 +%44 = OpVariable %45 Private %21 +%46 = OpVariable %43 Private %24 +%47 = OpVariable %43 Private %24 +%48 = OpVariable %43 Private %24 +%49 = OpVariable %43 Private %24 +%51 = OpTypePointer Private %9 +%50 = OpVariable %51 Private %31 +%52 = OpVariable %51 Private %31 +%53 = OpVariable %51 Private %31 +%54 = OpVariable %51 Private %31 +%55 = OpVariable %51 Private %31 +%56 = OpVariable %41 Private %33 +%57 = OpVariable %45 Private %34 +%58 = OpVariable %41 Private %33 +%59 = OpVariable %43 Private %36 +%60 = OpVariable %45 Private %34 +%61 = OpVariable %45 Private %34 +%63 = OpTypePointer Private %10 +%62 = OpVariable %63 Private %37 +%64 = OpVariable %63 Private %37 +%66 = OpTypePointer Private %12 +%65 = OpVariable %66 Private %39 +%67 = OpVariable %63 Private %37 +%68 = OpVariable %63 Private %37 +%69 = OpVariable %63 Private %37 +%72 = OpTypeFunction %2 +%74 = OpTypePointer Function %3 +%76 = OpTypePointer Function %5 +%78 = OpTypePointer Function %7 +%84 = OpTypePointer Function %9 +%100 = OpTypePointer Function %10 +%105 = OpTypePointer Function %12 +%116 = OpTypePointer Function %6 +%117 = OpConstantNull %6 +%119 = OpTypePointer Function %4 +%120 = OpConstantNull %4 +%122 = OpTypePointer Function %8 +%123 = OpConstantNull %8 +%125 = OpConstantNull %5 +%127 = OpConstantNull %5 +%129 = OpConstantNull %5 +%131 = OpConstantNull %5 +%133 = OpConstantNull %9 +%135 = OpConstantNull %9 +%137 = OpConstantNull %9 +%139 = OpConstantNull %9 +%141 = OpConstantNull %10 +%143 = OpConstantNull %10 +%145 = OpConstantNull %10 +%147 = OpConstantNull %10 +%149 = OpConstantNull %12 +%151 = OpConstantNull %12 +%153 = OpConstantNull %10 +%155 = OpConstantNull %10 +%157 = OpConstantNull %10 +%159 = OpConstantNull %10 +%161 = OpConstantNull %12 +%163 = OpConstantNull %12 +%71 = OpFunction %2 None %72 +%70 = OpLabel +%109 = OpVariable %100 Function %37 +%106 = OpVariable %105 Function %39 +%102 = OpVariable %100 Function %37 +%98 = OpVariable %78 Function %34 +%95 = OpVariable %74 Function %33 +%92 = OpVariable %84 Function %31 +%89 = OpVariable %84 Function %31 +%86 = OpVariable %84 Function %31 +%82 = OpVariable %76 Function %24 +%79 = OpVariable %76 Function %24 +%73 = OpVariable %74 Function %15 +%110 = OpVariable %100 Function %37 +%107 = OpVariable %105 Function %39 +%103 = OpVariable %100 Function %37 +%99 = OpVariable %100 Function %37 +%96 = OpVariable %76 Function %36 +%93 = OpVariable %74 Function %33 +%90 = OpVariable %84 Function %31 +%87 = OpVariable %84 Function %31 +%83 = OpVariable %84 Function %31 +%80 = OpVariable %76 Function %24 +%75 = OpVariable %76 Function %18 +%111 = OpVariable %100 Function %37 +%108 = OpVariable %100 Function %37 +%104 = OpVariable %105 Function %39 +%101 = OpVariable %100 Function %37 +%97 = OpVariable %78 Function %34 +%94 = OpVariable %78 Function %34 +%91 = OpVariable %84 Function %31 +%88 = OpVariable %84 Function %31 +%85 = OpVariable %84 Function %31 +%81 = OpVariable %76 Function %24 +%77 = OpVariable %78 Function %21 +OpBranch %112 +%112 = OpLabel +OpReturn +OpFunctionEnd +%114 = OpFunction %2 None %72 +%113 = OpLabel +%162 = OpVariable %105 Function %163 +%156 = OpVariable %100 Function %157 +%150 = OpVariable %105 Function %151 +%144 = OpVariable %100 Function %145 +%138 = OpVariable %84 Function %139 +%132 = OpVariable %84 Function %133 +%126 = OpVariable %76 Function %127 +%118 = OpVariable %119 Function %120 +%160 = OpVariable %105 Function %161 +%154 = OpVariable %100 Function %155 +%148 = OpVariable %105 Function %149 +%142 = OpVariable %100 Function %143 +%136 = OpVariable %84 Function %137 +%130 = OpVariable %76 Function %131 +%124 = OpVariable %76 Function %125 +%115 = OpVariable %116 Function %117 +%158 = OpVariable %100 Function %159 +%152 = OpVariable %100 Function %153 +%146 = OpVariable %100 Function %147 +%140 = OpVariable %100 Function %141 +%134 = OpVariable %84 Function %135 +%128 = OpVariable %76 Function %129 +%121 = OpVariable %122 Function %123 +OpBranch %164 +%164 = OpLabel +%165 = OpLoad %6 %115 +%166 = OpCompositeConstruct %5 %165 %23 +OpStore %124 %166 +%167 = OpLoad %6 %115 +%168 = OpCompositeConstruct %5 %22 %167 +OpStore %126 %168 +%169 = OpLoad %6 %115 +%170 = OpCompositeConstruct %5 %169 %23 +OpStore %128 %170 +%171 = OpLoad %6 %115 +%172 = OpCompositeConstruct %5 %22 %171 +OpStore %130 %172 +%173 = OpLoad %8 %121 +%174 = OpCompositeConstruct %7 %173 %26 +%175 = OpCompositeConstruct %9 %174 %30 +OpStore %132 %175 +%176 = OpLoad %8 %121 +%177 = OpCompositeConstruct %7 %25 %176 +%178 = OpCompositeConstruct %9 %177 %30 +OpStore %134 %178 +%179 = OpLoad %8 %121 +%180 = OpCompositeConstruct %7 %179 %29 +%181 = OpCompositeConstruct %9 %27 %180 +OpStore %136 %181 +%182 = OpLoad %8 %121 +%183 = OpCompositeConstruct %7 %28 %182 +%184 = OpCompositeConstruct %9 %27 %183 +OpStore %138 %184 +%185 = OpLoad %8 %121 +%186 = OpCompositeConstruct %10 %185 %26 +OpStore %140 %186 +%187 = OpLoad %8 %121 +%188 = OpCompositeConstruct %10 %25 %187 +OpStore %142 %188 +%189 = OpLoad %8 %121 +%190 = OpCompositeConstruct %10 %189 %26 +OpStore %144 %190 +%191 = OpLoad %8 %121 +%192 = OpCompositeConstruct %10 %25 %191 +OpStore %146 %192 +%193 = OpLoad %4 %118 +%194 = OpCompositeConstruct %12 %193 %38 +OpStore %148 %194 +%195 = OpLoad %4 %118 +%196 = OpCompositeConstruct %12 %32 %195 +OpStore %150 %196 +%197 = OpLoad %8 %121 +%198 = OpCompositeConstruct %10 %197 %26 +OpStore %152 %198 +%199 = OpLoad %8 %121 +%200 = OpCompositeConstruct %10 %25 %199 +OpStore %154 %200 +%201 = OpLoad %8 %121 +%202 = OpCompositeConstruct %10 %201 %26 +OpStore %156 %202 +%203 = OpLoad %8 %121 +%204 = OpCompositeConstruct %10 %25 %203 +OpStore %158 %204 +%205 = OpLoad %4 %118 +%206 = OpCompositeConstruct %12 %205 %38 +OpStore %160 %206 +%207 = OpLoad %4 %118 +%208 = OpCompositeConstruct %12 %32 %207 +OpStore %162 %208 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/access.spvasm b/naga/tests/out/spv/access.spvasm new file mode 100644 index 0000000000..3446878c9a --- /dev/null +++ b/naga/tests/out/spv/access.spvasm @@ -0,0 +1,461 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 301 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %219 "foo_vert" %214 %217 +OpEntryPoint Fragment %273 "foo_frag" %272 +OpEntryPoint GLCompute %291 "assign_through_ptr" +OpExecutionMode %273 OriginUpperLeft +OpExecutionMode %291 LocalSize 1 1 1 +OpMemberName %6 0 "a" +OpMemberName %6 1 "b" +OpMemberName %6 2 "c" +OpName %6 "GlobalConst" +OpMemberName %7 0 "value" +OpName %7 "AlignedWrapper" +OpMemberName %20 0 "_matrix" +OpMemberName %20 1 "matrix_array" +OpMemberName %20 2 "atom" +OpMemberName %20 3 "atom_arr" +OpMemberName %20 4 "arr" +OpMemberName %20 5 "data" +OpName %20 "Bar" +OpMemberName %22 0 "m" +OpName %22 "Baz" +OpMemberName %26 0 "am" +OpName %26 "MatCx2InArray" +OpName %40 "global_const" +OpName %42 "bar" +OpName %44 "baz" +OpName %47 "qux" +OpName %50 "nested_mat_cx2" +OpName %54 "test_matrix_within_struct_accesses" +OpName %82 "idx" +OpName %84 "t" +OpName %130 "test_matrix_within_array_within_struct_accesses" +OpName %140 "idx" +OpName %141 "t" +OpName %187 "foo" +OpName %188 "read_from_private" +OpName %193 "a" +OpName %194 "test_arr_as_arg" +OpName %200 "p" +OpName %201 "assign_through_ptr_fn" +OpName %206 "foo" +OpName %207 "assign_array_through_ptr_fn" +OpName %214 "vi" +OpName %219 "foo_vert" +OpName %231 "foo" +OpName %232 "c2" +OpName %273 "foo_frag" +OpName %291 "assign_through_ptr" +OpName %296 "val" +OpName %297 "arr" +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 16 +OpMemberDecorate %6 2 Offset 28 +OpMemberDecorate %7 0 Offset 0 +OpDecorate %13 ArrayStride 16 +OpDecorate %15 ArrayStride 4 +OpDecorate %18 ArrayStride 8 +OpDecorate %19 ArrayStride 8 +OpMemberDecorate %20 0 Offset 0 +OpMemberDecorate %20 0 ColMajor +OpMemberDecorate %20 0 MatrixStride 16 +OpMemberDecorate %20 1 Offset 64 +OpMemberDecorate %20 1 ColMajor +OpMemberDecorate %20 1 MatrixStride 8 +OpMemberDecorate %20 2 Offset 96 +OpMemberDecorate %20 3 Offset 100 +OpMemberDecorate %20 4 Offset 144 +OpMemberDecorate %20 5 Offset 160 +OpDecorate %20 Block +OpMemberDecorate %22 0 Offset 0 +OpMemberDecorate %22 0 ColMajor +OpMemberDecorate %22 0 MatrixStride 8 +OpDecorate %25 ArrayStride 32 +OpMemberDecorate %26 0 Offset 0 +OpMemberDecorate %26 0 ColMajor +OpMemberDecorate %26 0 MatrixStride 8 +OpDecorate %28 ArrayStride 4 +OpDecorate %29 ArrayStride 40 +OpDecorate %32 ArrayStride 4 +OpDecorate %34 ArrayStride 16 +OpDecorate %42 DescriptorSet 0 +OpDecorate %42 Binding 0 +OpDecorate %44 DescriptorSet 0 +OpDecorate %44 Binding 1 +OpDecorate %45 Block +OpMemberDecorate %45 0 Offset 0 +OpDecorate %47 DescriptorSet 0 +OpDecorate %47 Binding 2 +OpDecorate %48 Block +OpMemberDecorate %48 0 Offset 0 +OpDecorate %50 DescriptorSet 0 +OpDecorate %50 Binding 3 +OpDecorate %51 Block +OpMemberDecorate %51 0 Offset 0 +OpDecorate %214 BuiltIn VertexIndex +OpDecorate %217 BuiltIn Position +OpDecorate %272 Location 0 +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeVector %3 3 +%5 = OpTypeInt 32 1 +%6 = OpTypeStruct %3 %4 %5 +%7 = OpTypeStruct %5 +%10 = OpTypeFloat 32 +%9 = OpTypeVector %10 3 +%8 = OpTypeMatrix %9 4 +%12 = OpTypeVector %10 2 +%11 = OpTypeMatrix %12 2 +%14 = OpConstant %3 2 +%13 = OpTypeArray %11 %14 +%16 = OpConstant %3 10 +%15 = OpTypeArray %5 %16 +%17 = OpTypeVector %3 2 +%18 = OpTypeArray %17 %14 +%19 = OpTypeRuntimeArray %7 +%20 = OpTypeStruct %8 %13 %5 %15 %18 %19 +%21 = OpTypeMatrix %12 3 +%22 = OpTypeStruct %21 +%23 = OpTypeVector %5 2 +%24 = OpTypeMatrix %12 4 +%25 = OpTypeArray %24 %14 +%26 = OpTypeStruct %25 +%27 = OpTypePointer Function %10 +%28 = OpTypeArray %10 %16 +%30 = OpConstant %3 5 +%29 = OpTypeArray %28 %30 +%31 = OpTypeVector %10 4 +%32 = OpTypeArray %5 %30 +%33 = OpTypePointer Function %3 +%34 = OpTypeArray %31 %14 +%35 = OpTypePointer Function %34 +%36 = OpConstant %3 0 +%37 = OpConstantComposite %4 %36 %36 %36 +%38 = OpConstant %5 0 +%39 = OpConstantComposite %6 %36 %37 %38 +%41 = OpTypePointer Private %6 +%40 = OpVariable %41 Private %39 +%43 = OpTypePointer StorageBuffer %20 +%42 = OpVariable %43 StorageBuffer +%45 = OpTypeStruct %22 +%46 = OpTypePointer Uniform %45 +%44 = OpVariable %46 Uniform +%48 = OpTypeStruct %23 +%49 = OpTypePointer StorageBuffer %48 +%47 = OpVariable %49 StorageBuffer +%51 = OpTypeStruct %26 +%52 = OpTypePointer Uniform %51 +%50 = OpVariable %52 Uniform +%55 = OpTypeFunction %2 +%56 = OpTypePointer Uniform %22 +%58 = OpConstant %5 1 +%59 = OpConstant %10 1.0 +%60 = OpConstantComposite %12 %59 %59 +%61 = OpConstant %10 2.0 +%62 = OpConstantComposite %12 %61 %61 +%63 = OpConstant %10 3.0 +%64 = OpConstantComposite %12 %63 %63 +%65 = OpConstantComposite %21 %60 %62 %64 +%66 = OpConstantComposite %22 %65 +%67 = OpConstant %10 6.0 +%68 = OpConstantComposite %12 %67 %67 +%69 = OpConstant %10 5.0 +%70 = OpConstantComposite %12 %69 %69 +%71 = OpConstant %10 4.0 +%72 = OpConstantComposite %12 %71 %71 +%73 = OpConstantComposite %21 %68 %70 %72 +%74 = OpConstant %10 9.0 +%75 = OpConstantComposite %12 %74 %74 +%76 = OpConstant %10 90.0 +%77 = OpConstantComposite %12 %76 %76 +%78 = OpConstant %10 10.0 +%79 = OpConstant %10 20.0 +%80 = OpConstant %10 30.0 +%81 = OpConstant %10 40.0 +%83 = OpTypePointer Function %5 +%85 = OpTypePointer Function %22 +%89 = OpTypePointer Uniform %21 +%92 = OpTypePointer Uniform %12 +%98 = OpTypePointer Uniform %10 +%99 = OpConstant %3 1 +%114 = OpTypePointer Function %21 +%116 = OpTypePointer Function %12 +%120 = OpTypePointer Function %10 +%131 = OpTypePointer Uniform %26 +%133 = OpConstantNull %25 +%134 = OpConstantComposite %26 %133 +%135 = OpConstant %10 8.0 +%136 = OpConstantComposite %12 %135 %135 +%137 = OpConstant %10 7.0 +%138 = OpConstantComposite %12 %137 %137 +%139 = OpConstantComposite %24 %136 %138 %68 %70 +%142 = OpTypePointer Function %26 +%146 = OpTypePointer Uniform %25 +%149 = OpTypePointer Uniform %24 +%171 = OpTypePointer Function %25 +%173 = OpTypePointer Function %24 +%189 = OpTypeFunction %10 %27 +%195 = OpTypeFunction %10 %29 +%202 = OpTypeFunction %2 %33 +%203 = OpConstant %3 42 +%208 = OpTypeFunction %2 %35 +%209 = OpConstantComposite %31 %59 %59 %59 %59 +%210 = OpConstantComposite %31 %61 %61 %61 %61 +%211 = OpConstantComposite %34 %209 %210 +%215 = OpTypePointer Input %3 +%214 = OpVariable %215 Input +%218 = OpTypePointer Output %31 +%217 = OpVariable %218 Output +%221 = OpTypePointer StorageBuffer %23 +%224 = OpConstant %10 0.0 +%225 = OpConstant %3 3 +%226 = OpConstant %5 3 +%227 = OpConstant %5 4 +%228 = OpConstant %5 5 +%229 = OpConstant %5 42 +%230 = OpConstantNull %29 +%233 = OpTypePointer Function %32 +%234 = OpConstantNull %32 +%239 = OpTypePointer StorageBuffer %8 +%242 = OpTypePointer StorageBuffer %18 +%243 = OpConstant %3 4 +%246 = OpTypePointer StorageBuffer %9 +%247 = OpTypePointer StorageBuffer %10 +%250 = OpTypePointer StorageBuffer %19 +%253 = OpTypePointer StorageBuffer %7 +%254 = OpTypePointer StorageBuffer %5 +%266 = OpTypeVector %5 4 +%272 = OpVariable %218 Output +%275 = OpConstantComposite %9 %224 %224 %224 +%276 = OpConstantComposite %9 %59 %59 %59 +%277 = OpConstantComposite %9 %61 %61 %61 +%278 = OpConstantComposite %9 %63 %63 %63 +%279 = OpConstantComposite %8 %275 %276 %277 %278 +%280 = OpConstantComposite %17 %36 %36 +%281 = OpConstantComposite %17 %99 %99 +%282 = OpConstantComposite %18 %280 %281 +%283 = OpConstantNull %23 +%284 = OpConstantComposite %31 %224 %224 %224 %224 +%292 = OpConstant %3 33 +%293 = OpConstantComposite %31 %67 %67 %67 %67 +%294 = OpConstantComposite %31 %137 %137 %137 %137 +%295 = OpConstantComposite %34 %293 %294 +%54 = OpFunction %2 None %55 +%53 = OpLabel +%82 = OpVariable %83 Function %58 +%84 = OpVariable %85 Function %66 +%57 = OpAccessChain %56 %44 %36 +OpBranch %86 +%86 = OpLabel +%87 = OpLoad %5 %82 +%88 = OpISub %5 %87 %58 +OpStore %82 %88 +%90 = OpAccessChain %89 %57 %36 +%91 = OpLoad %21 %90 +%93 = OpAccessChain %92 %57 %36 %36 +%94 = OpLoad %12 %93 +%95 = OpLoad %5 %82 +%96 = OpAccessChain %92 %57 %36 %95 +%97 = OpLoad %12 %96 +%100 = OpAccessChain %98 %57 %36 %36 %99 +%101 = OpLoad %10 %100 +%102 = OpLoad %5 %82 +%103 = OpAccessChain %98 %57 %36 %36 %102 +%104 = OpLoad %10 %103 +%105 = OpLoad %5 %82 +%106 = OpAccessChain %98 %57 %36 %105 %99 +%107 = OpLoad %10 %106 +%108 = OpLoad %5 %82 +%109 = OpLoad %5 %82 +%110 = OpAccessChain %98 %57 %36 %108 %109 +%111 = OpLoad %10 %110 +%112 = OpLoad %5 %82 +%113 = OpIAdd %5 %112 %58 +OpStore %82 %113 +%115 = OpAccessChain %114 %84 %36 +OpStore %115 %73 +%117 = OpAccessChain %116 %84 %36 %36 +OpStore %117 %75 +%118 = OpLoad %5 %82 +%119 = OpAccessChain %116 %84 %36 %118 +OpStore %119 %77 +%121 = OpAccessChain %120 %84 %36 %36 %99 +OpStore %121 %78 +%122 = OpLoad %5 %82 +%123 = OpAccessChain %120 %84 %36 %36 %122 +OpStore %123 %79 +%124 = OpLoad %5 %82 +%125 = OpAccessChain %120 %84 %36 %124 %99 +OpStore %125 %80 +%126 = OpLoad %5 %82 +%127 = OpLoad %5 %82 +%128 = OpAccessChain %120 %84 %36 %126 %127 +OpStore %128 %81 +OpReturn +OpFunctionEnd +%130 = OpFunction %2 None %55 +%129 = OpLabel +%140 = OpVariable %83 Function %58 +%141 = OpVariable %142 Function %134 +%132 = OpAccessChain %131 %50 %36 +OpBranch %143 +%143 = OpLabel +%144 = OpLoad %5 %140 +%145 = OpISub %5 %144 %58 +OpStore %140 %145 +%147 = OpAccessChain %146 %132 %36 +%148 = OpLoad %25 %147 +%150 = OpAccessChain %149 %132 %36 %36 +%151 = OpLoad %24 %150 +%152 = OpAccessChain %92 %132 %36 %36 %36 +%153 = OpLoad %12 %152 +%154 = OpLoad %5 %140 +%155 = OpAccessChain %92 %132 %36 %36 %154 +%156 = OpLoad %12 %155 +%157 = OpAccessChain %98 %132 %36 %36 %36 %99 +%158 = OpLoad %10 %157 +%159 = OpLoad %5 %140 +%160 = OpAccessChain %98 %132 %36 %36 %36 %159 +%161 = OpLoad %10 %160 +%162 = OpLoad %5 %140 +%163 = OpAccessChain %98 %132 %36 %36 %162 %99 +%164 = OpLoad %10 %163 +%165 = OpLoad %5 %140 +%166 = OpLoad %5 %140 +%167 = OpAccessChain %98 %132 %36 %36 %165 %166 +%168 = OpLoad %10 %167 +%169 = OpLoad %5 %140 +%170 = OpIAdd %5 %169 %58 +OpStore %140 %170 +%172 = OpAccessChain %171 %141 %36 +OpStore %172 %133 +%174 = OpAccessChain %173 %141 %36 %36 +OpStore %174 %139 +%175 = OpAccessChain %116 %141 %36 %36 %36 +OpStore %175 %75 +%176 = OpLoad %5 %140 +%177 = OpAccessChain %116 %141 %36 %36 %176 +OpStore %177 %77 +%178 = OpAccessChain %120 %141 %36 %36 %36 %99 +OpStore %178 %78 +%179 = OpLoad %5 %140 +%180 = OpAccessChain %120 %141 %36 %36 %36 %179 +OpStore %180 %79 +%181 = OpLoad %5 %140 +%182 = OpAccessChain %120 %141 %36 %36 %181 %99 +OpStore %182 %80 +%183 = OpLoad %5 %140 +%184 = OpLoad %5 %140 +%185 = OpAccessChain %120 %141 %36 %36 %183 %184 +OpStore %185 %81 +OpReturn +OpFunctionEnd +%188 = OpFunction %10 None %189 +%187 = OpFunctionParameter %27 +%186 = OpLabel +OpBranch %190 +%190 = OpLabel +%191 = OpLoad %10 %187 +OpReturnValue %191 +OpFunctionEnd +%194 = OpFunction %10 None %195 +%193 = OpFunctionParameter %29 +%192 = OpLabel +OpBranch %196 +%196 = OpLabel +%197 = OpCompositeExtract %28 %193 4 +%198 = OpCompositeExtract %10 %197 9 +OpReturnValue %198 +OpFunctionEnd +%201 = OpFunction %2 None %202 +%200 = OpFunctionParameter %33 +%199 = OpLabel +OpBranch %204 +%204 = OpLabel +OpStore %200 %203 +OpReturn +OpFunctionEnd +%207 = OpFunction %2 None %208 +%206 = OpFunctionParameter %35 +%205 = OpLabel +OpBranch %212 +%212 = OpLabel +OpStore %206 %211 +OpReturn +OpFunctionEnd +%219 = OpFunction %2 None %55 +%213 = OpLabel +%231 = OpVariable %27 Function %224 +%232 = OpVariable %233 Function %234 +%216 = OpLoad %3 %214 +%220 = OpAccessChain %56 %44 %36 +%222 = OpAccessChain %221 %47 %36 +%223 = OpAccessChain %131 %50 %36 +OpBranch %235 +%235 = OpLabel +%236 = OpLoad %10 %231 +OpStore %231 %59 +%237 = OpFunctionCall %2 %54 +%238 = OpFunctionCall %2 %130 +%240 = OpAccessChain %239 %42 %36 +%241 = OpLoad %8 %240 +%244 = OpAccessChain %242 %42 %243 +%245 = OpLoad %18 %244 +%248 = OpAccessChain %247 %42 %36 %225 %36 +%249 = OpLoad %10 %248 +%251 = OpArrayLength %3 %42 5 +%252 = OpISub %3 %251 %14 +%255 = OpAccessChain %254 %42 %30 %252 %36 +%256 = OpLoad %5 %255 +%257 = OpLoad %23 %222 +%258 = OpFunctionCall %10 %188 %231 +%259 = OpConvertFToS %5 %249 +%260 = OpCompositeConstruct %32 %256 %259 %226 %227 %228 +OpStore %232 %260 +%261 = OpIAdd %3 %216 %99 +%262 = OpAccessChain %83 %232 %261 +OpStore %262 %229 +%263 = OpAccessChain %83 %232 %216 +%264 = OpLoad %5 %263 +%265 = OpFunctionCall %10 %194 %230 +%267 = OpCompositeConstruct %266 %264 %264 %264 %264 +%268 = OpConvertSToF %31 %267 +%269 = OpMatrixTimesVector %9 %241 %268 +%270 = OpCompositeConstruct %31 %269 %61 +OpStore %217 %270 +OpReturn +OpFunctionEnd +%273 = OpFunction %2 None %55 +%271 = OpLabel +%274 = OpAccessChain %221 %47 %36 +OpBranch %285 +%285 = OpLabel +%286 = OpAccessChain %247 %42 %36 %99 %14 +OpStore %286 %59 +%287 = OpAccessChain %239 %42 %36 +OpStore %287 %279 +%288 = OpAccessChain %242 %42 %243 +OpStore %288 %282 +%289 = OpAccessChain %254 %42 %30 %99 %36 +OpStore %289 %58 +OpStore %274 %283 +OpStore %272 %284 +OpReturn +OpFunctionEnd +%291 = OpFunction %2 None %55 +%290 = OpLabel +%296 = OpVariable %33 Function %292 +%297 = OpVariable %35 Function %295 +OpBranch %298 +%298 = OpLabel +%299 = OpFunctionCall %2 %201 %296 +%300 = OpFunctionCall %2 %207 %297 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/array-in-ctor.spvasm b/naga/tests/out/spv/array-in-ctor.spvasm new file mode 100644 index 0000000000..aca4ee6824 --- /dev/null +++ b/naga/tests/out/spv/array-in-ctor.spvasm @@ -0,0 +1,37 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 19 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %12 "cs_main" +OpExecutionMode %12 LocalSize 1 1 1 +OpDecorate %4 ArrayStride 4 +OpMemberDecorate %7 0 Offset 0 +OpDecorate %8 NonWritable +OpDecorate %8 DescriptorSet 0 +OpDecorate %8 Binding 0 +OpDecorate %9 Block +OpMemberDecorate %9 0 Offset 0 +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%6 = OpTypeInt 32 0 +%5 = OpConstant %6 2 +%4 = OpTypeArray %3 %5 +%7 = OpTypeStruct %4 +%9 = OpTypeStruct %7 +%10 = OpTypePointer StorageBuffer %9 +%8 = OpVariable %10 StorageBuffer +%13 = OpTypeFunction %2 +%14 = OpTypePointer StorageBuffer %7 +%15 = OpConstant %6 0 +%12 = OpFunction %2 None %13 +%11 = OpLabel +%16 = OpAccessChain %14 %8 %15 +OpBranch %17 +%17 = OpLabel +%18 = OpLoad %7 %16 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/array-in-function-return-type.spvasm b/naga/tests/out/spv/array-in-function-return-type.spvasm new file mode 100644 index 0000000000..79e94fba8a --- /dev/null +++ b/naga/tests/out/spv/array-in-function-return-type.spvasm @@ -0,0 +1,42 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 26 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %18 "main" %16 +OpExecutionMode %18 OriginUpperLeft +OpDecorate %4 ArrayStride 4 +OpDecorate %16 Location 0 +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%6 = OpTypeInt 32 0 +%5 = OpConstant %6 2 +%4 = OpTypeArray %3 %5 +%7 = OpTypeVector %3 4 +%10 = OpTypeFunction %4 +%11 = OpConstant %3 1.0 +%12 = OpConstant %3 2.0 +%13 = OpConstantComposite %4 %11 %12 +%17 = OpTypePointer Output %7 +%16 = OpVariable %17 Output +%19 = OpTypeFunction %2 +%20 = OpConstant %3 0.0 +%9 = OpFunction %4 None %10 +%8 = OpLabel +OpBranch %14 +%14 = OpLabel +OpReturnValue %13 +OpFunctionEnd +%18 = OpFunction %2 None %19 +%15 = OpLabel +OpBranch %21 +%21 = OpLabel +%22 = OpFunctionCall %4 %9 +%23 = OpCompositeExtract %3 %22 0 +%24 = OpCompositeExtract %3 %22 1 +%25 = OpCompositeConstruct %7 %23 %24 %20 %11 +OpStore %16 %25 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/atomicCompareExchange.spvasm b/naga/tests/out/spv/atomicCompareExchange.spvasm new file mode 100644 index 0000000000..bfd4591d49 --- /dev/null +++ b/naga/tests/out/spv/atomicCompareExchange.spvasm @@ -0,0 +1,204 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 124 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %18 "test_atomic_compare_exchange_i32" +OpEntryPoint GLCompute %77 "test_atomic_compare_exchange_u32" +OpExecutionMode %18 LocalSize 1 1 1 +OpExecutionMode %77 LocalSize 1 1 1 +OpDecorate %5 ArrayStride 4 +OpDecorate %7 ArrayStride 4 +OpMemberDecorate %9 0 Offset 0 +OpMemberDecorate %9 1 Offset 4 +OpMemberDecorate %10 0 Offset 0 +OpMemberDecorate %10 1 Offset 4 +OpDecorate %11 DescriptorSet 0 +OpDecorate %11 Binding 0 +OpDecorate %12 Block +OpMemberDecorate %12 0 Offset 0 +OpDecorate %14 DescriptorSet 0 +OpDecorate %14 Binding 1 +OpDecorate %15 Block +OpMemberDecorate %15 0 Offset 0 +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeInt 32 1 +%6 = OpConstant %3 128 +%5 = OpTypeArray %4 %6 +%7 = OpTypeArray %3 %6 +%8 = OpTypeBool +%9 = OpTypeStruct %4 %8 +%10 = OpTypeStruct %3 %8 +%12 = OpTypeStruct %5 +%13 = OpTypePointer StorageBuffer %12 +%11 = OpVariable %13 StorageBuffer +%15 = OpTypeStruct %7 +%16 = OpTypePointer StorageBuffer %15 +%14 = OpVariable %16 StorageBuffer +%19 = OpTypeFunction %2 +%20 = OpTypePointer StorageBuffer %5 +%21 = OpConstant %3 0 +%23 = OpConstantFalse %8 +%24 = OpTypeFloat 32 +%25 = OpConstant %24 1.0 +%26 = OpConstant %3 1 +%28 = OpTypePointer Function %3 +%30 = OpTypePointer Function %4 +%31 = OpConstantNull %4 +%33 = OpTypePointer Function %8 +%34 = OpConstantNull %8 +%47 = OpTypePointer StorageBuffer %4 +%50 = OpConstant %4 1 +%51 = OpConstant %3 64 +%78 = OpTypePointer StorageBuffer %7 +%82 = OpConstantNull %3 +%84 = OpConstantNull %8 +%97 = OpTypePointer StorageBuffer %3 +%18 = OpFunction %2 None %19 +%17 = OpLabel +%27 = OpVariable %28 Function %21 +%29 = OpVariable %30 Function %31 +%32 = OpVariable %33 Function %34 +%22 = OpAccessChain %20 %11 %21 +OpBranch %35 +%35 = OpLabel +OpBranch %36 +%36 = OpLabel +OpLoopMerge %37 %39 None +OpBranch %38 +%38 = OpLabel +%40 = OpLoad %3 %27 +%41 = OpULessThan %8 %40 %6 +OpSelectionMerge %42 None +OpBranchConditional %41 %42 %43 +%43 = OpLabel +OpBranch %37 +%42 = OpLabel +OpBranch %44 +%44 = OpLabel +%46 = OpLoad %3 %27 +%48 = OpAccessChain %47 %22 %46 +%49 = OpAtomicLoad %4 %48 %50 %51 +OpStore %29 %49 +OpStore %32 %23 +OpBranch %52 +%52 = OpLabel +OpLoopMerge %53 %55 None +OpBranch %54 +%54 = OpLabel +%56 = OpLoad %8 %32 +%57 = OpLogicalNot %8 %56 +OpSelectionMerge %58 None +OpBranchConditional %57 %58 %59 +%59 = OpLabel +OpBranch %53 +%58 = OpLabel +OpBranch %60 +%60 = OpLabel +%62 = OpLoad %4 %29 +%63 = OpBitcast %24 %62 +%64 = OpFAdd %24 %63 %25 +%65 = OpBitcast %4 %64 +%66 = OpLoad %3 %27 +%67 = OpLoad %4 %29 +%69 = OpAccessChain %47 %22 %66 +%70 = OpAtomicCompareExchange %4 %69 %50 %51 %51 %65 %67 +%71 = OpIEqual %8 %70 %67 +%68 = OpCompositeConstruct %9 %70 %71 +%72 = OpCompositeExtract %4 %68 0 +OpStore %29 %72 +%73 = OpCompositeExtract %8 %68 1 +OpStore %32 %73 +OpBranch %61 +%61 = OpLabel +OpBranch %55 +%55 = OpLabel +OpBranch %52 +%53 = OpLabel +OpBranch %45 +%45 = OpLabel +OpBranch %39 +%39 = OpLabel +%74 = OpLoad %3 %27 +%75 = OpIAdd %3 %74 %26 +OpStore %27 %75 +OpBranch %36 +%37 = OpLabel +OpReturn +OpFunctionEnd +%77 = OpFunction %2 None %19 +%76 = OpLabel +%80 = OpVariable %28 Function %21 +%81 = OpVariable %28 Function %82 +%83 = OpVariable %33 Function %84 +%79 = OpAccessChain %78 %14 %21 +OpBranch %85 +%85 = OpLabel +OpBranch %86 +%86 = OpLabel +OpLoopMerge %87 %89 None +OpBranch %88 +%88 = OpLabel +%90 = OpLoad %3 %80 +%91 = OpULessThan %8 %90 %6 +OpSelectionMerge %92 None +OpBranchConditional %91 %92 %93 +%93 = OpLabel +OpBranch %87 +%92 = OpLabel +OpBranch %94 +%94 = OpLabel +%96 = OpLoad %3 %80 +%98 = OpAccessChain %97 %79 %96 +%99 = OpAtomicLoad %3 %98 %50 %51 +OpStore %81 %99 +OpStore %83 %23 +OpBranch %100 +%100 = OpLabel +OpLoopMerge %101 %103 None +OpBranch %102 +%102 = OpLabel +%104 = OpLoad %8 %83 +%105 = OpLogicalNot %8 %104 +OpSelectionMerge %106 None +OpBranchConditional %105 %106 %107 +%107 = OpLabel +OpBranch %101 +%106 = OpLabel +OpBranch %108 +%108 = OpLabel +%110 = OpLoad %3 %81 +%111 = OpBitcast %24 %110 +%112 = OpFAdd %24 %111 %25 +%113 = OpBitcast %3 %112 +%114 = OpLoad %3 %80 +%115 = OpLoad %3 %81 +%117 = OpAccessChain %97 %79 %114 +%118 = OpAtomicCompareExchange %3 %117 %50 %51 %51 %113 %115 +%119 = OpIEqual %8 %118 %115 +%116 = OpCompositeConstruct %10 %118 %119 +%120 = OpCompositeExtract %3 %116 0 +OpStore %81 %120 +%121 = OpCompositeExtract %8 %116 1 +OpStore %83 %121 +OpBranch %109 +%109 = OpLabel +OpBranch %103 +%103 = OpLabel +OpBranch %100 +%101 = OpLabel +OpBranch %95 +%95 = OpLabel +OpBranch %89 +%89 = OpLabel +%122 = OpLoad %3 %80 +%123 = OpIAdd %3 %122 %26 +OpStore %80 %123 +OpBranch %86 +%87 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/atomicOps.spvasm b/naga/tests/out/spv/atomicOps.spvasm new file mode 100644 index 0000000000..de4d687824 --- /dev/null +++ b/naga/tests/out/spv/atomicOps.spvasm @@ -0,0 +1,240 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 189 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %28 "cs_main" %25 +OpExecutionMode %28 LocalSize 2 1 1 +OpDecorate %5 ArrayStride 4 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 4 +OpDecorate %9 DescriptorSet 0 +OpDecorate %9 Binding 0 +OpDecorate %10 Block +OpMemberDecorate %10 0 Offset 0 +OpDecorate %12 DescriptorSet 0 +OpDecorate %12 Binding 1 +OpDecorate %13 Block +OpMemberDecorate %13 0 Offset 0 +OpDecorate %15 DescriptorSet 0 +OpDecorate %15 Binding 2 +OpDecorate %16 Block +OpMemberDecorate %16 0 Offset 0 +OpDecorate %25 BuiltIn LocalInvocationId +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeInt 32 1 +%6 = OpConstant %3 2 +%5 = OpTypeArray %4 %6 +%7 = OpTypeStruct %3 %5 +%8 = OpTypeVector %3 3 +%10 = OpTypeStruct %3 +%11 = OpTypePointer StorageBuffer %10 +%9 = OpVariable %11 StorageBuffer +%13 = OpTypeStruct %5 +%14 = OpTypePointer StorageBuffer %13 +%12 = OpVariable %14 StorageBuffer +%16 = OpTypeStruct %7 +%17 = OpTypePointer StorageBuffer %16 +%15 = OpVariable %17 StorageBuffer +%19 = OpTypePointer Workgroup %3 +%18 = OpVariable %19 Workgroup +%21 = OpTypePointer Workgroup %5 +%20 = OpVariable %21 Workgroup +%23 = OpTypePointer Workgroup %7 +%22 = OpVariable %23 Workgroup +%26 = OpTypePointer Input %8 +%25 = OpVariable %26 Input +%29 = OpTypeFunction %2 +%30 = OpTypePointer StorageBuffer %3 +%31 = OpConstant %3 0 +%33 = OpTypePointer StorageBuffer %5 +%35 = OpTypePointer StorageBuffer %7 +%37 = OpConstant %3 1 +%38 = OpConstant %4 1 +%40 = OpConstantNull %3 +%41 = OpConstantNull %5 +%42 = OpConstantNull %7 +%43 = OpConstantNull %8 +%45 = OpTypeBool +%44 = OpTypeVector %45 3 +%50 = OpConstant %3 264 +%52 = OpConstant %3 64 +%53 = OpTypePointer StorageBuffer %4 +%57 = OpConstant %4 2 +%58 = OpConstant %3 256 +%59 = OpTypePointer Workgroup %4 +%28 = OpFunction %2 None %29 +%24 = OpLabel +%27 = OpLoad %8 %25 +%32 = OpAccessChain %30 %9 %31 +%34 = OpAccessChain %33 %12 %31 +%36 = OpAccessChain %35 %15 %31 +OpBranch %39 +%39 = OpLabel +%46 = OpIEqual %44 %27 %43 +%47 = OpAll %45 %46 +OpSelectionMerge %48 None +OpBranchConditional %47 %49 %48 +%49 = OpLabel +OpStore %18 %40 +OpStore %20 %41 +OpStore %22 %42 +OpBranch %48 +%48 = OpLabel +OpControlBarrier %6 %6 %50 +OpBranch %51 +%51 = OpLabel +OpAtomicStore %32 %38 %52 %37 +%54 = OpAccessChain %53 %34 %37 +OpAtomicStore %54 %38 %52 %38 +%55 = OpAccessChain %30 %36 %31 +OpAtomicStore %55 %38 %52 %37 +%56 = OpAccessChain %53 %36 %37 %37 +OpAtomicStore %56 %38 %52 %38 +OpAtomicStore %18 %57 %58 %37 +%60 = OpAccessChain %59 %20 %37 +OpAtomicStore %60 %57 %58 %38 +%61 = OpAccessChain %19 %22 %31 +OpAtomicStore %61 %57 %58 %37 +%62 = OpAccessChain %59 %22 %37 %37 +OpAtomicStore %62 %57 %58 %38 +OpControlBarrier %6 %6 %50 +%63 = OpAtomicLoad %3 %32 %38 %52 +%64 = OpAccessChain %53 %34 %37 +%65 = OpAtomicLoad %4 %64 %38 %52 +%66 = OpAccessChain %30 %36 %31 +%67 = OpAtomicLoad %3 %66 %38 %52 +%68 = OpAccessChain %53 %36 %37 %37 +%69 = OpAtomicLoad %4 %68 %38 %52 +%70 = OpAtomicLoad %3 %18 %57 %58 +%71 = OpAccessChain %59 %20 %37 +%72 = OpAtomicLoad %4 %71 %57 %58 +%73 = OpAccessChain %19 %22 %31 +%74 = OpAtomicLoad %3 %73 %57 %58 +%75 = OpAccessChain %59 %22 %37 %37 +%76 = OpAtomicLoad %4 %75 %57 %58 +OpControlBarrier %6 %6 %50 +%77 = OpAtomicIAdd %3 %32 %38 %52 %37 +%79 = OpAccessChain %53 %34 %37 +%78 = OpAtomicIAdd %4 %79 %38 %52 %38 +%81 = OpAccessChain %30 %36 %31 +%80 = OpAtomicIAdd %3 %81 %38 %52 %37 +%83 = OpAccessChain %53 %36 %37 %37 +%82 = OpAtomicIAdd %4 %83 %38 %52 %38 +%84 = OpAtomicIAdd %3 %18 %57 %58 %37 +%86 = OpAccessChain %59 %20 %37 +%85 = OpAtomicIAdd %4 %86 %57 %58 %38 +%88 = OpAccessChain %19 %22 %31 +%87 = OpAtomicIAdd %3 %88 %57 %58 %37 +%90 = OpAccessChain %59 %22 %37 %37 +%89 = OpAtomicIAdd %4 %90 %57 %58 %38 +OpControlBarrier %6 %6 %50 +%91 = OpAtomicISub %3 %32 %38 %52 %37 +%93 = OpAccessChain %53 %34 %37 +%92 = OpAtomicISub %4 %93 %38 %52 %38 +%95 = OpAccessChain %30 %36 %31 +%94 = OpAtomicISub %3 %95 %38 %52 %37 +%97 = OpAccessChain %53 %36 %37 %37 +%96 = OpAtomicISub %4 %97 %38 %52 %38 +%98 = OpAtomicISub %3 %18 %57 %58 %37 +%100 = OpAccessChain %59 %20 %37 +%99 = OpAtomicISub %4 %100 %57 %58 %38 +%102 = OpAccessChain %19 %22 %31 +%101 = OpAtomicISub %3 %102 %57 %58 %37 +%104 = OpAccessChain %59 %22 %37 %37 +%103 = OpAtomicISub %4 %104 %57 %58 %38 +OpControlBarrier %6 %6 %50 +%105 = OpAtomicUMax %3 %32 %38 %52 %37 +%107 = OpAccessChain %53 %34 %37 +%106 = OpAtomicSMax %4 %107 %38 %52 %38 +%109 = OpAccessChain %30 %36 %31 +%108 = OpAtomicUMax %3 %109 %38 %52 %37 +%111 = OpAccessChain %53 %36 %37 %37 +%110 = OpAtomicSMax %4 %111 %38 %52 %38 +%112 = OpAtomicUMax %3 %18 %57 %58 %37 +%114 = OpAccessChain %59 %20 %37 +%113 = OpAtomicSMax %4 %114 %57 %58 %38 +%116 = OpAccessChain %19 %22 %31 +%115 = OpAtomicUMax %3 %116 %57 %58 %37 +%118 = OpAccessChain %59 %22 %37 %37 +%117 = OpAtomicSMax %4 %118 %57 %58 %38 +OpControlBarrier %6 %6 %50 +%119 = OpAtomicUMin %3 %32 %38 %52 %37 +%121 = OpAccessChain %53 %34 %37 +%120 = OpAtomicSMin %4 %121 %38 %52 %38 +%123 = OpAccessChain %30 %36 %31 +%122 = OpAtomicUMin %3 %123 %38 %52 %37 +%125 = OpAccessChain %53 %36 %37 %37 +%124 = OpAtomicSMin %4 %125 %38 %52 %38 +%126 = OpAtomicUMin %3 %18 %57 %58 %37 +%128 = OpAccessChain %59 %20 %37 +%127 = OpAtomicSMin %4 %128 %57 %58 %38 +%130 = OpAccessChain %19 %22 %31 +%129 = OpAtomicUMin %3 %130 %57 %58 %37 +%132 = OpAccessChain %59 %22 %37 %37 +%131 = OpAtomicSMin %4 %132 %57 %58 %38 +OpControlBarrier %6 %6 %50 +%133 = OpAtomicAnd %3 %32 %38 %52 %37 +%135 = OpAccessChain %53 %34 %37 +%134 = OpAtomicAnd %4 %135 %38 %52 %38 +%137 = OpAccessChain %30 %36 %31 +%136 = OpAtomicAnd %3 %137 %38 %52 %37 +%139 = OpAccessChain %53 %36 %37 %37 +%138 = OpAtomicAnd %4 %139 %38 %52 %38 +%140 = OpAtomicAnd %3 %18 %57 %58 %37 +%142 = OpAccessChain %59 %20 %37 +%141 = OpAtomicAnd %4 %142 %57 %58 %38 +%144 = OpAccessChain %19 %22 %31 +%143 = OpAtomicAnd %3 %144 %57 %58 %37 +%146 = OpAccessChain %59 %22 %37 %37 +%145 = OpAtomicAnd %4 %146 %57 %58 %38 +OpControlBarrier %6 %6 %50 +%147 = OpAtomicOr %3 %32 %38 %52 %37 +%149 = OpAccessChain %53 %34 %37 +%148 = OpAtomicOr %4 %149 %38 %52 %38 +%151 = OpAccessChain %30 %36 %31 +%150 = OpAtomicOr %3 %151 %38 %52 %37 +%153 = OpAccessChain %53 %36 %37 %37 +%152 = OpAtomicOr %4 %153 %38 %52 %38 +%154 = OpAtomicOr %3 %18 %57 %58 %37 +%156 = OpAccessChain %59 %20 %37 +%155 = OpAtomicOr %4 %156 %57 %58 %38 +%158 = OpAccessChain %19 %22 %31 +%157 = OpAtomicOr %3 %158 %57 %58 %37 +%160 = OpAccessChain %59 %22 %37 %37 +%159 = OpAtomicOr %4 %160 %57 %58 %38 +OpControlBarrier %6 %6 %50 +%161 = OpAtomicXor %3 %32 %38 %52 %37 +%163 = OpAccessChain %53 %34 %37 +%162 = OpAtomicXor %4 %163 %38 %52 %38 +%165 = OpAccessChain %30 %36 %31 +%164 = OpAtomicXor %3 %165 %38 %52 %37 +%167 = OpAccessChain %53 %36 %37 %37 +%166 = OpAtomicXor %4 %167 %38 %52 %38 +%168 = OpAtomicXor %3 %18 %57 %58 %37 +%170 = OpAccessChain %59 %20 %37 +%169 = OpAtomicXor %4 %170 %57 %58 %38 +%172 = OpAccessChain %19 %22 %31 +%171 = OpAtomicXor %3 %172 %57 %58 %37 +%174 = OpAccessChain %59 %22 %37 %37 +%173 = OpAtomicXor %4 %174 %57 %58 %38 +%175 = OpAtomicExchange %3 %32 %38 %52 %37 +%177 = OpAccessChain %53 %34 %37 +%176 = OpAtomicExchange %4 %177 %38 %52 %38 +%179 = OpAccessChain %30 %36 %31 +%178 = OpAtomicExchange %3 %179 %38 %52 %37 +%181 = OpAccessChain %53 %36 %37 %37 +%180 = OpAtomicExchange %4 %181 %38 %52 %38 +%182 = OpAtomicExchange %3 %18 %57 %58 %37 +%184 = OpAccessChain %59 %20 %37 +%183 = OpAtomicExchange %4 %184 %57 %58 %38 +%186 = OpAccessChain %19 %22 %31 +%185 = OpAtomicExchange %3 %186 %57 %58 %37 +%188 = OpAccessChain %59 %22 %37 %37 +%187 = OpAtomicExchange %4 %188 %57 %58 %38 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/binding-arrays.spvasm b/naga/tests/out/spv/binding-arrays.spvasm new file mode 100644 index 0000000000..143ee269af --- /dev/null +++ b/naga/tests/out/spv/binding-arrays.spvasm @@ -0,0 +1,574 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 428 +OpCapability Shader +OpCapability ImageQuery +OpCapability ShaderNonUniform +OpExtension "SPV_EXT_descriptor_indexing" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %52 "main" %47 %50 +OpExecutionMode %52 OriginUpperLeft +OpMemberDecorate %4 0 Offset 0 +OpMemberDecorate %21 0 Offset 0 +OpDecorate %24 DescriptorSet 0 +OpDecorate %24 Binding 0 +OpDecorate %28 DescriptorSet 0 +OpDecorate %28 Binding 1 +OpDecorate %30 DescriptorSet 0 +OpDecorate %30 Binding 2 +OpDecorate %32 DescriptorSet 0 +OpDecorate %32 Binding 3 +OpDecorate %34 DescriptorSet 0 +OpDecorate %34 Binding 4 +OpDecorate %36 DescriptorSet 0 +OpDecorate %36 Binding 5 +OpDecorate %38 DescriptorSet 0 +OpDecorate %38 Binding 6 +OpDecorate %40 DescriptorSet 0 +OpDecorate %40 Binding 7 +OpDecorate %42 DescriptorSet 0 +OpDecorate %42 Binding 8 +OpDecorate %43 Block +OpMemberDecorate %43 0 Offset 0 +OpDecorate %47 Location 0 +OpDecorate %47 Flat +OpDecorate %50 Location 0 +OpDecorate %90 NonUniform +OpDecorate %91 NonUniform +OpDecorate %113 NonUniform +OpDecorate %114 NonUniform +OpDecorate %115 NonUniform +OpDecorate %116 NonUniform +OpDecorate %140 NonUniform +OpDecorate %141 NonUniform +OpDecorate %142 NonUniform +OpDecorate %143 NonUniform +OpDecorate %179 NonUniform +OpDecorate %180 NonUniform +OpDecorate %207 NonUniform +OpDecorate %208 NonUniform +OpDecorate %223 NonUniform +OpDecorate %224 NonUniform +OpDecorate %239 NonUniform +OpDecorate %240 NonUniform +OpDecorate %260 NonUniform +OpDecorate %261 NonUniform +OpDecorate %262 NonUniform +OpDecorate %263 NonUniform +OpDecorate %284 NonUniform +OpDecorate %285 NonUniform +OpDecorate %286 NonUniform +OpDecorate %287 NonUniform +OpDecorate %308 NonUniform +OpDecorate %309 NonUniform +OpDecorate %310 NonUniform +OpDecorate %311 NonUniform +OpDecorate %332 NonUniform +OpDecorate %333 NonUniform +OpDecorate %334 NonUniform +OpDecorate %335 NonUniform +OpDecorate %356 NonUniform +OpDecorate %357 NonUniform +OpDecorate %358 NonUniform +OpDecorate %359 NonUniform +OpDecorate %380 NonUniform +OpDecorate %381 NonUniform +OpDecorate %382 NonUniform +OpDecorate %383 NonUniform +OpDecorate %405 NonUniform +OpDecorate %406 NonUniform +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeStruct %3 +%6 = OpTypeFloat 32 +%5 = OpTypeImage %6 2D 0 0 0 1 Unknown +%7 = OpTypeRuntimeArray %5 +%9 = OpConstant %3 5 +%8 = OpTypeArray %5 %9 +%10 = OpTypeImage %6 2D 0 1 0 1 Unknown +%11 = OpTypeArray %10 %9 +%12 = OpTypeImage %6 2D 0 0 1 1 Unknown +%13 = OpTypeArray %12 %9 +%14 = OpTypeImage %6 2D 1 0 0 1 Unknown +%15 = OpTypeArray %14 %9 +%16 = OpTypeImage %6 2D 0 0 0 2 Rgba32f +%17 = OpTypeArray %16 %9 +%18 = OpTypeSampler +%19 = OpTypeArray %18 %9 +%20 = OpTypeArray %18 %9 +%21 = OpTypeStruct %3 +%22 = OpTypeVector %6 4 +%23 = OpTypeVector %3 2 +%27 = OpConstant %3 10 +%26 = OpTypeArray %5 %27 +%25 = OpTypePointer UniformConstant %26 +%24 = OpVariable %25 UniformConstant +%29 = OpTypePointer UniformConstant %8 +%28 = OpVariable %29 UniformConstant +%31 = OpTypePointer UniformConstant %11 +%30 = OpVariable %31 UniformConstant +%33 = OpTypePointer UniformConstant %13 +%32 = OpVariable %33 UniformConstant +%35 = OpTypePointer UniformConstant %15 +%34 = OpVariable %35 UniformConstant +%37 = OpTypePointer UniformConstant %17 +%36 = OpVariable %37 UniformConstant +%39 = OpTypePointer UniformConstant %19 +%38 = OpVariable %39 UniformConstant +%41 = OpTypePointer UniformConstant %20 +%40 = OpVariable %41 UniformConstant +%43 = OpTypeStruct %4 +%44 = OpTypePointer Uniform %43 +%42 = OpVariable %44 Uniform +%48 = OpTypePointer Input %3 +%47 = OpVariable %48 Input +%51 = OpTypePointer Output %22 +%50 = OpVariable %51 Output +%53 = OpTypeFunction %2 +%54 = OpTypePointer Uniform %4 +%55 = OpConstant %3 0 +%57 = OpConstantComposite %23 %55 %55 +%58 = OpConstant %6 0.0 +%59 = OpConstantComposite %22 %58 %58 %58 %58 +%60 = OpTypeVector %6 2 +%61 = OpConstantComposite %60 %58 %58 +%62 = OpTypeInt 32 1 +%63 = OpConstant %62 0 +%64 = OpTypeVector %62 2 +%65 = OpConstantComposite %64 %63 %63 +%67 = OpTypePointer Function %3 +%69 = OpTypePointer Function %23 +%71 = OpTypePointer Function %6 +%73 = OpTypePointer Function %22 +%75 = OpTypePointer Uniform %3 +%79 = OpTypePointer UniformConstant %5 +%97 = OpTypePointer UniformConstant %18 +%100 = OpTypeSampledImage %5 +%121 = OpTypePointer UniformConstant %14 +%124 = OpTypePointer UniformConstant %18 +%127 = OpTypeSampledImage %14 +%150 = OpTypeBool +%151 = OpConstantNull %22 +%157 = OpTypeVector %150 2 +%193 = OpTypePointer UniformConstant %10 +%196 = OpTypeVector %3 3 +%228 = OpTypePointer UniformConstant %12 +%388 = OpTypePointer UniformConstant %16 +%52 = OpFunction %2 None %53 +%45 = OpLabel +%68 = OpVariable %69 Function %57 +%72 = OpVariable %73 Function %59 +%66 = OpVariable %67 Function %55 +%70 = OpVariable %71 Function %58 +%49 = OpLoad %3 %47 +%46 = OpCompositeConstruct %21 %49 +%56 = OpAccessChain %54 %42 %55 +OpBranch %74 +%74 = OpLabel +%76 = OpAccessChain %75 %56 %55 +%77 = OpLoad %3 %76 +%78 = OpCompositeExtract %3 %46 0 +%80 = OpAccessChain %79 %24 %55 +%81 = OpLoad %5 %80 +%82 = OpImageQuerySizeLod %23 %81 %55 +%83 = OpLoad %23 %68 +%84 = OpIAdd %23 %83 %82 +OpStore %68 %84 +%85 = OpAccessChain %79 %24 %77 +%86 = OpLoad %5 %85 +%87 = OpImageQuerySizeLod %23 %86 %55 +%88 = OpLoad %23 %68 +%89 = OpIAdd %23 %88 %87 +OpStore %68 %89 +%90 = OpAccessChain %79 %24 %78 +%91 = OpLoad %5 %90 +%92 = OpImageQuerySizeLod %23 %91 %55 +%93 = OpLoad %23 %68 +%94 = OpIAdd %23 %93 %92 +OpStore %68 %94 +%95 = OpAccessChain %79 %28 %55 +%96 = OpLoad %5 %95 +%98 = OpAccessChain %97 %38 %55 +%99 = OpLoad %18 %98 +%101 = OpSampledImage %100 %96 %99 +%102 = OpImageGather %22 %101 %61 %55 +%103 = OpLoad %22 %72 +%104 = OpFAdd %22 %103 %102 +OpStore %72 %104 +%105 = OpAccessChain %79 %28 %77 +%106 = OpLoad %5 %105 +%107 = OpAccessChain %97 %38 %77 +%108 = OpLoad %18 %107 +%109 = OpSampledImage %100 %106 %108 +%110 = OpImageGather %22 %109 %61 %55 +%111 = OpLoad %22 %72 +%112 = OpFAdd %22 %111 %110 +OpStore %72 %112 +%113 = OpAccessChain %79 %28 %78 +%114 = OpLoad %5 %113 +%115 = OpAccessChain %97 %38 %78 +%116 = OpLoad %18 %115 +%117 = OpSampledImage %100 %114 %116 +%118 = OpImageGather %22 %117 %61 %55 +%119 = OpLoad %22 %72 +%120 = OpFAdd %22 %119 %118 +OpStore %72 %120 +%122 = OpAccessChain %121 %34 %55 +%123 = OpLoad %14 %122 +%125 = OpAccessChain %124 %40 %55 +%126 = OpLoad %18 %125 +%128 = OpSampledImage %127 %123 %126 +%129 = OpImageDrefGather %22 %128 %61 %58 +%130 = OpLoad %22 %72 +%131 = OpFAdd %22 %130 %129 +OpStore %72 %131 +%132 = OpAccessChain %121 %34 %77 +%133 = OpLoad %14 %132 +%134 = OpAccessChain %124 %40 %77 +%135 = OpLoad %18 %134 +%136 = OpSampledImage %127 %133 %135 +%137 = OpImageDrefGather %22 %136 %61 %58 +%138 = OpLoad %22 %72 +%139 = OpFAdd %22 %138 %137 +OpStore %72 %139 +%140 = OpAccessChain %121 %34 %78 +%141 = OpLoad %14 %140 +%142 = OpAccessChain %124 %40 %78 +%143 = OpLoad %18 %142 +%144 = OpSampledImage %127 %141 %143 +%145 = OpImageDrefGather %22 %144 %61 %58 +%146 = OpLoad %22 %72 +%147 = OpFAdd %22 %146 %145 +OpStore %72 %147 +%148 = OpAccessChain %79 %24 %55 +%149 = OpLoad %5 %148 +%152 = OpImageQueryLevels %62 %149 +%153 = OpULessThan %150 %63 %152 +OpSelectionMerge %154 None +OpBranchConditional %153 %155 %154 +%155 = OpLabel +%156 = OpImageQuerySizeLod %64 %149 %63 +%158 = OpULessThan %157 %65 %156 +%159 = OpAll %150 %158 +OpBranchConditional %159 %160 %154 +%160 = OpLabel +%161 = OpImageFetch %22 %149 %65 Lod %63 +OpBranch %154 +%154 = OpLabel +%162 = OpPhi %22 %151 %74 %151 %155 %161 %160 +%163 = OpLoad %22 %72 +%164 = OpFAdd %22 %163 %162 +OpStore %72 %164 +%165 = OpAccessChain %79 %24 %77 +%166 = OpLoad %5 %165 +%167 = OpImageQueryLevels %62 %166 +%168 = OpULessThan %150 %63 %167 +OpSelectionMerge %169 None +OpBranchConditional %168 %170 %169 +%170 = OpLabel +%171 = OpImageQuerySizeLod %64 %166 %63 +%172 = OpULessThan %157 %65 %171 +%173 = OpAll %150 %172 +OpBranchConditional %173 %174 %169 +%174 = OpLabel +%175 = OpImageFetch %22 %166 %65 Lod %63 +OpBranch %169 +%169 = OpLabel +%176 = OpPhi %22 %151 %154 %151 %170 %175 %174 +%177 = OpLoad %22 %72 +%178 = OpFAdd %22 %177 %176 +OpStore %72 %178 +%179 = OpAccessChain %79 %24 %78 +%180 = OpLoad %5 %179 +%181 = OpImageQueryLevels %62 %180 +%182 = OpULessThan %150 %63 %181 +OpSelectionMerge %183 None +OpBranchConditional %182 %184 %183 +%184 = OpLabel +%185 = OpImageQuerySizeLod %64 %180 %63 +%186 = OpULessThan %157 %65 %185 +%187 = OpAll %150 %186 +OpBranchConditional %187 %188 %183 +%188 = OpLabel +%189 = OpImageFetch %22 %180 %65 Lod %63 +OpBranch %183 +%183 = OpLabel +%190 = OpPhi %22 %151 %169 %151 %184 %189 %188 +%191 = OpLoad %22 %72 +%192 = OpFAdd %22 %191 %190 +OpStore %72 %192 +%194 = OpAccessChain %193 %30 %55 +%195 = OpLoad %10 %194 +%197 = OpImageQuerySizeLod %196 %195 %55 +%198 = OpCompositeExtract %3 %197 2 +%199 = OpLoad %3 %66 +%200 = OpIAdd %3 %199 %198 +OpStore %66 %200 +%201 = OpAccessChain %193 %30 %77 +%202 = OpLoad %10 %201 +%203 = OpImageQuerySizeLod %196 %202 %55 +%204 = OpCompositeExtract %3 %203 2 +%205 = OpLoad %3 %66 +%206 = OpIAdd %3 %205 %204 +OpStore %66 %206 +%207 = OpAccessChain %193 %30 %78 +%208 = OpLoad %10 %207 +%209 = OpImageQuerySizeLod %196 %208 %55 +%210 = OpCompositeExtract %3 %209 2 +%211 = OpLoad %3 %66 +%212 = OpIAdd %3 %211 %210 +OpStore %66 %212 +%213 = OpAccessChain %79 %28 %55 +%214 = OpLoad %5 %213 +%215 = OpImageQueryLevels %3 %214 +%216 = OpLoad %3 %66 +%217 = OpIAdd %3 %216 %215 +OpStore %66 %217 +%218 = OpAccessChain %79 %28 %77 +%219 = OpLoad %5 %218 +%220 = OpImageQueryLevels %3 %219 +%221 = OpLoad %3 %66 +%222 = OpIAdd %3 %221 %220 +OpStore %66 %222 +%223 = OpAccessChain %79 %28 %78 +%224 = OpLoad %5 %223 +%225 = OpImageQueryLevels %3 %224 +%226 = OpLoad %3 %66 +%227 = OpIAdd %3 %226 %225 +OpStore %66 %227 +%229 = OpAccessChain %228 %32 %55 +%230 = OpLoad %12 %229 +%231 = OpImageQuerySamples %3 %230 +%232 = OpLoad %3 %66 +%233 = OpIAdd %3 %232 %231 +OpStore %66 %233 +%234 = OpAccessChain %228 %32 %77 +%235 = OpLoad %12 %234 +%236 = OpImageQuerySamples %3 %235 +%237 = OpLoad %3 %66 +%238 = OpIAdd %3 %237 %236 +OpStore %66 %238 +%239 = OpAccessChain %228 %32 %78 +%240 = OpLoad %12 %239 +%241 = OpImageQuerySamples %3 %240 +%242 = OpLoad %3 %66 +%243 = OpIAdd %3 %242 %241 +OpStore %66 %243 +%244 = OpAccessChain %79 %28 %55 +%245 = OpLoad %5 %244 +%246 = OpAccessChain %97 %38 %55 +%247 = OpLoad %18 %246 +%248 = OpSampledImage %100 %245 %247 +%249 = OpImageSampleImplicitLod %22 %248 %61 +%250 = OpLoad %22 %72 +%251 = OpFAdd %22 %250 %249 +OpStore %72 %251 +%252 = OpAccessChain %79 %28 %77 +%253 = OpLoad %5 %252 +%254 = OpAccessChain %97 %38 %77 +%255 = OpLoad %18 %254 +%256 = OpSampledImage %100 %253 %255 +%257 = OpImageSampleImplicitLod %22 %256 %61 +%258 = OpLoad %22 %72 +%259 = OpFAdd %22 %258 %257 +OpStore %72 %259 +%260 = OpAccessChain %79 %28 %78 +%261 = OpLoad %5 %260 +%262 = OpAccessChain %97 %38 %78 +%263 = OpLoad %18 %262 +%264 = OpSampledImage %100 %261 %263 +%265 = OpImageSampleImplicitLod %22 %264 %61 +%266 = OpLoad %22 %72 +%267 = OpFAdd %22 %266 %265 +OpStore %72 %267 +%268 = OpAccessChain %79 %28 %55 +%269 = OpLoad %5 %268 +%270 = OpAccessChain %97 %38 %55 +%271 = OpLoad %18 %270 +%272 = OpSampledImage %100 %269 %271 +%273 = OpImageSampleImplicitLod %22 %272 %61 Bias %58 +%274 = OpLoad %22 %72 +%275 = OpFAdd %22 %274 %273 +OpStore %72 %275 +%276 = OpAccessChain %79 %28 %77 +%277 = OpLoad %5 %276 +%278 = OpAccessChain %97 %38 %77 +%279 = OpLoad %18 %278 +%280 = OpSampledImage %100 %277 %279 +%281 = OpImageSampleImplicitLod %22 %280 %61 Bias %58 +%282 = OpLoad %22 %72 +%283 = OpFAdd %22 %282 %281 +OpStore %72 %283 +%284 = OpAccessChain %79 %28 %78 +%285 = OpLoad %5 %284 +%286 = OpAccessChain %97 %38 %78 +%287 = OpLoad %18 %286 +%288 = OpSampledImage %100 %285 %287 +%289 = OpImageSampleImplicitLod %22 %288 %61 Bias %58 +%290 = OpLoad %22 %72 +%291 = OpFAdd %22 %290 %289 +OpStore %72 %291 +%292 = OpAccessChain %121 %34 %55 +%293 = OpLoad %14 %292 +%294 = OpAccessChain %124 %40 %55 +%295 = OpLoad %18 %294 +%296 = OpSampledImage %127 %293 %295 +%297 = OpImageSampleDrefImplicitLod %6 %296 %61 %58 +%298 = OpLoad %6 %70 +%299 = OpFAdd %6 %298 %297 +OpStore %70 %299 +%300 = OpAccessChain %121 %34 %77 +%301 = OpLoad %14 %300 +%302 = OpAccessChain %124 %40 %77 +%303 = OpLoad %18 %302 +%304 = OpSampledImage %127 %301 %303 +%305 = OpImageSampleDrefImplicitLod %6 %304 %61 %58 +%306 = OpLoad %6 %70 +%307 = OpFAdd %6 %306 %305 +OpStore %70 %307 +%308 = OpAccessChain %121 %34 %78 +%309 = OpLoad %14 %308 +%310 = OpAccessChain %124 %40 %78 +%311 = OpLoad %18 %310 +%312 = OpSampledImage %127 %309 %311 +%313 = OpImageSampleDrefImplicitLod %6 %312 %61 %58 +%314 = OpLoad %6 %70 +%315 = OpFAdd %6 %314 %313 +OpStore %70 %315 +%316 = OpAccessChain %121 %34 %55 +%317 = OpLoad %14 %316 +%318 = OpAccessChain %124 %40 %55 +%319 = OpLoad %18 %318 +%320 = OpSampledImage %127 %317 %319 +%321 = OpImageSampleDrefExplicitLod %6 %320 %61 %58 Lod %58 +%322 = OpLoad %6 %70 +%323 = OpFAdd %6 %322 %321 +OpStore %70 %323 +%324 = OpAccessChain %121 %34 %77 +%325 = OpLoad %14 %324 +%326 = OpAccessChain %124 %40 %77 +%327 = OpLoad %18 %326 +%328 = OpSampledImage %127 %325 %327 +%329 = OpImageSampleDrefExplicitLod %6 %328 %61 %58 Lod %58 +%330 = OpLoad %6 %70 +%331 = OpFAdd %6 %330 %329 +OpStore %70 %331 +%332 = OpAccessChain %121 %34 %78 +%333 = OpLoad %14 %332 +%334 = OpAccessChain %124 %40 %78 +%335 = OpLoad %18 %334 +%336 = OpSampledImage %127 %333 %335 +%337 = OpImageSampleDrefExplicitLod %6 %336 %61 %58 Lod %58 +%338 = OpLoad %6 %70 +%339 = OpFAdd %6 %338 %337 +OpStore %70 %339 +%340 = OpAccessChain %79 %28 %55 +%341 = OpLoad %5 %340 +%342 = OpAccessChain %97 %38 %55 +%343 = OpLoad %18 %342 +%344 = OpSampledImage %100 %341 %343 +%345 = OpImageSampleExplicitLod %22 %344 %61 Grad %61 %61 +%346 = OpLoad %22 %72 +%347 = OpFAdd %22 %346 %345 +OpStore %72 %347 +%348 = OpAccessChain %79 %28 %77 +%349 = OpLoad %5 %348 +%350 = OpAccessChain %97 %38 %77 +%351 = OpLoad %18 %350 +%352 = OpSampledImage %100 %349 %351 +%353 = OpImageSampleExplicitLod %22 %352 %61 Grad %61 %61 +%354 = OpLoad %22 %72 +%355 = OpFAdd %22 %354 %353 +OpStore %72 %355 +%356 = OpAccessChain %79 %28 %78 +%357 = OpLoad %5 %356 +%358 = OpAccessChain %97 %38 %78 +%359 = OpLoad %18 %358 +%360 = OpSampledImage %100 %357 %359 +%361 = OpImageSampleExplicitLod %22 %360 %61 Grad %61 %61 +%362 = OpLoad %22 %72 +%363 = OpFAdd %22 %362 %361 +OpStore %72 %363 +%364 = OpAccessChain %79 %28 %55 +%365 = OpLoad %5 %364 +%366 = OpAccessChain %97 %38 %55 +%367 = OpLoad %18 %366 +%368 = OpSampledImage %100 %365 %367 +%369 = OpImageSampleExplicitLod %22 %368 %61 Lod %58 +%370 = OpLoad %22 %72 +%371 = OpFAdd %22 %370 %369 +OpStore %72 %371 +%372 = OpAccessChain %79 %28 %77 +%373 = OpLoad %5 %372 +%374 = OpAccessChain %97 %38 %77 +%375 = OpLoad %18 %374 +%376 = OpSampledImage %100 %373 %375 +%377 = OpImageSampleExplicitLod %22 %376 %61 Lod %58 +%378 = OpLoad %22 %72 +%379 = OpFAdd %22 %378 %377 +OpStore %72 %379 +%380 = OpAccessChain %79 %28 %78 +%381 = OpLoad %5 %380 +%382 = OpAccessChain %97 %38 %78 +%383 = OpLoad %18 %382 +%384 = OpSampledImage %100 %381 %383 +%385 = OpImageSampleExplicitLod %22 %384 %61 Lod %58 +%386 = OpLoad %22 %72 +%387 = OpFAdd %22 %386 %385 +OpStore %72 %387 +%389 = OpAccessChain %388 %36 %55 +%390 = OpLoad %16 %389 +%391 = OpLoad %22 %72 +%392 = OpImageQuerySize %64 %390 +%393 = OpULessThan %157 %65 %392 +%394 = OpAll %150 %393 +OpSelectionMerge %395 None +OpBranchConditional %394 %396 %395 +%396 = OpLabel +OpImageWrite %390 %65 %391 +OpBranch %395 +%395 = OpLabel +%397 = OpAccessChain %388 %36 %77 +%398 = OpLoad %16 %397 +%399 = OpLoad %22 %72 +%400 = OpImageQuerySize %64 %398 +%401 = OpULessThan %157 %65 %400 +%402 = OpAll %150 %401 +OpSelectionMerge %403 None +OpBranchConditional %402 %404 %403 +%404 = OpLabel +OpImageWrite %398 %65 %399 +OpBranch %403 +%403 = OpLabel +%405 = OpAccessChain %388 %36 %78 +%406 = OpLoad %16 %405 +%407 = OpLoad %22 %72 +%408 = OpImageQuerySize %64 %406 +%409 = OpULessThan %157 %65 %408 +%410 = OpAll %150 %409 +OpSelectionMerge %411 None +OpBranchConditional %410 %412 %411 +%412 = OpLabel +OpImageWrite %406 %65 %407 +OpBranch %411 +%411 = OpLabel +%413 = OpLoad %23 %68 +%414 = OpLoad %3 %66 +%415 = OpCompositeConstruct %23 %414 %414 +%416 = OpIAdd %23 %413 %415 +%417 = OpConvertUToF %60 %416 +%418 = OpLoad %22 %72 +%419 = OpCompositeExtract %6 %417 0 +%420 = OpCompositeExtract %6 %417 1 +%421 = OpCompositeExtract %6 %417 0 +%422 = OpCompositeExtract %6 %417 1 +%423 = OpCompositeConstruct %22 %419 %420 %421 %422 +%424 = OpFAdd %22 %418 %423 +%425 = OpLoad %6 %70 +%426 = OpCompositeConstruct %22 %425 %425 %425 %425 +%427 = OpFAdd %22 %424 %426 +OpStore %50 %427 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/binding-buffer-arrays.spvasm b/naga/tests/out/spv/binding-buffer-arrays.spvasm new file mode 100644 index 0000000000..050372036d --- /dev/null +++ b/naga/tests/out/spv/binding-buffer-arrays.spvasm @@ -0,0 +1,99 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 61 +OpCapability Shader +OpCapability ShaderNonUniform +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_EXT_descriptor_indexing" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %23 "main" %18 %21 +OpExecutionMode %23 OriginUpperLeft +OpMemberDecorate %4 0 Offset 0 +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %8 0 Offset 0 +OpDecorate %9 NonWritable +OpDecorate %9 DescriptorSet 0 +OpDecorate %9 Binding 0 +OpDecorate %5 Block +OpDecorate %13 DescriptorSet 0 +OpDecorate %13 Binding 10 +OpDecorate %14 Block +OpMemberDecorate %14 0 Offset 0 +OpDecorate %18 Location 0 +OpDecorate %18 Flat +OpDecorate %21 Location 0 +OpDecorate %53 NonUniform +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeStruct %3 +%5 = OpTypeStruct %3 +%7 = OpConstant %3 1 +%6 = OpTypeArray %5 %7 +%8 = OpTypeStruct %3 +%12 = OpConstant %3 10 +%11 = OpTypeArray %5 %12 +%10 = OpTypePointer StorageBuffer %11 +%9 = OpVariable %10 StorageBuffer +%14 = OpTypeStruct %4 +%15 = OpTypePointer Uniform %14 +%13 = OpVariable %15 Uniform +%19 = OpTypePointer Input %3 +%18 = OpVariable %19 Input +%22 = OpTypePointer Output %3 +%21 = OpVariable %22 Output +%24 = OpTypeFunction %2 +%25 = OpTypePointer Uniform %4 +%26 = OpConstant %3 0 +%28 = OpTypePointer StorageBuffer %6 +%30 = OpTypePointer Function %3 +%32 = OpTypePointer Uniform %3 +%36 = OpTypePointer StorageBuffer %5 +%37 = OpTypePointer StorageBuffer %3 +%43 = OpTypeBool +%45 = OpConstantNull %3 +%23 = OpFunction %2 None %24 +%16 = OpLabel +%29 = OpVariable %30 Function %26 +%20 = OpLoad %3 %18 +%17 = OpCompositeConstruct %8 %20 +%27 = OpAccessChain %25 %13 %26 +OpBranch %31 +%31 = OpLabel +%33 = OpAccessChain %32 %27 %26 +%34 = OpLoad %3 %33 +%35 = OpCompositeExtract %3 %17 0 +%38 = OpAccessChain %37 %9 %26 %26 +%39 = OpLoad %3 %38 +%40 = OpLoad %3 %29 +%41 = OpIAdd %3 %40 %39 +OpStore %29 %41 +%42 = OpULessThan %43 %34 %7 +OpSelectionMerge %46 None +OpBranchConditional %42 %47 %46 +%47 = OpLabel +%44 = OpAccessChain %37 %9 %34 %26 +%48 = OpLoad %3 %44 +OpBranch %46 +%46 = OpLabel +%49 = OpPhi %3 %45 %31 %48 %47 +%50 = OpLoad %3 %29 +%51 = OpIAdd %3 %50 %49 +OpStore %29 %51 +%52 = OpULessThan %43 %35 %7 +OpSelectionMerge %54 None +OpBranchConditional %52 %55 %54 +%55 = OpLabel +%53 = OpAccessChain %37 %9 %35 %26 +%56 = OpLoad %3 %53 +OpBranch %54 +%54 = OpLabel +%57 = OpPhi %3 %45 %46 %56 %55 +%58 = OpLoad %3 %29 +%59 = OpIAdd %3 %58 %57 +OpStore %29 %59 +%60 = OpLoad %3 %29 +OpStore %21 %60 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/bitcast.spvasm b/naga/tests/out/spv/bitcast.spvasm new file mode 100644 index 0000000000..43dfa8df33 --- /dev/null +++ b/naga/tests/out/spv/bitcast.spvasm @@ -0,0 +1,86 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 67 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %16 "main" +OpExecutionMode %16 LocalSize 1 1 1 +%2 = OpTypeVoid +%4 = OpTypeInt 32 1 +%3 = OpTypeVector %4 2 +%5 = OpTypeVector %4 3 +%6 = OpTypeVector %4 4 +%8 = OpTypeInt 32 0 +%7 = OpTypeVector %8 2 +%9 = OpTypeVector %8 3 +%10 = OpTypeVector %8 4 +%12 = OpTypeFloat 32 +%11 = OpTypeVector %12 2 +%13 = OpTypeVector %12 3 +%14 = OpTypeVector %12 4 +%17 = OpTypeFunction %2 +%18 = OpConstant %4 0 +%19 = OpConstantComposite %3 %18 %18 +%20 = OpConstantComposite %5 %18 %18 %18 +%21 = OpConstantComposite %6 %18 %18 %18 %18 +%22 = OpConstant %8 0 +%23 = OpConstantComposite %7 %22 %22 +%24 = OpConstantComposite %9 %22 %22 %22 +%25 = OpConstantComposite %10 %22 %22 %22 %22 +%26 = OpConstant %12 0.0 +%27 = OpConstantComposite %11 %26 %26 +%28 = OpConstantComposite %13 %26 %26 %26 +%29 = OpConstantComposite %14 %26 %26 %26 %26 +%31 = OpTypePointer Function %3 +%33 = OpTypePointer Function %5 +%35 = OpTypePointer Function %6 +%37 = OpTypePointer Function %7 +%39 = OpTypePointer Function %9 +%41 = OpTypePointer Function %10 +%43 = OpTypePointer Function %11 +%45 = OpTypePointer Function %13 +%47 = OpTypePointer Function %14 +%16 = OpFunction %2 None %17 +%15 = OpLabel +%42 = OpVariable %43 Function %27 +%36 = OpVariable %37 Function %23 +%30 = OpVariable %31 Function %19 +%44 = OpVariable %45 Function %28 +%38 = OpVariable %39 Function %24 +%32 = OpVariable %33 Function %20 +%46 = OpVariable %47 Function %29 +%40 = OpVariable %41 Function %25 +%34 = OpVariable %35 Function %21 +OpBranch %48 +%48 = OpLabel +%49 = OpLoad %3 %30 +%50 = OpBitcast %7 %49 +OpStore %36 %50 +%51 = OpLoad %5 %32 +%52 = OpBitcast %9 %51 +OpStore %38 %52 +%53 = OpLoad %6 %34 +%54 = OpBitcast %10 %53 +OpStore %40 %54 +%55 = OpLoad %7 %36 +%56 = OpBitcast %3 %55 +OpStore %30 %56 +%57 = OpLoad %9 %38 +%58 = OpBitcast %5 %57 +OpStore %32 %58 +%59 = OpLoad %10 %40 +%60 = OpBitcast %6 %59 +OpStore %34 %60 +%61 = OpLoad %3 %30 +%62 = OpBitcast %11 %61 +OpStore %42 %62 +%63 = OpLoad %5 %32 +%64 = OpBitcast %13 %63 +OpStore %44 %64 +%65 = OpLoad %6 %34 +%66 = OpBitcast %14 %65 +OpStore %46 %66 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/bits.spvasm b/naga/tests/out/spv/bits.spvasm new file mode 100644 index 0000000000..a77c4470a6 --- /dev/null +++ b/naga/tests/out/spv/bits.spvasm @@ -0,0 +1,213 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 155 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %15 "main" +OpExecutionMode %15 LocalSize 1 1 1 +%2 = OpTypeVoid +%3 = OpTypeInt 32 1 +%4 = OpTypeVector %3 2 +%5 = OpTypeVector %3 3 +%6 = OpTypeVector %3 4 +%7 = OpTypeInt 32 0 +%8 = OpTypeVector %7 2 +%9 = OpTypeVector %7 3 +%10 = OpTypeVector %7 4 +%12 = OpTypeFloat 32 +%11 = OpTypeVector %12 2 +%13 = OpTypeVector %12 4 +%16 = OpTypeFunction %2 +%17 = OpConstant %3 0 +%18 = OpConstantComposite %4 %17 %17 +%19 = OpConstantComposite %5 %17 %17 %17 +%20 = OpConstantComposite %6 %17 %17 %17 %17 +%21 = OpConstant %7 0 +%22 = OpConstantComposite %8 %21 %21 +%23 = OpConstantComposite %9 %21 %21 %21 +%24 = OpConstantComposite %10 %21 %21 %21 %21 +%25 = OpConstant %12 0.0 +%26 = OpConstantComposite %11 %25 %25 +%27 = OpConstantComposite %13 %25 %25 %25 %25 +%28 = OpConstant %7 5 +%29 = OpConstant %7 10 +%31 = OpTypePointer Function %3 +%33 = OpTypePointer Function %4 +%35 = OpTypePointer Function %5 +%37 = OpTypePointer Function %6 +%39 = OpTypePointer Function %7 +%41 = OpTypePointer Function %8 +%43 = OpTypePointer Function %9 +%45 = OpTypePointer Function %10 +%47 = OpTypePointer Function %11 +%49 = OpTypePointer Function %13 +%15 = OpFunction %2 None %16 +%14 = OpLabel +%48 = OpVariable %49 Function %27 +%42 = OpVariable %43 Function %23 +%36 = OpVariable %37 Function %20 +%30 = OpVariable %31 Function %17 +%44 = OpVariable %45 Function %24 +%38 = OpVariable %39 Function %21 +%32 = OpVariable %33 Function %18 +%46 = OpVariable %47 Function %26 +%40 = OpVariable %41 Function %22 +%34 = OpVariable %35 Function %19 +OpBranch %50 +%50 = OpLabel +%51 = OpLoad %13 %48 +%52 = OpExtInst %7 %1 PackSnorm4x8 %51 +OpStore %38 %52 +%53 = OpLoad %13 %48 +%54 = OpExtInst %7 %1 PackUnorm4x8 %53 +OpStore %38 %54 +%55 = OpLoad %11 %46 +%56 = OpExtInst %7 %1 PackSnorm2x16 %55 +OpStore %38 %56 +%57 = OpLoad %11 %46 +%58 = OpExtInst %7 %1 PackUnorm2x16 %57 +OpStore %38 %58 +%59 = OpLoad %11 %46 +%60 = OpExtInst %7 %1 PackHalf2x16 %59 +OpStore %38 %60 +%61 = OpLoad %7 %38 +%62 = OpExtInst %13 %1 UnpackSnorm4x8 %61 +OpStore %48 %62 +%63 = OpLoad %7 %38 +%64 = OpExtInst %13 %1 UnpackUnorm4x8 %63 +OpStore %48 %64 +%65 = OpLoad %7 %38 +%66 = OpExtInst %11 %1 UnpackSnorm2x16 %65 +OpStore %46 %66 +%67 = OpLoad %7 %38 +%68 = OpExtInst %11 %1 UnpackUnorm2x16 %67 +OpStore %46 %68 +%69 = OpLoad %7 %38 +%70 = OpExtInst %11 %1 UnpackHalf2x16 %69 +OpStore %46 %70 +%71 = OpLoad %3 %30 +%72 = OpLoad %3 %30 +%73 = OpBitFieldInsert %3 %71 %72 %28 %29 +OpStore %30 %73 +%74 = OpLoad %4 %32 +%75 = OpLoad %4 %32 +%76 = OpBitFieldInsert %4 %74 %75 %28 %29 +OpStore %32 %76 +%77 = OpLoad %5 %34 +%78 = OpLoad %5 %34 +%79 = OpBitFieldInsert %5 %77 %78 %28 %29 +OpStore %34 %79 +%80 = OpLoad %6 %36 +%81 = OpLoad %6 %36 +%82 = OpBitFieldInsert %6 %80 %81 %28 %29 +OpStore %36 %82 +%83 = OpLoad %7 %38 +%84 = OpLoad %7 %38 +%85 = OpBitFieldInsert %7 %83 %84 %28 %29 +OpStore %38 %85 +%86 = OpLoad %8 %40 +%87 = OpLoad %8 %40 +%88 = OpBitFieldInsert %8 %86 %87 %28 %29 +OpStore %40 %88 +%89 = OpLoad %9 %42 +%90 = OpLoad %9 %42 +%91 = OpBitFieldInsert %9 %89 %90 %28 %29 +OpStore %42 %91 +%92 = OpLoad %10 %44 +%93 = OpLoad %10 %44 +%94 = OpBitFieldInsert %10 %92 %93 %28 %29 +OpStore %44 %94 +%95 = OpLoad %3 %30 +%96 = OpBitFieldSExtract %3 %95 %28 %29 +OpStore %30 %96 +%97 = OpLoad %4 %32 +%98 = OpBitFieldSExtract %4 %97 %28 %29 +OpStore %32 %98 +%99 = OpLoad %5 %34 +%100 = OpBitFieldSExtract %5 %99 %28 %29 +OpStore %34 %100 +%101 = OpLoad %6 %36 +%102 = OpBitFieldSExtract %6 %101 %28 %29 +OpStore %36 %102 +%103 = OpLoad %7 %38 +%104 = OpBitFieldUExtract %7 %103 %28 %29 +OpStore %38 %104 +%105 = OpLoad %8 %40 +%106 = OpBitFieldUExtract %8 %105 %28 %29 +OpStore %40 %106 +%107 = OpLoad %9 %42 +%108 = OpBitFieldUExtract %9 %107 %28 %29 +OpStore %42 %108 +%109 = OpLoad %10 %44 +%110 = OpBitFieldUExtract %10 %109 %28 %29 +OpStore %44 %110 +%111 = OpLoad %3 %30 +%112 = OpExtInst %3 %1 FindILsb %111 +OpStore %30 %112 +%113 = OpLoad %8 %40 +%114 = OpExtInst %8 %1 FindILsb %113 +OpStore %40 %114 +%115 = OpLoad %5 %34 +%116 = OpExtInst %5 %1 FindSMsb %115 +OpStore %34 %116 +%117 = OpLoad %9 %42 +%118 = OpExtInst %9 %1 FindUMsb %117 +OpStore %42 %118 +%119 = OpLoad %3 %30 +%120 = OpExtInst %3 %1 FindSMsb %119 +OpStore %30 %120 +%121 = OpLoad %7 %38 +%122 = OpExtInst %7 %1 FindUMsb %121 +OpStore %38 %122 +%123 = OpLoad %3 %30 +%124 = OpBitCount %3 %123 +OpStore %30 %124 +%125 = OpLoad %4 %32 +%126 = OpBitCount %4 %125 +OpStore %32 %126 +%127 = OpLoad %5 %34 +%128 = OpBitCount %5 %127 +OpStore %34 %128 +%129 = OpLoad %6 %36 +%130 = OpBitCount %6 %129 +OpStore %36 %130 +%131 = OpLoad %7 %38 +%132 = OpBitCount %7 %131 +OpStore %38 %132 +%133 = OpLoad %8 %40 +%134 = OpBitCount %8 %133 +OpStore %40 %134 +%135 = OpLoad %9 %42 +%136 = OpBitCount %9 %135 +OpStore %42 %136 +%137 = OpLoad %10 %44 +%138 = OpBitCount %10 %137 +OpStore %44 %138 +%139 = OpLoad %3 %30 +%140 = OpBitReverse %3 %139 +OpStore %30 %140 +%141 = OpLoad %4 %32 +%142 = OpBitReverse %4 %141 +OpStore %32 %142 +%143 = OpLoad %5 %34 +%144 = OpBitReverse %5 %143 +OpStore %34 %144 +%145 = OpLoad %6 %36 +%146 = OpBitReverse %6 %145 +OpStore %36 %146 +%147 = OpLoad %7 %38 +%148 = OpBitReverse %7 %147 +OpStore %38 %148 +%149 = OpLoad %8 %40 +%150 = OpBitReverse %8 %149 +OpStore %40 %150 +%151 = OpLoad %9 %42 +%152 = OpBitReverse %9 %151 +OpStore %42 %152 +%153 = OpLoad %10 %44 +%154 = OpBitReverse %10 %153 +OpStore %44 %154 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/boids.spvasm b/naga/tests/out/spv/boids.spvasm new file mode 100644 index 0000000000..0e48e0f559 --- /dev/null +++ b/naga/tests/out/spv/boids.spvasm @@ -0,0 +1,332 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 208 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %23 "main" %20 +OpExecutionMode %23 LocalSize 64 1 1 +OpMemberName %6 0 "pos" +OpMemberName %6 1 "vel" +OpName %6 "Particle" +OpMemberName %7 0 "deltaT" +OpMemberName %7 1 "rule1Distance" +OpMemberName %7 2 "rule2Distance" +OpMemberName %7 3 "rule3Distance" +OpMemberName %7 4 "rule1Scale" +OpMemberName %7 5 "rule2Scale" +OpMemberName %7 6 "rule3Scale" +OpName %7 "SimParams" +OpMemberName %9 0 "particles" +OpName %9 "Particles" +OpName %12 "NUM_PARTICLES" +OpName %13 "params" +OpName %16 "particlesSrc" +OpName %18 "particlesDst" +OpName %20 "global_invocation_id" +OpName %23 "main" +OpName %36 "vPos" +OpName %39 "vVel" +OpName %41 "cMass" +OpName %42 "cVel" +OpName %43 "colVel" +OpName %44 "cMassCount" +OpName %46 "cVelCount" +OpName %47 "pos" +OpName %49 "vel" +OpName %51 "i" +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 8 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 4 +OpMemberDecorate %7 2 Offset 8 +OpMemberDecorate %7 3 Offset 12 +OpMemberDecorate %7 4 Offset 16 +OpMemberDecorate %7 5 Offset 20 +OpMemberDecorate %7 6 Offset 24 +OpDecorate %8 ArrayStride 16 +OpMemberDecorate %9 0 Offset 0 +OpDecorate %9 Block +OpDecorate %13 DescriptorSet 0 +OpDecorate %13 Binding 0 +OpDecorate %14 Block +OpMemberDecorate %14 0 Offset 0 +OpDecorate %16 NonWritable +OpDecorate %16 DescriptorSet 0 +OpDecorate %16 Binding 1 +OpDecorate %18 DescriptorSet 0 +OpDecorate %18 Binding 2 +OpDecorate %20 BuiltIn GlobalInvocationId +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%5 = OpTypeFloat 32 +%4 = OpTypeVector %5 2 +%6 = OpTypeStruct %4 %4 +%7 = OpTypeStruct %5 %5 %5 %5 %5 %5 %5 +%8 = OpTypeRuntimeArray %6 +%9 = OpTypeStruct %8 +%10 = OpTypeVector %3 3 +%11 = OpTypeInt 32 1 +%12 = OpConstant %3 1500 +%14 = OpTypeStruct %7 +%15 = OpTypePointer Uniform %14 +%13 = OpVariable %15 Uniform +%17 = OpTypePointer StorageBuffer %9 +%16 = OpVariable %17 StorageBuffer +%18 = OpVariable %17 StorageBuffer +%21 = OpTypePointer Input %10 +%20 = OpVariable %21 Input +%24 = OpTypeFunction %2 +%25 = OpTypePointer Uniform %7 +%26 = OpConstant %3 0 +%28 = OpConstant %5 0.0 +%29 = OpConstantComposite %4 %28 %28 +%30 = OpConstant %11 0 +%31 = OpConstant %11 1 +%32 = OpConstant %3 1 +%33 = OpConstant %5 0.1 +%34 = OpConstant %5 -1.0 +%35 = OpConstant %5 1.0 +%37 = OpTypePointer Function %4 +%38 = OpConstantNull %4 +%40 = OpConstantNull %4 +%45 = OpTypePointer Function %11 +%48 = OpConstantNull %4 +%50 = OpConstantNull %4 +%52 = OpTypePointer Function %3 +%55 = OpTypeBool +%59 = OpTypePointer StorageBuffer %8 +%60 = OpTypePointer StorageBuffer %6 +%61 = OpTypePointer StorageBuffer %4 +%87 = OpTypePointer Uniform %5 +%101 = OpConstant %3 2 +%115 = OpConstant %3 3 +%150 = OpConstant %3 4 +%156 = OpConstant %3 5 +%162 = OpConstant %3 6 +%179 = OpTypePointer Function %5 +%23 = OpFunction %2 None %24 +%19 = OpLabel +%51 = OpVariable %52 Function %26 +%46 = OpVariable %45 Function %30 +%42 = OpVariable %37 Function %29 +%36 = OpVariable %37 Function %38 +%47 = OpVariable %37 Function %48 +%43 = OpVariable %37 Function %29 +%39 = OpVariable %37 Function %40 +%49 = OpVariable %37 Function %50 +%44 = OpVariable %45 Function %30 +%41 = OpVariable %37 Function %29 +%22 = OpLoad %10 %20 +%27 = OpAccessChain %25 %13 %26 +OpBranch %53 +%53 = OpLabel +%54 = OpCompositeExtract %3 %22 0 +%56 = OpUGreaterThanEqual %55 %54 %12 +OpSelectionMerge %57 None +OpBranchConditional %56 %58 %57 +%58 = OpLabel +OpReturn +%57 = OpLabel +%62 = OpAccessChain %61 %16 %26 %54 %26 +%63 = OpLoad %4 %62 +OpStore %36 %63 +%64 = OpAccessChain %61 %16 %26 %54 %32 +%65 = OpLoad %4 %64 +OpStore %39 %65 +OpBranch %66 +%66 = OpLabel +OpLoopMerge %67 %69 None +OpBranch %68 +%68 = OpLabel +%70 = OpLoad %3 %51 +%71 = OpUGreaterThanEqual %55 %70 %12 +OpSelectionMerge %72 None +OpBranchConditional %71 %73 %72 +%73 = OpLabel +OpBranch %67 +%72 = OpLabel +%74 = OpLoad %3 %51 +%75 = OpIEqual %55 %74 %54 +OpSelectionMerge %76 None +OpBranchConditional %75 %77 %76 +%77 = OpLabel +OpBranch %69 +%76 = OpLabel +%78 = OpLoad %3 %51 +%79 = OpAccessChain %61 %16 %26 %78 %26 +%80 = OpLoad %4 %79 +OpStore %47 %80 +%81 = OpLoad %3 %51 +%82 = OpAccessChain %61 %16 %26 %81 %32 +%83 = OpLoad %4 %82 +OpStore %49 %83 +%84 = OpLoad %4 %47 +%85 = OpLoad %4 %36 +%86 = OpExtInst %5 %1 Distance %84 %85 +%88 = OpAccessChain %87 %27 %32 +%89 = OpLoad %5 %88 +%90 = OpFOrdLessThan %55 %86 %89 +OpSelectionMerge %91 None +OpBranchConditional %90 %92 %91 +%92 = OpLabel +%93 = OpLoad %4 %41 +%94 = OpLoad %4 %47 +%95 = OpFAdd %4 %93 %94 +OpStore %41 %95 +%96 = OpLoad %11 %44 +%97 = OpIAdd %11 %96 %31 +OpStore %44 %97 +OpBranch %91 +%91 = OpLabel +%98 = OpLoad %4 %47 +%99 = OpLoad %4 %36 +%100 = OpExtInst %5 %1 Distance %98 %99 +%102 = OpAccessChain %87 %27 %101 +%103 = OpLoad %5 %102 +%104 = OpFOrdLessThan %55 %100 %103 +OpSelectionMerge %105 None +OpBranchConditional %104 %106 %105 +%106 = OpLabel +%107 = OpLoad %4 %43 +%108 = OpLoad %4 %47 +%109 = OpLoad %4 %36 +%110 = OpFSub %4 %108 %109 +%111 = OpFSub %4 %107 %110 +OpStore %43 %111 +OpBranch %105 +%105 = OpLabel +%112 = OpLoad %4 %47 +%113 = OpLoad %4 %36 +%114 = OpExtInst %5 %1 Distance %112 %113 +%116 = OpAccessChain %87 %27 %115 +%117 = OpLoad %5 %116 +%118 = OpFOrdLessThan %55 %114 %117 +OpSelectionMerge %119 None +OpBranchConditional %118 %120 %119 +%120 = OpLabel +%121 = OpLoad %4 %42 +%122 = OpLoad %4 %49 +%123 = OpFAdd %4 %121 %122 +OpStore %42 %123 +%124 = OpLoad %11 %46 +%125 = OpIAdd %11 %124 %31 +OpStore %46 %125 +OpBranch %119 +%119 = OpLabel +OpBranch %69 +%69 = OpLabel +%126 = OpLoad %3 %51 +%127 = OpIAdd %3 %126 %32 +OpStore %51 %127 +OpBranch %66 +%67 = OpLabel +%128 = OpLoad %11 %44 +%129 = OpSGreaterThan %55 %128 %30 +OpSelectionMerge %130 None +OpBranchConditional %129 %131 %130 +%131 = OpLabel +%132 = OpLoad %4 %41 +%133 = OpLoad %11 %44 +%134 = OpConvertSToF %5 %133 +%135 = OpCompositeConstruct %4 %134 %134 +%136 = OpFDiv %4 %132 %135 +%137 = OpLoad %4 %36 +%138 = OpFSub %4 %136 %137 +OpStore %41 %138 +OpBranch %130 +%130 = OpLabel +%139 = OpLoad %11 %46 +%140 = OpSGreaterThan %55 %139 %30 +OpSelectionMerge %141 None +OpBranchConditional %140 %142 %141 +%142 = OpLabel +%143 = OpLoad %4 %42 +%144 = OpLoad %11 %46 +%145 = OpConvertSToF %5 %144 +%146 = OpCompositeConstruct %4 %145 %145 +%147 = OpFDiv %4 %143 %146 +OpStore %42 %147 +OpBranch %141 +%141 = OpLabel +%148 = OpLoad %4 %39 +%149 = OpLoad %4 %41 +%151 = OpAccessChain %87 %27 %150 +%152 = OpLoad %5 %151 +%153 = OpVectorTimesScalar %4 %149 %152 +%154 = OpFAdd %4 %148 %153 +%155 = OpLoad %4 %43 +%157 = OpAccessChain %87 %27 %156 +%158 = OpLoad %5 %157 +%159 = OpVectorTimesScalar %4 %155 %158 +%160 = OpFAdd %4 %154 %159 +%161 = OpLoad %4 %42 +%163 = OpAccessChain %87 %27 %162 +%164 = OpLoad %5 %163 +%165 = OpVectorTimesScalar %4 %161 %164 +%166 = OpFAdd %4 %160 %165 +OpStore %39 %166 +%167 = OpLoad %4 %39 +%168 = OpExtInst %4 %1 Normalize %167 +%169 = OpLoad %4 %39 +%170 = OpExtInst %5 %1 Length %169 +%171 = OpExtInst %5 %1 FClamp %170 %28 %33 +%172 = OpVectorTimesScalar %4 %168 %171 +OpStore %39 %172 +%173 = OpLoad %4 %36 +%174 = OpLoad %4 %39 +%175 = OpAccessChain %87 %27 %26 +%176 = OpLoad %5 %175 +%177 = OpVectorTimesScalar %4 %174 %176 +%178 = OpFAdd %4 %173 %177 +OpStore %36 %178 +%180 = OpAccessChain %179 %36 %26 +%181 = OpLoad %5 %180 +%182 = OpFOrdLessThan %55 %181 %34 +OpSelectionMerge %183 None +OpBranchConditional %182 %184 %183 +%184 = OpLabel +%185 = OpAccessChain %179 %36 %26 +OpStore %185 %35 +OpBranch %183 +%183 = OpLabel +%186 = OpAccessChain %179 %36 %26 +%187 = OpLoad %5 %186 +%188 = OpFOrdGreaterThan %55 %187 %35 +OpSelectionMerge %189 None +OpBranchConditional %188 %190 %189 +%190 = OpLabel +%191 = OpAccessChain %179 %36 %26 +OpStore %191 %34 +OpBranch %189 +%189 = OpLabel +%192 = OpAccessChain %179 %36 %32 +%193 = OpLoad %5 %192 +%194 = OpFOrdLessThan %55 %193 %34 +OpSelectionMerge %195 None +OpBranchConditional %194 %196 %195 +%196 = OpLabel +%197 = OpAccessChain %179 %36 %32 +OpStore %197 %35 +OpBranch %195 +%195 = OpLabel +%198 = OpAccessChain %179 %36 %32 +%199 = OpLoad %5 %198 +%200 = OpFOrdGreaterThan %55 %199 %35 +OpSelectionMerge %201 None +OpBranchConditional %200 %202 %201 +%202 = OpLabel +%203 = OpAccessChain %179 %36 %32 +OpStore %203 %34 +OpBranch %201 +%201 = OpLabel +%204 = OpLoad %4 %36 +%205 = OpAccessChain %61 %18 %26 %54 %26 +OpStore %205 %204 +%206 = OpLoad %4 %39 +%207 = OpAccessChain %61 %18 %26 %54 %32 +OpStore %207 %206 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/bounds-check-image-restrict.spvasm b/naga/tests/out/spv/bounds-check-image-restrict.spvasm new file mode 100644 index 0000000000..038685a559 --- /dev/null +++ b/naga/tests/out/spv/bounds-check-image-restrict.spvasm @@ -0,0 +1,456 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 299 +OpCapability Shader +OpCapability Sampled1D +OpCapability Image1D +OpCapability ImageQuery +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %269 "fragment_shader" %267 +OpExecutionMode %269 OriginUpperLeft +OpName %21 "image_1d" +OpName %23 "image_2d" +OpName %25 "image_2d_array" +OpName %27 "image_3d" +OpName %29 "image_multisampled_2d" +OpName %31 "image_depth_2d" +OpName %33 "image_depth_2d_array" +OpName %35 "image_depth_multisampled_2d" +OpName %37 "image_storage_1d" +OpName %39 "image_storage_2d" +OpName %41 "image_storage_2d_array" +OpName %43 "image_storage_3d" +OpName %46 "coords" +OpName %47 "level" +OpName %48 "test_textureLoad_1d" +OpName %61 "coords" +OpName %62 "level" +OpName %63 "test_textureLoad_2d" +OpName %76 "coords" +OpName %77 "index" +OpName %78 "level" +OpName %79 "test_textureLoad_2d_array_u" +OpName %94 "coords" +OpName %95 "index" +OpName %96 "level" +OpName %97 "test_textureLoad_2d_array_s" +OpName %111 "coords" +OpName %112 "level" +OpName %113 "test_textureLoad_3d" +OpName %126 "coords" +OpName %127 "_sample" +OpName %128 "test_textureLoad_multisampled_2d" +OpName %140 "coords" +OpName %141 "level" +OpName %142 "test_textureLoad_depth_2d" +OpName %156 "coords" +OpName %157 "index" +OpName %158 "level" +OpName %159 "test_textureLoad_depth_2d_array_u" +OpName %175 "coords" +OpName %176 "index" +OpName %177 "level" +OpName %178 "test_textureLoad_depth_2d_array_s" +OpName %193 "coords" +OpName %194 "_sample" +OpName %195 "test_textureLoad_depth_multisampled_2d" +OpName %208 "coords" +OpName %209 "value" +OpName %210 "test_textureStore_1d" +OpName %218 "coords" +OpName %219 "value" +OpName %220 "test_textureStore_2d" +OpName %229 "coords" +OpName %230 "array_index" +OpName %231 "value" +OpName %232 "test_textureStore_2d_array_u" +OpName %243 "coords" +OpName %244 "array_index" +OpName %245 "value" +OpName %246 "test_textureStore_2d_array_s" +OpName %256 "coords" +OpName %257 "value" +OpName %258 "test_textureStore_3d" +OpName %269 "fragment_shader" +OpDecorate %21 DescriptorSet 0 +OpDecorate %21 Binding 0 +OpDecorate %23 DescriptorSet 0 +OpDecorate %23 Binding 1 +OpDecorate %25 DescriptorSet 0 +OpDecorate %25 Binding 2 +OpDecorate %27 DescriptorSet 0 +OpDecorate %27 Binding 3 +OpDecorate %29 DescriptorSet 0 +OpDecorate %29 Binding 4 +OpDecorate %31 DescriptorSet 0 +OpDecorate %31 Binding 5 +OpDecorate %33 DescriptorSet 0 +OpDecorate %33 Binding 6 +OpDecorate %35 DescriptorSet 0 +OpDecorate %35 Binding 7 +OpDecorate %37 NonReadable +OpDecorate %37 DescriptorSet 0 +OpDecorate %37 Binding 8 +OpDecorate %39 NonReadable +OpDecorate %39 DescriptorSet 0 +OpDecorate %39 Binding 9 +OpDecorate %41 NonReadable +OpDecorate %41 DescriptorSet 0 +OpDecorate %41 Binding 10 +OpDecorate %43 NonReadable +OpDecorate %43 DescriptorSet 0 +OpDecorate %43 Binding 11 +OpDecorate %267 Location 0 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeImage %4 1D 0 0 0 1 Unknown +%5 = OpTypeInt 32 1 +%6 = OpTypeVector %4 4 +%7 = OpTypeImage %4 2D 0 0 0 1 Unknown +%8 = OpTypeVector %5 2 +%9 = OpTypeImage %4 2D 0 1 0 1 Unknown +%10 = OpTypeInt 32 0 +%11 = OpTypeImage %4 3D 0 0 0 1 Unknown +%12 = OpTypeVector %5 3 +%13 = OpTypeImage %4 2D 0 0 1 1 Unknown +%14 = OpTypeImage %4 2D 1 0 0 1 Unknown +%15 = OpTypeImage %4 2D 1 1 0 1 Unknown +%16 = OpTypeImage %4 2D 1 0 1 1 Unknown +%17 = OpTypeImage %4 1D 0 0 0 2 Rgba8 +%18 = OpTypeImage %4 2D 0 0 0 2 Rgba8 +%19 = OpTypeImage %4 2D 0 1 0 2 Rgba8 +%20 = OpTypeImage %4 3D 0 0 0 2 Rgba8 +%22 = OpTypePointer UniformConstant %3 +%21 = OpVariable %22 UniformConstant +%24 = OpTypePointer UniformConstant %7 +%23 = OpVariable %24 UniformConstant +%26 = OpTypePointer UniformConstant %9 +%25 = OpVariable %26 UniformConstant +%28 = OpTypePointer UniformConstant %11 +%27 = OpVariable %28 UniformConstant +%30 = OpTypePointer UniformConstant %13 +%29 = OpVariable %30 UniformConstant +%32 = OpTypePointer UniformConstant %14 +%31 = OpVariable %32 UniformConstant +%34 = OpTypePointer UniformConstant %15 +%33 = OpVariable %34 UniformConstant +%36 = OpTypePointer UniformConstant %16 +%35 = OpVariable %36 UniformConstant +%38 = OpTypePointer UniformConstant %17 +%37 = OpVariable %38 UniformConstant +%40 = OpTypePointer UniformConstant %18 +%39 = OpVariable %40 UniformConstant +%42 = OpTypePointer UniformConstant %19 +%41 = OpVariable %42 UniformConstant +%44 = OpTypePointer UniformConstant %20 +%43 = OpVariable %44 UniformConstant +%49 = OpTypeFunction %6 %5 %5 +%53 = OpConstant %5 1 +%64 = OpTypeFunction %6 %8 %5 +%71 = OpConstantComposite %8 %53 %53 +%80 = OpTypeFunction %6 %8 %10 %5 +%89 = OpConstantComposite %12 %53 %53 %53 +%98 = OpTypeFunction %6 %8 %5 %5 +%106 = OpConstantComposite %12 %53 %53 %53 +%114 = OpTypeFunction %6 %12 %5 +%121 = OpConstantComposite %12 %53 %53 %53 +%135 = OpConstantComposite %8 %53 %53 +%143 = OpTypeFunction %4 %8 %5 +%150 = OpConstantComposite %8 %53 %53 +%160 = OpTypeFunction %4 %8 %10 %5 +%169 = OpConstantComposite %12 %53 %53 %53 +%179 = OpTypeFunction %4 %8 %5 %5 +%187 = OpConstantComposite %12 %53 %53 %53 +%202 = OpConstantComposite %8 %53 %53 +%211 = OpTypeFunction %2 %5 %6 +%221 = OpTypeFunction %2 %8 %6 +%225 = OpConstantComposite %8 %53 %53 +%233 = OpTypeFunction %2 %8 %10 %6 +%239 = OpConstantComposite %12 %53 %53 %53 +%247 = OpTypeFunction %2 %8 %5 %6 +%252 = OpConstantComposite %12 %53 %53 %53 +%259 = OpTypeFunction %2 %12 %6 +%263 = OpConstantComposite %12 %53 %53 %53 +%268 = OpTypePointer Output %6 +%267 = OpVariable %268 Output +%270 = OpTypeFunction %2 +%280 = OpConstant %5 0 +%281 = OpConstantNull %8 +%282 = OpConstant %10 0 +%283 = OpConstantNull %12 +%284 = OpConstantNull %6 +%285 = OpConstant %4 0.0 +%286 = OpConstantComposite %6 %285 %285 %285 %285 +%48 = OpFunction %6 None %49 +%46 = OpFunctionParameter %5 +%47 = OpFunctionParameter %5 +%45 = OpLabel +%50 = OpLoad %3 %21 +OpBranch %51 +%51 = OpLabel +%52 = OpImageQueryLevels %5 %50 +%54 = OpISub %5 %52 %53 +%55 = OpExtInst %5 %1 UMin %47 %54 +%56 = OpImageQuerySizeLod %5 %50 %55 +%57 = OpISub %5 %56 %53 +%58 = OpExtInst %5 %1 UMin %46 %57 +%59 = OpImageFetch %6 %50 %58 Lod %55 +OpReturnValue %59 +OpFunctionEnd +%63 = OpFunction %6 None %64 +%61 = OpFunctionParameter %8 +%62 = OpFunctionParameter %5 +%60 = OpLabel +%65 = OpLoad %7 %23 +OpBranch %66 +%66 = OpLabel +%67 = OpImageQueryLevels %5 %65 +%68 = OpISub %5 %67 %53 +%69 = OpExtInst %5 %1 UMin %62 %68 +%70 = OpImageQuerySizeLod %8 %65 %69 +%72 = OpISub %8 %70 %71 +%73 = OpExtInst %8 %1 UMin %61 %72 +%74 = OpImageFetch %6 %65 %73 Lod %69 +OpReturnValue %74 +OpFunctionEnd +%79 = OpFunction %6 None %80 +%76 = OpFunctionParameter %8 +%77 = OpFunctionParameter %10 +%78 = OpFunctionParameter %5 +%75 = OpLabel +%81 = OpLoad %9 %25 +OpBranch %82 +%82 = OpLabel +%83 = OpBitcast %5 %77 +%84 = OpCompositeConstruct %12 %76 %83 +%85 = OpImageQueryLevels %5 %81 +%86 = OpISub %5 %85 %53 +%87 = OpExtInst %5 %1 UMin %78 %86 +%88 = OpImageQuerySizeLod %12 %81 %87 +%90 = OpISub %12 %88 %89 +%91 = OpExtInst %12 %1 UMin %84 %90 +%92 = OpImageFetch %6 %81 %91 Lod %87 +OpReturnValue %92 +OpFunctionEnd +%97 = OpFunction %6 None %98 +%94 = OpFunctionParameter %8 +%95 = OpFunctionParameter %5 +%96 = OpFunctionParameter %5 +%93 = OpLabel +%99 = OpLoad %9 %25 +OpBranch %100 +%100 = OpLabel +%101 = OpCompositeConstruct %12 %94 %95 +%102 = OpImageQueryLevels %5 %99 +%103 = OpISub %5 %102 %53 +%104 = OpExtInst %5 %1 UMin %96 %103 +%105 = OpImageQuerySizeLod %12 %99 %104 +%107 = OpISub %12 %105 %106 +%108 = OpExtInst %12 %1 UMin %101 %107 +%109 = OpImageFetch %6 %99 %108 Lod %104 +OpReturnValue %109 +OpFunctionEnd +%113 = OpFunction %6 None %114 +%111 = OpFunctionParameter %12 +%112 = OpFunctionParameter %5 +%110 = OpLabel +%115 = OpLoad %11 %27 +OpBranch %116 +%116 = OpLabel +%117 = OpImageQueryLevels %5 %115 +%118 = OpISub %5 %117 %53 +%119 = OpExtInst %5 %1 UMin %112 %118 +%120 = OpImageQuerySizeLod %12 %115 %119 +%122 = OpISub %12 %120 %121 +%123 = OpExtInst %12 %1 UMin %111 %122 +%124 = OpImageFetch %6 %115 %123 Lod %119 +OpReturnValue %124 +OpFunctionEnd +%128 = OpFunction %6 None %64 +%126 = OpFunctionParameter %8 +%127 = OpFunctionParameter %5 +%125 = OpLabel +%129 = OpLoad %13 %29 +OpBranch %130 +%130 = OpLabel +%131 = OpImageQuerySamples %5 %129 +%132 = OpISub %5 %131 %53 +%133 = OpExtInst %5 %1 UMin %127 %132 +%134 = OpImageQuerySize %8 %129 +%136 = OpISub %8 %134 %135 +%137 = OpExtInst %8 %1 UMin %126 %136 +%138 = OpImageFetch %6 %129 %137 Sample %133 +OpReturnValue %138 +OpFunctionEnd +%142 = OpFunction %4 None %143 +%140 = OpFunctionParameter %8 +%141 = OpFunctionParameter %5 +%139 = OpLabel +%144 = OpLoad %14 %31 +OpBranch %145 +%145 = OpLabel +%146 = OpImageQueryLevels %5 %144 +%147 = OpISub %5 %146 %53 +%148 = OpExtInst %5 %1 UMin %141 %147 +%149 = OpImageQuerySizeLod %8 %144 %148 +%151 = OpISub %8 %149 %150 +%152 = OpExtInst %8 %1 UMin %140 %151 +%153 = OpImageFetch %6 %144 %152 Lod %148 +%154 = OpCompositeExtract %4 %153 0 +OpReturnValue %154 +OpFunctionEnd +%159 = OpFunction %4 None %160 +%156 = OpFunctionParameter %8 +%157 = OpFunctionParameter %10 +%158 = OpFunctionParameter %5 +%155 = OpLabel +%161 = OpLoad %15 %33 +OpBranch %162 +%162 = OpLabel +%163 = OpBitcast %5 %157 +%164 = OpCompositeConstruct %12 %156 %163 +%165 = OpImageQueryLevels %5 %161 +%166 = OpISub %5 %165 %53 +%167 = OpExtInst %5 %1 UMin %158 %166 +%168 = OpImageQuerySizeLod %12 %161 %167 +%170 = OpISub %12 %168 %169 +%171 = OpExtInst %12 %1 UMin %164 %170 +%172 = OpImageFetch %6 %161 %171 Lod %167 +%173 = OpCompositeExtract %4 %172 0 +OpReturnValue %173 +OpFunctionEnd +%178 = OpFunction %4 None %179 +%175 = OpFunctionParameter %8 +%176 = OpFunctionParameter %5 +%177 = OpFunctionParameter %5 +%174 = OpLabel +%180 = OpLoad %15 %33 +OpBranch %181 +%181 = OpLabel +%182 = OpCompositeConstruct %12 %175 %176 +%183 = OpImageQueryLevels %5 %180 +%184 = OpISub %5 %183 %53 +%185 = OpExtInst %5 %1 UMin %177 %184 +%186 = OpImageQuerySizeLod %12 %180 %185 +%188 = OpISub %12 %186 %187 +%189 = OpExtInst %12 %1 UMin %182 %188 +%190 = OpImageFetch %6 %180 %189 Lod %185 +%191 = OpCompositeExtract %4 %190 0 +OpReturnValue %191 +OpFunctionEnd +%195 = OpFunction %4 None %143 +%193 = OpFunctionParameter %8 +%194 = OpFunctionParameter %5 +%192 = OpLabel +%196 = OpLoad %16 %35 +OpBranch %197 +%197 = OpLabel +%198 = OpImageQuerySamples %5 %196 +%199 = OpISub %5 %198 %53 +%200 = OpExtInst %5 %1 UMin %194 %199 +%201 = OpImageQuerySize %8 %196 +%203 = OpISub %8 %201 %202 +%204 = OpExtInst %8 %1 UMin %193 %203 +%205 = OpImageFetch %6 %196 %204 Sample %200 +%206 = OpCompositeExtract %4 %205 0 +OpReturnValue %206 +OpFunctionEnd +%210 = OpFunction %2 None %211 +%208 = OpFunctionParameter %5 +%209 = OpFunctionParameter %6 +%207 = OpLabel +%212 = OpLoad %17 %37 +OpBranch %213 +%213 = OpLabel +%214 = OpImageQuerySize %5 %212 +%215 = OpISub %5 %214 %53 +%216 = OpExtInst %5 %1 UMin %208 %215 +OpImageWrite %212 %216 %209 +OpReturn +OpFunctionEnd +%220 = OpFunction %2 None %221 +%218 = OpFunctionParameter %8 +%219 = OpFunctionParameter %6 +%217 = OpLabel +%222 = OpLoad %18 %39 +OpBranch %223 +%223 = OpLabel +%224 = OpImageQuerySize %8 %222 +%226 = OpISub %8 %224 %225 +%227 = OpExtInst %8 %1 UMin %218 %226 +OpImageWrite %222 %227 %219 +OpReturn +OpFunctionEnd +%232 = OpFunction %2 None %233 +%229 = OpFunctionParameter %8 +%230 = OpFunctionParameter %10 +%231 = OpFunctionParameter %6 +%228 = OpLabel +%234 = OpLoad %19 %41 +OpBranch %235 +%235 = OpLabel +%236 = OpBitcast %5 %230 +%237 = OpCompositeConstruct %12 %229 %236 +%238 = OpImageQuerySize %12 %234 +%240 = OpISub %12 %238 %239 +%241 = OpExtInst %12 %1 UMin %237 %240 +OpImageWrite %234 %241 %231 +OpReturn +OpFunctionEnd +%246 = OpFunction %2 None %247 +%243 = OpFunctionParameter %8 +%244 = OpFunctionParameter %5 +%245 = OpFunctionParameter %6 +%242 = OpLabel +%248 = OpLoad %19 %41 +OpBranch %249 +%249 = OpLabel +%250 = OpCompositeConstruct %12 %243 %244 +%251 = OpImageQuerySize %12 %248 +%253 = OpISub %12 %251 %252 +%254 = OpExtInst %12 %1 UMin %250 %253 +OpImageWrite %248 %254 %245 +OpReturn +OpFunctionEnd +%258 = OpFunction %2 None %259 +%256 = OpFunctionParameter %12 +%257 = OpFunctionParameter %6 +%255 = OpLabel +%260 = OpLoad %20 %43 +OpBranch %261 +%261 = OpLabel +%262 = OpImageQuerySize %12 %260 +%264 = OpISub %12 %262 %263 +%265 = OpExtInst %12 %1 UMin %256 %264 +OpImageWrite %260 %265 %257 +OpReturn +OpFunctionEnd +%269 = OpFunction %2 None %270 +%266 = OpLabel +%271 = OpLoad %3 %21 +%272 = OpLoad %7 %23 +%273 = OpLoad %9 %25 +%274 = OpLoad %11 %27 +%275 = OpLoad %13 %29 +%276 = OpLoad %17 %37 +%277 = OpLoad %18 %39 +%278 = OpLoad %19 %41 +%279 = OpLoad %20 %43 +OpBranch %287 +%287 = OpLabel +%288 = OpFunctionCall %6 %48 %280 %280 +%289 = OpFunctionCall %6 %63 %281 %280 +%290 = OpFunctionCall %6 %79 %281 %282 %280 +%291 = OpFunctionCall %6 %97 %281 %280 %280 +%292 = OpFunctionCall %6 %113 %283 %280 +%293 = OpFunctionCall %6 %128 %281 %280 +%294 = OpFunctionCall %2 %210 %280 %284 +%295 = OpFunctionCall %2 %220 %281 %284 +%296 = OpFunctionCall %2 %232 %281 %282 %284 +%297 = OpFunctionCall %2 %246 %281 %280 %284 +%298 = OpFunctionCall %2 %258 %283 %284 +OpStore %267 %286 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/bounds-check-image-rzsw.spvasm b/naga/tests/out/spv/bounds-check-image-rzsw.spvasm new file mode 100644 index 0000000000..a9eeb42047 --- /dev/null +++ b/naga/tests/out/spv/bounds-check-image-rzsw.spvasm @@ -0,0 +1,538 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 326 +OpCapability Shader +OpCapability Sampled1D +OpCapability Image1D +OpCapability ImageQuery +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %297 "fragment_shader" %295 +OpExecutionMode %297 OriginUpperLeft +OpName %21 "image_1d" +OpName %23 "image_2d" +OpName %25 "image_2d_array" +OpName %27 "image_3d" +OpName %29 "image_multisampled_2d" +OpName %31 "image_depth_2d" +OpName %33 "image_depth_2d_array" +OpName %35 "image_depth_multisampled_2d" +OpName %37 "image_storage_1d" +OpName %39 "image_storage_2d" +OpName %41 "image_storage_2d_array" +OpName %43 "image_storage_3d" +OpName %46 "coords" +OpName %47 "level" +OpName %48 "test_textureLoad_1d" +OpName %64 "coords" +OpName %65 "level" +OpName %66 "test_textureLoad_2d" +OpName %82 "coords" +OpName %83 "index" +OpName %84 "level" +OpName %85 "test_textureLoad_2d_array_u" +OpName %103 "coords" +OpName %104 "index" +OpName %105 "level" +OpName %106 "test_textureLoad_2d_array_s" +OpName %122 "coords" +OpName %123 "level" +OpName %124 "test_textureLoad_3d" +OpName %139 "coords" +OpName %140 "_sample" +OpName %141 "test_textureLoad_multisampled_2d" +OpName %155 "coords" +OpName %156 "level" +OpName %157 "test_textureLoad_depth_2d" +OpName %173 "coords" +OpName %174 "index" +OpName %175 "level" +OpName %176 "test_textureLoad_depth_2d_array_u" +OpName %194 "coords" +OpName %195 "index" +OpName %196 "level" +OpName %197 "test_textureLoad_depth_2d_array_s" +OpName %214 "coords" +OpName %215 "_sample" +OpName %216 "test_textureLoad_depth_multisampled_2d" +OpName %231 "coords" +OpName %232 "value" +OpName %233 "test_textureStore_1d" +OpName %242 "coords" +OpName %243 "value" +OpName %244 "test_textureStore_2d" +OpName %254 "coords" +OpName %255 "array_index" +OpName %256 "value" +OpName %257 "test_textureStore_2d_array_u" +OpName %269 "coords" +OpName %270 "array_index" +OpName %271 "value" +OpName %272 "test_textureStore_2d_array_s" +OpName %283 "coords" +OpName %284 "value" +OpName %285 "test_textureStore_3d" +OpName %297 "fragment_shader" +OpDecorate %21 DescriptorSet 0 +OpDecorate %21 Binding 0 +OpDecorate %23 DescriptorSet 0 +OpDecorate %23 Binding 1 +OpDecorate %25 DescriptorSet 0 +OpDecorate %25 Binding 2 +OpDecorate %27 DescriptorSet 0 +OpDecorate %27 Binding 3 +OpDecorate %29 DescriptorSet 0 +OpDecorate %29 Binding 4 +OpDecorate %31 DescriptorSet 0 +OpDecorate %31 Binding 5 +OpDecorate %33 DescriptorSet 0 +OpDecorate %33 Binding 6 +OpDecorate %35 DescriptorSet 0 +OpDecorate %35 Binding 7 +OpDecorate %37 NonReadable +OpDecorate %37 DescriptorSet 0 +OpDecorate %37 Binding 8 +OpDecorate %39 NonReadable +OpDecorate %39 DescriptorSet 0 +OpDecorate %39 Binding 9 +OpDecorate %41 NonReadable +OpDecorate %41 DescriptorSet 0 +OpDecorate %41 Binding 10 +OpDecorate %43 NonReadable +OpDecorate %43 DescriptorSet 0 +OpDecorate %43 Binding 11 +OpDecorate %295 Location 0 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeImage %4 1D 0 0 0 1 Unknown +%5 = OpTypeInt 32 1 +%6 = OpTypeVector %4 4 +%7 = OpTypeImage %4 2D 0 0 0 1 Unknown +%8 = OpTypeVector %5 2 +%9 = OpTypeImage %4 2D 0 1 0 1 Unknown +%10 = OpTypeInt 32 0 +%11 = OpTypeImage %4 3D 0 0 0 1 Unknown +%12 = OpTypeVector %5 3 +%13 = OpTypeImage %4 2D 0 0 1 1 Unknown +%14 = OpTypeImage %4 2D 1 0 0 1 Unknown +%15 = OpTypeImage %4 2D 1 1 0 1 Unknown +%16 = OpTypeImage %4 2D 1 0 1 1 Unknown +%17 = OpTypeImage %4 1D 0 0 0 2 Rgba8 +%18 = OpTypeImage %4 2D 0 0 0 2 Rgba8 +%19 = OpTypeImage %4 2D 0 1 0 2 Rgba8 +%20 = OpTypeImage %4 3D 0 0 0 2 Rgba8 +%22 = OpTypePointer UniformConstant %3 +%21 = OpVariable %22 UniformConstant +%24 = OpTypePointer UniformConstant %7 +%23 = OpVariable %24 UniformConstant +%26 = OpTypePointer UniformConstant %9 +%25 = OpVariable %26 UniformConstant +%28 = OpTypePointer UniformConstant %11 +%27 = OpVariable %28 UniformConstant +%30 = OpTypePointer UniformConstant %13 +%29 = OpVariable %30 UniformConstant +%32 = OpTypePointer UniformConstant %14 +%31 = OpVariable %32 UniformConstant +%34 = OpTypePointer UniformConstant %15 +%33 = OpVariable %34 UniformConstant +%36 = OpTypePointer UniformConstant %16 +%35 = OpVariable %36 UniformConstant +%38 = OpTypePointer UniformConstant %17 +%37 = OpVariable %38 UniformConstant +%40 = OpTypePointer UniformConstant %18 +%39 = OpVariable %40 UniformConstant +%42 = OpTypePointer UniformConstant %19 +%41 = OpVariable %42 UniformConstant +%44 = OpTypePointer UniformConstant %20 +%43 = OpVariable %44 UniformConstant +%49 = OpTypeFunction %6 %5 %5 +%52 = OpTypeBool +%53 = OpConstantNull %6 +%67 = OpTypeFunction %6 %8 %5 +%75 = OpTypeVector %52 2 +%86 = OpTypeFunction %6 %8 %10 %5 +%96 = OpTypeVector %52 3 +%107 = OpTypeFunction %6 %8 %5 %5 +%125 = OpTypeFunction %6 %12 %5 +%158 = OpTypeFunction %4 %8 %5 +%177 = OpTypeFunction %4 %8 %10 %5 +%198 = OpTypeFunction %4 %8 %5 %5 +%234 = OpTypeFunction %2 %5 %6 +%245 = OpTypeFunction %2 %8 %6 +%258 = OpTypeFunction %2 %8 %10 %6 +%273 = OpTypeFunction %2 %8 %5 %6 +%286 = OpTypeFunction %2 %12 %6 +%296 = OpTypePointer Output %6 +%295 = OpVariable %296 Output +%298 = OpTypeFunction %2 +%308 = OpConstant %5 0 +%309 = OpConstantNull %8 +%310 = OpConstant %10 0 +%311 = OpConstantNull %12 +%312 = OpConstant %4 0.0 +%313 = OpConstantComposite %6 %312 %312 %312 %312 +%48 = OpFunction %6 None %49 +%46 = OpFunctionParameter %5 +%47 = OpFunctionParameter %5 +%45 = OpLabel +%50 = OpLoad %3 %21 +OpBranch %51 +%51 = OpLabel +%54 = OpImageQueryLevels %5 %50 +%55 = OpULessThan %52 %47 %54 +OpSelectionMerge %56 None +OpBranchConditional %55 %57 %56 +%57 = OpLabel +%58 = OpImageQuerySizeLod %5 %50 %47 +%59 = OpULessThan %52 %46 %58 +OpBranchConditional %59 %60 %56 +%60 = OpLabel +%61 = OpImageFetch %6 %50 %46 Lod %47 +OpBranch %56 +%56 = OpLabel +%62 = OpPhi %6 %53 %51 %53 %57 %61 %60 +OpReturnValue %62 +OpFunctionEnd +%66 = OpFunction %6 None %67 +%64 = OpFunctionParameter %8 +%65 = OpFunctionParameter %5 +%63 = OpLabel +%68 = OpLoad %7 %23 +OpBranch %69 +%69 = OpLabel +%70 = OpImageQueryLevels %5 %68 +%71 = OpULessThan %52 %65 %70 +OpSelectionMerge %72 None +OpBranchConditional %71 %73 %72 +%73 = OpLabel +%74 = OpImageQuerySizeLod %8 %68 %65 +%76 = OpULessThan %75 %64 %74 +%77 = OpAll %52 %76 +OpBranchConditional %77 %78 %72 +%78 = OpLabel +%79 = OpImageFetch %6 %68 %64 Lod %65 +OpBranch %72 +%72 = OpLabel +%80 = OpPhi %6 %53 %69 %53 %73 %79 %78 +OpReturnValue %80 +OpFunctionEnd +%85 = OpFunction %6 None %86 +%82 = OpFunctionParameter %8 +%83 = OpFunctionParameter %10 +%84 = OpFunctionParameter %5 +%81 = OpLabel +%87 = OpLoad %9 %25 +OpBranch %88 +%88 = OpLabel +%89 = OpBitcast %5 %83 +%90 = OpCompositeConstruct %12 %82 %89 +%91 = OpImageQueryLevels %5 %87 +%92 = OpULessThan %52 %84 %91 +OpSelectionMerge %93 None +OpBranchConditional %92 %94 %93 +%94 = OpLabel +%95 = OpImageQuerySizeLod %12 %87 %84 +%97 = OpULessThan %96 %90 %95 +%98 = OpAll %52 %97 +OpBranchConditional %98 %99 %93 +%99 = OpLabel +%100 = OpImageFetch %6 %87 %90 Lod %84 +OpBranch %93 +%93 = OpLabel +%101 = OpPhi %6 %53 %88 %53 %94 %100 %99 +OpReturnValue %101 +OpFunctionEnd +%106 = OpFunction %6 None %107 +%103 = OpFunctionParameter %8 +%104 = OpFunctionParameter %5 +%105 = OpFunctionParameter %5 +%102 = OpLabel +%108 = OpLoad %9 %25 +OpBranch %109 +%109 = OpLabel +%110 = OpCompositeConstruct %12 %103 %104 +%111 = OpImageQueryLevels %5 %108 +%112 = OpULessThan %52 %105 %111 +OpSelectionMerge %113 None +OpBranchConditional %112 %114 %113 +%114 = OpLabel +%115 = OpImageQuerySizeLod %12 %108 %105 +%116 = OpULessThan %96 %110 %115 +%117 = OpAll %52 %116 +OpBranchConditional %117 %118 %113 +%118 = OpLabel +%119 = OpImageFetch %6 %108 %110 Lod %105 +OpBranch %113 +%113 = OpLabel +%120 = OpPhi %6 %53 %109 %53 %114 %119 %118 +OpReturnValue %120 +OpFunctionEnd +%124 = OpFunction %6 None %125 +%122 = OpFunctionParameter %12 +%123 = OpFunctionParameter %5 +%121 = OpLabel +%126 = OpLoad %11 %27 +OpBranch %127 +%127 = OpLabel +%128 = OpImageQueryLevels %5 %126 +%129 = OpULessThan %52 %123 %128 +OpSelectionMerge %130 None +OpBranchConditional %129 %131 %130 +%131 = OpLabel +%132 = OpImageQuerySizeLod %12 %126 %123 +%133 = OpULessThan %96 %122 %132 +%134 = OpAll %52 %133 +OpBranchConditional %134 %135 %130 +%135 = OpLabel +%136 = OpImageFetch %6 %126 %122 Lod %123 +OpBranch %130 +%130 = OpLabel +%137 = OpPhi %6 %53 %127 %53 %131 %136 %135 +OpReturnValue %137 +OpFunctionEnd +%141 = OpFunction %6 None %67 +%139 = OpFunctionParameter %8 +%140 = OpFunctionParameter %5 +%138 = OpLabel +%142 = OpLoad %13 %29 +OpBranch %143 +%143 = OpLabel +%144 = OpImageQuerySamples %5 %142 +%145 = OpULessThan %52 %140 %144 +OpSelectionMerge %146 None +OpBranchConditional %145 %147 %146 +%147 = OpLabel +%148 = OpImageQuerySize %8 %142 +%149 = OpULessThan %75 %139 %148 +%150 = OpAll %52 %149 +OpBranchConditional %150 %151 %146 +%151 = OpLabel +%152 = OpImageFetch %6 %142 %139 Sample %140 +OpBranch %146 +%146 = OpLabel +%153 = OpPhi %6 %53 %143 %53 %147 %152 %151 +OpReturnValue %153 +OpFunctionEnd +%157 = OpFunction %4 None %158 +%155 = OpFunctionParameter %8 +%156 = OpFunctionParameter %5 +%154 = OpLabel +%159 = OpLoad %14 %31 +OpBranch %160 +%160 = OpLabel +%161 = OpImageQueryLevels %5 %159 +%162 = OpULessThan %52 %156 %161 +OpSelectionMerge %163 None +OpBranchConditional %162 %164 %163 +%164 = OpLabel +%165 = OpImageQuerySizeLod %8 %159 %156 +%166 = OpULessThan %75 %155 %165 +%167 = OpAll %52 %166 +OpBranchConditional %167 %168 %163 +%168 = OpLabel +%169 = OpImageFetch %6 %159 %155 Lod %156 +OpBranch %163 +%163 = OpLabel +%170 = OpPhi %6 %53 %160 %53 %164 %169 %168 +%171 = OpCompositeExtract %4 %170 0 +OpReturnValue %171 +OpFunctionEnd +%176 = OpFunction %4 None %177 +%173 = OpFunctionParameter %8 +%174 = OpFunctionParameter %10 +%175 = OpFunctionParameter %5 +%172 = OpLabel +%178 = OpLoad %15 %33 +OpBranch %179 +%179 = OpLabel +%180 = OpBitcast %5 %174 +%181 = OpCompositeConstruct %12 %173 %180 +%182 = OpImageQueryLevels %5 %178 +%183 = OpULessThan %52 %175 %182 +OpSelectionMerge %184 None +OpBranchConditional %183 %185 %184 +%185 = OpLabel +%186 = OpImageQuerySizeLod %12 %178 %175 +%187 = OpULessThan %96 %181 %186 +%188 = OpAll %52 %187 +OpBranchConditional %188 %189 %184 +%189 = OpLabel +%190 = OpImageFetch %6 %178 %181 Lod %175 +OpBranch %184 +%184 = OpLabel +%191 = OpPhi %6 %53 %179 %53 %185 %190 %189 +%192 = OpCompositeExtract %4 %191 0 +OpReturnValue %192 +OpFunctionEnd +%197 = OpFunction %4 None %198 +%194 = OpFunctionParameter %8 +%195 = OpFunctionParameter %5 +%196 = OpFunctionParameter %5 +%193 = OpLabel +%199 = OpLoad %15 %33 +OpBranch %200 +%200 = OpLabel +%201 = OpCompositeConstruct %12 %194 %195 +%202 = OpImageQueryLevels %5 %199 +%203 = OpULessThan %52 %196 %202 +OpSelectionMerge %204 None +OpBranchConditional %203 %205 %204 +%205 = OpLabel +%206 = OpImageQuerySizeLod %12 %199 %196 +%207 = OpULessThan %96 %201 %206 +%208 = OpAll %52 %207 +OpBranchConditional %208 %209 %204 +%209 = OpLabel +%210 = OpImageFetch %6 %199 %201 Lod %196 +OpBranch %204 +%204 = OpLabel +%211 = OpPhi %6 %53 %200 %53 %205 %210 %209 +%212 = OpCompositeExtract %4 %211 0 +OpReturnValue %212 +OpFunctionEnd +%216 = OpFunction %4 None %158 +%214 = OpFunctionParameter %8 +%215 = OpFunctionParameter %5 +%213 = OpLabel +%217 = OpLoad %16 %35 +OpBranch %218 +%218 = OpLabel +%219 = OpImageQuerySamples %5 %217 +%220 = OpULessThan %52 %215 %219 +OpSelectionMerge %221 None +OpBranchConditional %220 %222 %221 +%222 = OpLabel +%223 = OpImageQuerySize %8 %217 +%224 = OpULessThan %75 %214 %223 +%225 = OpAll %52 %224 +OpBranchConditional %225 %226 %221 +%226 = OpLabel +%227 = OpImageFetch %6 %217 %214 Sample %215 +OpBranch %221 +%221 = OpLabel +%228 = OpPhi %6 %53 %218 %53 %222 %227 %226 +%229 = OpCompositeExtract %4 %228 0 +OpReturnValue %229 +OpFunctionEnd +%233 = OpFunction %2 None %234 +%231 = OpFunctionParameter %5 +%232 = OpFunctionParameter %6 +%230 = OpLabel +%235 = OpLoad %17 %37 +OpBranch %236 +%236 = OpLabel +%237 = OpImageQuerySize %5 %235 +%238 = OpULessThan %52 %231 %237 +OpSelectionMerge %239 None +OpBranchConditional %238 %240 %239 +%240 = OpLabel +OpImageWrite %235 %231 %232 +OpBranch %239 +%239 = OpLabel +OpReturn +OpFunctionEnd +%244 = OpFunction %2 None %245 +%242 = OpFunctionParameter %8 +%243 = OpFunctionParameter %6 +%241 = OpLabel +%246 = OpLoad %18 %39 +OpBranch %247 +%247 = OpLabel +%248 = OpImageQuerySize %8 %246 +%249 = OpULessThan %75 %242 %248 +%250 = OpAll %52 %249 +OpSelectionMerge %251 None +OpBranchConditional %250 %252 %251 +%252 = OpLabel +OpImageWrite %246 %242 %243 +OpBranch %251 +%251 = OpLabel +OpReturn +OpFunctionEnd +%257 = OpFunction %2 None %258 +%254 = OpFunctionParameter %8 +%255 = OpFunctionParameter %10 +%256 = OpFunctionParameter %6 +%253 = OpLabel +%259 = OpLoad %19 %41 +OpBranch %260 +%260 = OpLabel +%261 = OpBitcast %5 %255 +%262 = OpCompositeConstruct %12 %254 %261 +%263 = OpImageQuerySize %12 %259 +%264 = OpULessThan %96 %262 %263 +%265 = OpAll %52 %264 +OpSelectionMerge %266 None +OpBranchConditional %265 %267 %266 +%267 = OpLabel +OpImageWrite %259 %262 %256 +OpBranch %266 +%266 = OpLabel +OpReturn +OpFunctionEnd +%272 = OpFunction %2 None %273 +%269 = OpFunctionParameter %8 +%270 = OpFunctionParameter %5 +%271 = OpFunctionParameter %6 +%268 = OpLabel +%274 = OpLoad %19 %41 +OpBranch %275 +%275 = OpLabel +%276 = OpCompositeConstruct %12 %269 %270 +%277 = OpImageQuerySize %12 %274 +%278 = OpULessThan %96 %276 %277 +%279 = OpAll %52 %278 +OpSelectionMerge %280 None +OpBranchConditional %279 %281 %280 +%281 = OpLabel +OpImageWrite %274 %276 %271 +OpBranch %280 +%280 = OpLabel +OpReturn +OpFunctionEnd +%285 = OpFunction %2 None %286 +%283 = OpFunctionParameter %12 +%284 = OpFunctionParameter %6 +%282 = OpLabel +%287 = OpLoad %20 %43 +OpBranch %288 +%288 = OpLabel +%289 = OpImageQuerySize %12 %287 +%290 = OpULessThan %96 %283 %289 +%291 = OpAll %52 %290 +OpSelectionMerge %292 None +OpBranchConditional %291 %293 %292 +%293 = OpLabel +OpImageWrite %287 %283 %284 +OpBranch %292 +%292 = OpLabel +OpReturn +OpFunctionEnd +%297 = OpFunction %2 None %298 +%294 = OpLabel +%299 = OpLoad %3 %21 +%300 = OpLoad %7 %23 +%301 = OpLoad %9 %25 +%302 = OpLoad %11 %27 +%303 = OpLoad %13 %29 +%304 = OpLoad %17 %37 +%305 = OpLoad %18 %39 +%306 = OpLoad %19 %41 +%307 = OpLoad %20 %43 +OpBranch %314 +%314 = OpLabel +%315 = OpFunctionCall %6 %48 %308 %308 +%316 = OpFunctionCall %6 %66 %309 %308 +%317 = OpFunctionCall %6 %85 %309 %310 %308 +%318 = OpFunctionCall %6 %106 %309 %308 %308 +%319 = OpFunctionCall %6 %124 %311 %308 +%320 = OpFunctionCall %6 %141 %309 %308 +%321 = OpFunctionCall %2 %233 %308 %53 +%322 = OpFunctionCall %2 %244 %309 %53 +%323 = OpFunctionCall %2 %257 %309 %310 %53 +%324 = OpFunctionCall %2 %272 %309 %308 %53 +%325 = OpFunctionCall %2 %285 %311 %53 +OpStore %295 %313 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/bounds-check-restrict.spvasm b/naga/tests/out/spv/bounds-check-restrict.spvasm new file mode 100644 index 0000000000..c7cf675a17 --- /dev/null +++ b/naga/tests/out/spv/bounds-check-restrict.spvasm @@ -0,0 +1,235 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 163 +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpDecorate %4 ArrayStride 4 +OpDecorate %9 ArrayStride 4 +OpMemberDecorate %10 0 Offset 0 +OpMemberDecorate %10 1 Offset 48 +OpMemberDecorate %10 2 Offset 64 +OpMemberDecorate %10 2 ColMajor +OpMemberDecorate %10 2 MatrixStride 16 +OpMemberDecorate %10 3 Offset 112 +OpDecorate %10 Block +OpDecorate %12 DescriptorSet 0 +OpDecorate %12 Binding 0 +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%6 = OpTypeInt 32 0 +%5 = OpConstant %6 10 +%4 = OpTypeArray %3 %5 +%7 = OpTypeVector %3 4 +%8 = OpTypeMatrix %7 3 +%9 = OpTypeRuntimeArray %3 +%10 = OpTypeStruct %4 %7 %8 %9 +%11 = OpTypeInt 32 1 +%13 = OpTypePointer StorageBuffer %10 +%12 = OpVariable %13 StorageBuffer +%17 = OpTypeFunction %3 %11 +%19 = OpTypePointer StorageBuffer %4 +%20 = OpTypePointer StorageBuffer %3 +%21 = OpConstant %6 9 +%23 = OpConstant %6 0 +%30 = OpTypePointer StorageBuffer %9 +%32 = OpConstant %6 1 +%35 = OpConstant %6 3 +%42 = OpTypePointer StorageBuffer %7 +%43 = OpTypePointer StorageBuffer %3 +%51 = OpTypeFunction %3 %7 %11 +%58 = OpTypeFunction %7 %11 +%60 = OpTypePointer StorageBuffer %8 +%61 = OpTypePointer StorageBuffer %7 +%62 = OpConstant %6 2 +%70 = OpTypeFunction %3 %11 %11 +%79 = OpConstant %3 100.0 +%91 = OpTypeFunction %3 +%105 = OpTypeFunction %2 %11 %3 +%129 = OpTypeFunction %2 %11 %7 +%138 = OpTypeFunction %2 %11 %11 %3 +%158 = OpTypeFunction %2 %3 +%16 = OpFunction %3 None %17 +%15 = OpFunctionParameter %11 +%14 = OpLabel +OpBranch %18 +%18 = OpLabel +%22 = OpExtInst %6 %1 UMin %15 %21 +%24 = OpAccessChain %20 %12 %23 %22 +%25 = OpLoad %3 %24 +OpReturnValue %25 +OpFunctionEnd +%28 = OpFunction %3 None %17 +%27 = OpFunctionParameter %11 +%26 = OpLabel +OpBranch %29 +%29 = OpLabel +%31 = OpArrayLength %6 %12 3 +%33 = OpISub %6 %31 %32 +%34 = OpExtInst %6 %1 UMin %27 %33 +%36 = OpAccessChain %20 %12 %35 %34 +%37 = OpLoad %3 %36 +OpReturnValue %37 +OpFunctionEnd +%40 = OpFunction %3 None %17 +%39 = OpFunctionParameter %11 +%38 = OpLabel +OpBranch %41 +%41 = OpLabel +%44 = OpExtInst %6 %1 UMin %39 %35 +%45 = OpAccessChain %43 %12 %32 %44 +%46 = OpLoad %3 %45 +OpReturnValue %46 +OpFunctionEnd +%50 = OpFunction %3 None %51 +%48 = OpFunctionParameter %7 +%49 = OpFunctionParameter %11 +%47 = OpLabel +OpBranch %52 +%52 = OpLabel +%53 = OpExtInst %6 %1 UMin %49 %35 +%54 = OpVectorExtractDynamic %3 %48 %53 +OpReturnValue %54 +OpFunctionEnd +%57 = OpFunction %7 None %58 +%56 = OpFunctionParameter %11 +%55 = OpLabel +OpBranch %59 +%59 = OpLabel +%63 = OpExtInst %6 %1 UMin %56 %62 +%64 = OpAccessChain %61 %12 %62 %63 +%65 = OpLoad %7 %64 +OpReturnValue %65 +OpFunctionEnd +%69 = OpFunction %3 None %70 +%67 = OpFunctionParameter %11 +%68 = OpFunctionParameter %11 +%66 = OpLabel +OpBranch %71 +%71 = OpLabel +%72 = OpExtInst %6 %1 UMin %68 %35 +%73 = OpExtInst %6 %1 UMin %67 %62 +%74 = OpAccessChain %43 %12 %62 %73 %72 +%75 = OpLoad %3 %74 +OpReturnValue %75 +OpFunctionEnd +%78 = OpFunction %3 None %17 +%77 = OpFunctionParameter %11 +%76 = OpLabel +OpBranch %80 +%80 = OpLabel +%81 = OpConvertSToF %3 %77 +%82 = OpFDiv %3 %81 %79 +%83 = OpExtInst %3 %1 Sin %82 +%84 = OpFMul %3 %83 %79 +%85 = OpConvertFToS %11 %84 +%86 = OpExtInst %6 %1 UMin %85 %21 +%87 = OpAccessChain %20 %12 %23 %86 +%88 = OpLoad %3 %87 +OpReturnValue %88 +OpFunctionEnd +%90 = OpFunction %3 None %91 +%89 = OpLabel +OpBranch %92 +%92 = OpLabel +%93 = OpAccessChain %20 %12 %23 %21 +%94 = OpLoad %3 %93 +%95 = OpAccessChain %43 %12 %32 %35 +%96 = OpLoad %3 %95 +%97 = OpFAdd %3 %94 %96 +%98 = OpAccessChain %43 %12 %62 %62 %35 +%99 = OpLoad %3 %98 +%100 = OpFAdd %3 %97 %99 +OpReturnValue %100 +OpFunctionEnd +%104 = OpFunction %2 None %105 +%102 = OpFunctionParameter %11 +%103 = OpFunctionParameter %3 +%101 = OpLabel +OpBranch %106 +%106 = OpLabel +%107 = OpExtInst %6 %1 UMin %102 %21 +%108 = OpAccessChain %20 %12 %23 %107 +OpStore %108 %103 +OpReturn +OpFunctionEnd +%112 = OpFunction %2 None %105 +%110 = OpFunctionParameter %11 +%111 = OpFunctionParameter %3 +%109 = OpLabel +OpBranch %113 +%113 = OpLabel +%114 = OpArrayLength %6 %12 3 +%115 = OpISub %6 %114 %32 +%116 = OpExtInst %6 %1 UMin %110 %115 +%117 = OpAccessChain %20 %12 %35 %116 +OpStore %117 %111 +OpReturn +OpFunctionEnd +%121 = OpFunction %2 None %105 +%119 = OpFunctionParameter %11 +%120 = OpFunctionParameter %3 +%118 = OpLabel +OpBranch %122 +%122 = OpLabel +%123 = OpExtInst %6 %1 UMin %119 %35 +%124 = OpAccessChain %43 %12 %32 %123 +OpStore %124 %120 +OpReturn +OpFunctionEnd +%128 = OpFunction %2 None %129 +%126 = OpFunctionParameter %11 +%127 = OpFunctionParameter %7 +%125 = OpLabel +OpBranch %130 +%130 = OpLabel +%131 = OpExtInst %6 %1 UMin %126 %62 +%132 = OpAccessChain %61 %12 %62 %131 +OpStore %132 %127 +OpReturn +OpFunctionEnd +%137 = OpFunction %2 None %138 +%134 = OpFunctionParameter %11 +%135 = OpFunctionParameter %11 +%136 = OpFunctionParameter %3 +%133 = OpLabel +OpBranch %139 +%139 = OpLabel +%140 = OpExtInst %6 %1 UMin %135 %35 +%141 = OpExtInst %6 %1 UMin %134 %62 +%142 = OpAccessChain %43 %12 %62 %141 %140 +OpStore %142 %136 +OpReturn +OpFunctionEnd +%146 = OpFunction %2 None %105 +%144 = OpFunctionParameter %11 +%145 = OpFunctionParameter %3 +%143 = OpLabel +OpBranch %147 +%147 = OpLabel +%148 = OpConvertSToF %3 %144 +%149 = OpFDiv %3 %148 %79 +%150 = OpExtInst %3 %1 Sin %149 +%151 = OpFMul %3 %150 %79 +%152 = OpConvertFToS %11 %151 +%153 = OpExtInst %6 %1 UMin %152 %21 +%154 = OpAccessChain %20 %12 %23 %153 +OpStore %154 %145 +OpReturn +OpFunctionEnd +%157 = OpFunction %2 None %158 +%156 = OpFunctionParameter %3 +%155 = OpLabel +OpBranch %159 +%159 = OpLabel +%160 = OpAccessChain %20 %12 %23 %21 +OpStore %160 %156 +%161 = OpAccessChain %43 %12 %32 %35 +OpStore %161 %156 +%162 = OpAccessChain %43 %12 %62 %62 %35 +OpStore %162 %156 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/bounds-check-zero.spvasm b/naga/tests/out/spv/bounds-check-zero.spvasm new file mode 100644 index 0000000000..2bb81261e1 --- /dev/null +++ b/naga/tests/out/spv/bounds-check-zero.spvasm @@ -0,0 +1,311 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 200 +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpDecorate %4 ArrayStride 4 +OpDecorate %9 ArrayStride 4 +OpMemberDecorate %10 0 Offset 0 +OpMemberDecorate %10 1 Offset 48 +OpMemberDecorate %10 2 Offset 64 +OpMemberDecorate %10 2 ColMajor +OpMemberDecorate %10 2 MatrixStride 16 +OpMemberDecorate %10 3 Offset 112 +OpDecorate %10 Block +OpDecorate %12 DescriptorSet 0 +OpDecorate %12 Binding 0 +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%6 = OpTypeInt 32 0 +%5 = OpConstant %6 10 +%4 = OpTypeArray %3 %5 +%7 = OpTypeVector %3 4 +%8 = OpTypeMatrix %7 3 +%9 = OpTypeRuntimeArray %3 +%10 = OpTypeStruct %4 %7 %8 %9 +%11 = OpTypeInt 32 1 +%13 = OpTypePointer StorageBuffer %10 +%12 = OpVariable %13 StorageBuffer +%17 = OpTypeFunction %3 %11 +%19 = OpTypePointer StorageBuffer %4 +%20 = OpTypePointer StorageBuffer %3 +%22 = OpTypeBool +%23 = OpConstant %6 0 +%25 = OpConstantNull %3 +%34 = OpTypePointer StorageBuffer %9 +%37 = OpConstant %6 3 +%47 = OpTypePointer StorageBuffer %7 +%48 = OpTypePointer StorageBuffer %3 +%49 = OpConstant %6 4 +%51 = OpConstant %6 1 +%61 = OpTypeFunction %3 %7 %11 +%71 = OpTypeFunction %7 %11 +%73 = OpTypePointer StorageBuffer %8 +%74 = OpTypePointer StorageBuffer %7 +%76 = OpConstant %6 2 +%78 = OpConstantNull %7 +%87 = OpTypeFunction %3 %11 %11 +%100 = OpConstant %3 100.0 +%115 = OpTypeFunction %3 +%117 = OpConstant %6 9 +%130 = OpTypeFunction %2 %11 %3 +%159 = OpTypeFunction %2 %11 %7 +%170 = OpTypeFunction %2 %11 %11 %3 +%195 = OpTypeFunction %2 %3 +%16 = OpFunction %3 None %17 +%15 = OpFunctionParameter %11 +%14 = OpLabel +OpBranch %18 +%18 = OpLabel +%21 = OpULessThan %22 %15 %5 +OpSelectionMerge %26 None +OpBranchConditional %21 %27 %26 +%27 = OpLabel +%24 = OpAccessChain %20 %12 %23 %15 +%28 = OpLoad %3 %24 +OpBranch %26 +%26 = OpLabel +%29 = OpPhi %3 %25 %18 %28 %27 +OpReturnValue %29 +OpFunctionEnd +%32 = OpFunction %3 None %17 +%31 = OpFunctionParameter %11 +%30 = OpLabel +OpBranch %33 +%33 = OpLabel +%35 = OpArrayLength %6 %12 3 +%36 = OpULessThan %22 %31 %35 +OpSelectionMerge %39 None +OpBranchConditional %36 %40 %39 +%40 = OpLabel +%38 = OpAccessChain %20 %12 %37 %31 +%41 = OpLoad %3 %38 +OpBranch %39 +%39 = OpLabel +%42 = OpPhi %3 %25 %33 %41 %40 +OpReturnValue %42 +OpFunctionEnd +%45 = OpFunction %3 None %17 +%44 = OpFunctionParameter %11 +%43 = OpLabel +OpBranch %46 +%46 = OpLabel +%50 = OpULessThan %22 %44 %49 +OpSelectionMerge %53 None +OpBranchConditional %50 %54 %53 +%54 = OpLabel +%52 = OpAccessChain %48 %12 %51 %44 +%55 = OpLoad %3 %52 +OpBranch %53 +%53 = OpLabel +%56 = OpPhi %3 %25 %46 %55 %54 +OpReturnValue %56 +OpFunctionEnd +%60 = OpFunction %3 None %61 +%58 = OpFunctionParameter %7 +%59 = OpFunctionParameter %11 +%57 = OpLabel +OpBranch %62 +%62 = OpLabel +%63 = OpULessThan %22 %59 %49 +OpSelectionMerge %64 None +OpBranchConditional %63 %65 %64 +%65 = OpLabel +%66 = OpVectorExtractDynamic %3 %58 %59 +OpBranch %64 +%64 = OpLabel +%67 = OpPhi %3 %25 %62 %66 %65 +OpReturnValue %67 +OpFunctionEnd +%70 = OpFunction %7 None %71 +%69 = OpFunctionParameter %11 +%68 = OpLabel +OpBranch %72 +%72 = OpLabel +%75 = OpULessThan %22 %69 %37 +OpSelectionMerge %79 None +OpBranchConditional %75 %80 %79 +%80 = OpLabel +%77 = OpAccessChain %74 %12 %76 %69 +%81 = OpLoad %7 %77 +OpBranch %79 +%79 = OpLabel +%82 = OpPhi %7 %78 %72 %81 %80 +OpReturnValue %82 +OpFunctionEnd +%86 = OpFunction %3 None %87 +%84 = OpFunctionParameter %11 +%85 = OpFunctionParameter %11 +%83 = OpLabel +OpBranch %88 +%88 = OpLabel +%89 = OpULessThan %22 %85 %49 +%90 = OpULessThan %22 %84 %37 +%91 = OpLogicalAnd %22 %89 %90 +OpSelectionMerge %93 None +OpBranchConditional %91 %94 %93 +%94 = OpLabel +%92 = OpAccessChain %48 %12 %76 %84 %85 +%95 = OpLoad %3 %92 +OpBranch %93 +%93 = OpLabel +%96 = OpPhi %3 %25 %88 %95 %94 +OpReturnValue %96 +OpFunctionEnd +%99 = OpFunction %3 None %17 +%98 = OpFunctionParameter %11 +%97 = OpLabel +OpBranch %101 +%101 = OpLabel +%102 = OpConvertSToF %3 %98 +%103 = OpFDiv %3 %102 %100 +%104 = OpExtInst %3 %1 Sin %103 +%105 = OpFMul %3 %104 %100 +%106 = OpConvertFToS %11 %105 +%107 = OpULessThan %22 %106 %5 +OpSelectionMerge %109 None +OpBranchConditional %107 %110 %109 +%110 = OpLabel +%108 = OpAccessChain %20 %12 %23 %106 +%111 = OpLoad %3 %108 +OpBranch %109 +%109 = OpLabel +%112 = OpPhi %3 %25 %101 %111 %110 +OpReturnValue %112 +OpFunctionEnd +%114 = OpFunction %3 None %115 +%113 = OpLabel +OpBranch %116 +%116 = OpLabel +%118 = OpAccessChain %20 %12 %23 %117 +%119 = OpLoad %3 %118 +%120 = OpAccessChain %48 %12 %51 %37 +%121 = OpLoad %3 %120 +%122 = OpFAdd %3 %119 %121 +%123 = OpAccessChain %48 %12 %76 %76 %37 +%124 = OpLoad %3 %123 +%125 = OpFAdd %3 %122 %124 +OpReturnValue %125 +OpFunctionEnd +%129 = OpFunction %2 None %130 +%127 = OpFunctionParameter %11 +%128 = OpFunctionParameter %3 +%126 = OpLabel +OpBranch %131 +%131 = OpLabel +%132 = OpULessThan %22 %127 %5 +OpSelectionMerge %134 None +OpBranchConditional %132 %135 %134 +%135 = OpLabel +%133 = OpAccessChain %20 %12 %23 %127 +OpStore %133 %128 +OpBranch %134 +%134 = OpLabel +OpReturn +OpFunctionEnd +%139 = OpFunction %2 None %130 +%137 = OpFunctionParameter %11 +%138 = OpFunctionParameter %3 +%136 = OpLabel +OpBranch %140 +%140 = OpLabel +%141 = OpArrayLength %6 %12 3 +%142 = OpULessThan %22 %137 %141 +OpSelectionMerge %144 None +OpBranchConditional %142 %145 %144 +%145 = OpLabel +%143 = OpAccessChain %20 %12 %37 %137 +OpStore %143 %138 +OpBranch %144 +%144 = OpLabel +OpReturn +OpFunctionEnd +%149 = OpFunction %2 None %130 +%147 = OpFunctionParameter %11 +%148 = OpFunctionParameter %3 +%146 = OpLabel +OpBranch %150 +%150 = OpLabel +%151 = OpULessThan %22 %147 %49 +OpSelectionMerge %153 None +OpBranchConditional %151 %154 %153 +%154 = OpLabel +%152 = OpAccessChain %48 %12 %51 %147 +OpStore %152 %148 +OpBranch %153 +%153 = OpLabel +OpReturn +OpFunctionEnd +%158 = OpFunction %2 None %159 +%156 = OpFunctionParameter %11 +%157 = OpFunctionParameter %7 +%155 = OpLabel +OpBranch %160 +%160 = OpLabel +%161 = OpULessThan %22 %156 %37 +OpSelectionMerge %163 None +OpBranchConditional %161 %164 %163 +%164 = OpLabel +%162 = OpAccessChain %74 %12 %76 %156 +OpStore %162 %157 +OpBranch %163 +%163 = OpLabel +OpReturn +OpFunctionEnd +%169 = OpFunction %2 None %170 +%166 = OpFunctionParameter %11 +%167 = OpFunctionParameter %11 +%168 = OpFunctionParameter %3 +%165 = OpLabel +OpBranch %171 +%171 = OpLabel +%172 = OpULessThan %22 %167 %49 +%173 = OpULessThan %22 %166 %37 +%174 = OpLogicalAnd %22 %172 %173 +OpSelectionMerge %176 None +OpBranchConditional %174 %177 %176 +%177 = OpLabel +%175 = OpAccessChain %48 %12 %76 %166 %167 +OpStore %175 %168 +OpBranch %176 +%176 = OpLabel +OpReturn +OpFunctionEnd +%181 = OpFunction %2 None %130 +%179 = OpFunctionParameter %11 +%180 = OpFunctionParameter %3 +%178 = OpLabel +OpBranch %182 +%182 = OpLabel +%183 = OpConvertSToF %3 %179 +%184 = OpFDiv %3 %183 %100 +%185 = OpExtInst %3 %1 Sin %184 +%186 = OpFMul %3 %185 %100 +%187 = OpConvertFToS %11 %186 +%188 = OpULessThan %22 %187 %5 +OpSelectionMerge %190 None +OpBranchConditional %188 %191 %190 +%191 = OpLabel +%189 = OpAccessChain %20 %12 %23 %187 +OpStore %189 %180 +OpBranch %190 +%190 = OpLabel +OpReturn +OpFunctionEnd +%194 = OpFunction %2 None %195 +%193 = OpFunctionParameter %3 +%192 = OpLabel +OpBranch %196 +%196 = OpLabel +%197 = OpAccessChain %20 %12 %23 %117 +OpStore %197 %193 +%198 = OpAccessChain %48 %12 %51 %37 +OpStore %198 %193 +%199 = OpAccessChain %48 %12 %76 %76 %37 +OpStore %199 %193 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/break-if.spvasm b/naga/tests/out/spv/break-if.spvasm new file mode 100644 index 0000000000..8c8c3edf49 --- /dev/null +++ b/naga/tests/out/spv/break-if.spvasm @@ -0,0 +1,114 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 67 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %65 "main" +OpExecutionMode %65 LocalSize 1 1 1 +%2 = OpTypeVoid +%3 = OpTypeBool +%4 = OpTypeInt 32 0 +%7 = OpTypeFunction %2 +%8 = OpConstantTrue %3 +%17 = OpTypeFunction %2 %3 +%19 = OpTypePointer Function %3 +%20 = OpConstantNull %3 +%22 = OpConstantNull %3 +%36 = OpConstantNull %3 +%38 = OpConstantNull %3 +%50 = OpConstant %4 0 +%51 = OpConstant %4 1 +%52 = OpConstant %4 5 +%54 = OpTypePointer Function %4 +%6 = OpFunction %2 None %7 +%5 = OpLabel +OpBranch %9 +%9 = OpLabel +OpBranch %10 +%10 = OpLabel +OpLoopMerge %11 %13 None +OpBranch %12 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +OpBranchConditional %8 %11 %10 +%11 = OpLabel +OpReturn +OpFunctionEnd +%16 = OpFunction %2 None %17 +%15 = OpFunctionParameter %3 +%14 = OpLabel +%18 = OpVariable %19 Function %20 +%21 = OpVariable %19 Function %22 +OpBranch %23 +%23 = OpLabel +OpBranch %24 +%24 = OpLabel +OpLoopMerge %25 %27 None +OpBranch %26 +%26 = OpLabel +OpBranch %27 +%27 = OpLabel +OpStore %18 %15 +%28 = OpLoad %3 %18 +%29 = OpLogicalNotEqual %3 %15 %28 +OpStore %21 %29 +%30 = OpLoad %3 %21 +%31 = OpLogicalEqual %3 %15 %30 +OpBranchConditional %31 %25 %24 +%25 = OpLabel +OpReturn +OpFunctionEnd +%34 = OpFunction %2 None %17 +%33 = OpFunctionParameter %3 +%32 = OpLabel +%35 = OpVariable %19 Function %36 +%37 = OpVariable %19 Function %38 +OpBranch %39 +%39 = OpLabel +OpBranch %40 +%40 = OpLabel +OpLoopMerge %41 %43 None +OpBranch %42 +%42 = OpLabel +OpStore %35 %33 +%44 = OpLoad %3 %35 +%45 = OpLogicalNotEqual %3 %33 %44 +OpStore %37 %45 +OpBranch %43 +%43 = OpLabel +%46 = OpLoad %3 %37 +%47 = OpLogicalEqual %3 %33 %46 +OpBranchConditional %47 %41 %40 +%41 = OpLabel +OpReturn +OpFunctionEnd +%49 = OpFunction %2 None %7 +%48 = OpLabel +%53 = OpVariable %54 Function %50 +OpBranch %55 +%55 = OpLabel +OpBranch %56 +%56 = OpLabel +OpLoopMerge %57 %59 None +OpBranch %58 +%58 = OpLabel +%60 = OpLoad %4 %53 +%61 = OpIAdd %4 %60 %51 +OpStore %53 %61 +OpBranch %59 +%59 = OpLabel +%62 = OpLoad %4 %53 +%63 = OpIEqual %3 %62 %52 +OpBranchConditional %63 %57 %56 +%57 = OpLabel +OpReturn +OpFunctionEnd +%65 = OpFunction %2 None %7 +%64 = OpLabel +OpBranch %66 +%66 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/collatz.spvasm b/naga/tests/out/spv/collatz.spvasm new file mode 100644 index 0000000000..5c77a52269 --- /dev/null +++ b/naga/tests/out/spv/collatz.spvasm @@ -0,0 +1,110 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 62 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %51 "main" %48 +OpExecutionMode %51 LocalSize 1 1 1 +OpMemberName %5 0 "data" +OpName %5 "PrimeIndices" +OpName %7 "v_indices" +OpName %10 "n_base" +OpName %11 "collatz_iterations" +OpName %17 "n" +OpName %20 "i" +OpName %48 "global_id" +OpName %51 "main" +OpDecorate %4 ArrayStride 4 +OpMemberDecorate %5 0 Offset 0 +OpDecorate %5 Block +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +OpDecorate %48 BuiltIn GlobalInvocationId +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeRuntimeArray %3 +%5 = OpTypeStruct %4 +%6 = OpTypeVector %3 3 +%8 = OpTypePointer StorageBuffer %5 +%7 = OpVariable %8 StorageBuffer +%12 = OpTypeFunction %3 %3 +%13 = OpConstant %3 0 +%14 = OpConstant %3 1 +%15 = OpConstant %3 2 +%16 = OpConstant %3 3 +%18 = OpTypePointer Function %3 +%19 = OpConstantNull %3 +%27 = OpTypeBool +%49 = OpTypePointer Input %6 +%48 = OpVariable %49 Input +%52 = OpTypeFunction %2 +%54 = OpTypePointer StorageBuffer %4 +%56 = OpTypePointer StorageBuffer %3 +%11 = OpFunction %3 None %12 +%10 = OpFunctionParameter %3 +%9 = OpLabel +%17 = OpVariable %18 Function %19 +%20 = OpVariable %18 Function %13 +OpBranch %21 +%21 = OpLabel +OpStore %17 %10 +OpBranch %22 +%22 = OpLabel +OpLoopMerge %23 %25 None +OpBranch %24 +%24 = OpLabel +%26 = OpLoad %3 %17 +%28 = OpUGreaterThan %27 %26 %14 +OpSelectionMerge %29 None +OpBranchConditional %28 %29 %30 +%30 = OpLabel +OpBranch %23 +%29 = OpLabel +OpBranch %31 +%31 = OpLabel +%33 = OpLoad %3 %17 +%34 = OpUMod %3 %33 %15 +%35 = OpIEqual %27 %34 %13 +OpSelectionMerge %36 None +OpBranchConditional %35 %37 %38 +%37 = OpLabel +%39 = OpLoad %3 %17 +%40 = OpUDiv %3 %39 %15 +OpStore %17 %40 +OpBranch %36 +%38 = OpLabel +%41 = OpLoad %3 %17 +%42 = OpIMul %3 %16 %41 +%43 = OpIAdd %3 %42 %14 +OpStore %17 %43 +OpBranch %36 +%36 = OpLabel +%44 = OpLoad %3 %20 +%45 = OpIAdd %3 %44 %14 +OpStore %20 %45 +OpBranch %32 +%32 = OpLabel +OpBranch %25 +%25 = OpLabel +OpBranch %22 +%23 = OpLabel +%46 = OpLoad %3 %20 +OpReturnValue %46 +OpFunctionEnd +%51 = OpFunction %2 None %52 +%47 = OpLabel +%50 = OpLoad %6 %48 +OpBranch %53 +%53 = OpLabel +%55 = OpCompositeExtract %3 %50 0 +%57 = OpCompositeExtract %3 %50 0 +%58 = OpAccessChain %56 %7 %13 %57 +%59 = OpLoad %3 %58 +%60 = OpFunctionCall %3 %11 %59 +%61 = OpAccessChain %56 %7 %13 %55 +OpStore %61 %60 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/const-exprs.spvasm b/naga/tests/out/spv/const-exprs.spvasm new file mode 100644 index 0000000000..22fef53749 --- /dev/null +++ b/naga/tests/out/spv/const-exprs.spvasm @@ -0,0 +1,152 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 109 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %100 "main" +OpExecutionMode %100 LocalSize 2 3 1 +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeInt 32 1 +%5 = OpTypeVector %4 4 +%7 = OpTypeFloat 32 +%6 = OpTypeVector %7 4 +%8 = OpTypeVector %7 2 +%10 = OpTypeBool +%9 = OpTypeVector %10 2 +%11 = OpConstant %3 2 +%12 = OpConstant %4 3 +%13 = OpConstant %4 4 +%14 = OpConstant %4 8 +%15 = OpConstant %7 3.141 +%16 = OpConstant %7 6.282 +%17 = OpConstant %7 0.44444445 +%18 = OpConstant %7 0.0 +%19 = OpConstantComposite %6 %17 %18 %18 %18 +%20 = OpConstant %4 0 +%21 = OpConstant %4 1 +%22 = OpConstant %4 2 +%23 = OpConstant %7 4.0 +%24 = OpConstant %7 5.0 +%25 = OpConstantComposite %8 %23 %24 +%26 = OpConstantTrue %10 +%27 = OpConstantFalse %10 +%28 = OpConstantComposite %9 %26 %27 +%31 = OpTypeFunction %2 +%32 = OpConstantComposite %5 %13 %12 %22 %21 +%34 = OpTypePointer Function %5 +%39 = OpTypePointer Function %4 +%43 = OpConstant %4 6 +%48 = OpConstant %4 30 +%49 = OpConstant %4 70 +%52 = OpConstantNull %4 +%54 = OpConstantNull %4 +%57 = OpConstantNull %5 +%68 = OpConstant %4 -4 +%69 = OpConstantComposite %5 %68 %68 %68 %68 +%78 = OpConstant %7 1.0 +%79 = OpConstant %7 2.0 +%80 = OpConstantComposite %6 %79 %78 %78 %78 +%82 = OpTypePointer Function %6 +%87 = OpTypeFunction %3 %4 +%88 = OpConstant %3 10 +%89 = OpConstant %3 20 +%90 = OpConstant %3 30 +%91 = OpConstant %3 0 +%98 = OpConstantNull %3 +%30 = OpFunction %2 None %31 +%29 = OpLabel +%33 = OpVariable %34 Function %32 +OpBranch %35 +%35 = OpLabel +OpReturn +OpFunctionEnd +%37 = OpFunction %2 None %31 +%36 = OpLabel +%38 = OpVariable %39 Function %22 +OpBranch %40 +%40 = OpLabel +OpReturn +OpFunctionEnd +%42 = OpFunction %2 None %31 +%41 = OpLabel +%44 = OpVariable %39 Function %43 +OpBranch %45 +%45 = OpLabel +OpReturn +OpFunctionEnd +%47 = OpFunction %2 None %31 +%46 = OpLabel +%56 = OpVariable %34 Function %57 +%51 = OpVariable %39 Function %52 +%55 = OpVariable %39 Function %49 +%50 = OpVariable %39 Function %48 +%53 = OpVariable %39 Function %54 +OpBranch %58 +%58 = OpLabel +%59 = OpLoad %4 %50 +OpStore %51 %59 +%60 = OpLoad %4 %51 +OpStore %53 %60 +%61 = OpLoad %4 %50 +%62 = OpLoad %4 %51 +%63 = OpLoad %4 %53 +%64 = OpLoad %4 %55 +%65 = OpCompositeConstruct %5 %61 %62 %63 %64 +OpStore %56 %65 +OpReturn +OpFunctionEnd +%67 = OpFunction %2 None %31 +%66 = OpLabel +%70 = OpVariable %34 Function %69 +OpBranch %71 +%71 = OpLabel +OpReturn +OpFunctionEnd +%73 = OpFunction %2 None %31 +%72 = OpLabel +%74 = OpVariable %34 Function %69 +OpBranch %75 +%75 = OpLabel +OpReturn +OpFunctionEnd +%77 = OpFunction %2 None %31 +%76 = OpLabel +%81 = OpVariable %82 Function %80 +OpBranch %83 +%83 = OpLabel +OpReturn +OpFunctionEnd +%86 = OpFunction %3 None %87 +%85 = OpFunctionParameter %4 +%84 = OpLabel +OpBranch %92 +%92 = OpLabel +OpSelectionMerge %93 None +OpSwitch %85 %97 0 %94 1 %95 2 %96 +%94 = OpLabel +OpReturnValue %88 +%95 = OpLabel +OpReturnValue %89 +%96 = OpLabel +OpReturnValue %90 +%97 = OpLabel +OpReturnValue %91 +%93 = OpLabel +OpReturnValue %98 +OpFunctionEnd +%100 = OpFunction %2 None %31 +%99 = OpLabel +OpBranch %101 +%101 = OpLabel +%102 = OpFunctionCall %2 %30 +%103 = OpFunctionCall %2 %37 +%104 = OpFunctionCall %2 %42 +%105 = OpFunctionCall %2 %47 +%106 = OpFunctionCall %2 %67 +%107 = OpFunctionCall %2 %73 +%108 = OpFunctionCall %2 %77 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/constructors.spvasm b/naga/tests/out/spv/constructors.spvasm new file mode 100644 index 0000000000..1a481aa95e --- /dev/null +++ b/naga/tests/out/spv/constructors.spvasm @@ -0,0 +1,83 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 68 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %44 "main" +OpExecutionMode %44 LocalSize 1 1 1 +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 16 +OpDecorate %10 ArrayStride 16 +OpDecorate %15 ArrayStride 32 +OpDecorate %17 ArrayStride 4 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeInt 32 1 +%6 = OpTypeStruct %3 %5 +%7 = OpTypeVector %4 3 +%9 = OpTypeVector %4 2 +%8 = OpTypeMatrix %9 2 +%12 = OpTypeInt 32 0 +%11 = OpConstant %12 1 +%10 = OpTypeArray %8 %11 +%13 = OpTypeBool +%14 = OpTypeVector %12 2 +%16 = OpConstant %12 3 +%15 = OpTypeArray %6 %16 +%18 = OpConstant %12 4 +%17 = OpTypeArray %5 %18 +%19 = OpTypeMatrix %3 4 +%20 = OpTypeMatrix %7 2 +%21 = OpConstant %4 0.0 +%22 = OpConstant %4 1.0 +%23 = OpConstant %4 2.0 +%24 = OpConstantComposite %7 %21 %22 %23 +%25 = OpConstant %4 3.0 +%26 = OpConstantComposite %9 %21 %22 +%27 = OpConstantComposite %9 %23 %25 +%28 = OpConstantComposite %8 %26 %27 +%29 = OpConstantComposite %10 %28 +%30 = OpConstantNull %13 +%31 = OpConstantNull %5 +%32 = OpConstantNull %12 +%33 = OpConstantNull %4 +%34 = OpConstantNull %14 +%35 = OpConstantNull %8 +%36 = OpConstantNull %15 +%37 = OpConstantNull %6 +%38 = OpConstant %5 0 +%39 = OpConstant %5 1 +%40 = OpConstant %5 2 +%41 = OpConstant %5 3 +%42 = OpConstantComposite %17 %38 %39 %40 %41 +%45 = OpTypeFunction %2 +%46 = OpConstantComposite %3 %22 %22 %22 %22 +%47 = OpConstantComposite %6 %46 %39 +%48 = OpConstantComposite %9 %22 %21 +%49 = OpConstantComposite %8 %48 %26 +%50 = OpConstantComposite %3 %22 %21 %21 %21 +%51 = OpConstantComposite %3 %21 %22 %21 %21 +%52 = OpConstantComposite %3 %21 %21 %22 %21 +%53 = OpConstantComposite %3 %21 %21 %21 %22 +%54 = OpConstantComposite %19 %50 %51 %52 %53 +%55 = OpConstant %12 0 +%56 = OpConstantComposite %14 %55 %55 +%57 = OpConstantComposite %9 %21 %21 +%58 = OpConstantComposite %8 %57 %57 +%59 = OpConstantComposite %14 %55 %55 +%60 = OpConstantComposite %7 %21 %21 %21 +%61 = OpConstantComposite %20 %60 %60 +%62 = OpConstantNull %20 +%64 = OpTypePointer Function %6 +%65 = OpConstantNull %6 +%44 = OpFunction %2 None %45 +%43 = OpLabel +%63 = OpVariable %64 Function %65 +OpBranch %66 +%66 = OpLabel +OpStore %63 %47 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/control-flow.spvasm b/naga/tests/out/spv/control-flow.spvasm new file mode 100644 index 0000000000..2fc9337cfe --- /dev/null +++ b/naga/tests/out/spv/control-flow.spvasm @@ -0,0 +1,138 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 69 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %36 "main" %33 +OpExecutionMode %36 LocalSize 1 1 1 +OpDecorate %33 BuiltIn GlobalInvocationId +%2 = OpTypeVoid +%4 = OpTypeInt 32 0 +%3 = OpTypeVector %4 3 +%5 = OpTypeInt 32 1 +%9 = OpTypeFunction %2 %5 +%15 = OpTypeFunction %2 +%16 = OpConstant %5 0 +%34 = OpTypePointer Input %3 +%33 = OpVariable %34 Input +%37 = OpConstant %5 1 +%38 = OpConstant %5 2 +%39 = OpConstant %5 3 +%40 = OpConstant %5 4 +%41 = OpConstant %4 0 +%43 = OpTypePointer Function %5 +%44 = OpConstantNull %5 +%46 = OpConstant %4 2 +%47 = OpConstant %4 1 +%48 = OpConstant %4 72 +%49 = OpConstant %4 264 +%8 = OpFunction %2 None %9 +%7 = OpFunctionParameter %5 +%6 = OpLabel +OpBranch %10 +%10 = OpLabel +OpSelectionMerge %11 None +OpSwitch %7 %12 +%12 = OpLabel +OpBranch %11 +%11 = OpLabel +OpReturn +OpFunctionEnd +%14 = OpFunction %2 None %15 +%13 = OpLabel +OpBranch %17 +%17 = OpLabel +OpSelectionMerge %18 None +OpSwitch %16 %20 0 %19 +%19 = OpLabel +OpBranch %18 +%20 = OpLabel +OpBranch %18 +%18 = OpLabel +OpReturn +OpFunctionEnd +%23 = OpFunction %2 None %9 +%22 = OpFunctionParameter %5 +%21 = OpLabel +OpBranch %24 +%24 = OpLabel +OpBranch %25 +%25 = OpLabel +OpLoopMerge %26 %28 None +OpBranch %27 +%27 = OpLabel +OpSelectionMerge %29 None +OpSwitch %22 %31 1 %30 +%30 = OpLabel +OpBranch %28 +%31 = OpLabel +OpBranch %29 +%29 = OpLabel +OpBranch %28 +%28 = OpLabel +OpBranch %25 +%26 = OpLabel +OpReturn +OpFunctionEnd +%36 = OpFunction %2 None %15 +%32 = OpLabel +%42 = OpVariable %43 Function %44 +%35 = OpLoad %3 %33 +OpBranch %45 +%45 = OpLabel +OpControlBarrier %46 %47 %48 +OpControlBarrier %46 %46 %49 +OpSelectionMerge %50 None +OpSwitch %37 %51 +%51 = OpLabel +OpStore %42 %37 +OpBranch %50 +%50 = OpLabel +%52 = OpLoad %5 %42 +OpSelectionMerge %53 None +OpSwitch %52 %58 1 %54 2 %55 3 %56 4 %56 5 %57 6 %58 +%54 = OpLabel +OpStore %42 %16 +OpBranch %53 +%55 = OpLabel +OpStore %42 %37 +OpBranch %53 +%56 = OpLabel +OpStore %42 %38 +OpBranch %53 +%57 = OpLabel +OpStore %42 %39 +OpBranch %53 +%58 = OpLabel +OpStore %42 %40 +OpBranch %53 +%53 = OpLabel +OpSelectionMerge %59 None +OpSwitch %41 %61 0 %60 +%60 = OpLabel +OpBranch %59 +%61 = OpLabel +OpBranch %59 +%59 = OpLabel +%62 = OpLoad %5 %42 +OpSelectionMerge %63 None +OpSwitch %62 %68 1 %64 2 %65 3 %66 4 %67 +%64 = OpLabel +OpStore %42 %16 +OpBranch %63 +%65 = OpLabel +OpStore %42 %37 +OpReturn +%66 = OpLabel +OpStore %42 %38 +OpReturn +%67 = OpLabel +OpReturn +%68 = OpLabel +OpStore %42 %39 +OpReturn +%63 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/debug-symbol-simple.spvasm b/naga/tests/out/spv/debug-symbol-simple.spvasm new file mode 100644 index 0000000000..b2fd1f2607 --- /dev/null +++ b/naga/tests/out/spv/debug-symbol-simple.spvasm @@ -0,0 +1,214 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 94 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %21 "vs_main" %12 %15 %17 %19 +OpEntryPoint Fragment %49 "fs_main" %43 %46 %48 +OpExecutionMode %49 OriginUpperLeft +%3 = OpString "debug-symbol-simple.wgsl" +OpSource Unknown 0 %3 "struct VertexInput { + @location(0) position: vec3, + @location(1) color: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) color: vec3, +}; + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.color = model.color; + out.clip_position = vec4(model.position, 1.0); + return out; +} + +// Fragment shader + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + var color = in.color; + for (var i = 0; i < 10; i += 1) { + var ii = f32(i); + color.x += ii*0.001; + color.y += ii*0.002; + } + + return vec4(color, 1.0); +}" +OpMemberName %6 0 "position" +OpMemberName %6 1 "color" +OpName %6 "VertexInput" +OpMemberName %8 0 "clip_position" +OpMemberName %8 1 "color" +OpName %8 "VertexOutput" +OpName %12 "position" +OpName %15 "color" +OpName %17 "clip_position" +OpName %19 "color" +OpName %21 "vs_main" +OpName %24 "out" +OpName %43 "clip_position" +OpName %46 "color" +OpName %49 "fs_main" +OpName %55 "color" +OpName %57 "i" +OpName %59 "ii" +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 16 +OpMemberDecorate %8 0 Offset 0 +OpMemberDecorate %8 1 Offset 16 +OpDecorate %12 Location 0 +OpDecorate %15 Location 1 +OpDecorate %17 BuiltIn Position +OpDecorate %19 Location 0 +OpDecorate %43 BuiltIn FragCoord +OpDecorate %46 Location 0 +OpDecorate %48 Location 0 +%2 = OpTypeVoid +%5 = OpTypeFloat 32 +%4 = OpTypeVector %5 3 +%6 = OpTypeStruct %4 %4 +%7 = OpTypeVector %5 4 +%8 = OpTypeStruct %7 %4 +%9 = OpTypeInt 32 1 +%13 = OpTypePointer Input %4 +%12 = OpVariable %13 Input +%15 = OpVariable %13 Input +%18 = OpTypePointer Output %7 +%17 = OpVariable %18 Output +%20 = OpTypePointer Output %4 +%19 = OpVariable %20 Output +%22 = OpTypeFunction %2 +%23 = OpConstant %5 1.0 +%25 = OpTypePointer Function %8 +%26 = OpConstantNull %8 +%28 = OpTypePointer Function %4 +%31 = OpTypeInt 32 0 +%30 = OpConstant %31 1 +%33 = OpTypePointer Function %7 +%36 = OpConstant %31 0 +%44 = OpTypePointer Input %7 +%43 = OpVariable %44 Input +%46 = OpVariable %13 Input +%48 = OpVariable %18 Output +%50 = OpConstant %9 0 +%51 = OpConstant %9 10 +%52 = OpConstant %5 0.001 +%53 = OpConstant %5 0.002 +%54 = OpConstant %9 1 +%56 = OpConstantNull %4 +%58 = OpTypePointer Function %9 +%60 = OpTypePointer Function %5 +%61 = OpConstantNull %5 +%69 = OpTypeBool +%77 = OpTypePointer Function %5 +%21 = OpFunction %2 None %22 +%10 = OpLabel +%24 = OpVariable %25 Function %26 +%14 = OpLoad %4 %12 +%16 = OpLoad %4 %15 +%11 = OpCompositeConstruct %6 %14 %16 +OpBranch %27 +%27 = OpLabel +OpLine %3 16 5 +%29 = OpCompositeExtract %4 %11 1 +OpLine %3 16 5 +%32 = OpAccessChain %28 %24 %30 +OpStore %32 %29 +OpLine %3 17 5 +%34 = OpCompositeExtract %4 %11 0 +OpLine %3 17 25 +%35 = OpCompositeConstruct %7 %34 %23 +OpLine %3 17 5 +%37 = OpAccessChain %33 %24 %36 +OpStore %37 %35 +OpLine %3 1 1 +%38 = OpLoad %8 %24 +%39 = OpCompositeExtract %7 %38 0 +OpStore %17 %39 +%40 = OpCompositeExtract %4 %38 1 +OpStore %19 %40 +OpReturn +OpFunctionEnd +%49 = OpFunction %2 None %22 +%41 = OpLabel +%55 = OpVariable %28 Function %56 +%57 = OpVariable %58 Function %50 +%59 = OpVariable %60 Function %61 +%45 = OpLoad %7 %43 +%47 = OpLoad %4 %46 +%42 = OpCompositeConstruct %8 %45 %47 +OpBranch %62 +%62 = OpLabel +OpLine %3 25 17 +%63 = OpCompositeExtract %4 %42 1 +OpLine %3 25 5 +OpStore %55 %63 +OpBranch %64 +%64 = OpLabel +OpLine %3 26 5 +OpLoopMerge %65 %67 None +OpBranch %66 +%66 = OpLabel +OpLine %3 1 1 +%68 = OpLoad %9 %57 +OpLine %3 26 21 +%70 = OpSLessThan %69 %68 %51 +OpLine %3 26 20 +OpSelectionMerge %71 None +OpBranchConditional %70 %71 %72 +%72 = OpLabel +OpBranch %65 +%71 = OpLabel +OpBranch %73 +%73 = OpLabel +OpLine %3 27 18 +%75 = OpLoad %9 %57 +%76 = OpConvertSToF %5 %75 +OpLine %3 27 9 +OpStore %59 %76 +OpLine %3 28 9 +%78 = OpLoad %5 %59 +OpLine %3 28 9 +%79 = OpFMul %5 %78 %52 +%80 = OpAccessChain %77 %55 %36 +%81 = OpLoad %5 %80 +%82 = OpFAdd %5 %81 %79 +OpLine %3 28 9 +%83 = OpAccessChain %77 %55 %36 +OpStore %83 %82 +OpLine %3 29 9 +%84 = OpLoad %5 %59 +OpLine %3 29 9 +%85 = OpFMul %5 %84 %53 +%86 = OpAccessChain %77 %55 %30 +%87 = OpLoad %5 %86 +%88 = OpFAdd %5 %87 %85 +OpLine %3 29 9 +%89 = OpAccessChain %77 %55 %30 +OpStore %89 %88 +OpBranch %74 +%74 = OpLabel +OpBranch %67 +%67 = OpLabel +OpLine %3 26 29 +%90 = OpLoad %9 %57 +%91 = OpIAdd %9 %90 %54 +OpLine %3 26 29 +OpStore %57 %91 +OpBranch %64 +%65 = OpLabel +OpLine %3 1 1 +%92 = OpLoad %4 %55 +OpLine %3 32 12 +%93 = OpCompositeConstruct %7 %92 %23 +OpStore %48 %93 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/debug-symbol-terrain.spvasm b/naga/tests/out/spv/debug-symbol-terrain.spvasm new file mode 100644 index 0000000000..623b8dc2c1 --- /dev/null +++ b/naga/tests/out/spv/debug-symbol-terrain.spvasm @@ -0,0 +1,1461 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 644 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %345 "gen_terrain_compute" %342 +OpEntryPoint Vertex %415 "gen_terrain_vertex" %406 %409 %411 %413 +OpEntryPoint Fragment %465 "gen_terrain_fragment" %455 %457 %460 %463 %464 +OpEntryPoint Vertex %558 "vs_main" %549 %552 %554 %555 %557 +OpEntryPoint Fragment %583 "fs_main" %576 %578 %580 %582 +OpExecutionMode %345 LocalSize 64 1 1 +OpExecutionMode %465 OriginUpperLeft +OpExecutionMode %583 OriginUpperLeft +%3 = OpString "debug-symbol-terrain.wgsl" +OpSource Unknown 0 %3 "// Taken from https://github.com/sotrh/learn-wgpu/blob/11820796f5e1dbce42fb1119f04ddeb4b167d2a0/code/intermediate/tutorial13-terrain/src/terrain.wgsl +// ============================ +// Terrain Generation +// ============================ + +// https://gist.github.com/munrocket/236ed5ba7e409b8bdf1ff6eca5dcdc39 +// MIT License. © Ian McEwan, Stefan Gustavson, Munrocket +// - Less condensed glsl implementation with comments can be found at https://weber.itn.liu.se/~stegu/jgt2012/article.pdf + +fn permute3(x: vec3) -> vec3 { return (((x * 34.) + 1.) * x) % vec3(289.); } + +fn snoise2(v: vec2) -> f32 { + let C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); + var i: vec2 = floor(v + dot(v, C.yy)); + let x0 = v - i + dot(i, C.xx); + // I flipped the condition here from > to < as it fixed some artifacting I was observing + var i1: vec2 = select(vec2(1., 0.), vec2(0., 1.), (x0.x < x0.y)); + var x12: vec4 = x0.xyxy + C.xxzz - vec4(i1, 0., 0.); + i = i % vec2(289.); + let p = permute3(permute3(i.y + vec3(0., i1.y, 1.)) + i.x + vec3(0., i1.x, 1.)); + var m: vec3 = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), vec3(0.)); + m = m * m; + m = m * m; + let x = 2. * fract(p * C.www) - 1.; + let h = abs(x) - 0.5; + let ox = floor(x + 0.5); + let a0 = x - ox; + m = m * (1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h)); + let g = vec3(a0.x * x0.x + h.x * x0.y, a0.yz * x12.xz + h.yz * x12.yw); + return 130. * dot(m, g); +} + + +fn fbm(p: vec2) -> f32 { + let NUM_OCTAVES: u32 = 5u; + var x = p * 0.01; + var v = 0.0; + var a = 0.5; + let shift = vec2(100.0); + let cs = vec2(cos(0.5), sin(0.5)); + let rot = mat2x2(cs.x, cs.y, -cs.y, cs.x); + + for (var i = 0u; i < NUM_OCTAVES; i = i + 1u) { + v = v + a * snoise2(x); + x = rot * x * 2.0 + shift; + a = a * 0.5; + } + + return v; +} + +struct ChunkData { + chunk_size: vec2, + chunk_corner: vec2, + min_max_height: vec2, +} + +struct Vertex { + @location(0) position: vec3, + @location(1) normal: vec3, +} + +struct VertexBuffer { + data: array, // stride: 32 +} + +struct IndexBuffer { + data: array, +} + +@group(0) @binding(0) var chunk_data: ChunkData; +@group(0) @binding(1) var vertices: VertexBuffer; +@group(0) @binding(2) var indices: IndexBuffer; + +fn terrain_point(p: vec2, min_max_height: vec2) -> vec3 { + return vec3( + p.x, + mix(min_max_height.x, min_max_height.y, fbm(p)), + p.y, + ); +} + +fn terrain_vertex(p: vec2, min_max_height: vec2) -> Vertex { + let v = terrain_point(p, min_max_height); + + let tpx = terrain_point(p + vec2(0.1, 0.0), min_max_height) - v; + let tpz = terrain_point(p + vec2(0.0, 0.1), min_max_height) - v; + let tnx = terrain_point(p + vec2(-0.1, 0.0), min_max_height) - v; + let tnz = terrain_point(p + vec2(0.0, -0.1), min_max_height) - v; + + let pn = normalize(cross(tpz, tpx)); + let nn = normalize(cross(tnz, tnx)); + + let n = (pn + nn) * 0.5; + + return Vertex(v, n); +} + +fn index_to_p(vert_index: u32, chunk_size: vec2, chunk_corner: vec2) -> vec2 { + return vec2( + f32(vert_index) % f32(chunk_size.x + 1u), + f32(vert_index / (chunk_size.x + 1u)), + ) + vec2(chunk_corner); +} + +@compute @workgroup_size(64) +fn gen_terrain_compute( + @builtin(global_invocation_id) gid: vec3 +) { + // Create vert_component + let vert_index = gid.x; + + let p = index_to_p(vert_index, chunk_data.chunk_size, chunk_data.chunk_corner); + + vertices.data[vert_index] = terrain_vertex(p, chunk_data.min_max_height); + + // Create indices + let start_index = gid.x * 6u; // using TriangleList + + if (start_index >= (chunk_data.chunk_size.x * chunk_data.chunk_size.y * 6u)) { return; } + + let v00 = vert_index + gid.x / chunk_data.chunk_size.x; + let v10 = v00 + 1u; + let v01 = v00 + chunk_data.chunk_size.x + 1u; + let v11 = v01 + 1u; + + indices.data[start_index] = v00; + indices.data[start_index + 1u] = v01; + indices.data[start_index + 2u] = v11; + indices.data[start_index + 3u] = v00; + indices.data[start_index + 4u] = v11; + indices.data[start_index + 5u] = v10; +} + +// ============================ +// Terrain Gen (Fragment Shader) +// ============================ + +struct GenData { + chunk_size: vec2, + chunk_corner: vec2, + min_max_height: vec2, + texture_size: u32, + start_index: u32, +} +@group(0) +@binding(0) +var gen_data: GenData; + +struct GenVertexOutput { + @location(0) + index: u32, + @builtin(position) + position: vec4, + @location(1) + uv: vec2, +}; + +@vertex +fn gen_terrain_vertex(@builtin(vertex_index) vindex: u32) -> GenVertexOutput { + let u = f32(((vindex + 2u) / 3u) % 2u); + let v = f32(((vindex + 1u) / 3u) % 2u); + let uv = vec2(u, v); + + let position = vec4(-1.0 + uv * 2.0, 0.0, 1.0); + + // TODO: maybe replace this with u32(dot(uv, vec2(f32(gen_data.texture_dim.x)))) + let index = u32(uv.x * f32(gen_data.texture_size) + uv.y * f32(gen_data.texture_size)) + gen_data.start_index; + + return GenVertexOutput(index, position, uv); +} + + +struct GenFragmentOutput { + @location(0) vert_component: u32, + @location(1) index: u32, +} + +@fragment +fn gen_terrain_fragment(in: GenVertexOutput) -> GenFragmentOutput { + let i = u32(in.uv.x * f32(gen_data.texture_size) + in.uv.y * f32(gen_data.texture_size * gen_data.texture_size)) + gen_data.start_index; + let vert_index = u32(floor(f32(i) / 6.)); + let comp_index = i % 6u; + + let p = index_to_p(vert_index, gen_data.chunk_size, gen_data.chunk_corner); + let v = terrain_vertex(p, gen_data.min_max_height); + + var vert_component: f32 = 0.; + + switch comp_index { + case 0u: { vert_component = v.position.x; } + case 1u: { vert_component = v.position.y; } + case 2u: { vert_component = v.position.z; } + case 3u: { vert_component = v.normal.x; } + case 4u: { vert_component = v.normal.y; } + case 5u: { vert_component = v.normal.z; } + default: {} + } + + let v00 = vert_index + vert_index / gen_data.chunk_size.x; + let v10 = v00 + 1u; + let v01 = v00 + gen_data.chunk_size.x + 1u; + let v11 = v01 + 1u; + + var index = 0u; + switch comp_index { + case 0u, 3u: { index = v00; } + case 2u, 4u: { index = v11; } + case 1u: { index = v01; } + case 5u: { index = v10; } + default: {} + } + index = in.index; + // index = gen_data.start_index; + // indices.data[start_index] = v00; + // indices.data[start_index + 1u] = v01; + // indices.data[start_index + 2u] = v11; + // indices.data[start_index + 3u] = v00; + // indices.data[start_index + 4u] = v11; + // indices.data[start_index + 5u] = v10; + + let ivert_component = bitcast(vert_component); + return GenFragmentOutput(ivert_component, index); +} + +// ============================ +// Terrain Rendering +// ============================ + +struct Camera { + view_pos: vec4, + view_proj: mat4x4, +} +@group(0) @binding(0) +var camera: Camera; + +struct Light { + position: vec3, + color: vec3, +} +@group(1) @binding(0) +var light: Light; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) normal: vec3, + @location(1) world_pos: vec3, +} + +@vertex +fn vs_main( + vertex: Vertex, +) -> VertexOutput { + let clip_position = camera.view_proj * vec4(vertex.position, 1.); + let normal = vertex.normal; + return VertexOutput(clip_position, normal, vertex.position); +} + +@group(2) @binding(0) +var t_diffuse: texture_2d; +@group(2) @binding(1) +var s_diffuse: sampler; +@group(2) @binding(2) +var t_normal: texture_2d; +@group(2) @binding(3) +var s_normal: sampler; + +fn color23(p: vec2) -> vec3 { + return vec3( + snoise2(p) * 0.5 + 0.5, + snoise2(p + vec2(23., 32.)) * 0.5 + 0.5, + snoise2(p + vec2(-43., 3.)) * 0.5 + 0.5, + ); +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + var color = smoothstep(vec3(0.0), vec3(0.1), fract(in.world_pos)); + color = mix(vec3(0.5, 0.1, 0.7), vec3(0.2, 0.2, 0.2), vec3(color.x * color.y * color.z)); + + let ambient_strength = 0.1; + let ambient_color = light.color * ambient_strength; + + let light_dir = normalize(light.position - in.world_pos); + let view_dir = normalize(camera.view_pos.xyz - in.world_pos); + let half_dir = normalize(view_dir + light_dir); + + let diffuse_strength = max(dot(in.normal, light_dir), 0.0); + let diffuse_color = diffuse_strength * light.color; + + let specular_strength = pow(max(dot(in.normal, half_dir), 0.0), 32.0); + let specular_color = specular_strength * light.color; + + let result = (ambient_color + diffuse_color + specular_color) * color; + + return vec4(result, 1.0); +}" +OpMemberName %13 0 "chunk_size" +OpMemberName %13 1 "chunk_corner" +OpMemberName %13 2 "min_max_height" +OpName %13 "ChunkData" +OpMemberName %14 0 "position" +OpMemberName %14 1 "normal" +OpName %14 "Vertex" +OpMemberName %16 0 "data" +OpName %16 "VertexBuffer" +OpMemberName %18 0 "data" +OpName %18 "IndexBuffer" +OpMemberName %20 0 "chunk_size" +OpMemberName %20 1 "chunk_corner" +OpMemberName %20 2 "min_max_height" +OpMemberName %20 3 "texture_size" +OpMemberName %20 4 "start_index" +OpName %20 "GenData" +OpMemberName %21 0 "index" +OpMemberName %21 1 "position" +OpMemberName %21 2 "uv" +OpName %21 "GenVertexOutput" +OpMemberName %22 0 "vert_component" +OpMemberName %22 1 "index" +OpName %22 "GenFragmentOutput" +OpMemberName %24 0 "view_pos" +OpMemberName %24 1 "view_proj" +OpName %24 "Camera" +OpMemberName %25 0 "position" +OpMemberName %25 1 "color" +OpName %25 "Light" +OpMemberName %26 0 "clip_position" +OpMemberName %26 1 "normal" +OpMemberName %26 2 "world_pos" +OpName %26 "VertexOutput" +OpName %29 "chunk_data" +OpName %32 "vertices" +OpName %34 "indices" +OpName %36 "gen_data" +OpName %39 "camera" +OpName %42 "light" +OpName %45 "t_diffuse" +OpName %47 "s_diffuse" +OpName %49 "t_normal" +OpName %50 "s_normal" +OpName %52 "x" +OpName %53 "permute3" +OpName %66 "v" +OpName %67 "snoise2" +OpName %86 "i" +OpName %89 "i1" +OpName %91 "x12" +OpName %94 "m" +OpName %203 "p" +OpName %204 "fbm" +OpName %209 "x" +OpName %211 "v" +OpName %213 "a" +OpName %214 "i" +OpName %255 "p" +OpName %256 "min_max_height" +OpName %257 "terrain_point" +OpName %268 "p" +OpName %269 "min_max_height" +OpName %270 "terrain_vertex" +OpName %300 "vert_index" +OpName %301 "chunk_size" +OpName %302 "chunk_corner" +OpName %303 "index_to_p" +OpName %319 "p" +OpName %320 "color23" +OpName %342 "gid" +OpName %345 "gen_terrain_compute" +OpName %406 "vindex" +OpName %409 "index" +OpName %411 "position" +OpName %413 "uv" +OpName %415 "gen_terrain_vertex" +OpName %455 "index" +OpName %457 "position" +OpName %460 "uv" +OpName %463 "vert_component" +OpName %464 "index" +OpName %465 "gen_terrain_fragment" +OpName %468 "vert_component" +OpName %469 "index" +OpName %549 "position" +OpName %552 "normal" +OpName %554 "clip_position" +OpName %555 "normal" +OpName %557 "world_pos" +OpName %558 "vs_main" +OpName %576 "clip_position" +OpName %578 "normal" +OpName %580 "world_pos" +OpName %583 "fs_main" +OpName %592 "color" +OpMemberDecorate %13 0 Offset 0 +OpMemberDecorate %13 1 Offset 8 +OpMemberDecorate %13 2 Offset 16 +OpMemberDecorate %14 0 Offset 0 +OpMemberDecorate %14 1 Offset 16 +OpDecorate %15 ArrayStride 32 +OpMemberDecorate %16 0 Offset 0 +OpDecorate %16 Block +OpDecorate %17 ArrayStride 4 +OpMemberDecorate %18 0 Offset 0 +OpDecorate %18 Block +OpMemberDecorate %20 0 Offset 0 +OpMemberDecorate %20 1 Offset 8 +OpMemberDecorate %20 2 Offset 16 +OpMemberDecorate %20 3 Offset 24 +OpMemberDecorate %20 4 Offset 28 +OpMemberDecorate %21 0 Offset 0 +OpMemberDecorate %21 1 Offset 16 +OpMemberDecorate %21 2 Offset 32 +OpMemberDecorate %22 0 Offset 0 +OpMemberDecorate %22 1 Offset 4 +OpMemberDecorate %24 0 Offset 0 +OpMemberDecorate %24 1 Offset 16 +OpMemberDecorate %24 1 ColMajor +OpMemberDecorate %24 1 MatrixStride 16 +OpMemberDecorate %25 0 Offset 0 +OpMemberDecorate %25 1 Offset 16 +OpMemberDecorate %26 0 Offset 0 +OpMemberDecorate %26 1 Offset 16 +OpMemberDecorate %26 2 Offset 32 +OpDecorate %29 DescriptorSet 0 +OpDecorate %29 Binding 0 +OpDecorate %30 Block +OpMemberDecorate %30 0 Offset 0 +OpDecorate %32 DescriptorSet 0 +OpDecorate %32 Binding 1 +OpDecorate %34 DescriptorSet 0 +OpDecorate %34 Binding 2 +OpDecorate %36 DescriptorSet 0 +OpDecorate %36 Binding 0 +OpDecorate %37 Block +OpMemberDecorate %37 0 Offset 0 +OpDecorate %39 DescriptorSet 0 +OpDecorate %39 Binding 0 +OpDecorate %40 Block +OpMemberDecorate %40 0 Offset 0 +OpDecorate %42 DescriptorSet 1 +OpDecorate %42 Binding 0 +OpDecorate %43 Block +OpMemberDecorate %43 0 Offset 0 +OpDecorate %45 DescriptorSet 2 +OpDecorate %45 Binding 0 +OpDecorate %47 DescriptorSet 2 +OpDecorate %47 Binding 1 +OpDecorate %49 DescriptorSet 2 +OpDecorate %49 Binding 2 +OpDecorate %50 DescriptorSet 2 +OpDecorate %50 Binding 3 +OpDecorate %342 BuiltIn GlobalInvocationId +OpDecorate %406 BuiltIn VertexIndex +OpDecorate %409 Location 0 +OpDecorate %409 Flat +OpDecorate %411 BuiltIn Position +OpDecorate %413 Location 1 +OpDecorate %455 Location 0 +OpDecorate %455 Flat +OpDecorate %457 BuiltIn FragCoord +OpDecorate %460 Location 1 +OpDecorate %463 Location 0 +OpDecorate %464 Location 1 +OpDecorate %549 Location 0 +OpDecorate %552 Location 1 +OpDecorate %554 BuiltIn Position +OpDecorate %555 Location 0 +OpDecorate %557 Location 1 +OpDecorate %576 BuiltIn FragCoord +OpDecorate %578 Location 0 +OpDecorate %580 Location 1 +OpDecorate %582 Location 0 +%2 = OpTypeVoid +%5 = OpTypeFloat 32 +%4 = OpTypeVector %5 3 +%6 = OpTypeVector %5 2 +%7 = OpTypeVector %5 4 +%8 = OpTypeInt 32 0 +%9 = OpTypeMatrix %6 2 +%10 = OpTypeVector %8 2 +%12 = OpTypeInt 32 1 +%11 = OpTypeVector %12 2 +%13 = OpTypeStruct %10 %11 %6 +%14 = OpTypeStruct %4 %4 +%15 = OpTypeRuntimeArray %14 +%16 = OpTypeStruct %15 +%17 = OpTypeRuntimeArray %8 +%18 = OpTypeStruct %17 +%19 = OpTypeVector %8 3 +%20 = OpTypeStruct %10 %11 %6 %8 %8 +%21 = OpTypeStruct %8 %7 %6 +%22 = OpTypeStruct %8 %8 +%23 = OpTypeMatrix %7 4 +%24 = OpTypeStruct %7 %23 +%25 = OpTypeStruct %4 %4 +%26 = OpTypeStruct %7 %4 %4 +%27 = OpTypeImage %5 2D 0 0 0 1 Unknown +%28 = OpTypeSampler +%30 = OpTypeStruct %13 +%31 = OpTypePointer Uniform %30 +%29 = OpVariable %31 Uniform +%33 = OpTypePointer StorageBuffer %16 +%32 = OpVariable %33 StorageBuffer +%35 = OpTypePointer StorageBuffer %18 +%34 = OpVariable %35 StorageBuffer +%37 = OpTypeStruct %20 +%38 = OpTypePointer Uniform %37 +%36 = OpVariable %38 Uniform +%40 = OpTypeStruct %24 +%41 = OpTypePointer Uniform %40 +%39 = OpVariable %41 Uniform +%43 = OpTypeStruct %25 +%44 = OpTypePointer Uniform %43 +%42 = OpVariable %44 Uniform +%46 = OpTypePointer UniformConstant %27 +%45 = OpVariable %46 UniformConstant +%48 = OpTypePointer UniformConstant %28 +%47 = OpVariable %48 UniformConstant +%49 = OpVariable %46 UniformConstant +%50 = OpVariable %48 UniformConstant +%54 = OpTypeFunction %4 %4 +%55 = OpConstant %5 34.0 +%56 = OpConstant %5 1.0 +%57 = OpConstantComposite %4 %56 %56 %56 +%58 = OpConstant %5 289.0 +%59 = OpConstantComposite %4 %58 %58 %58 +%68 = OpTypeFunction %5 %6 +%69 = OpConstant %5 0.21132487 +%70 = OpConstant %5 0.36602542 +%71 = OpConstant %5 -0.57735026 +%72 = OpConstant %5 0.024390243 +%73 = OpConstantComposite %7 %69 %70 %71 %72 +%74 = OpConstant %5 0.0 +%75 = OpConstantComposite %6 %56 %74 +%76 = OpConstantComposite %6 %74 %56 +%77 = OpConstantComposite %6 %58 %58 +%78 = OpConstant %5 0.5 +%79 = OpConstantComposite %4 %78 %78 %78 +%80 = OpConstantComposite %4 %74 %74 %74 +%81 = OpConstant %5 2.0 +%82 = OpConstant %5 0.85373473 +%83 = OpConstant %5 1.7928429 +%84 = OpConstantComposite %4 %83 %83 %83 +%85 = OpConstant %5 130.0 +%87 = OpTypePointer Function %6 +%88 = OpConstantNull %6 +%90 = OpConstantNull %6 +%92 = OpTypePointer Function %7 +%93 = OpConstantNull %7 +%95 = OpTypePointer Function %4 +%96 = OpConstantNull %4 +%112 = OpTypeBool +%115 = OpTypeVector %112 2 +%125 = OpTypePointer Function %5 +%126 = OpConstant %8 1 +%135 = OpConstant %8 0 +%205 = OpConstant %8 5 +%206 = OpConstant %5 0.01 +%207 = OpConstant %5 100.0 +%208 = OpConstantComposite %6 %207 %207 +%210 = OpConstantNull %6 +%212 = OpTypePointer Function %5 +%215 = OpTypePointer Function %8 +%258 = OpTypeFunction %4 %6 %6 +%271 = OpTypeFunction %14 %6 %6 +%272 = OpConstant %5 0.1 +%273 = OpConstantComposite %6 %272 %74 +%274 = OpConstantComposite %6 %74 %272 +%275 = OpConstant %5 -0.1 +%276 = OpConstantComposite %6 %275 %74 +%277 = OpConstantComposite %6 %74 %275 +%304 = OpTypeFunction %6 %8 %10 %11 +%321 = OpTypeFunction %4 %6 +%322 = OpConstant %5 23.0 +%323 = OpConstant %5 32.0 +%324 = OpConstantComposite %6 %322 %323 +%325 = OpConstant %5 -43.0 +%326 = OpConstant %5 3.0 +%327 = OpConstantComposite %6 %325 %326 +%343 = OpTypePointer Input %19 +%342 = OpVariable %343 Input +%346 = OpTypeFunction %2 +%347 = OpTypePointer Uniform %13 +%349 = OpConstant %8 6 +%350 = OpConstant %8 2 +%351 = OpConstant %8 3 +%352 = OpConstant %8 4 +%355 = OpTypePointer Uniform %10 +%358 = OpTypePointer Uniform %11 +%362 = OpTypePointer StorageBuffer %15 +%363 = OpTypePointer StorageBuffer %14 +%364 = OpTypePointer Uniform %6 +%371 = OpTypePointer Uniform %8 +%392 = OpTypePointer StorageBuffer %17 +%393 = OpTypePointer StorageBuffer %8 +%407 = OpTypePointer Input %8 +%406 = OpVariable %407 Input +%410 = OpTypePointer Output %8 +%409 = OpVariable %410 Output +%412 = OpTypePointer Output %7 +%411 = OpVariable %412 Output +%414 = OpTypePointer Output %6 +%413 = OpVariable %414 Output +%416 = OpTypePointer Uniform %20 +%418 = OpConstant %5 -1.0 +%419 = OpConstantComposite %6 %418 %418 +%434 = OpTypePointer Uniform %8 +%455 = OpVariable %407 Input +%458 = OpTypePointer Input %7 +%457 = OpVariable %458 Input +%461 = OpTypePointer Input %6 +%460 = OpVariable %461 Input +%463 = OpVariable %410 Output +%464 = OpVariable %410 Output +%467 = OpConstant %5 6.0 +%550 = OpTypePointer Input %4 +%549 = OpVariable %550 Input +%552 = OpVariable %550 Input +%554 = OpVariable %412 Output +%556 = OpTypePointer Output %4 +%555 = OpVariable %556 Output +%557 = OpVariable %556 Output +%559 = OpTypePointer Uniform %24 +%562 = OpTypePointer Uniform %23 +%576 = OpVariable %458 Input +%578 = OpVariable %550 Input +%580 = OpVariable %550 Input +%582 = OpVariable %412 Output +%585 = OpTypePointer Uniform %25 +%587 = OpConstantComposite %4 %272 %272 %272 +%588 = OpConstant %5 0.7 +%589 = OpConstantComposite %4 %78 %272 %588 +%590 = OpConstant %5 0.2 +%591 = OpConstantComposite %4 %590 %590 %590 +%593 = OpConstantNull %4 +%608 = OpTypePointer Uniform %4 +%617 = OpTypePointer Uniform %7 +%53 = OpFunction %4 None %54 +%52 = OpFunctionParameter %4 +%51 = OpLabel +OpBranch %60 +%60 = OpLabel +OpLine %3 10 52 +%61 = OpVectorTimesScalar %4 %52 %55 +OpLine %3 10 63 +OpLine %3 10 50 +%62 = OpFAdd %4 %61 %57 +%63 = OpFMul %4 %62 %52 +OpLine %3 10 49 +%64 = OpFRem %4 %63 %59 +OpReturnValue %64 +OpFunctionEnd +%67 = OpFunction %5 None %68 +%66 = OpFunctionParameter %6 +%65 = OpLabel +%89 = OpVariable %87 Function %90 +%94 = OpVariable %95 Function %96 +%86 = OpVariable %87 Function %88 +%91 = OpVariable %92 Function %93 +OpBranch %97 +%97 = OpLabel +OpLine %3 13 13 +OpLine %3 14 24 +%98 = OpVectorShuffle %6 %73 %73 1 1 +%99 = OpDot %5 %66 %98 +%100 = OpCompositeConstruct %6 %99 %99 +%101 = OpFAdd %6 %66 %100 +%102 = OpExtInst %6 %1 Floor %101 +OpLine %3 14 5 +OpStore %86 %102 +OpLine %3 15 14 +%103 = OpLoad %6 %86 +%104 = OpFSub %6 %66 %103 +%105 = OpLoad %6 %86 +%106 = OpVectorShuffle %6 %73 %73 0 0 +%107 = OpDot %5 %105 %106 +%108 = OpCompositeConstruct %6 %107 %107 +%109 = OpFAdd %6 %104 %108 +OpLine %3 17 32 +OpLine %3 17 25 +%110 = OpCompositeExtract %5 %109 0 +%111 = OpCompositeExtract %5 %109 1 +%113 = OpFOrdLessThan %112 %110 %111 +%116 = OpCompositeConstruct %115 %113 %113 +%114 = OpSelect %6 %116 %76 %75 +OpLine %3 17 5 +OpStore %89 %114 +OpLine %3 18 26 +%117 = OpVectorShuffle %7 %109 %109 0 1 0 1 +%118 = OpVectorShuffle %7 %73 %73 0 0 2 2 +%119 = OpFAdd %7 %117 %118 +%120 = OpLoad %6 %89 +OpLine %3 18 26 +%121 = OpCompositeConstruct %7 %120 %74 %74 +%122 = OpFSub %7 %119 %121 +OpLine %3 18 5 +OpStore %91 %122 +OpLine %3 1 1 +%123 = OpLoad %6 %86 +OpLine %3 19 9 +%124 = OpFRem %6 %123 %77 +OpLine %3 19 5 +OpStore %86 %124 +OpLine %3 20 31 +%127 = OpAccessChain %125 %86 %126 +%128 = OpLoad %5 %127 +OpLine %3 20 51 +%129 = OpAccessChain %125 %89 %126 +%130 = OpLoad %5 %129 +OpLine %3 20 31 +%131 = OpCompositeConstruct %4 %74 %130 %56 +%132 = OpCompositeConstruct %4 %128 %128 %128 +%133 = OpFAdd %4 %132 %131 +OpLine %3 20 22 +%134 = OpFunctionCall %4 %53 %133 +OpLine %3 20 22 +%136 = OpAccessChain %125 %86 %135 +%137 = OpLoad %5 %136 +%138 = OpCompositeConstruct %4 %137 %137 %137 +%139 = OpFAdd %4 %134 %138 +OpLine %3 20 84 +%140 = OpAccessChain %125 %89 %135 +%141 = OpLoad %5 %140 +OpLine %3 20 22 +%142 = OpCompositeConstruct %4 %74 %141 %56 +%143 = OpFAdd %4 %139 %142 +OpLine %3 20 13 +%144 = OpFunctionCall %4 %53 %143 +OpLine %3 21 28 +%145 = OpDot %5 %109 %109 +%146 = OpLoad %7 %91 +%147 = OpVectorShuffle %6 %146 %146 0 1 +%148 = OpLoad %7 %91 +%149 = OpVectorShuffle %6 %148 %148 0 1 +%150 = OpDot %5 %147 %149 +%151 = OpLoad %7 %91 +%152 = OpVectorShuffle %6 %151 %151 2 3 +%153 = OpLoad %7 %91 +%154 = OpVectorShuffle %6 %153 %153 2 3 +%155 = OpDot %5 %152 %154 +%156 = OpCompositeConstruct %4 %145 %150 %155 +OpLine %3 21 28 +%157 = OpFSub %4 %79 %156 +OpLine %3 21 24 +%158 = OpExtInst %4 %1 FMax %157 %80 +OpLine %3 21 5 +OpStore %94 %158 +OpLine %3 22 9 +%159 = OpLoad %4 %94 +%160 = OpLoad %4 %94 +%161 = OpFMul %4 %159 %160 +OpLine %3 22 5 +OpStore %94 %161 +OpLine %3 23 9 +%162 = OpLoad %4 %94 +%163 = OpLoad %4 %94 +%164 = OpFMul %4 %162 %163 +OpLine %3 23 5 +OpStore %94 %164 +OpLine %3 24 18 +%165 = OpVectorShuffle %4 %73 %73 3 3 3 +%166 = OpFMul %4 %144 %165 +%167 = OpExtInst %4 %1 Fract %166 +OpLine %3 24 13 +%168 = OpVectorTimesScalar %4 %167 %81 +OpLine %3 24 37 +OpLine %3 24 13 +%169 = OpFSub %4 %168 %57 +OpLine %3 25 13 +%170 = OpExtInst %4 %1 FAbs %169 +OpLine %3 25 22 +OpLine %3 25 13 +%171 = OpFSub %4 %170 %79 +OpLine %3 26 24 +OpLine %3 26 14 +%172 = OpFAdd %4 %169 %79 +%173 = OpExtInst %4 %1 Floor %172 +OpLine %3 27 14 +%174 = OpFSub %4 %169 %173 +OpLine %3 1 1 +%175 = OpLoad %4 %94 +OpLine %3 28 53 +%176 = OpFMul %4 %174 %174 +%177 = OpFMul %4 %171 %171 +%178 = OpFAdd %4 %176 %177 +OpLine %3 28 14 +%179 = OpVectorTimesScalar %4 %178 %82 +OpLine %3 28 9 +%180 = OpFSub %4 %84 %179 +%181 = OpFMul %4 %175 %180 +OpLine %3 28 5 +OpStore %94 %181 +OpLine %3 29 13 +%182 = OpCompositeExtract %5 %174 0 +%183 = OpCompositeExtract %5 %109 0 +%184 = OpFMul %5 %182 %183 +%185 = OpCompositeExtract %5 %171 0 +%186 = OpCompositeExtract %5 %109 1 +%187 = OpFMul %5 %185 %186 +%188 = OpFAdd %5 %184 %187 +%189 = OpVectorShuffle %6 %174 %174 1 2 +%190 = OpLoad %7 %91 +%191 = OpVectorShuffle %6 %190 %190 0 2 +%192 = OpFMul %6 %189 %191 +%193 = OpVectorShuffle %6 %171 %171 1 2 +%194 = OpLoad %7 %91 +%195 = OpVectorShuffle %6 %194 %194 1 3 +%196 = OpFMul %6 %193 %195 +%197 = OpFAdd %6 %192 %196 +%198 = OpCompositeConstruct %4 %188 %197 +OpLine %3 30 19 +%199 = OpLoad %4 %94 +%200 = OpDot %5 %199 %198 +OpLine %3 30 12 +%201 = OpFMul %5 %85 %200 +OpReturnValue %201 +OpFunctionEnd +%204 = OpFunction %5 None %68 +%203 = OpFunctionParameter %6 +%202 = OpLabel +%211 = OpVariable %212 Function %74 +%214 = OpVariable %215 Function %135 +%209 = OpVariable %87 Function %210 +%213 = OpVariable %212 Function %78 +OpBranch %216 +%216 = OpLabel +OpLine %3 36 13 +%217 = OpVectorTimesScalar %6 %203 %206 +OpLine %3 36 5 +OpStore %209 %217 +OpLine %3 39 17 +OpLine %3 40 24 +%218 = OpExtInst %5 %1 Cos %78 +OpLine %3 40 14 +%219 = OpExtInst %5 %1 Sin %78 +%220 = OpCompositeConstruct %6 %218 %219 +OpLine %3 41 15 +%221 = OpCompositeExtract %5 %220 0 +%222 = OpCompositeExtract %5 %220 1 +%223 = OpCompositeExtract %5 %220 1 +%224 = OpFNegate %5 %223 +%225 = OpCompositeExtract %5 %220 0 +%226 = OpCompositeConstruct %6 %221 %222 +%227 = OpCompositeConstruct %6 %224 %225 +%228 = OpCompositeConstruct %9 %226 %227 +OpBranch %229 +%229 = OpLabel +OpLine %3 43 5 +OpLoopMerge %230 %232 None +OpBranch %231 +%231 = OpLabel +OpLine %3 43 22 +%233 = OpLoad %8 %214 +%234 = OpULessThan %112 %233 %205 +OpLine %3 43 21 +OpSelectionMerge %235 None +OpBranchConditional %234 %235 %236 +%236 = OpLabel +OpBranch %230 +%235 = OpLabel +OpBranch %237 +%237 = OpLabel +OpLine %3 1 1 +%239 = OpLoad %5 %211 +%240 = OpLoad %5 %213 +%241 = OpLoad %6 %209 +OpLine %3 44 21 +%242 = OpFunctionCall %5 %67 %241 +OpLine %3 44 13 +%243 = OpFMul %5 %240 %242 +%244 = OpFAdd %5 %239 %243 +OpLine %3 44 9 +OpStore %211 %244 +OpLine %3 45 13 +%245 = OpLoad %6 %209 +%246 = OpMatrixTimesVector %6 %228 %245 +OpLine %3 45 13 +%247 = OpVectorTimesScalar %6 %246 %81 +%248 = OpFAdd %6 %247 %208 +OpLine %3 45 9 +OpStore %209 %248 +OpLine %3 1 1 +%249 = OpLoad %5 %213 +OpLine %3 46 13 +%250 = OpFMul %5 %249 %78 +OpLine %3 46 9 +OpStore %213 %250 +OpBranch %238 +%238 = OpLabel +OpBranch %232 +%232 = OpLabel +OpLine %3 1 1 +%251 = OpLoad %8 %214 +OpLine %3 43 43 +%252 = OpIAdd %8 %251 %126 +OpLine %3 43 39 +OpStore %214 %252 +OpBranch %229 +%230 = OpLabel +OpLine %3 1 1 +%253 = OpLoad %5 %211 +OpReturnValue %253 +OpFunctionEnd +%257 = OpFunction %4 None %258 +%255 = OpFunctionParameter %6 +%256 = OpFunctionParameter %6 +%254 = OpLabel +OpBranch %259 +%259 = OpLabel +OpLine %3 77 9 +%260 = OpCompositeExtract %5 %255 0 +%261 = OpCompositeExtract %5 %256 0 +%262 = OpCompositeExtract %5 %256 1 +OpLine %3 78 49 +%263 = OpFunctionCall %5 %204 %255 +OpLine %3 76 12 +%264 = OpExtInst %5 %1 FMix %261 %262 %263 +%265 = OpCompositeExtract %5 %255 1 +%266 = OpCompositeConstruct %4 %260 %264 %265 +OpReturnValue %266 +OpFunctionEnd +%270 = OpFunction %14 None %271 +%268 = OpFunctionParameter %6 +%269 = OpFunctionParameter %6 +%267 = OpLabel +OpBranch %278 +%278 = OpLabel +OpLine %3 84 13 +%279 = OpFunctionCall %4 %257 %268 %269 +OpLine %3 86 29 +%280 = OpFAdd %6 %268 %273 +OpLine %3 86 15 +%281 = OpFunctionCall %4 %257 %280 %269 +OpLine %3 86 15 +%282 = OpFSub %4 %281 %279 +OpLine %3 87 29 +%283 = OpFAdd %6 %268 %274 +OpLine %3 87 15 +%284 = OpFunctionCall %4 %257 %283 %269 +OpLine %3 87 15 +%285 = OpFSub %4 %284 %279 +OpLine %3 88 29 +%286 = OpFAdd %6 %268 %276 +OpLine %3 88 15 +%287 = OpFunctionCall %4 %257 %286 %269 +OpLine %3 88 15 +%288 = OpFSub %4 %287 %279 +OpLine %3 89 29 +%289 = OpFAdd %6 %268 %277 +OpLine %3 89 15 +%290 = OpFunctionCall %4 %257 %289 %269 +OpLine %3 89 15 +%291 = OpFSub %4 %290 %279 +OpLine %3 91 14 +%292 = OpExtInst %4 %1 Cross %285 %282 +%293 = OpExtInst %4 %1 Normalize %292 +OpLine %3 92 14 +%294 = OpExtInst %4 %1 Cross %291 %288 +%295 = OpExtInst %4 %1 Normalize %294 +OpLine %3 94 14 +%296 = OpFAdd %4 %293 %295 +OpLine %3 94 13 +%297 = OpVectorTimesScalar %4 %296 %78 +OpLine %3 96 12 +%298 = OpCompositeConstruct %14 %279 %297 +OpReturnValue %298 +OpFunctionEnd +%303 = OpFunction %6 None %304 +%300 = OpFunctionParameter %8 +%301 = OpFunctionParameter %10 +%302 = OpFunctionParameter %11 +%299 = OpLabel +OpBranch %305 +%305 = OpLabel +OpLine %3 101 9 +%306 = OpConvertUToF %5 %300 +%307 = OpCompositeExtract %8 %301 0 +OpLine %3 101 9 +%308 = OpIAdd %8 %307 %126 +%309 = OpConvertUToF %5 %308 +%310 = OpFRem %5 %306 %309 +%311 = OpCompositeExtract %8 %301 0 +OpLine %3 100 12 +%312 = OpIAdd %8 %311 %126 +%313 = OpUDiv %8 %300 %312 +%314 = OpConvertUToF %5 %313 +%315 = OpCompositeConstruct %6 %310 %314 +%316 = OpConvertSToF %6 %302 +%317 = OpFAdd %6 %315 %316 +OpReturnValue %317 +OpFunctionEnd +%320 = OpFunction %4 None %321 +%319 = OpFunctionParameter %6 +%318 = OpLabel +OpBranch %328 +%328 = OpLabel +OpLine %3 270 9 +%329 = OpFunctionCall %5 %67 %319 +OpLine %3 270 9 +%330 = OpFMul %5 %329 %78 +OpLine %3 270 9 +%331 = OpFAdd %5 %330 %78 +OpLine %3 271 17 +%332 = OpFAdd %6 %319 %324 +OpLine %3 271 9 +%333 = OpFunctionCall %5 %67 %332 +OpLine %3 271 9 +%334 = OpFMul %5 %333 %78 +OpLine %3 271 9 +%335 = OpFAdd %5 %334 %78 +OpLine %3 272 17 +%336 = OpFAdd %6 %319 %327 +OpLine %3 272 9 +%337 = OpFunctionCall %5 %67 %336 +OpLine %3 272 9 +%338 = OpFMul %5 %337 %78 +OpLine %3 269 12 +%339 = OpFAdd %5 %338 %78 +%340 = OpCompositeConstruct %4 %331 %335 %339 +OpReturnValue %340 +OpFunctionEnd +%345 = OpFunction %2 None %346 +%341 = OpLabel +%344 = OpLoad %19 %342 +%348 = OpAccessChain %347 %29 %135 +OpBranch %353 +%353 = OpLabel +OpLine %3 111 22 +%354 = OpCompositeExtract %8 %344 0 +OpLine %3 113 36 +%356 = OpAccessChain %355 %348 %135 +%357 = OpLoad %10 %356 +OpLine %3 113 59 +%359 = OpAccessChain %358 %348 %126 +%360 = OpLoad %11 %359 +OpLine %3 113 13 +%361 = OpFunctionCall %6 %303 %354 %357 %360 +OpLine %3 115 5 +OpLine %3 115 51 +%365 = OpAccessChain %364 %348 %350 +%366 = OpLoad %6 %365 +OpLine %3 115 33 +%367 = OpFunctionCall %14 %270 %361 %366 +OpLine %3 115 5 +%368 = OpAccessChain %363 %32 %135 %354 +OpStore %368 %367 +OpLine %3 118 23 +%369 = OpCompositeExtract %8 %344 0 +OpLine %3 118 23 +%370 = OpIMul %8 %369 %349 +OpLine %3 120 25 +%372 = OpAccessChain %371 %348 %135 %135 +%373 = OpLoad %8 %372 +OpLine %3 120 25 +%374 = OpAccessChain %371 %348 %135 %126 +%375 = OpLoad %8 %374 +%376 = OpIMul %8 %373 %375 +OpLine %3 120 9 +%377 = OpIMul %8 %376 %349 +%378 = OpUGreaterThanEqual %112 %370 %377 +OpLine %3 120 5 +OpSelectionMerge %379 None +OpBranchConditional %378 %380 %379 +%380 = OpLabel +OpReturn +%379 = OpLabel +OpLine %3 122 28 +%381 = OpCompositeExtract %8 %344 0 +OpLine %3 122 15 +%382 = OpAccessChain %371 %348 %135 %135 +%383 = OpLoad %8 %382 +%384 = OpUDiv %8 %381 %383 +%385 = OpIAdd %8 %354 %384 +OpLine %3 123 15 +%386 = OpIAdd %8 %385 %126 +OpLine %3 124 15 +%387 = OpAccessChain %371 %348 %135 %135 +%388 = OpLoad %8 %387 +%389 = OpIAdd %8 %385 %388 +OpLine %3 124 15 +%390 = OpIAdd %8 %389 %126 +OpLine %3 125 15 +%391 = OpIAdd %8 %390 %126 +OpLine %3 127 5 +OpLine %3 127 5 +%394 = OpAccessChain %393 %34 %135 %370 +OpStore %394 %385 +OpLine %3 128 5 +OpLine %3 128 5 +%395 = OpIAdd %8 %370 %126 +OpLine %3 128 5 +%396 = OpAccessChain %393 %34 %135 %395 +OpStore %396 %390 +OpLine %3 129 5 +OpLine %3 129 5 +%397 = OpIAdd %8 %370 %350 +OpLine %3 129 5 +%398 = OpAccessChain %393 %34 %135 %397 +OpStore %398 %391 +OpLine %3 130 5 +OpLine %3 130 5 +%399 = OpIAdd %8 %370 %351 +OpLine %3 130 5 +%400 = OpAccessChain %393 %34 %135 %399 +OpStore %400 %385 +OpLine %3 131 5 +OpLine %3 131 5 +%401 = OpIAdd %8 %370 %352 +OpLine %3 131 5 +%402 = OpAccessChain %393 %34 %135 %401 +OpStore %402 %391 +OpLine %3 132 5 +OpLine %3 132 5 +%403 = OpIAdd %8 %370 %205 +OpLine %3 132 5 +%404 = OpAccessChain %393 %34 %135 %403 +OpStore %404 %386 +OpReturn +OpFunctionEnd +%415 = OpFunction %2 None %346 +%405 = OpLabel +%408 = OpLoad %8 %406 +%417 = OpAccessChain %416 %36 %135 +OpBranch %420 +%420 = OpLabel +OpLine %3 161 19 +%421 = OpIAdd %8 %408 %350 +OpLine %3 161 18 +%422 = OpUDiv %8 %421 %351 +OpLine %3 161 13 +%423 = OpUMod %8 %422 %350 +%424 = OpConvertUToF %5 %423 +OpLine %3 162 19 +%425 = OpIAdd %8 %408 %126 +OpLine %3 162 18 +%426 = OpUDiv %8 %425 %351 +OpLine %3 162 13 +%427 = OpUMod %8 %426 %350 +%428 = OpConvertUToF %5 %427 +OpLine %3 163 14 +%429 = OpCompositeConstruct %6 %424 %428 +OpLine %3 165 30 +%430 = OpVectorTimesScalar %6 %429 %81 +OpLine %3 165 30 +%431 = OpFAdd %6 %419 %430 +OpLine %3 165 20 +%432 = OpCompositeConstruct %7 %431 %74 %56 +OpLine %3 168 21 +%433 = OpCompositeExtract %5 %429 0 +OpLine %3 168 21 +%435 = OpAccessChain %434 %417 %351 +%436 = OpLoad %8 %435 +%437 = OpConvertUToF %5 %436 +%438 = OpFMul %5 %433 %437 +%439 = OpCompositeExtract %5 %429 1 +OpLine %3 168 17 +%440 = OpAccessChain %434 %417 %351 +%441 = OpLoad %8 %440 +%442 = OpConvertUToF %5 %441 +%443 = OpFMul %5 %439 %442 +%444 = OpFAdd %5 %438 %443 +%445 = OpConvertFToU %8 %444 +OpLine %3 168 17 +%446 = OpAccessChain %434 %417 %352 +%447 = OpLoad %8 %446 +%448 = OpIAdd %8 %445 %447 +OpLine %3 170 12 +%449 = OpCompositeConstruct %21 %448 %432 %429 +%450 = OpCompositeExtract %8 %449 0 +OpStore %409 %450 +%451 = OpCompositeExtract %7 %449 1 +OpStore %411 %451 +%452 = OpCompositeExtract %6 %449 2 +OpStore %413 %452 +OpReturn +OpFunctionEnd +%465 = OpFunction %2 None %346 +%453 = OpLabel +%468 = OpVariable %212 Function %74 +%469 = OpVariable %215 Function %135 +%456 = OpLoad %8 %455 +%459 = OpLoad %7 %457 +%462 = OpLoad %6 %460 +%454 = OpCompositeConstruct %21 %456 %459 %462 +%466 = OpAccessChain %416 %36 %135 +OpBranch %470 +%470 = OpLabel +OpLine %3 181 17 +%471 = OpCompositeExtract %6 %454 2 +%472 = OpCompositeExtract %5 %471 0 +OpLine %3 181 17 +%473 = OpAccessChain %434 %466 %351 +%474 = OpLoad %8 %473 +%475 = OpConvertUToF %5 %474 +%476 = OpFMul %5 %472 %475 +%477 = OpCompositeExtract %6 %454 2 +%478 = OpCompositeExtract %5 %477 1 +OpLine %3 181 70 +%479 = OpAccessChain %434 %466 %351 +%480 = OpLoad %8 %479 +OpLine %3 181 13 +%481 = OpAccessChain %434 %466 %351 +%482 = OpLoad %8 %481 +%483 = OpIMul %8 %480 %482 +%484 = OpConvertUToF %5 %483 +%485 = OpFMul %5 %478 %484 +%486 = OpFAdd %5 %476 %485 +%487 = OpConvertFToU %8 %486 +OpLine %3 181 13 +%488 = OpAccessChain %434 %466 %352 +%489 = OpLoad %8 %488 +%490 = OpIAdd %8 %487 %489 +OpLine %3 182 32 +%491 = OpConvertUToF %5 %490 +OpLine %3 182 22 +%492 = OpFDiv %5 %491 %467 +%493 = OpExtInst %5 %1 Floor %492 +%494 = OpConvertFToU %8 %493 +OpLine %3 183 22 +%495 = OpUMod %8 %490 %349 +OpLine %3 185 36 +%496 = OpAccessChain %355 %466 %135 +%497 = OpLoad %10 %496 +OpLine %3 185 57 +%498 = OpAccessChain %358 %466 %126 +%499 = OpLoad %11 %498 +OpLine %3 185 13 +%500 = OpFunctionCall %6 %303 %494 %497 %499 +OpLine %3 186 31 +%501 = OpAccessChain %364 %466 %350 +%502 = OpLoad %6 %501 +OpLine %3 186 13 +%503 = OpFunctionCall %14 %270 %500 %502 +OpLine %3 190 5 +OpSelectionMerge %504 None +OpSwitch %495 %511 0 %505 1 %506 2 %507 3 %508 4 %509 5 %510 +%505 = OpLabel +OpLine %3 191 37 +%512 = OpCompositeExtract %4 %503 0 +%513 = OpCompositeExtract %5 %512 0 +OpLine %3 191 20 +OpStore %468 %513 +OpBranch %504 +%506 = OpLabel +OpLine %3 192 37 +%514 = OpCompositeExtract %4 %503 0 +%515 = OpCompositeExtract %5 %514 1 +OpLine %3 192 20 +OpStore %468 %515 +OpBranch %504 +%507 = OpLabel +OpLine %3 193 37 +%516 = OpCompositeExtract %4 %503 0 +%517 = OpCompositeExtract %5 %516 2 +OpLine %3 193 20 +OpStore %468 %517 +OpBranch %504 +%508 = OpLabel +OpLine %3 194 37 +%518 = OpCompositeExtract %4 %503 1 +%519 = OpCompositeExtract %5 %518 0 +OpLine %3 194 20 +OpStore %468 %519 +OpBranch %504 +%509 = OpLabel +OpLine %3 195 37 +%520 = OpCompositeExtract %4 %503 1 +%521 = OpCompositeExtract %5 %520 1 +OpLine %3 195 20 +OpStore %468 %521 +OpBranch %504 +%510 = OpLabel +OpLine %3 196 37 +%522 = OpCompositeExtract %4 %503 1 +%523 = OpCompositeExtract %5 %522 2 +OpLine %3 196 20 +OpStore %468 %523 +OpBranch %504 +%511 = OpLabel +OpBranch %504 +%504 = OpLabel +OpLine %3 200 15 +%524 = OpAccessChain %371 %466 %135 %135 +%525 = OpLoad %8 %524 +%526 = OpUDiv %8 %494 %525 +%527 = OpIAdd %8 %494 %526 +OpLine %3 201 15 +%528 = OpIAdd %8 %527 %126 +OpLine %3 202 15 +%529 = OpAccessChain %371 %466 %135 %135 +%530 = OpLoad %8 %529 +%531 = OpIAdd %8 %527 %530 +OpLine %3 202 15 +%532 = OpIAdd %8 %531 %126 +OpLine %3 203 15 +%533 = OpIAdd %8 %532 %126 +OpLine %3 206 5 +OpSelectionMerge %534 None +OpSwitch %495 %539 0 %535 3 %535 2 %536 4 %536 1 %537 5 %538 +%535 = OpLabel +OpLine %3 207 24 +OpStore %469 %527 +OpBranch %534 +%536 = OpLabel +OpLine %3 208 24 +OpStore %469 %533 +OpBranch %534 +%537 = OpLabel +OpLine %3 209 20 +OpStore %469 %532 +OpBranch %534 +%538 = OpLabel +OpLine %3 210 20 +OpStore %469 %528 +OpBranch %534 +%539 = OpLabel +OpBranch %534 +%534 = OpLabel +OpLine %3 213 13 +%540 = OpCompositeExtract %8 %454 0 +OpLine %3 213 5 +OpStore %469 %540 +OpLine %3 222 27 +%541 = OpLoad %5 %468 +%542 = OpBitcast %8 %541 +OpLine %3 223 12 +%543 = OpLoad %8 %469 +%544 = OpCompositeConstruct %22 %542 %543 +%545 = OpCompositeExtract %8 %544 0 +OpStore %463 %545 +%546 = OpCompositeExtract %8 %544 1 +OpStore %464 %546 +OpReturn +OpFunctionEnd +%558 = OpFunction %2 None %346 +%547 = OpLabel +%551 = OpLoad %4 %549 +%553 = OpLoad %4 %552 +%548 = OpCompositeConstruct %14 %551 %553 +%560 = OpAccessChain %559 %39 %135 +OpBranch %561 +%561 = OpLabel +OpLine %3 254 25 +%563 = OpAccessChain %562 %560 %126 +%564 = OpLoad %23 %563 +%565 = OpCompositeExtract %4 %548 0 +OpLine %3 254 25 +%566 = OpCompositeConstruct %7 %565 %56 +%567 = OpMatrixTimesVector %7 %564 %566 +OpLine %3 255 18 +%568 = OpCompositeExtract %4 %548 1 +OpLine %3 256 12 +%569 = OpCompositeExtract %4 %548 0 +%570 = OpCompositeConstruct %26 %567 %568 %569 +%571 = OpCompositeExtract %7 %570 0 +OpStore %554 %571 +%572 = OpCompositeExtract %4 %570 1 +OpStore %555 %572 +%573 = OpCompositeExtract %4 %570 2 +OpStore %557 %573 +OpReturn +OpFunctionEnd +%583 = OpFunction %2 None %346 +%574 = OpLabel +%592 = OpVariable %95 Function %593 +%577 = OpLoad %7 %576 +%579 = OpLoad %4 %578 +%581 = OpLoad %4 %580 +%575 = OpCompositeConstruct %26 %577 %579 %581 +%584 = OpAccessChain %559 %39 %135 +%586 = OpAccessChain %585 %42 %135 +OpBranch %594 +%594 = OpLabel +OpLine %3 278 28 +OpLine %3 278 17 +%595 = OpCompositeExtract %4 %575 2 +%596 = OpExtInst %4 %1 Fract %595 +%597 = OpExtInst %4 %1 SmoothStep %80 %587 %596 +OpLine %3 278 5 +OpStore %592 %597 +OpLine %3 279 17 +OpLine %3 279 13 +%598 = OpAccessChain %125 %592 %135 +%599 = OpLoad %5 %598 +%600 = OpAccessChain %125 %592 %126 +%601 = OpLoad %5 %600 +%602 = OpFMul %5 %599 %601 +%603 = OpAccessChain %125 %592 %350 +%604 = OpLoad %5 %603 +%605 = OpFMul %5 %602 %604 +%606 = OpCompositeConstruct %4 %605 %605 %605 +%607 = OpExtInst %4 %1 FMix %589 %591 %606 +OpLine %3 279 5 +OpStore %592 %607 +OpLine %3 282 25 +%609 = OpAccessChain %608 %586 %126 +%610 = OpLoad %4 %609 +%611 = OpVectorTimesScalar %4 %610 %272 +OpLine %3 284 21 +%612 = OpAccessChain %608 %586 %135 +%613 = OpLoad %4 %612 +%614 = OpCompositeExtract %4 %575 2 +%615 = OpFSub %4 %613 %614 +%616 = OpExtInst %4 %1 Normalize %615 +OpLine %3 285 20 +%618 = OpAccessChain %617 %584 %135 +%619 = OpLoad %7 %618 +%620 = OpVectorShuffle %4 %619 %619 0 1 2 +%621 = OpCompositeExtract %4 %575 2 +%622 = OpFSub %4 %620 %621 +%623 = OpExtInst %4 %1 Normalize %622 +OpLine %3 286 20 +%624 = OpFAdd %4 %623 %616 +%625 = OpExtInst %4 %1 Normalize %624 +OpLine %3 288 32 +%626 = OpCompositeExtract %4 %575 1 +%627 = OpDot %5 %626 %616 +OpLine %3 288 28 +%628 = OpExtInst %5 %1 FMax %627 %74 +OpLine %3 289 25 +%629 = OpAccessChain %608 %586 %126 +%630 = OpLoad %4 %629 +%631 = OpVectorTimesScalar %4 %630 %628 +OpLine %3 291 37 +%632 = OpCompositeExtract %4 %575 1 +%633 = OpDot %5 %632 %625 +OpLine %3 291 33 +%634 = OpExtInst %5 %1 FMax %633 %74 +OpLine %3 291 29 +%635 = OpExtInst %5 %1 Pow %634 %323 +OpLine %3 292 26 +%636 = OpAccessChain %608 %586 %126 +%637 = OpLoad %4 %636 +%638 = OpVectorTimesScalar %4 %637 %635 +OpLine %3 294 18 +%639 = OpFAdd %4 %611 %631 +%640 = OpFAdd %4 %639 %638 +%641 = OpLoad %4 %592 +%642 = OpFMul %4 %640 %641 +OpLine %3 296 12 +%643 = OpCompositeConstruct %7 %642 %56 +OpStore %582 %643 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/dualsource.spvasm b/naga/tests/out/spv/dualsource.spvasm new file mode 100644 index 0000000000..a5c692b4c4 --- /dev/null +++ b/naga/tests/out/spv/dualsource.spvasm @@ -0,0 +1,52 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 34 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %13 "main" %7 %10 %12 +OpExecutionMode %13 OriginUpperLeft +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %5 1 Offset 16 +OpDecorate %7 BuiltIn FragCoord +OpDecorate %10 Location 0 +OpDecorate %12 Location 0 +OpDecorate %12 Index 1 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeStruct %3 %3 +%8 = OpTypePointer Input %3 +%7 = OpVariable %8 Input +%11 = OpTypePointer Output %3 +%10 = OpVariable %11 Output +%12 = OpVariable %11 Output +%14 = OpTypeFunction %2 +%15 = OpConstant %4 0.4 +%16 = OpConstant %4 0.3 +%17 = OpConstant %4 0.2 +%18 = OpConstant %4 0.1 +%19 = OpConstantComposite %3 %15 %16 %17 %18 +%20 = OpConstant %4 0.9 +%21 = OpConstant %4 0.8 +%22 = OpConstant %4 0.7 +%23 = OpConstant %4 0.6 +%24 = OpConstantComposite %3 %20 %21 %22 %23 +%26 = OpTypePointer Function %3 +%13 = OpFunction %2 None %14 +%6 = OpLabel +%25 = OpVariable %26 Function %19 +%27 = OpVariable %26 Function %24 +%9 = OpLoad %3 %7 +OpBranch %28 +%28 = OpLabel +%29 = OpLoad %3 %25 +%30 = OpLoad %3 %27 +%31 = OpCompositeConstruct %5 %29 %30 +%32 = OpCompositeExtract %3 %31 0 +OpStore %10 %32 +%33 = OpCompositeExtract %3 %31 1 +OpStore %12 %33 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/empty.spvasm b/naga/tests/out/spv/empty.spvasm new file mode 100644 index 0000000000..47d3c17565 --- /dev/null +++ b/naga/tests/out/spv/empty.spvasm @@ -0,0 +1,17 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 7 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %4 "main" +OpExecutionMode %4 LocalSize 1 1 1 +%2 = OpTypeVoid +%5 = OpTypeFunction %2 +%4 = OpFunction %2 None %5 +%3 = OpLabel +OpBranch %6 +%6 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/extra.spvasm b/naga/tests/out/spv/extra.spvasm new file mode 100644 index 0000000000..9c434a8ce2 --- /dev/null +++ b/naga/tests/out/spv/extra.spvasm @@ -0,0 +1,76 @@ +; SPIR-V +; Version: 1.2 +; Generator: rspirv +; Bound: 48 +OpCapability Shader +OpCapability Float64 +OpCapability Geometry +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %23 "main" %15 %18 %21 +OpExecutionMode %23 OriginUpperLeft +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 16 +OpMemberDecorate %9 0 Offset 0 +OpMemberDecorate %9 1 Offset 16 +OpDecorate %11 Block +OpMemberDecorate %11 0 Offset 0 +OpDecorate %15 Location 0 +OpDecorate %18 BuiltIn PrimitiveId +OpDecorate %18 Flat +OpDecorate %21 Location 0 +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%5 = OpTypeFloat 64 +%4 = OpTypeVector %5 2 +%6 = OpTypeStruct %3 %4 +%8 = OpTypeFloat 32 +%7 = OpTypeVector %8 4 +%9 = OpTypeStruct %7 %3 +%11 = OpTypeStruct %6 +%12 = OpTypePointer PushConstant %11 +%10 = OpVariable %12 PushConstant +%16 = OpTypePointer Input %7 +%15 = OpVariable %16 Input +%19 = OpTypePointer Input %3 +%18 = OpVariable %19 Input +%22 = OpTypePointer Output %7 +%21 = OpVariable %22 Output +%24 = OpTypeFunction %2 +%25 = OpTypePointer PushConstant %6 +%26 = OpConstant %3 0 +%28 = OpConstant %8 1.0 +%29 = OpTypeVector %8 3 +%30 = OpConstantComposite %29 %28 %28 %28 +%33 = OpTypePointer PushConstant %3 +%36 = OpTypeBool +%23 = OpFunction %2 None %24 +%13 = OpLabel +%17 = OpLoad %7 %15 +%20 = OpLoad %3 %18 +%14 = OpCompositeConstruct %9 %17 %20 +%27 = OpAccessChain %25 %10 %26 +OpBranch %31 +%31 = OpLabel +%32 = OpCompositeExtract %3 %14 1 +%34 = OpAccessChain %33 %27 %26 +%35 = OpLoad %3 %34 +%37 = OpIEqual %36 %32 %35 +OpSelectionMerge %38 None +OpBranchConditional %37 %39 %40 +%39 = OpLabel +%41 = OpCompositeExtract %7 %14 0 +OpStore %21 %41 +OpReturn +%40 = OpLabel +%42 = OpCompositeExtract %7 %14 0 +%43 = OpVectorShuffle %29 %42 %42 0 1 2 +%44 = OpFSub %29 %30 %43 +%45 = OpCompositeExtract %7 %14 0 +%46 = OpCompositeExtract %8 %45 3 +%47 = OpCompositeConstruct %7 %44 %46 +OpStore %21 %47 +OpReturn +%38 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/f64.spvasm b/naga/tests/out/spv/f64.spvasm new file mode 100644 index 0000000000..1edf0d8cb6 --- /dev/null +++ b/naga/tests/out/spv/f64.spvasm @@ -0,0 +1,45 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 30 +OpCapability Shader +OpCapability Float64 +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %25 "main" +OpExecutionMode %25 LocalSize 1 1 1 +%2 = OpTypeVoid +%3 = OpTypeFloat 64 +%4 = OpConstant %3 1.0 +%5 = OpConstant %3 2.0 +%7 = OpTypePointer Private %3 +%6 = OpVariable %7 Private %4 +%11 = OpTypeFunction %3 %3 +%12 = OpConstant %3 30.0 +%13 = OpConstant %3 400.0 +%14 = OpConstant %3 5.0 +%16 = OpTypePointer Function %3 +%17 = OpConstantNull %3 +%26 = OpTypeFunction %2 +%27 = OpConstant %3 6.0 +%10 = OpFunction %3 None %11 +%9 = OpFunctionParameter %3 +%8 = OpLabel +%15 = OpVariable %16 Function %17 +OpBranch %18 +%18 = OpLabel +%19 = OpFAdd %3 %12 %13 +%20 = OpFAdd %3 %19 %14 +OpStore %15 %20 +%21 = OpFAdd %3 %9 %19 +%22 = OpFAdd %3 %21 %5 +%23 = OpFAdd %3 %22 %14 +OpReturnValue %23 +OpFunctionEnd +%25 = OpFunction %2 None %26 +%24 = OpLabel +OpBranch %28 +%28 = OpLabel +%29 = OpFunctionCall %3 %10 %27 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/fragment-output.spvasm b/naga/tests/out/spv/fragment-output.spvasm new file mode 100644 index 0000000000..c61ffb8258 --- /dev/null +++ b/naga/tests/out/spv/fragment-output.spvasm @@ -0,0 +1,172 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 109 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %30 "main_vec4vec3" %18 %20 %22 %24 %26 %28 +OpEntryPoint Fragment %82 "main_vec2scalar" %70 %72 %74 %76 %78 %80 +OpExecutionMode %30 OriginUpperLeft +OpExecutionMode %82 OriginUpperLeft +OpMemberDecorate %12 0 Offset 0 +OpMemberDecorate %12 1 Offset 16 +OpMemberDecorate %12 2 Offset 32 +OpMemberDecorate %12 3 Offset 48 +OpMemberDecorate %12 4 Offset 64 +OpMemberDecorate %12 5 Offset 80 +OpMemberDecorate %16 0 Offset 0 +OpMemberDecorate %16 1 Offset 8 +OpMemberDecorate %16 2 Offset 16 +OpMemberDecorate %16 3 Offset 24 +OpMemberDecorate %16 4 Offset 28 +OpMemberDecorate %16 5 Offset 32 +OpDecorate %18 Location 0 +OpDecorate %20 Location 1 +OpDecorate %22 Location 2 +OpDecorate %24 Location 3 +OpDecorate %26 Location 4 +OpDecorate %28 Location 5 +OpDecorate %70 Location 0 +OpDecorate %72 Location 1 +OpDecorate %74 Location 2 +OpDecorate %76 Location 3 +OpDecorate %78 Location 4 +OpDecorate %80 Location 5 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%6 = OpTypeInt 32 1 +%5 = OpTypeVector %6 4 +%8 = OpTypeInt 32 0 +%7 = OpTypeVector %8 4 +%9 = OpTypeVector %4 3 +%10 = OpTypeVector %6 3 +%11 = OpTypeVector %8 3 +%12 = OpTypeStruct %3 %5 %7 %9 %10 %11 +%13 = OpTypeVector %4 2 +%14 = OpTypeVector %6 2 +%15 = OpTypeVector %8 2 +%16 = OpTypeStruct %13 %14 %15 %4 %6 %8 +%19 = OpTypePointer Output %3 +%18 = OpVariable %19 Output +%21 = OpTypePointer Output %5 +%20 = OpVariable %21 Output +%23 = OpTypePointer Output %7 +%22 = OpVariable %23 Output +%25 = OpTypePointer Output %9 +%24 = OpVariable %25 Output +%27 = OpTypePointer Output %10 +%26 = OpVariable %27 Output +%29 = OpTypePointer Output %11 +%28 = OpVariable %29 Output +%31 = OpTypeFunction %2 +%32 = OpConstant %4 0.0 +%33 = OpConstantComposite %3 %32 %32 %32 %32 +%34 = OpConstant %6 0 +%35 = OpConstantComposite %5 %34 %34 %34 %34 +%36 = OpConstant %8 0 +%37 = OpConstantComposite %7 %36 %36 %36 %36 +%38 = OpConstantComposite %9 %32 %32 %32 +%39 = OpConstantComposite %10 %34 %34 %34 +%40 = OpConstantComposite %11 %36 %36 %36 +%42 = OpTypePointer Function %12 +%43 = OpConstantNull %12 +%45 = OpTypePointer Function %3 +%47 = OpTypePointer Function %5 +%48 = OpConstant %8 1 +%50 = OpTypePointer Function %7 +%51 = OpConstant %8 2 +%53 = OpTypePointer Function %9 +%54 = OpConstant %8 3 +%56 = OpTypePointer Function %10 +%57 = OpConstant %8 4 +%59 = OpTypePointer Function %11 +%60 = OpConstant %8 5 +%71 = OpTypePointer Output %13 +%70 = OpVariable %71 Output +%73 = OpTypePointer Output %14 +%72 = OpVariable %73 Output +%75 = OpTypePointer Output %15 +%74 = OpVariable %75 Output +%77 = OpTypePointer Output %4 +%76 = OpVariable %77 Output +%79 = OpTypePointer Output %6 +%78 = OpVariable %79 Output +%81 = OpTypePointer Output %8 +%80 = OpVariable %81 Output +%83 = OpConstantComposite %13 %32 %32 +%84 = OpConstantComposite %14 %34 %34 +%85 = OpConstantComposite %15 %36 %36 +%87 = OpTypePointer Function %16 +%88 = OpConstantNull %16 +%90 = OpTypePointer Function %13 +%92 = OpTypePointer Function %14 +%94 = OpTypePointer Function %15 +%96 = OpTypePointer Function %4 +%98 = OpTypePointer Function %6 +%100 = OpTypePointer Function %8 +%30 = OpFunction %2 None %31 +%17 = OpLabel +%41 = OpVariable %42 Function %43 +OpBranch %44 +%44 = OpLabel +%46 = OpAccessChain %45 %41 %36 +OpStore %46 %33 +%49 = OpAccessChain %47 %41 %48 +OpStore %49 %35 +%52 = OpAccessChain %50 %41 %51 +OpStore %52 %37 +%55 = OpAccessChain %53 %41 %54 +OpStore %55 %38 +%58 = OpAccessChain %56 %41 %57 +OpStore %58 %39 +%61 = OpAccessChain %59 %41 %60 +OpStore %61 %40 +%62 = OpLoad %12 %41 +%63 = OpCompositeExtract %3 %62 0 +OpStore %18 %63 +%64 = OpCompositeExtract %5 %62 1 +OpStore %20 %64 +%65 = OpCompositeExtract %7 %62 2 +OpStore %22 %65 +%66 = OpCompositeExtract %9 %62 3 +OpStore %24 %66 +%67 = OpCompositeExtract %10 %62 4 +OpStore %26 %67 +%68 = OpCompositeExtract %11 %62 5 +OpStore %28 %68 +OpReturn +OpFunctionEnd +%82 = OpFunction %2 None %31 +%69 = OpLabel +%86 = OpVariable %87 Function %88 +OpBranch %89 +%89 = OpLabel +%91 = OpAccessChain %90 %86 %36 +OpStore %91 %83 +%93 = OpAccessChain %92 %86 %48 +OpStore %93 %84 +%95 = OpAccessChain %94 %86 %51 +OpStore %95 %85 +%97 = OpAccessChain %96 %86 %54 +OpStore %97 %32 +%99 = OpAccessChain %98 %86 %57 +OpStore %99 %34 +%101 = OpAccessChain %100 %86 %60 +OpStore %101 %36 +%102 = OpLoad %16 %86 +%103 = OpCompositeExtract %13 %102 0 +OpStore %70 %103 +%104 = OpCompositeExtract %14 %102 1 +OpStore %72 %104 +%105 = OpCompositeExtract %15 %102 2 +OpStore %74 %105 +%106 = OpCompositeExtract %4 %102 3 +OpStore %76 %106 +%107 = OpCompositeExtract %6 %102 4 +OpStore %78 %107 +%108 = OpCompositeExtract %8 %102 5 +OpStore %80 %108 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/functions.spvasm b/naga/tests/out/spv/functions.spvasm new file mode 100644 index 0000000000..463f7da0b2 --- /dev/null +++ b/naga/tests/out/spv/functions.spvasm @@ -0,0 +1,91 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 75 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %70 "main" +OpExecutionMode %70 LocalSize 1 1 1 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 2 +%5 = OpTypeInt 32 1 +%8 = OpTypeFunction %3 +%9 = OpConstant %4 2.0 +%10 = OpConstantComposite %3 %9 %9 +%11 = OpConstant %4 0.5 +%12 = OpConstantComposite %3 %11 %11 +%17 = OpTypeFunction %5 +%18 = OpConstant %5 1 +%19 = OpTypeVector %5 2 +%20 = OpConstantComposite %19 %18 %18 +%21 = OpTypeInt 32 0 +%22 = OpConstant %21 1 +%23 = OpTypeVector %21 3 +%24 = OpConstantComposite %23 %22 %22 %22 +%25 = OpConstant %5 4 +%26 = OpTypeVector %5 4 +%27 = OpConstantComposite %26 %25 %25 %25 %25 +%28 = OpConstant %5 2 +%29 = OpConstantComposite %26 %28 %28 %28 %28 +%32 = OpConstantNull %5 +%41 = OpConstantNull %21 +%71 = OpTypeFunction %2 +%7 = OpFunction %3 None %8 +%6 = OpLabel +OpBranch %13 +%13 = OpLabel +%14 = OpExtInst %3 %1 Fma %10 %12 %12 +OpReturnValue %14 +OpFunctionEnd +%16 = OpFunction %5 None %17 +%15 = OpLabel +OpBranch %30 +%30 = OpLabel +%33 = OpCompositeExtract %5 %20 0 +%34 = OpCompositeExtract %5 %20 0 +%35 = OpIMul %5 %33 %34 +%36 = OpIAdd %5 %32 %35 +%37 = OpCompositeExtract %5 %20 1 +%38 = OpCompositeExtract %5 %20 1 +%39 = OpIMul %5 %37 %38 +%31 = OpIAdd %5 %36 %39 +%42 = OpCompositeExtract %21 %24 0 +%43 = OpCompositeExtract %21 %24 0 +%44 = OpIMul %21 %42 %43 +%45 = OpIAdd %21 %41 %44 +%46 = OpCompositeExtract %21 %24 1 +%47 = OpCompositeExtract %21 %24 1 +%48 = OpIMul %21 %46 %47 +%49 = OpIAdd %21 %45 %48 +%50 = OpCompositeExtract %21 %24 2 +%51 = OpCompositeExtract %21 %24 2 +%52 = OpIMul %21 %50 %51 +%40 = OpIAdd %21 %49 %52 +%54 = OpCompositeExtract %5 %27 0 +%55 = OpCompositeExtract %5 %29 0 +%56 = OpIMul %5 %54 %55 +%57 = OpIAdd %5 %32 %56 +%58 = OpCompositeExtract %5 %27 1 +%59 = OpCompositeExtract %5 %29 1 +%60 = OpIMul %5 %58 %59 +%61 = OpIAdd %5 %57 %60 +%62 = OpCompositeExtract %5 %27 2 +%63 = OpCompositeExtract %5 %29 2 +%64 = OpIMul %5 %62 %63 +%65 = OpIAdd %5 %61 %64 +%66 = OpCompositeExtract %5 %27 3 +%67 = OpCompositeExtract %5 %29 3 +%68 = OpIMul %5 %66 %67 +%53 = OpIAdd %5 %65 %68 +OpReturnValue %53 +OpFunctionEnd +%70 = OpFunction %2 None %71 +%69 = OpLabel +OpBranch %72 +%72 = OpLabel +%73 = OpFunctionCall %3 %7 +%74 = OpFunctionCall %5 %16 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/globals.spvasm b/naga/tests/out/spv/globals.spvasm new file mode 100644 index 0000000000..4aa6a10ad5 --- /dev/null +++ b/naga/tests/out/spv/globals.spvasm @@ -0,0 +1,252 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 174 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %93 "main" %116 +OpExecutionMode %93 LocalSize 1 1 1 +OpDecorate %5 ArrayStride 4 +OpMemberDecorate %9 0 Offset 0 +OpMemberDecorate %9 1 Offset 12 +OpDecorate %11 ArrayStride 8 +OpDecorate %13 ArrayStride 16 +OpDecorate %17 ArrayStride 32 +OpDecorate %19 ArrayStride 64 +OpDecorate %21 ArrayStride 32 +OpDecorate %22 ArrayStride 64 +OpDecorate %30 DescriptorSet 0 +OpDecorate %30 Binding 1 +OpDecorate %31 Block +OpMemberDecorate %31 0 Offset 0 +OpDecorate %33 NonWritable +OpDecorate %33 DescriptorSet 0 +OpDecorate %33 Binding 2 +OpDecorate %34 Block +OpMemberDecorate %34 0 Offset 0 +OpDecorate %36 DescriptorSet 0 +OpDecorate %36 Binding 3 +OpDecorate %37 Block +OpMemberDecorate %37 0 Offset 0 +OpDecorate %39 DescriptorSet 0 +OpDecorate %39 Binding 4 +OpDecorate %40 Block +OpMemberDecorate %40 0 Offset 0 +OpDecorate %42 DescriptorSet 0 +OpDecorate %42 Binding 5 +OpDecorate %43 Block +OpMemberDecorate %43 0 Offset 0 +OpMemberDecorate %43 0 ColMajor +OpMemberDecorate %43 0 MatrixStride 8 +OpDecorate %45 DescriptorSet 0 +OpDecorate %45 Binding 6 +OpDecorate %46 Block +OpMemberDecorate %46 0 Offset 0 +OpDecorate %48 DescriptorSet 0 +OpDecorate %48 Binding 7 +OpDecorate %49 Block +OpMemberDecorate %49 0 Offset 0 +OpDecorate %116 BuiltIn LocalInvocationId +%2 = OpTypeVoid +%3 = OpTypeBool +%4 = OpTypeFloat 32 +%7 = OpTypeInt 32 0 +%6 = OpConstant %7 10 +%5 = OpTypeArray %4 %6 +%8 = OpTypeVector %4 3 +%9 = OpTypeStruct %8 %4 +%10 = OpTypeVector %4 2 +%11 = OpTypeRuntimeArray %10 +%12 = OpTypeVector %4 4 +%14 = OpConstant %7 20 +%13 = OpTypeArray %12 %14 +%15 = OpTypeMatrix %10 3 +%16 = OpTypeMatrix %12 2 +%18 = OpConstant %7 2 +%17 = OpTypeArray %16 %18 +%19 = OpTypeArray %17 %18 +%20 = OpTypeMatrix %10 4 +%21 = OpTypeArray %20 %18 +%22 = OpTypeArray %21 %18 +%23 = OpTypeInt 32 1 +%24 = OpTypeMatrix %8 3 +%25 = OpConstantTrue %3 +%27 = OpTypePointer Workgroup %5 +%26 = OpVariable %27 Workgroup +%29 = OpTypePointer Workgroup %7 +%28 = OpVariable %29 Workgroup +%31 = OpTypeStruct %9 +%32 = OpTypePointer StorageBuffer %31 +%30 = OpVariable %32 StorageBuffer +%34 = OpTypeStruct %11 +%35 = OpTypePointer StorageBuffer %34 +%33 = OpVariable %35 StorageBuffer +%37 = OpTypeStruct %13 +%38 = OpTypePointer Uniform %37 +%36 = OpVariable %38 Uniform +%40 = OpTypeStruct %8 +%41 = OpTypePointer Uniform %40 +%39 = OpVariable %41 Uniform +%43 = OpTypeStruct %15 +%44 = OpTypePointer Uniform %43 +%42 = OpVariable %44 Uniform +%46 = OpTypeStruct %19 +%47 = OpTypePointer Uniform %46 +%45 = OpVariable %47 Uniform +%49 = OpTypeStruct %22 +%50 = OpTypePointer Uniform %49 +%48 = OpVariable %50 Uniform +%54 = OpTypeFunction %2 %8 +%58 = OpTypeFunction %2 +%59 = OpTypePointer StorageBuffer %9 +%60 = OpConstant %7 0 +%62 = OpConstant %4 1.0 +%63 = OpConstantComposite %8 %62 %62 %62 +%64 = OpConstant %23 1 +%65 = OpConstant %4 2.0 +%66 = OpConstant %4 3.0 +%67 = OpConstantNull %24 +%69 = OpTypePointer Function %23 +%71 = OpTypePointer StorageBuffer %8 +%73 = OpTypePointer StorageBuffer %4 +%95 = OpTypePointer StorageBuffer %11 +%97 = OpTypePointer Uniform %13 +%99 = OpTypePointer Uniform %8 +%101 = OpTypePointer Uniform %15 +%103 = OpTypePointer Uniform %19 +%105 = OpTypePointer Uniform %22 +%107 = OpConstant %4 4.0 +%109 = OpTypePointer Function %4 +%111 = OpTypePointer Function %3 +%113 = OpConstantNull %5 +%114 = OpConstantNull %7 +%115 = OpTypeVector %7 3 +%117 = OpTypePointer Input %115 +%116 = OpVariable %117 Input +%119 = OpConstantNull %115 +%120 = OpTypeVector %3 3 +%125 = OpConstant %7 264 +%128 = OpTypePointer Workgroup %4 +%129 = OpTypePointer Uniform %21 +%130 = OpTypePointer Uniform %20 +%133 = OpTypePointer Uniform %17 +%134 = OpTypePointer Uniform %16 +%135 = OpTypePointer Uniform %12 +%140 = OpConstant %7 7 +%146 = OpConstant %7 6 +%148 = OpTypePointer StorageBuffer %10 +%149 = OpConstant %7 1 +%152 = OpConstant %7 5 +%154 = OpTypePointer Uniform %12 +%155 = OpTypePointer Uniform %4 +%156 = OpConstant %7 3 +%159 = OpConstant %7 4 +%161 = OpTypePointer StorageBuffer %4 +%172 = OpConstant %23 2 +%173 = OpConstant %7 256 +%53 = OpFunction %2 None %54 +%52 = OpFunctionParameter %8 +%51 = OpLabel +OpBranch %55 +%55 = OpLabel +OpReturn +OpFunctionEnd +%57 = OpFunction %2 None %58 +%56 = OpLabel +%68 = OpVariable %69 Function %64 +%61 = OpAccessChain %59 %30 %60 +OpBranch %70 +%70 = OpLabel +%72 = OpAccessChain %71 %61 %60 +OpStore %72 %63 +%74 = OpAccessChain %73 %61 %60 %60 +OpStore %74 %62 +%75 = OpAccessChain %73 %61 %60 %60 +OpStore %75 %65 +%76 = OpLoad %23 %68 +%77 = OpAccessChain %73 %61 %60 %76 +OpStore %77 %66 +%78 = OpLoad %9 %61 +%79 = OpCompositeExtract %8 %78 0 +%80 = OpCompositeExtract %8 %78 0 +%81 = OpVectorShuffle %10 %80 %80 2 0 +%82 = OpCompositeExtract %8 %78 0 +%83 = OpFunctionCall %2 %53 %82 +%84 = OpCompositeExtract %8 %78 0 +%85 = OpVectorTimesMatrix %8 %84 %67 +%86 = OpCompositeExtract %8 %78 0 +%87 = OpMatrixTimesVector %8 %67 %86 +%88 = OpCompositeExtract %8 %78 0 +%89 = OpVectorTimesScalar %8 %88 %65 +%90 = OpCompositeExtract %8 %78 0 +%91 = OpVectorTimesScalar %8 %90 %65 +OpReturn +OpFunctionEnd +%93 = OpFunction %2 None %58 +%92 = OpLabel +%108 = OpVariable %109 Function %62 +%110 = OpVariable %111 Function %25 +%94 = OpAccessChain %59 %30 %60 +%96 = OpAccessChain %95 %33 %60 +%98 = OpAccessChain %97 %36 %60 +%100 = OpAccessChain %99 %39 %60 +%102 = OpAccessChain %101 %42 %60 +%104 = OpAccessChain %103 %45 %60 +%106 = OpAccessChain %105 %48 %60 +OpBranch %112 +%112 = OpLabel +%118 = OpLoad %115 %116 +%121 = OpIEqual %120 %118 %119 +%122 = OpAll %3 %121 +OpSelectionMerge %123 None +OpBranchConditional %122 %124 %123 +%124 = OpLabel +OpStore %26 %113 +OpStore %28 %114 +OpBranch %123 +%123 = OpLabel +OpControlBarrier %18 %18 %125 +OpBranch %126 +%126 = OpLabel +%127 = OpFunctionCall %2 %57 +%131 = OpAccessChain %130 %106 %60 %60 +%132 = OpLoad %20 %131 +%136 = OpAccessChain %135 %104 %60 %60 %60 +%137 = OpLoad %12 %136 +%138 = OpMatrixTimesVector %10 %132 %137 +%139 = OpCompositeExtract %4 %138 0 +%141 = OpAccessChain %128 %26 %140 +OpStore %141 %139 +%142 = OpLoad %15 %102 +%143 = OpLoad %8 %100 +%144 = OpMatrixTimesVector %10 %142 %143 +%145 = OpCompositeExtract %4 %144 0 +%147 = OpAccessChain %128 %26 %146 +OpStore %147 %145 +%150 = OpAccessChain %73 %96 %149 %149 +%151 = OpLoad %4 %150 +%153 = OpAccessChain %128 %26 %152 +OpStore %153 %151 +%157 = OpAccessChain %155 %98 %60 %156 +%158 = OpLoad %4 %157 +%160 = OpAccessChain %128 %26 %159 +OpStore %160 %158 +%162 = OpAccessChain %161 %94 %149 +%163 = OpLoad %4 %162 +%164 = OpAccessChain %128 %26 %156 +OpStore %164 %163 +%165 = OpAccessChain %73 %94 %60 %60 +%166 = OpLoad %4 %165 +%167 = OpAccessChain %128 %26 %18 +OpStore %167 %166 +%168 = OpAccessChain %161 %94 %149 +OpStore %168 %107 +%169 = OpArrayLength %7 %33 0 +%170 = OpConvertUToF %4 %169 +%171 = OpAccessChain %128 %26 %149 +OpStore %171 %170 +OpAtomicStore %28 %172 %173 %18 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/image.spvasm b/naga/tests/out/spv/image.spvasm new file mode 100644 index 0000000000..708cd65f28 --- /dev/null +++ b/naga/tests/out/spv/image.spvasm @@ -0,0 +1,692 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 518 +OpCapability Shader +OpCapability Image1D +OpCapability Sampled1D +OpCapability SampledCubeArray +OpCapability ImageQuery +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %78 "main" %75 +OpEntryPoint GLCompute %169 "depth_load" %167 +OpEntryPoint Vertex %189 "queries" %187 +OpEntryPoint Vertex %241 "levels_queries" %240 +OpEntryPoint Fragment %270 "texture_sample" %269 +OpEntryPoint Fragment %417 "texture_sample_comparison" %415 +OpEntryPoint Fragment %473 "gather" %472 +OpEntryPoint Fragment %507 "depth_no_comparison" %506 +OpExecutionMode %78 LocalSize 16 1 1 +OpExecutionMode %169 LocalSize 16 1 1 +OpExecutionMode %270 OriginUpperLeft +OpExecutionMode %417 OriginUpperLeft +OpExecutionMode %473 OriginUpperLeft +OpExecutionMode %507 OriginUpperLeft +OpName %31 "image_mipmapped_src" +OpName %33 "image_multisampled_src" +OpName %35 "image_depth_multisampled_src" +OpName %37 "image_storage_src" +OpName %39 "image_array_src" +OpName %41 "image_dup_src" +OpName %43 "image_1d_src" +OpName %45 "image_dst" +OpName %47 "image_1d" +OpName %49 "image_2d" +OpName %51 "image_2d_u32" +OpName %52 "image_2d_i32" +OpName %54 "image_2d_array" +OpName %56 "image_cube" +OpName %58 "image_cube_array" +OpName %60 "image_3d" +OpName %62 "image_aa" +OpName %64 "sampler_reg" +OpName %66 "sampler_cmp" +OpName %68 "image_2d_depth" +OpName %70 "image_2d_array_depth" +OpName %72 "image_cube_depth" +OpName %75 "local_id" +OpName %78 "main" +OpName %167 "local_id" +OpName %169 "depth_load" +OpName %189 "queries" +OpName %241 "levels_queries" +OpName %270 "texture_sample" +OpName %284 "a" +OpName %417 "texture_sample_comparison" +OpName %422 "a" +OpName %473 "gather" +OpName %507 "depth_no_comparison" +OpDecorate %31 DescriptorSet 0 +OpDecorate %31 Binding 0 +OpDecorate %33 DescriptorSet 0 +OpDecorate %33 Binding 3 +OpDecorate %35 DescriptorSet 0 +OpDecorate %35 Binding 4 +OpDecorate %37 NonWritable +OpDecorate %37 DescriptorSet 0 +OpDecorate %37 Binding 1 +OpDecorate %39 DescriptorSet 0 +OpDecorate %39 Binding 5 +OpDecorate %41 NonWritable +OpDecorate %41 DescriptorSet 0 +OpDecorate %41 Binding 6 +OpDecorate %43 DescriptorSet 0 +OpDecorate %43 Binding 7 +OpDecorate %45 NonReadable +OpDecorate %45 DescriptorSet 0 +OpDecorate %45 Binding 2 +OpDecorate %47 DescriptorSet 0 +OpDecorate %47 Binding 0 +OpDecorate %49 DescriptorSet 0 +OpDecorate %49 Binding 1 +OpDecorate %51 DescriptorSet 0 +OpDecorate %51 Binding 2 +OpDecorate %52 DescriptorSet 0 +OpDecorate %52 Binding 3 +OpDecorate %54 DescriptorSet 0 +OpDecorate %54 Binding 4 +OpDecorate %56 DescriptorSet 0 +OpDecorate %56 Binding 5 +OpDecorate %58 DescriptorSet 0 +OpDecorate %58 Binding 6 +OpDecorate %60 DescriptorSet 0 +OpDecorate %60 Binding 7 +OpDecorate %62 DescriptorSet 0 +OpDecorate %62 Binding 8 +OpDecorate %64 DescriptorSet 1 +OpDecorate %64 Binding 0 +OpDecorate %66 DescriptorSet 1 +OpDecorate %66 Binding 1 +OpDecorate %68 DescriptorSet 1 +OpDecorate %68 Binding 2 +OpDecorate %70 DescriptorSet 1 +OpDecorate %70 Binding 3 +OpDecorate %72 DescriptorSet 1 +OpDecorate %72 Binding 4 +OpDecorate %75 BuiltIn LocalInvocationId +OpDecorate %167 BuiltIn LocalInvocationId +OpDecorate %187 BuiltIn Position +OpDecorate %240 BuiltIn Position +OpDecorate %269 Location 0 +OpDecorate %415 Location 0 +OpDecorate %472 Location 0 +OpDecorate %506 Location 0 +%2 = OpTypeVoid +%4 = OpTypeInt 32 0 +%3 = OpTypeImage %4 2D 0 0 0 1 Unknown +%5 = OpTypeImage %4 2D 0 0 1 1 Unknown +%7 = OpTypeFloat 32 +%6 = OpTypeImage %7 2D 1 0 1 1 Unknown +%8 = OpTypeImage %4 2D 0 0 0 2 Rgba8ui +%9 = OpTypeImage %4 2D 0 1 0 1 Unknown +%10 = OpTypeImage %4 1D 0 0 0 2 R32ui +%11 = OpTypeImage %4 1D 0 0 0 1 Unknown +%12 = OpTypeVector %4 3 +%14 = OpTypeInt 32 1 +%13 = OpTypeVector %14 2 +%15 = OpTypeImage %7 1D 0 0 0 1 Unknown +%16 = OpTypeImage %7 2D 0 0 0 1 Unknown +%17 = OpTypeImage %14 2D 0 0 0 1 Unknown +%18 = OpTypeImage %7 2D 0 1 0 1 Unknown +%19 = OpTypeImage %7 Cube 0 0 0 1 Unknown +%20 = OpTypeImage %7 Cube 0 1 0 1 Unknown +%21 = OpTypeImage %7 3D 0 0 0 1 Unknown +%22 = OpTypeImage %7 2D 0 0 1 1 Unknown +%23 = OpTypeVector %7 4 +%24 = OpTypeSampler +%25 = OpTypeImage %7 2D 1 0 0 1 Unknown +%26 = OpTypeImage %7 2D 1 1 0 1 Unknown +%27 = OpTypeImage %7 Cube 1 0 0 1 Unknown +%28 = OpConstant %14 3 +%29 = OpConstant %14 1 +%30 = OpConstantComposite %13 %28 %29 +%32 = OpTypePointer UniformConstant %3 +%31 = OpVariable %32 UniformConstant +%34 = OpTypePointer UniformConstant %5 +%33 = OpVariable %34 UniformConstant +%36 = OpTypePointer UniformConstant %6 +%35 = OpVariable %36 UniformConstant +%38 = OpTypePointer UniformConstant %8 +%37 = OpVariable %38 UniformConstant +%40 = OpTypePointer UniformConstant %9 +%39 = OpVariable %40 UniformConstant +%42 = OpTypePointer UniformConstant %10 +%41 = OpVariable %42 UniformConstant +%44 = OpTypePointer UniformConstant %11 +%43 = OpVariable %44 UniformConstant +%46 = OpTypePointer UniformConstant %10 +%45 = OpVariable %46 UniformConstant +%48 = OpTypePointer UniformConstant %15 +%47 = OpVariable %48 UniformConstant +%50 = OpTypePointer UniformConstant %16 +%49 = OpVariable %50 UniformConstant +%51 = OpVariable %32 UniformConstant +%53 = OpTypePointer UniformConstant %17 +%52 = OpVariable %53 UniformConstant +%55 = OpTypePointer UniformConstant %18 +%54 = OpVariable %55 UniformConstant +%57 = OpTypePointer UniformConstant %19 +%56 = OpVariable %57 UniformConstant +%59 = OpTypePointer UniformConstant %20 +%58 = OpVariable %59 UniformConstant +%61 = OpTypePointer UniformConstant %21 +%60 = OpVariable %61 UniformConstant +%63 = OpTypePointer UniformConstant %22 +%62 = OpVariable %63 UniformConstant +%65 = OpTypePointer UniformConstant %24 +%64 = OpVariable %65 UniformConstant +%67 = OpTypePointer UniformConstant %24 +%66 = OpVariable %67 UniformConstant +%69 = OpTypePointer UniformConstant %25 +%68 = OpVariable %69 UniformConstant +%71 = OpTypePointer UniformConstant %26 +%70 = OpVariable %71 UniformConstant +%73 = OpTypePointer UniformConstant %27 +%72 = OpVariable %73 UniformConstant +%76 = OpTypePointer Input %12 +%75 = OpVariable %76 Input +%79 = OpTypeFunction %2 +%86 = OpConstant %14 10 +%87 = OpConstant %14 20 +%88 = OpConstantComposite %13 %86 %87 +%90 = OpTypeVector %4 2 +%98 = OpTypeVector %4 4 +%109 = OpTypeVector %14 3 +%167 = OpVariable %76 Input +%188 = OpTypePointer Output %23 +%187 = OpVariable %188 Output +%198 = OpConstant %4 0 +%240 = OpVariable %188 Output +%269 = OpVariable %188 Output +%276 = OpConstant %7 0.5 +%277 = OpTypeVector %7 2 +%278 = OpConstantComposite %277 %276 %276 +%279 = OpTypeVector %7 3 +%280 = OpConstantComposite %279 %276 %276 %276 +%281 = OpConstant %7 2.3 +%282 = OpConstant %7 2.0 +%283 = OpConstant %14 0 +%285 = OpTypePointer Function %23 +%286 = OpConstantNull %23 +%289 = OpTypeSampledImage %15 +%294 = OpTypeSampledImage %16 +%315 = OpTypeSampledImage %18 +%376 = OpTypeSampledImage %20 +%416 = OpTypePointer Output %7 +%415 = OpVariable %416 Output +%423 = OpTypePointer Function %7 +%424 = OpConstantNull %7 +%426 = OpTypeSampledImage %25 +%431 = OpTypeSampledImage %26 +%444 = OpTypeSampledImage %27 +%451 = OpConstant %7 0.0 +%472 = OpVariable %188 Output +%483 = OpConstant %4 1 +%486 = OpConstant %4 3 +%491 = OpTypeSampledImage %3 +%494 = OpTypeVector %14 4 +%495 = OpTypeSampledImage %17 +%506 = OpVariable %188 Output +%78 = OpFunction %2 None %79 +%74 = OpLabel +%77 = OpLoad %12 %75 +%80 = OpLoad %3 %31 +%81 = OpLoad %5 %33 +%82 = OpLoad %8 %37 +%83 = OpLoad %9 %39 +%84 = OpLoad %11 %43 +%85 = OpLoad %10 %45 +OpBranch %89 +%89 = OpLabel +%91 = OpImageQuerySize %90 %82 +%92 = OpVectorShuffle %90 %77 %77 0 1 +%93 = OpIMul %90 %91 %92 +%94 = OpBitcast %13 %93 +%95 = OpSRem %13 %94 %88 +%96 = OpCompositeExtract %4 %77 2 +%97 = OpBitcast %14 %96 +%99 = OpImageFetch %98 %80 %95 Lod %97 +%100 = OpCompositeExtract %4 %77 2 +%101 = OpBitcast %14 %100 +%102 = OpImageFetch %98 %81 %95 Sample %101 +%103 = OpImageRead %98 %82 %95 +%104 = OpCompositeExtract %4 %77 2 +%105 = OpCompositeExtract %4 %77 2 +%106 = OpBitcast %14 %105 +%107 = OpIAdd %14 %106 %29 +%108 = OpBitcast %14 %104 +%110 = OpCompositeConstruct %109 %95 %108 +%111 = OpImageFetch %98 %83 %110 Lod %107 +%112 = OpCompositeExtract %4 %77 2 +%113 = OpBitcast %14 %112 +%114 = OpCompositeExtract %4 %77 2 +%115 = OpBitcast %14 %114 +%116 = OpIAdd %14 %115 %29 +%117 = OpCompositeConstruct %109 %95 %113 +%118 = OpImageFetch %98 %83 %117 Lod %116 +%119 = OpCompositeExtract %4 %77 0 +%120 = OpBitcast %14 %119 +%121 = OpCompositeExtract %4 %77 2 +%122 = OpBitcast %14 %121 +%123 = OpImageFetch %98 %84 %120 Lod %122 +%124 = OpBitcast %90 %95 +%125 = OpCompositeExtract %4 %77 2 +%126 = OpBitcast %14 %125 +%127 = OpImageFetch %98 %80 %124 Lod %126 +%128 = OpBitcast %90 %95 +%129 = OpCompositeExtract %4 %77 2 +%130 = OpBitcast %14 %129 +%131 = OpImageFetch %98 %81 %128 Sample %130 +%132 = OpBitcast %90 %95 +%133 = OpImageRead %98 %82 %132 +%134 = OpBitcast %90 %95 +%135 = OpCompositeExtract %4 %77 2 +%136 = OpCompositeExtract %4 %77 2 +%137 = OpBitcast %14 %136 +%138 = OpIAdd %14 %137 %29 +%139 = OpCompositeConstruct %12 %134 %135 +%140 = OpImageFetch %98 %83 %139 Lod %138 +%141 = OpBitcast %90 %95 +%142 = OpCompositeExtract %4 %77 2 +%143 = OpBitcast %14 %142 +%144 = OpCompositeExtract %4 %77 2 +%145 = OpBitcast %14 %144 +%146 = OpIAdd %14 %145 %29 +%147 = OpBitcast %4 %143 +%148 = OpCompositeConstruct %12 %141 %147 +%149 = OpImageFetch %98 %83 %148 Lod %146 +%150 = OpCompositeExtract %4 %77 0 +%152 = OpCompositeExtract %4 %77 2 +%153 = OpBitcast %14 %152 +%154 = OpImageFetch %98 %84 %150 Lod %153 +%155 = OpCompositeExtract %14 %95 0 +%156 = OpIAdd %98 %99 %102 +%157 = OpIAdd %98 %156 %103 +%158 = OpIAdd %98 %157 %111 +%159 = OpIAdd %98 %158 %118 +OpImageWrite %85 %155 %159 +%160 = OpCompositeExtract %14 %95 0 +%161 = OpBitcast %4 %160 +%162 = OpIAdd %98 %127 %131 +%163 = OpIAdd %98 %162 %133 +%164 = OpIAdd %98 %163 %140 +%165 = OpIAdd %98 %164 %149 +OpImageWrite %85 %161 %165 +OpReturn +OpFunctionEnd +%169 = OpFunction %2 None %79 +%166 = OpLabel +%168 = OpLoad %12 %167 +%170 = OpLoad %6 %35 +%171 = OpLoad %8 %37 +%172 = OpLoad %10 %45 +OpBranch %173 +%173 = OpLabel +%174 = OpImageQuerySize %90 %171 +%175 = OpVectorShuffle %90 %168 %168 0 1 +%176 = OpIMul %90 %174 %175 +%177 = OpBitcast %13 %176 +%178 = OpSRem %13 %177 %88 +%179 = OpCompositeExtract %4 %168 2 +%180 = OpBitcast %14 %179 +%181 = OpImageFetch %23 %170 %178 Sample %180 +%182 = OpCompositeExtract %7 %181 0 +%183 = OpCompositeExtract %14 %178 0 +%184 = OpConvertFToU %4 %182 +%185 = OpCompositeConstruct %98 %184 %184 %184 %184 +OpImageWrite %172 %183 %185 +OpReturn +OpFunctionEnd +%189 = OpFunction %2 None %79 +%186 = OpLabel +%190 = OpLoad %15 %47 +%191 = OpLoad %16 %49 +%192 = OpLoad %18 %54 +%193 = OpLoad %19 %56 +%194 = OpLoad %20 %58 +%195 = OpLoad %21 %60 +%196 = OpLoad %22 %62 +OpBranch %197 +%197 = OpLabel +%199 = OpImageQuerySizeLod %4 %190 %198 +%200 = OpBitcast %14 %199 +%201 = OpImageQuerySizeLod %4 %190 %200 +%202 = OpImageQuerySizeLod %90 %191 %198 +%203 = OpImageQuerySizeLod %90 %191 %29 +%204 = OpImageQuerySizeLod %12 %192 %198 +%205 = OpVectorShuffle %90 %204 %204 0 1 +%206 = OpImageQuerySizeLod %12 %192 %29 +%207 = OpVectorShuffle %90 %206 %206 0 1 +%208 = OpImageQuerySizeLod %90 %193 %198 +%209 = OpImageQuerySizeLod %90 %193 %29 +%210 = OpImageQuerySizeLod %12 %194 %198 +%211 = OpVectorShuffle %90 %210 %210 0 0 +%212 = OpImageQuerySizeLod %12 %194 %29 +%213 = OpVectorShuffle %90 %212 %212 0 0 +%214 = OpImageQuerySizeLod %12 %195 %198 +%215 = OpImageQuerySizeLod %12 %195 %29 +%216 = OpImageQuerySize %90 %196 +%217 = OpCompositeExtract %4 %202 1 +%218 = OpIAdd %4 %199 %217 +%219 = OpCompositeExtract %4 %203 1 +%220 = OpIAdd %4 %218 %219 +%221 = OpCompositeExtract %4 %205 1 +%222 = OpIAdd %4 %220 %221 +%223 = OpCompositeExtract %4 %207 1 +%224 = OpIAdd %4 %222 %223 +%225 = OpCompositeExtract %4 %208 1 +%226 = OpIAdd %4 %224 %225 +%227 = OpCompositeExtract %4 %209 1 +%228 = OpIAdd %4 %226 %227 +%229 = OpCompositeExtract %4 %211 1 +%230 = OpIAdd %4 %228 %229 +%231 = OpCompositeExtract %4 %213 1 +%232 = OpIAdd %4 %230 %231 +%233 = OpCompositeExtract %4 %214 2 +%234 = OpIAdd %4 %232 %233 +%235 = OpCompositeExtract %4 %215 2 +%236 = OpIAdd %4 %234 %235 +%237 = OpConvertUToF %7 %236 +%238 = OpCompositeConstruct %23 %237 %237 %237 %237 +OpStore %187 %238 +OpReturn +OpFunctionEnd +%241 = OpFunction %2 None %79 +%239 = OpLabel +%242 = OpLoad %16 %49 +%243 = OpLoad %18 %54 +%244 = OpLoad %19 %56 +%245 = OpLoad %20 %58 +%246 = OpLoad %21 %60 +%247 = OpLoad %22 %62 +OpBranch %248 +%248 = OpLabel +%249 = OpImageQueryLevels %4 %242 +%250 = OpImageQueryLevels %4 %243 +%251 = OpImageQuerySizeLod %12 %243 %198 +%252 = OpCompositeExtract %4 %251 2 +%253 = OpImageQueryLevels %4 %244 +%254 = OpImageQueryLevels %4 %245 +%255 = OpImageQuerySizeLod %12 %245 %198 +%256 = OpCompositeExtract %4 %255 2 +%257 = OpImageQueryLevels %4 %246 +%258 = OpImageQuerySamples %4 %247 +%259 = OpIAdd %4 %252 %256 +%260 = OpIAdd %4 %259 %258 +%261 = OpIAdd %4 %260 %249 +%262 = OpIAdd %4 %261 %250 +%263 = OpIAdd %4 %262 %257 +%264 = OpIAdd %4 %263 %253 +%265 = OpIAdd %4 %264 %254 +%266 = OpConvertUToF %7 %265 +%267 = OpCompositeConstruct %23 %266 %266 %266 %266 +OpStore %240 %267 +OpReturn +OpFunctionEnd +%270 = OpFunction %2 None %79 +%268 = OpLabel +%284 = OpVariable %285 Function %286 +%271 = OpLoad %15 %47 +%272 = OpLoad %16 %49 +%273 = OpLoad %18 %54 +%274 = OpLoad %20 %58 +%275 = OpLoad %24 %64 +OpBranch %287 +%287 = OpLabel +%288 = OpCompositeExtract %7 %278 0 +%290 = OpSampledImage %289 %271 %275 +%291 = OpImageSampleImplicitLod %23 %290 %288 +%292 = OpLoad %23 %284 +%293 = OpFAdd %23 %292 %291 +OpStore %284 %293 +%295 = OpSampledImage %294 %272 %275 +%296 = OpImageSampleImplicitLod %23 %295 %278 +%297 = OpLoad %23 %284 +%298 = OpFAdd %23 %297 %296 +OpStore %284 %298 +%299 = OpSampledImage %294 %272 %275 +%300 = OpImageSampleImplicitLod %23 %299 %278 ConstOffset %30 +%301 = OpLoad %23 %284 +%302 = OpFAdd %23 %301 %300 +OpStore %284 %302 +%303 = OpSampledImage %294 %272 %275 +%304 = OpImageSampleExplicitLod %23 %303 %278 Lod %281 +%305 = OpLoad %23 %284 +%306 = OpFAdd %23 %305 %304 +OpStore %284 %306 +%307 = OpSampledImage %294 %272 %275 +%308 = OpImageSampleExplicitLod %23 %307 %278 Lod|ConstOffset %281 %30 +%309 = OpLoad %23 %284 +%310 = OpFAdd %23 %309 %308 +OpStore %284 %310 +%311 = OpSampledImage %294 %272 %275 +%312 = OpImageSampleImplicitLod %23 %311 %278 Bias|ConstOffset %282 %30 +%313 = OpLoad %23 %284 +%314 = OpFAdd %23 %313 %312 +OpStore %284 %314 +%316 = OpConvertUToF %7 %198 +%317 = OpCompositeConstruct %279 %278 %316 +%318 = OpSampledImage %315 %273 %275 +%319 = OpImageSampleImplicitLod %23 %318 %317 +%320 = OpLoad %23 %284 +%321 = OpFAdd %23 %320 %319 +OpStore %284 %321 +%322 = OpConvertUToF %7 %198 +%323 = OpCompositeConstruct %279 %278 %322 +%324 = OpSampledImage %315 %273 %275 +%325 = OpImageSampleImplicitLod %23 %324 %323 ConstOffset %30 +%326 = OpLoad %23 %284 +%327 = OpFAdd %23 %326 %325 +OpStore %284 %327 +%328 = OpConvertUToF %7 %198 +%329 = OpCompositeConstruct %279 %278 %328 +%330 = OpSampledImage %315 %273 %275 +%331 = OpImageSampleExplicitLod %23 %330 %329 Lod %281 +%332 = OpLoad %23 %284 +%333 = OpFAdd %23 %332 %331 +OpStore %284 %333 +%334 = OpConvertUToF %7 %198 +%335 = OpCompositeConstruct %279 %278 %334 +%336 = OpSampledImage %315 %273 %275 +%337 = OpImageSampleExplicitLod %23 %336 %335 Lod|ConstOffset %281 %30 +%338 = OpLoad %23 %284 +%339 = OpFAdd %23 %338 %337 +OpStore %284 %339 +%340 = OpConvertUToF %7 %198 +%341 = OpCompositeConstruct %279 %278 %340 +%342 = OpSampledImage %315 %273 %275 +%343 = OpImageSampleImplicitLod %23 %342 %341 Bias|ConstOffset %282 %30 +%344 = OpLoad %23 %284 +%345 = OpFAdd %23 %344 %343 +OpStore %284 %345 +%346 = OpConvertSToF %7 %283 +%347 = OpCompositeConstruct %279 %278 %346 +%348 = OpSampledImage %315 %273 %275 +%349 = OpImageSampleImplicitLod %23 %348 %347 +%350 = OpLoad %23 %284 +%351 = OpFAdd %23 %350 %349 +OpStore %284 %351 +%352 = OpConvertSToF %7 %283 +%353 = OpCompositeConstruct %279 %278 %352 +%354 = OpSampledImage %315 %273 %275 +%355 = OpImageSampleImplicitLod %23 %354 %353 ConstOffset %30 +%356 = OpLoad %23 %284 +%357 = OpFAdd %23 %356 %355 +OpStore %284 %357 +%358 = OpConvertSToF %7 %283 +%359 = OpCompositeConstruct %279 %278 %358 +%360 = OpSampledImage %315 %273 %275 +%361 = OpImageSampleExplicitLod %23 %360 %359 Lod %281 +%362 = OpLoad %23 %284 +%363 = OpFAdd %23 %362 %361 +OpStore %284 %363 +%364 = OpConvertSToF %7 %283 +%365 = OpCompositeConstruct %279 %278 %364 +%366 = OpSampledImage %315 %273 %275 +%367 = OpImageSampleExplicitLod %23 %366 %365 Lod|ConstOffset %281 %30 +%368 = OpLoad %23 %284 +%369 = OpFAdd %23 %368 %367 +OpStore %284 %369 +%370 = OpConvertSToF %7 %283 +%371 = OpCompositeConstruct %279 %278 %370 +%372 = OpSampledImage %315 %273 %275 +%373 = OpImageSampleImplicitLod %23 %372 %371 Bias|ConstOffset %282 %30 +%374 = OpLoad %23 %284 +%375 = OpFAdd %23 %374 %373 +OpStore %284 %375 +%377 = OpConvertUToF %7 %198 +%378 = OpCompositeConstruct %23 %280 %377 +%379 = OpSampledImage %376 %274 %275 +%380 = OpImageSampleImplicitLod %23 %379 %378 +%381 = OpLoad %23 %284 +%382 = OpFAdd %23 %381 %380 +OpStore %284 %382 +%383 = OpConvertUToF %7 %198 +%384 = OpCompositeConstruct %23 %280 %383 +%385 = OpSampledImage %376 %274 %275 +%386 = OpImageSampleExplicitLod %23 %385 %384 Lod %281 +%387 = OpLoad %23 %284 +%388 = OpFAdd %23 %387 %386 +OpStore %284 %388 +%389 = OpConvertUToF %7 %198 +%390 = OpCompositeConstruct %23 %280 %389 +%391 = OpSampledImage %376 %274 %275 +%392 = OpImageSampleImplicitLod %23 %391 %390 Bias %282 +%393 = OpLoad %23 %284 +%394 = OpFAdd %23 %393 %392 +OpStore %284 %394 +%395 = OpConvertSToF %7 %283 +%396 = OpCompositeConstruct %23 %280 %395 +%397 = OpSampledImage %376 %274 %275 +%398 = OpImageSampleImplicitLod %23 %397 %396 +%399 = OpLoad %23 %284 +%400 = OpFAdd %23 %399 %398 +OpStore %284 %400 +%401 = OpConvertSToF %7 %283 +%402 = OpCompositeConstruct %23 %280 %401 +%403 = OpSampledImage %376 %274 %275 +%404 = OpImageSampleExplicitLod %23 %403 %402 Lod %281 +%405 = OpLoad %23 %284 +%406 = OpFAdd %23 %405 %404 +OpStore %284 %406 +%407 = OpConvertSToF %7 %283 +%408 = OpCompositeConstruct %23 %280 %407 +%409 = OpSampledImage %376 %274 %275 +%410 = OpImageSampleImplicitLod %23 %409 %408 Bias %282 +%411 = OpLoad %23 %284 +%412 = OpFAdd %23 %411 %410 +OpStore %284 %412 +%413 = OpLoad %23 %284 +OpStore %269 %413 +OpReturn +OpFunctionEnd +%417 = OpFunction %2 None %79 +%414 = OpLabel +%422 = OpVariable %423 Function %424 +%418 = OpLoad %24 %66 +%419 = OpLoad %25 %68 +%420 = OpLoad %26 %70 +%421 = OpLoad %27 %72 +OpBranch %425 +%425 = OpLabel +%427 = OpSampledImage %426 %419 %418 +%428 = OpImageSampleDrefImplicitLod %7 %427 %278 %276 +%429 = OpLoad %7 %422 +%430 = OpFAdd %7 %429 %428 +OpStore %422 %430 +%432 = OpConvertUToF %7 %198 +%433 = OpCompositeConstruct %279 %278 %432 +%434 = OpSampledImage %431 %420 %418 +%435 = OpImageSampleDrefImplicitLod %7 %434 %433 %276 +%436 = OpLoad %7 %422 +%437 = OpFAdd %7 %436 %435 +OpStore %422 %437 +%438 = OpConvertSToF %7 %283 +%439 = OpCompositeConstruct %279 %278 %438 +%440 = OpSampledImage %431 %420 %418 +%441 = OpImageSampleDrefImplicitLod %7 %440 %439 %276 +%442 = OpLoad %7 %422 +%443 = OpFAdd %7 %442 %441 +OpStore %422 %443 +%445 = OpSampledImage %444 %421 %418 +%446 = OpImageSampleDrefImplicitLod %7 %445 %280 %276 +%447 = OpLoad %7 %422 +%448 = OpFAdd %7 %447 %446 +OpStore %422 %448 +%449 = OpSampledImage %426 %419 %418 +%450 = OpImageSampleDrefExplicitLod %7 %449 %278 %276 Lod %451 +%452 = OpLoad %7 %422 +%453 = OpFAdd %7 %452 %450 +OpStore %422 %453 +%454 = OpConvertUToF %7 %198 +%455 = OpCompositeConstruct %279 %278 %454 +%456 = OpSampledImage %431 %420 %418 +%457 = OpImageSampleDrefExplicitLod %7 %456 %455 %276 Lod %451 +%458 = OpLoad %7 %422 +%459 = OpFAdd %7 %458 %457 +OpStore %422 %459 +%460 = OpConvertSToF %7 %283 +%461 = OpCompositeConstruct %279 %278 %460 +%462 = OpSampledImage %431 %420 %418 +%463 = OpImageSampleDrefExplicitLod %7 %462 %461 %276 Lod %451 +%464 = OpLoad %7 %422 +%465 = OpFAdd %7 %464 %463 +OpStore %422 %465 +%466 = OpSampledImage %444 %421 %418 +%467 = OpImageSampleDrefExplicitLod %7 %466 %280 %276 Lod %451 +%468 = OpLoad %7 %422 +%469 = OpFAdd %7 %468 %467 +OpStore %422 %469 +%470 = OpLoad %7 %422 +OpStore %415 %470 +OpReturn +OpFunctionEnd +%473 = OpFunction %2 None %79 +%471 = OpLabel +%474 = OpLoad %16 %49 +%475 = OpLoad %3 %51 +%476 = OpLoad %17 %52 +%477 = OpLoad %24 %64 +%478 = OpLoad %24 %66 +%479 = OpLoad %25 %68 +OpBranch %480 +%480 = OpLabel +%481 = OpSampledImage %294 %474 %477 +%482 = OpImageGather %23 %481 %278 %483 +%484 = OpSampledImage %294 %474 %477 +%485 = OpImageGather %23 %484 %278 %486 ConstOffset %30 +%487 = OpSampledImage %426 %479 %478 +%488 = OpImageDrefGather %23 %487 %278 %276 +%489 = OpSampledImage %426 %479 %478 +%490 = OpImageDrefGather %23 %489 %278 %276 ConstOffset %30 +%492 = OpSampledImage %491 %475 %477 +%493 = OpImageGather %98 %492 %278 %198 +%496 = OpSampledImage %495 %476 %477 +%497 = OpImageGather %494 %496 %278 %198 +%498 = OpConvertUToF %23 %493 +%499 = OpConvertSToF %23 %497 +%500 = OpFAdd %23 %498 %499 +%501 = OpFAdd %23 %482 %485 +%502 = OpFAdd %23 %501 %488 +%503 = OpFAdd %23 %502 %490 +%504 = OpFAdd %23 %503 %500 +OpStore %472 %504 +OpReturn +OpFunctionEnd +%507 = OpFunction %2 None %79 +%505 = OpLabel +%508 = OpLoad %24 %64 +%509 = OpLoad %25 %68 +OpBranch %510 +%510 = OpLabel +%511 = OpSampledImage %426 %509 %508 +%512 = OpImageSampleImplicitLod %23 %511 %278 +%513 = OpCompositeExtract %7 %512 0 +%514 = OpSampledImage %426 %509 %508 +%515 = OpImageGather %23 %514 %278 %198 +%516 = OpCompositeConstruct %23 %513 %513 %513 %513 +%517 = OpFAdd %23 %516 %515 +OpStore %506 %517 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/interface.compute.spvasm b/naga/tests/out/spv/interface.compute.spvasm new file mode 100644 index 0000000000..73f6ecb2c2 --- /dev/null +++ b/naga/tests/out/spv/interface.compute.spvasm @@ -0,0 +1,83 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 53 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %29 "compute" %17 %20 %22 %25 %27 +OpExecutionMode %29 LocalSize 1 1 1 +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %5 1 Offset 16 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 4 +OpMemberDecorate %7 2 Offset 8 +OpDecorate %9 ArrayStride 4 +OpMemberDecorate %12 0 Offset 0 +OpMemberDecorate %13 0 Offset 0 +OpDecorate %17 BuiltIn GlobalInvocationId +OpDecorate %20 BuiltIn LocalInvocationId +OpDecorate %22 BuiltIn LocalInvocationIndex +OpDecorate %25 BuiltIn WorkgroupId +OpDecorate %27 BuiltIn NumWorkgroups +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeStruct %3 %4 +%6 = OpTypeInt 32 0 +%7 = OpTypeStruct %4 %6 %4 +%8 = OpTypeBool +%10 = OpConstant %6 1 +%9 = OpTypeArray %6 %10 +%11 = OpTypeVector %6 3 +%12 = OpTypeStruct %6 +%13 = OpTypeStruct %6 +%15 = OpTypePointer Workgroup %9 +%14 = OpVariable %15 Workgroup +%18 = OpTypePointer Input %11 +%17 = OpVariable %18 Input +%20 = OpVariable %18 Input +%23 = OpTypePointer Input %6 +%22 = OpVariable %23 Input +%25 = OpVariable %18 Input +%27 = OpVariable %18 Input +%30 = OpTypeFunction %2 +%32 = OpConstantNull %9 +%33 = OpConstantNull %11 +%34 = OpTypeVector %8 3 +%39 = OpConstant %6 2 +%40 = OpConstant %6 264 +%42 = OpTypePointer Workgroup %6 +%51 = OpConstant %6 0 +%29 = OpFunction %2 None %30 +%16 = OpLabel +%19 = OpLoad %11 %17 +%21 = OpLoad %11 %20 +%24 = OpLoad %6 %22 +%26 = OpLoad %11 %25 +%28 = OpLoad %11 %27 +OpBranch %31 +%31 = OpLabel +%35 = OpIEqual %34 %21 %33 +%36 = OpAll %8 %35 +OpSelectionMerge %37 None +OpBranchConditional %36 %38 %37 +%38 = OpLabel +OpStore %14 %32 +OpBranch %37 +%37 = OpLabel +OpControlBarrier %39 %39 %40 +OpBranch %41 +%41 = OpLabel +%43 = OpCompositeExtract %6 %19 0 +%44 = OpCompositeExtract %6 %21 0 +%45 = OpIAdd %6 %43 %44 +%46 = OpIAdd %6 %45 %24 +%47 = OpCompositeExtract %6 %26 0 +%48 = OpIAdd %6 %46 %47 +%49 = OpCompositeExtract %6 %28 0 +%50 = OpIAdd %6 %48 %49 +%52 = OpAccessChain %42 %14 %51 +OpStore %52 %50 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/interface.fragment.spvasm b/naga/tests/out/spv/interface.fragment.spvasm new file mode 100644 index 0000000000..bb42c678ec --- /dev/null +++ b/naga/tests/out/spv/interface.fragment.spvasm @@ -0,0 +1,86 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 50 +OpCapability Shader +OpCapability SampleRateShading +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %35 "fragment" %16 %19 %22 %25 %28 %30 %32 %34 +OpExecutionMode %35 OriginUpperLeft +OpExecutionMode %35 DepthReplacing +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %5 1 Offset 16 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 4 +OpMemberDecorate %7 2 Offset 8 +OpDecorate %9 ArrayStride 4 +OpMemberDecorate %12 0 Offset 0 +OpMemberDecorate %13 0 Offset 0 +OpDecorate %16 Invariant +OpDecorate %16 BuiltIn FragCoord +OpDecorate %19 Location 1 +OpDecorate %22 BuiltIn FrontFacing +OpDecorate %22 Flat +OpDecorate %25 BuiltIn SampleId +OpDecorate %25 Flat +OpDecorate %28 BuiltIn SampleMask +OpDecorate %28 Flat +OpDecorate %30 BuiltIn FragDepth +OpDecorate %32 BuiltIn SampleMask +OpDecorate %34 Location 0 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeStruct %3 %4 +%6 = OpTypeInt 32 0 +%7 = OpTypeStruct %4 %6 %4 +%8 = OpTypeBool +%10 = OpConstant %6 1 +%9 = OpTypeArray %6 %10 +%11 = OpTypeVector %6 3 +%12 = OpTypeStruct %6 +%13 = OpTypeStruct %6 +%17 = OpTypePointer Input %3 +%16 = OpVariable %17 Input +%20 = OpTypePointer Input %4 +%19 = OpVariable %20 Input +%23 = OpTypePointer Input %8 +%22 = OpVariable %23 Input +%26 = OpTypePointer Input %6 +%25 = OpVariable %26 Input +%28 = OpVariable %26 Input +%31 = OpTypePointer Output %4 +%30 = OpVariable %31 Output +%33 = OpTypePointer Output %6 +%32 = OpVariable %33 Output +%34 = OpVariable %31 Output +%36 = OpTypeFunction %2 +%37 = OpConstant %4 0.0 +%38 = OpConstant %4 1.0 +%35 = OpFunction %2 None %36 +%14 = OpLabel +%18 = OpLoad %3 %16 +%21 = OpLoad %4 %19 +%15 = OpCompositeConstruct %5 %18 %21 +%24 = OpLoad %8 %22 +%27 = OpLoad %6 %25 +%29 = OpLoad %6 %28 +OpBranch %39 +%39 = OpLabel +%40 = OpShiftLeftLogical %6 %10 %27 +%41 = OpBitwiseAnd %6 %29 %40 +%42 = OpSelect %4 %24 %38 %37 +%43 = OpCompositeExtract %4 %15 1 +%44 = OpCompositeConstruct %7 %43 %41 %42 +%45 = OpCompositeExtract %4 %44 0 +OpStore %30 %45 +%46 = OpLoad %4 %30 +%47 = OpExtInst %4 %1 FClamp %46 %37 %38 +OpStore %30 %47 +%48 = OpCompositeExtract %6 %44 1 +OpStore %32 %48 +%49 = OpCompositeExtract %4 %44 2 +OpStore %34 %49 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/interface.vertex.spvasm b/naga/tests/out/spv/interface.vertex.spvasm new file mode 100644 index 0000000000..eaa947f5b3 --- /dev/null +++ b/naga/tests/out/spv/interface.vertex.spvasm @@ -0,0 +1,66 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 39 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %29 "vertex" %15 %18 %20 %22 %24 %26 +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %5 1 Offset 16 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 4 +OpMemberDecorate %7 2 Offset 8 +OpDecorate %9 ArrayStride 4 +OpMemberDecorate %12 0 Offset 0 +OpMemberDecorate %13 0 Offset 0 +OpDecorate %15 BuiltIn VertexIndex +OpDecorate %18 BuiltIn InstanceIndex +OpDecorate %20 Location 10 +OpDecorate %22 Invariant +OpDecorate %22 BuiltIn Position +OpDecorate %24 Location 1 +OpDecorate %26 BuiltIn PointSize +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeStruct %3 %4 +%6 = OpTypeInt 32 0 +%7 = OpTypeStruct %4 %6 %4 +%8 = OpTypeBool +%10 = OpConstant %6 1 +%9 = OpTypeArray %6 %10 +%11 = OpTypeVector %6 3 +%12 = OpTypeStruct %6 +%13 = OpTypeStruct %6 +%16 = OpTypePointer Input %6 +%15 = OpVariable %16 Input +%18 = OpVariable %16 Input +%20 = OpVariable %16 Input +%23 = OpTypePointer Output %3 +%22 = OpVariable %23 Output +%25 = OpTypePointer Output %4 +%24 = OpVariable %25 Output +%27 = OpTypePointer Output %4 +%26 = OpVariable %27 Output +%28 = OpConstant %4 1.0 +%30 = OpTypeFunction %2 +%31 = OpConstantComposite %3 %28 %28 %28 %28 +%29 = OpFunction %2 None %30 +%14 = OpLabel +%17 = OpLoad %6 %15 +%19 = OpLoad %6 %18 +%21 = OpLoad %6 %20 +OpStore %26 %28 +OpBranch %32 +%32 = OpLabel +%33 = OpIAdd %6 %17 %19 +%34 = OpIAdd %6 %33 %21 +%35 = OpConvertUToF %4 %34 +%36 = OpCompositeConstruct %5 %31 %35 +%37 = OpCompositeExtract %3 %36 0 +OpStore %22 %37 +%38 = OpCompositeExtract %4 %36 1 +OpStore %24 %38 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/interface.vertex_two_structs.spvasm b/naga/tests/out/spv/interface.vertex_two_structs.spvasm new file mode 100644 index 0000000000..bcc4aab4e5 --- /dev/null +++ b/naga/tests/out/spv/interface.vertex_two_structs.spvasm @@ -0,0 +1,65 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 41 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %27 "vertex_two_structs" %16 %20 %22 %24 +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %5 1 Offset 16 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 4 +OpMemberDecorate %7 2 Offset 8 +OpDecorate %9 ArrayStride 4 +OpMemberDecorate %12 0 Offset 0 +OpMemberDecorate %13 0 Offset 0 +OpDecorate %16 BuiltIn VertexIndex +OpDecorate %20 BuiltIn InstanceIndex +OpDecorate %22 Invariant +OpDecorate %22 BuiltIn Position +OpDecorate %24 BuiltIn PointSize +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeStruct %3 %4 +%6 = OpTypeInt 32 0 +%7 = OpTypeStruct %4 %6 %4 +%8 = OpTypeBool +%10 = OpConstant %6 1 +%9 = OpTypeArray %6 %10 +%11 = OpTypeVector %6 3 +%12 = OpTypeStruct %6 +%13 = OpTypeStruct %6 +%17 = OpTypePointer Input %6 +%16 = OpVariable %17 Input +%20 = OpVariable %17 Input +%23 = OpTypePointer Output %3 +%22 = OpVariable %23 Output +%25 = OpTypePointer Output %4 +%24 = OpVariable %25 Output +%26 = OpConstant %4 1.0 +%28 = OpTypeFunction %2 +%29 = OpConstant %6 2 +%30 = OpConstant %4 0.0 +%32 = OpTypePointer Function %6 +%27 = OpFunction %2 None %28 +%14 = OpLabel +%31 = OpVariable %32 Function %29 +%18 = OpLoad %6 %16 +%15 = OpCompositeConstruct %12 %18 +%21 = OpLoad %6 %20 +%19 = OpCompositeConstruct %13 %21 +OpStore %24 %26 +OpBranch %33 +%33 = OpLabel +%34 = OpCompositeExtract %6 %15 0 +%35 = OpConvertUToF %4 %34 +%36 = OpCompositeExtract %6 %19 0 +%37 = OpConvertUToF %4 %36 +%38 = OpLoad %6 %31 +%39 = OpConvertUToF %4 %38 +%40 = OpCompositeConstruct %3 %35 %37 %39 %30 +OpStore %22 %40 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/interpolate.spvasm b/naga/tests/out/spv/interpolate.spvasm new file mode 100644 index 0000000000..d2a67a9fd2 --- /dev/null +++ b/naga/tests/out/spv/interpolate.spvasm @@ -0,0 +1,213 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 111 +OpCapability Shader +OpCapability SampleRateShading +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %26 "vert_main" %10 %12 %14 %16 %18 %20 %21 %22 %23 +OpEntryPoint Fragment %109 "frag_main" %88 %91 %94 %97 %100 %103 %105 %107 +OpExecutionMode %109 OriginUpperLeft +OpMemberName %8 0 "position" +OpMemberName %8 1 "_flat" +OpMemberName %8 2 "_linear" +OpMemberName %8 3 "linear_centroid" +OpMemberName %8 4 "linear_sample" +OpMemberName %8 5 "perspective" +OpMemberName %8 6 "perspective_centroid" +OpMemberName %8 7 "perspective_sample" +OpName %8 "FragmentInput" +OpName %10 "position" +OpName %12 "_flat" +OpName %14 "_linear" +OpName %16 "linear_centroid" +OpName %18 "linear_sample" +OpName %20 "perspective" +OpName %21 "perspective_centroid" +OpName %22 "perspective_sample" +OpName %26 "vert_main" +OpName %49 "out" +OpName %88 "position" +OpName %91 "_flat" +OpName %94 "_linear" +OpName %97 "linear_centroid" +OpName %100 "linear_sample" +OpName %103 "perspective" +OpName %105 "perspective_centroid" +OpName %107 "perspective_sample" +OpName %109 "frag_main" +OpMemberDecorate %8 0 Offset 0 +OpMemberDecorate %8 1 Offset 16 +OpMemberDecorate %8 2 Offset 20 +OpMemberDecorate %8 3 Offset 24 +OpMemberDecorate %8 4 Offset 32 +OpMemberDecorate %8 5 Offset 48 +OpMemberDecorate %8 6 Offset 64 +OpMemberDecorate %8 7 Offset 68 +OpDecorate %10 BuiltIn Position +OpDecorate %12 Location 0 +OpDecorate %12 Flat +OpDecorate %14 Location 1 +OpDecorate %14 NoPerspective +OpDecorate %16 Location 2 +OpDecorate %16 NoPerspective +OpDecorate %16 Centroid +OpDecorate %18 Location 3 +OpDecorate %18 NoPerspective +OpDecorate %18 Sample +OpDecorate %20 Location 4 +OpDecorate %21 Location 5 +OpDecorate %21 Centroid +OpDecorate %22 Location 6 +OpDecorate %22 Sample +OpDecorate %23 BuiltIn PointSize +OpDecorate %88 BuiltIn FragCoord +OpDecorate %91 Location 0 +OpDecorate %91 Flat +OpDecorate %94 Location 1 +OpDecorate %94 NoPerspective +OpDecorate %97 Location 2 +OpDecorate %97 NoPerspective +OpDecorate %97 Centroid +OpDecorate %100 Location 3 +OpDecorate %100 NoPerspective +OpDecorate %100 Sample +OpDecorate %103 Location 4 +OpDecorate %105 Location 5 +OpDecorate %105 Centroid +OpDecorate %107 Location 6 +OpDecorate %107 Sample +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeInt 32 0 +%6 = OpTypeVector %4 2 +%7 = OpTypeVector %4 3 +%8 = OpTypeStruct %3 %5 %4 %6 %7 %3 %4 %4 +%11 = OpTypePointer Output %3 +%10 = OpVariable %11 Output +%13 = OpTypePointer Output %5 +%12 = OpVariable %13 Output +%15 = OpTypePointer Output %4 +%14 = OpVariable %15 Output +%17 = OpTypePointer Output %6 +%16 = OpVariable %17 Output +%19 = OpTypePointer Output %7 +%18 = OpVariable %19 Output +%20 = OpVariable %11 Output +%21 = OpVariable %15 Output +%22 = OpVariable %15 Output +%24 = OpTypePointer Output %4 +%23 = OpVariable %24 Output +%25 = OpConstant %4 1.0 +%27 = OpTypeFunction %2 +%28 = OpConstant %4 2.0 +%29 = OpConstant %4 4.0 +%30 = OpConstant %4 5.0 +%31 = OpConstant %4 6.0 +%32 = OpConstantComposite %3 %28 %29 %30 %31 +%33 = OpConstant %5 8 +%34 = OpConstant %4 27.0 +%35 = OpConstant %4 64.0 +%36 = OpConstant %4 125.0 +%37 = OpConstantComposite %6 %35 %36 +%38 = OpConstant %4 216.0 +%39 = OpConstant %4 343.0 +%40 = OpConstant %4 512.0 +%41 = OpConstantComposite %7 %38 %39 %40 +%42 = OpConstant %4 729.0 +%43 = OpConstant %4 1000.0 +%44 = OpConstant %4 1331.0 +%45 = OpConstant %4 1728.0 +%46 = OpConstantComposite %3 %42 %43 %44 %45 +%47 = OpConstant %4 2197.0 +%48 = OpConstant %4 2744.0 +%50 = OpTypePointer Function %8 +%51 = OpConstantNull %8 +%53 = OpTypePointer Function %3 +%54 = OpConstant %5 0 +%56 = OpTypePointer Function %5 +%57 = OpConstant %5 1 +%59 = OpTypePointer Function %4 +%60 = OpConstant %5 2 +%62 = OpTypePointer Function %6 +%63 = OpConstant %5 3 +%65 = OpTypePointer Function %7 +%66 = OpConstant %5 4 +%68 = OpConstant %5 5 +%70 = OpConstant %5 6 +%72 = OpConstant %5 7 +%89 = OpTypePointer Input %3 +%88 = OpVariable %89 Input +%92 = OpTypePointer Input %5 +%91 = OpVariable %92 Input +%95 = OpTypePointer Input %4 +%94 = OpVariable %95 Input +%98 = OpTypePointer Input %6 +%97 = OpVariable %98 Input +%101 = OpTypePointer Input %7 +%100 = OpVariable %101 Input +%103 = OpVariable %89 Input +%105 = OpVariable %95 Input +%107 = OpVariable %95 Input +%26 = OpFunction %2 None %27 +%9 = OpLabel +%49 = OpVariable %50 Function %51 +OpStore %23 %25 +OpBranch %52 +%52 = OpLabel +%55 = OpAccessChain %53 %49 %54 +OpStore %55 %32 +%58 = OpAccessChain %56 %49 %57 +OpStore %58 %33 +%61 = OpAccessChain %59 %49 %60 +OpStore %61 %34 +%64 = OpAccessChain %62 %49 %63 +OpStore %64 %37 +%67 = OpAccessChain %65 %49 %66 +OpStore %67 %41 +%69 = OpAccessChain %53 %49 %68 +OpStore %69 %46 +%71 = OpAccessChain %59 %49 %70 +OpStore %71 %47 +%73 = OpAccessChain %59 %49 %72 +OpStore %73 %48 +%74 = OpLoad %8 %49 +%75 = OpCompositeExtract %3 %74 0 +OpStore %10 %75 +%76 = OpAccessChain %24 %10 %57 +%77 = OpLoad %4 %76 +%78 = OpFNegate %4 %77 +OpStore %76 %78 +%79 = OpCompositeExtract %5 %74 1 +OpStore %12 %79 +%80 = OpCompositeExtract %4 %74 2 +OpStore %14 %80 +%81 = OpCompositeExtract %6 %74 3 +OpStore %16 %81 +%82 = OpCompositeExtract %7 %74 4 +OpStore %18 %82 +%83 = OpCompositeExtract %3 %74 5 +OpStore %20 %83 +%84 = OpCompositeExtract %4 %74 6 +OpStore %21 %84 +%85 = OpCompositeExtract %4 %74 7 +OpStore %22 %85 +OpReturn +OpFunctionEnd +%109 = OpFunction %2 None %27 +%86 = OpLabel +%90 = OpLoad %3 %88 +%93 = OpLoad %5 %91 +%96 = OpLoad %4 %94 +%99 = OpLoad %6 %97 +%102 = OpLoad %7 %100 +%104 = OpLoad %3 %103 +%106 = OpLoad %4 %105 +%108 = OpLoad %4 %107 +%87 = OpCompositeConstruct %8 %90 %93 %96 %99 %102 %104 %106 %108 +OpBranch %110 +%110 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/math-functions.spvasm b/naga/tests/out/spv/math-functions.spvasm new file mode 100644 index 0000000000..ba3e7cffb9 --- /dev/null +++ b/naga/tests/out/spv/math-functions.spvasm @@ -0,0 +1,146 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 126 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %15 "main" +OpExecutionMode %15 OriginUpperLeft +OpMemberDecorate %8 0 Offset 0 +OpMemberDecorate %8 1 Offset 4 +OpMemberDecorate %9 0 Offset 0 +OpMemberDecorate %9 1 Offset 8 +OpMemberDecorate %10 0 Offset 0 +OpMemberDecorate %10 1 Offset 16 +OpMemberDecorate %11 0 Offset 0 +OpMemberDecorate %11 1 Offset 4 +OpMemberDecorate %13 0 Offset 0 +OpMemberDecorate %13 1 Offset 16 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%6 = OpTypeInt 32 1 +%5 = OpTypeVector %6 2 +%7 = OpTypeVector %4 2 +%8 = OpTypeStruct %4 %4 +%9 = OpTypeStruct %7 %7 +%10 = OpTypeStruct %3 %3 +%11 = OpTypeStruct %4 %6 +%12 = OpTypeVector %6 4 +%13 = OpTypeStruct %3 %12 +%16 = OpTypeFunction %2 +%17 = OpConstant %4 1.0 +%18 = OpConstant %4 0.0 +%19 = OpConstantComposite %3 %18 %18 %18 %18 +%20 = OpConstant %6 -1 +%21 = OpConstantComposite %12 %20 %20 %20 %20 +%22 = OpConstant %4 -1.0 +%23 = OpConstantComposite %3 %22 %22 %22 %22 +%24 = OpConstantNull %5 +%25 = OpTypeInt 32 0 +%26 = OpConstant %25 0 +%27 = OpConstantComposite %5 %20 %20 +%28 = OpConstant %25 1 +%29 = OpTypeVector %25 2 +%30 = OpConstantComposite %29 %28 %28 +%31 = OpConstant %6 0 +%32 = OpConstant %25 4294967295 +%33 = OpConstantComposite %29 %26 %26 +%34 = OpConstantComposite %5 %31 %31 +%35 = OpConstant %6 1 +%36 = OpConstantComposite %5 %35 %35 +%37 = OpConstant %6 2 +%38 = OpConstant %4 2.0 +%39 = OpConstantComposite %7 %17 %38 +%40 = OpConstant %6 3 +%41 = OpConstant %6 4 +%42 = OpConstantComposite %5 %40 %41 +%43 = OpConstant %4 1.5 +%44 = OpConstantComposite %7 %43 %43 +%45 = OpConstantComposite %3 %43 %43 %43 %43 +%52 = OpConstantComposite %3 %17 %17 %17 %17 +%59 = OpConstantNull %6 +%77 = OpConstant %25 32 +%86 = OpConstantComposite %29 %77 %77 +%95 = OpConstant %6 31 +%100 = OpConstantComposite %5 %95 %95 +%15 = OpFunction %2 None %16 +%14 = OpLabel +OpBranch %46 +%46 = OpLabel +%47 = OpExtInst %4 %1 Degrees %17 +%48 = OpExtInst %4 %1 Radians %17 +%49 = OpExtInst %3 %1 Degrees %19 +%50 = OpExtInst %3 %1 Radians %19 +%51 = OpExtInst %3 %1 FClamp %19 %19 %52 +%53 = OpExtInst %3 %1 Refract %19 %19 %17 +%54 = OpExtInst %6 %1 SSign %20 +%55 = OpExtInst %12 %1 SSign %21 +%56 = OpExtInst %4 %1 FSign %22 +%57 = OpExtInst %3 %1 FSign %23 +%60 = OpCompositeExtract %6 %24 0 +%61 = OpCompositeExtract %6 %24 0 +%62 = OpIMul %6 %60 %61 +%63 = OpIAdd %6 %59 %62 +%64 = OpCompositeExtract %6 %24 1 +%65 = OpCompositeExtract %6 %24 1 +%66 = OpIMul %6 %64 %65 +%58 = OpIAdd %6 %63 %66 +%67 = OpCopyObject %25 %26 +%68 = OpExtInst %25 %1 FindUMsb %67 +%69 = OpExtInst %6 %1 FindSMsb %20 +%70 = OpExtInst %5 %1 FindSMsb %27 +%71 = OpExtInst %29 %1 FindUMsb %30 +%72 = OpExtInst %6 %1 FindILsb %20 +%73 = OpExtInst %25 %1 FindILsb %28 +%74 = OpExtInst %5 %1 FindILsb %27 +%75 = OpExtInst %29 %1 FindILsb %30 +%78 = OpExtInst %25 %1 FindILsb %26 +%76 = OpExtInst %25 %1 UMin %77 %78 +%80 = OpExtInst %6 %1 FindILsb %31 +%79 = OpExtInst %6 %1 UMin %77 %80 +%82 = OpExtInst %25 %1 FindILsb %32 +%81 = OpExtInst %25 %1 UMin %77 %82 +%84 = OpExtInst %6 %1 FindILsb %20 +%83 = OpExtInst %6 %1 UMin %77 %84 +%87 = OpExtInst %29 %1 FindILsb %33 +%85 = OpExtInst %29 %1 UMin %86 %87 +%89 = OpExtInst %5 %1 FindILsb %34 +%88 = OpExtInst %5 %1 UMin %86 %89 +%91 = OpExtInst %29 %1 FindILsb %30 +%90 = OpExtInst %29 %1 UMin %86 %91 +%93 = OpExtInst %5 %1 FindILsb %36 +%92 = OpExtInst %5 %1 UMin %86 %93 +%96 = OpExtInst %6 %1 FindUMsb %20 +%94 = OpISub %6 %95 %96 +%98 = OpExtInst %6 %1 FindUMsb %28 +%97 = OpISub %25 %95 %98 +%101 = OpExtInst %5 %1 FindUMsb %27 +%99 = OpISub %5 %100 %101 +%103 = OpExtInst %5 %1 FindUMsb %30 +%102 = OpISub %29 %100 %103 +%104 = OpExtInst %4 %1 Ldexp %17 %37 +%105 = OpExtInst %7 %1 Ldexp %39 %42 +%106 = OpExtInst %8 %1 ModfStruct %43 +%107 = OpExtInst %8 %1 ModfStruct %43 +%108 = OpCompositeExtract %4 %107 0 +%109 = OpExtInst %8 %1 ModfStruct %43 +%110 = OpCompositeExtract %4 %109 1 +%111 = OpExtInst %9 %1 ModfStruct %44 +%112 = OpExtInst %10 %1 ModfStruct %45 +%113 = OpCompositeExtract %3 %112 1 +%114 = OpCompositeExtract %4 %113 0 +%115 = OpExtInst %9 %1 ModfStruct %44 +%116 = OpCompositeExtract %7 %115 0 +%117 = OpCompositeExtract %4 %116 1 +%118 = OpExtInst %11 %1 FrexpStruct %43 +%119 = OpExtInst %11 %1 FrexpStruct %43 +%120 = OpCompositeExtract %4 %119 0 +%121 = OpExtInst %11 %1 FrexpStruct %43 +%122 = OpCompositeExtract %6 %121 1 +%123 = OpExtInst %13 %1 FrexpStruct %45 +%124 = OpCompositeExtract %12 %123 1 +%125 = OpCompositeExtract %6 %124 0 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/multiview.spvasm b/naga/tests/out/spv/multiview.spvasm new file mode 100644 index 0000000000..792dea5593 --- /dev/null +++ b/naga/tests/out/spv/multiview.spvasm @@ -0,0 +1,25 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 11 +OpCapability Shader +OpCapability MultiView +OpExtension "SPV_KHR_multiview" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %8 "main" %5 +OpExecutionMode %8 OriginUpperLeft +OpDecorate %5 BuiltIn ViewIndex +OpDecorate %5 Flat +%2 = OpTypeVoid +%3 = OpTypeInt 32 1 +%6 = OpTypePointer Input %3 +%5 = OpVariable %6 Input +%9 = OpTypeFunction %2 +%8 = OpFunction %2 None %9 +%4 = OpLabel +%7 = OpLoad %3 %5 +OpBranch %10 +%10 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/operators.spvasm b/naga/tests/out/spv/operators.spvasm new file mode 100644 index 0000000000..974623bc70 --- /dev/null +++ b/naga/tests/out/spv/operators.spvasm @@ -0,0 +1,450 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 389 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %374 "main" %371 +OpExecutionMode %374 LocalSize 1 1 1 +OpDecorate %371 BuiltIn WorkgroupId +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%6 = OpTypeInt 32 1 +%5 = OpTypeVector %6 4 +%8 = OpTypeBool +%7 = OpTypeVector %8 4 +%9 = OpTypeVector %4 2 +%10 = OpTypeVector %4 3 +%11 = OpTypeMatrix %10 3 +%12 = OpTypeMatrix %10 4 +%13 = OpTypeMatrix %3 3 +%14 = OpTypeVector %6 3 +%16 = OpTypeInt 32 0 +%15 = OpTypeVector %16 3 +%17 = OpConstant %4 1.0 +%18 = OpConstantComposite %3 %17 %17 %17 %17 +%19 = OpConstant %4 0.0 +%20 = OpConstantComposite %3 %19 %19 %19 %19 +%21 = OpConstant %4 0.5 +%22 = OpConstantComposite %3 %21 %21 %21 %21 +%23 = OpConstant %6 1 +%24 = OpConstantComposite %5 %23 %23 %23 %23 +%27 = OpTypeFunction %3 +%28 = OpConstantTrue %8 +%29 = OpConstant %6 0 +%30 = OpConstantFalse %8 +%31 = OpConstantComposite %7 %30 %30 %30 %30 +%32 = OpConstant %4 0.1 +%33 = OpConstantComposite %5 %29 %29 %29 %29 +%57 = OpTypeFunction %3 %4 %6 +%58 = OpConstant %4 2.0 +%59 = OpConstantComposite %9 %58 %58 +%60 = OpConstant %4 4.0 +%61 = OpConstantComposite %9 %60 %60 +%62 = OpConstant %4 8.0 +%63 = OpConstantComposite %9 %62 %62 +%64 = OpConstant %6 2 +%65 = OpConstantComposite %5 %64 %64 %64 %64 +%78 = OpTypeFunction %9 +%79 = OpConstantComposite %9 %17 %17 +%80 = OpConstant %4 3.0 +%81 = OpConstantComposite %9 %80 %80 +%83 = OpTypePointer Function %9 +%95 = OpTypeFunction %10 %10 +%97 = OpTypeVector %8 3 +%98 = OpConstantComposite %10 %19 %19 %19 +%100 = OpConstantComposite %10 %17 %17 %17 +%104 = OpTypeFunction %2 +%105 = OpTypeVector %8 2 +%106 = OpConstantComposite %105 %28 %28 +%107 = OpConstantComposite %97 %28 %28 %28 +%108 = OpConstantComposite %97 %30 %30 %30 +%109 = OpConstantComposite %7 %28 %28 %28 %28 +%110 = OpConstantComposite %7 %30 %30 %30 %30 +%122 = OpConstant %16 1 +%123 = OpConstant %16 2 +%124 = OpTypeVector %6 2 +%125 = OpConstantComposite %124 %23 %23 +%126 = OpConstantComposite %124 %64 %64 +%127 = OpConstantComposite %15 %123 %123 %123 +%128 = OpConstantComposite %15 %122 %122 %122 +%129 = OpConstantComposite %3 %58 %58 %58 %58 +%130 = OpConstantComposite %3 %17 %17 %17 %17 +%131 = OpTypeVector %16 2 +%132 = OpConstantComposite %131 %123 %123 +%133 = OpConstantComposite %131 %122 %122 +%134 = OpConstantNull %11 +%135 = OpConstantNull %12 +%136 = OpConstantComposite %10 %58 %58 %58 +%137 = OpConstantNull %13 +%301 = OpConstantNull %14 +%303 = OpTypePointer Function %6 +%304 = OpConstantNull %6 +%306 = OpTypePointer Function %14 +%334 = OpTypePointer Function %6 +%372 = OpTypePointer Input %15 +%371 = OpVariable %372 Input +%375 = OpConstantComposite %10 %17 %17 %17 +%26 = OpFunction %3 None %27 +%25 = OpLabel +OpBranch %34 +%34 = OpLabel +%35 = OpSelect %6 %28 %23 %29 +%37 = OpCompositeConstruct %7 %28 %28 %28 %28 +%36 = OpSelect %3 %37 %18 %20 +%38 = OpSelect %3 %31 %20 %18 +%39 = OpExtInst %3 %1 FMix %20 %18 %22 +%41 = OpCompositeConstruct %3 %32 %32 %32 %32 +%40 = OpExtInst %3 %1 FMix %20 %18 %41 +%42 = OpBitcast %4 %23 +%43 = OpBitcast %3 %24 +%44 = OpCompositeConstruct %5 %35 %35 %35 %35 +%45 = OpIAdd %5 %44 %33 +%46 = OpConvertSToF %3 %45 +%47 = OpFAdd %3 %46 %36 +%48 = OpFAdd %3 %47 %39 +%49 = OpFAdd %3 %48 %40 +%50 = OpCompositeConstruct %3 %42 %42 %42 %42 +%51 = OpFAdd %3 %49 %50 +%52 = OpFAdd %3 %51 %43 +OpReturnValue %52 +OpFunctionEnd +%56 = OpFunction %3 None %57 +%54 = OpFunctionParameter %4 +%55 = OpFunctionParameter %6 +%53 = OpLabel +OpBranch %66 +%66 = OpLabel +%67 = OpCompositeConstruct %9 %54 %54 +%68 = OpFAdd %9 %59 %67 +%69 = OpFSub %9 %68 %61 +%70 = OpFDiv %9 %69 %63 +%71 = OpCompositeConstruct %5 %55 %55 %55 %55 +%72 = OpSRem %5 %71 %65 +%73 = OpVectorShuffle %3 %70 %70 0 1 0 1 +%74 = OpConvertSToF %3 %72 +%75 = OpFAdd %3 %73 %74 +OpReturnValue %75 +OpFunctionEnd +%77 = OpFunction %9 None %78 +%76 = OpLabel +%82 = OpVariable %83 Function %59 +OpBranch %84 +%84 = OpLabel +%85 = OpLoad %9 %82 +%86 = OpFAdd %9 %85 %79 +OpStore %82 %86 +%87 = OpLoad %9 %82 +%88 = OpFSub %9 %87 %81 +OpStore %82 %88 +%89 = OpLoad %9 %82 +%90 = OpFDiv %9 %89 %61 +OpStore %82 %90 +%91 = OpLoad %9 %82 +OpReturnValue %91 +OpFunctionEnd +%94 = OpFunction %10 None %95 +%93 = OpFunctionParameter %10 +%92 = OpLabel +OpBranch %96 +%96 = OpLabel +%99 = OpFUnordNotEqual %97 %93 %98 +%101 = OpSelect %10 %99 %100 %98 +OpReturnValue %101 +OpFunctionEnd +%103 = OpFunction %2 None %104 +%102 = OpLabel +OpBranch %111 +%111 = OpLabel +%112 = OpLogicalNot %8 %28 +%113 = OpLogicalNot %105 %106 +%114 = OpLogicalOr %8 %28 %30 +%115 = OpLogicalAnd %8 %28 %30 +%116 = OpLogicalOr %8 %28 %30 +%117 = OpLogicalOr %97 %107 %108 +%118 = OpLogicalAnd %8 %28 %30 +%119 = OpLogicalAnd %7 %109 %110 +OpReturn +OpFunctionEnd +%121 = OpFunction %2 None %104 +%120 = OpLabel +OpBranch %138 +%138 = OpLabel +%139 = OpFNegate %4 %17 +%140 = OpSNegate %124 %125 +%141 = OpFNegate %9 %79 +%142 = OpIAdd %6 %64 %23 +%143 = OpIAdd %16 %123 %122 +%144 = OpFAdd %4 %58 %17 +%145 = OpIAdd %124 %126 %125 +%146 = OpIAdd %15 %127 %128 +%147 = OpFAdd %3 %129 %130 +%148 = OpISub %6 %64 %23 +%149 = OpISub %16 %123 %122 +%150 = OpFSub %4 %58 %17 +%151 = OpISub %124 %126 %125 +%152 = OpISub %15 %127 %128 +%153 = OpFSub %3 %129 %130 +%154 = OpIMul %6 %64 %23 +%155 = OpIMul %16 %123 %122 +%156 = OpFMul %4 %58 %17 +%157 = OpIMul %124 %126 %125 +%158 = OpIMul %15 %127 %128 +%159 = OpFMul %3 %129 %130 +%160 = OpSDiv %6 %64 %23 +%161 = OpUDiv %16 %123 %122 +%162 = OpFDiv %4 %58 %17 +%163 = OpSDiv %124 %126 %125 +%164 = OpUDiv %15 %127 %128 +%165 = OpFDiv %3 %129 %130 +%166 = OpSRem %6 %64 %23 +%167 = OpUMod %16 %123 %122 +%168 = OpFRem %4 %58 %17 +%169 = OpSRem %124 %126 %125 +%170 = OpUMod %15 %127 %128 +%171 = OpFRem %3 %129 %130 +OpBranch %172 +%172 = OpLabel +%174 = OpIAdd %124 %126 %125 +%175 = OpIAdd %124 %126 %125 +%176 = OpIAdd %131 %132 %133 +%177 = OpIAdd %131 %132 %133 +%178 = OpFAdd %9 %59 %79 +%179 = OpFAdd %9 %59 %79 +%180 = OpISub %124 %126 %125 +%181 = OpISub %124 %126 %125 +%182 = OpISub %131 %132 %133 +%183 = OpISub %131 %132 %133 +%184 = OpFSub %9 %59 %79 +%185 = OpFSub %9 %59 %79 +%187 = OpCompositeConstruct %124 %23 %23 +%186 = OpIMul %124 %126 %187 +%189 = OpCompositeConstruct %124 %64 %64 +%188 = OpIMul %124 %125 %189 +%191 = OpCompositeConstruct %131 %122 %122 +%190 = OpIMul %131 %132 %191 +%193 = OpCompositeConstruct %131 %123 %123 +%192 = OpIMul %131 %133 %193 +%194 = OpVectorTimesScalar %9 %59 %17 +%195 = OpVectorTimesScalar %9 %79 %58 +%196 = OpSDiv %124 %126 %125 +%197 = OpSDiv %124 %126 %125 +%198 = OpUDiv %131 %132 %133 +%199 = OpUDiv %131 %132 %133 +%200 = OpFDiv %9 %59 %79 +%201 = OpFDiv %9 %59 %79 +%202 = OpSRem %124 %126 %125 +%203 = OpSRem %124 %126 %125 +%204 = OpUMod %131 %132 %133 +%205 = OpUMod %131 %132 %133 +%206 = OpFRem %9 %59 %79 +%207 = OpFRem %9 %59 %79 +OpBranch %173 +%173 = OpLabel +%209 = OpCompositeExtract %10 %134 0 +%210 = OpCompositeExtract %10 %134 0 +%211 = OpFAdd %10 %209 %210 +%212 = OpCompositeExtract %10 %134 1 +%213 = OpCompositeExtract %10 %134 1 +%214 = OpFAdd %10 %212 %213 +%215 = OpCompositeExtract %10 %134 2 +%216 = OpCompositeExtract %10 %134 2 +%217 = OpFAdd %10 %215 %216 +%208 = OpCompositeConstruct %11 %211 %214 %217 +%219 = OpCompositeExtract %10 %134 0 +%220 = OpCompositeExtract %10 %134 0 +%221 = OpFSub %10 %219 %220 +%222 = OpCompositeExtract %10 %134 1 +%223 = OpCompositeExtract %10 %134 1 +%224 = OpFSub %10 %222 %223 +%225 = OpCompositeExtract %10 %134 2 +%226 = OpCompositeExtract %10 %134 2 +%227 = OpFSub %10 %225 %226 +%218 = OpCompositeConstruct %11 %221 %224 %227 +%228 = OpMatrixTimesScalar %11 %134 %17 +%229 = OpMatrixTimesScalar %11 %134 %58 +%230 = OpMatrixTimesVector %10 %135 %130 +%231 = OpVectorTimesMatrix %3 %136 %135 +%232 = OpMatrixTimesMatrix %11 %135 %137 +OpReturn +OpFunctionEnd +%234 = OpFunction %2 None %104 +%233 = OpLabel +OpBranch %235 +%235 = OpLabel +%236 = OpNot %6 %23 +%237 = OpNot %16 %122 +%238 = OpNot %124 %125 +%239 = OpNot %15 %128 +%240 = OpBitwiseOr %6 %64 %23 +%241 = OpBitwiseOr %16 %123 %122 +%242 = OpBitwiseOr %124 %126 %125 +%243 = OpBitwiseOr %15 %127 %128 +%244 = OpBitwiseAnd %6 %64 %23 +%245 = OpBitwiseAnd %16 %123 %122 +%246 = OpBitwiseAnd %124 %126 %125 +%247 = OpBitwiseAnd %15 %127 %128 +%248 = OpBitwiseXor %6 %64 %23 +%249 = OpBitwiseXor %16 %123 %122 +%250 = OpBitwiseXor %124 %126 %125 +%251 = OpBitwiseXor %15 %127 %128 +%252 = OpShiftLeftLogical %6 %64 %122 +%253 = OpShiftLeftLogical %16 %123 %122 +%254 = OpShiftLeftLogical %124 %126 %133 +%255 = OpShiftLeftLogical %15 %127 %128 +%256 = OpShiftRightArithmetic %6 %64 %122 +%257 = OpShiftRightLogical %16 %123 %122 +%258 = OpShiftRightArithmetic %124 %126 %133 +%259 = OpShiftRightLogical %15 %127 %128 +OpReturn +OpFunctionEnd +%261 = OpFunction %2 None %104 +%260 = OpLabel +OpBranch %262 +%262 = OpLabel +%263 = OpIEqual %8 %64 %23 +%264 = OpIEqual %8 %123 %122 +%265 = OpFOrdEqual %8 %58 %17 +%266 = OpIEqual %105 %126 %125 +%267 = OpIEqual %97 %127 %128 +%268 = OpFOrdEqual %7 %129 %130 +%269 = OpINotEqual %8 %64 %23 +%270 = OpINotEqual %8 %123 %122 +%271 = OpFOrdNotEqual %8 %58 %17 +%272 = OpINotEqual %105 %126 %125 +%273 = OpINotEqual %97 %127 %128 +%274 = OpFOrdNotEqual %7 %129 %130 +%275 = OpSLessThan %8 %64 %23 +%276 = OpULessThan %8 %123 %122 +%277 = OpFOrdLessThan %8 %58 %17 +%278 = OpSLessThan %105 %126 %125 +%279 = OpULessThan %97 %127 %128 +%280 = OpFOrdLessThan %7 %129 %130 +%281 = OpSLessThanEqual %8 %64 %23 +%282 = OpULessThanEqual %8 %123 %122 +%283 = OpFOrdLessThanEqual %8 %58 %17 +%284 = OpSLessThanEqual %105 %126 %125 +%285 = OpULessThanEqual %97 %127 %128 +%286 = OpFOrdLessThanEqual %7 %129 %130 +%287 = OpSGreaterThan %8 %64 %23 +%288 = OpUGreaterThan %8 %123 %122 +%289 = OpFOrdGreaterThan %8 %58 %17 +%290 = OpSGreaterThan %105 %126 %125 +%291 = OpUGreaterThan %97 %127 %128 +%292 = OpFOrdGreaterThan %7 %129 %130 +%293 = OpSGreaterThanEqual %8 %64 %23 +%294 = OpUGreaterThanEqual %8 %123 %122 +%295 = OpFOrdGreaterThanEqual %8 %58 %17 +%296 = OpSGreaterThanEqual %105 %126 %125 +%297 = OpUGreaterThanEqual %97 %127 %128 +%298 = OpFOrdGreaterThanEqual %7 %129 %130 +OpReturn +OpFunctionEnd +%300 = OpFunction %2 None %104 +%299 = OpLabel +%302 = OpVariable %303 Function %304 +%305 = OpVariable %306 Function %301 +OpBranch %307 +%307 = OpLabel +OpStore %302 %23 +%308 = OpLoad %6 %302 +%309 = OpIAdd %6 %308 %23 +OpStore %302 %309 +%310 = OpLoad %6 %302 +%311 = OpISub %6 %310 %23 +OpStore %302 %311 +%312 = OpLoad %6 %302 +%313 = OpLoad %6 %302 +%314 = OpIMul %6 %313 %312 +OpStore %302 %314 +%315 = OpLoad %6 %302 +%316 = OpLoad %6 %302 +%317 = OpSDiv %6 %316 %315 +OpStore %302 %317 +%318 = OpLoad %6 %302 +%319 = OpSRem %6 %318 %23 +OpStore %302 %319 +%320 = OpLoad %6 %302 +%321 = OpBitwiseAnd %6 %320 %29 +OpStore %302 %321 +%322 = OpLoad %6 %302 +%323 = OpBitwiseOr %6 %322 %29 +OpStore %302 %323 +%324 = OpLoad %6 %302 +%325 = OpBitwiseXor %6 %324 %29 +OpStore %302 %325 +%326 = OpLoad %6 %302 +%327 = OpShiftLeftLogical %6 %326 %123 +OpStore %302 %327 +%328 = OpLoad %6 %302 +%329 = OpShiftRightArithmetic %6 %328 %122 +OpStore %302 %329 +%330 = OpLoad %6 %302 +%331 = OpIAdd %6 %330 %23 +OpStore %302 %331 +%332 = OpLoad %6 %302 +%333 = OpISub %6 %332 %23 +OpStore %302 %333 +%335 = OpAccessChain %334 %305 %23 +%336 = OpLoad %6 %335 +%337 = OpIAdd %6 %336 %23 +%338 = OpAccessChain %334 %305 %23 +OpStore %338 %337 +%339 = OpAccessChain %334 %305 %23 +%340 = OpLoad %6 %339 +%341 = OpISub %6 %340 %23 +%342 = OpAccessChain %334 %305 %23 +OpStore %342 %341 +OpReturn +OpFunctionEnd +%344 = OpFunction %2 None %104 +%343 = OpLabel +OpBranch %345 +%345 = OpLabel +%346 = OpSNegate %6 %23 +%347 = OpSNegate %6 %23 +%348 = OpSNegate %6 %347 +%349 = OpSNegate %6 %23 +%350 = OpSNegate %6 %349 +%351 = OpSNegate %6 %23 +%352 = OpSNegate %6 %351 +%353 = OpSNegate %6 %23 +%354 = OpSNegate %6 %353 +%355 = OpSNegate %6 %354 +%356 = OpSNegate %6 %23 +%357 = OpSNegate %6 %356 +%358 = OpSNegate %6 %357 +%359 = OpSNegate %6 %358 +%360 = OpSNegate %6 %23 +%361 = OpSNegate %6 %360 +%362 = OpSNegate %6 %361 +%363 = OpSNegate %6 %362 +%364 = OpSNegate %6 %363 +%365 = OpSNegate %6 %23 +%366 = OpSNegate %6 %365 +%367 = OpSNegate %6 %366 +%368 = OpSNegate %6 %367 +%369 = OpSNegate %6 %368 +OpReturn +OpFunctionEnd +%374 = OpFunction %2 None %104 +%370 = OpLabel +%373 = OpLoad %15 %371 +OpBranch %376 +%376 = OpLabel +%377 = OpFunctionCall %3 %26 +%378 = OpCompositeExtract %16 %373 0 +%379 = OpConvertUToF %4 %378 +%380 = OpCompositeExtract %16 %373 1 +%381 = OpBitcast %6 %380 +%382 = OpFunctionCall %3 %56 %379 %381 +%383 = OpFunctionCall %10 %94 %375 +%384 = OpFunctionCall %2 %103 +%385 = OpFunctionCall %2 %121 +%386 = OpFunctionCall %2 %234 +%387 = OpFunctionCall %2 %261 +%388 = OpFunctionCall %2 %300 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/padding.spvasm b/naga/tests/out/spv/padding.spvasm new file mode 100644 index 0000000000..aae9f2cb74 --- /dev/null +++ b/naga/tests/out/spv/padding.spvasm @@ -0,0 +1,97 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 49 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %26 "vertex" %24 +OpMemberName %5 0 "a" +OpName %5 "S" +OpMemberName %6 0 "a" +OpMemberName %6 1 "b" +OpName %6 "Test" +OpMemberName %10 0 "a" +OpMemberName %10 1 "b" +OpName %10 "Test2" +OpMemberName %12 0 "a" +OpMemberName %12 1 "b" +OpName %12 "Test3" +OpName %14 "input1" +OpName %17 "input2" +OpName %20 "input3" +OpName %26 "vertex" +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 16 +OpDecorate %7 ArrayStride 16 +OpMemberDecorate %10 0 Offset 0 +OpMemberDecorate %10 1 Offset 32 +OpMemberDecorate %12 0 Offset 0 +OpMemberDecorate %12 0 ColMajor +OpMemberDecorate %12 0 MatrixStride 16 +OpMemberDecorate %12 1 Offset 64 +OpDecorate %14 DescriptorSet 0 +OpDecorate %14 Binding 0 +OpDecorate %15 Block +OpMemberDecorate %15 0 Offset 0 +OpDecorate %17 DescriptorSet 0 +OpDecorate %17 Binding 1 +OpDecorate %18 Block +OpMemberDecorate %18 0 Offset 0 +OpDecorate %20 DescriptorSet 0 +OpDecorate %20 Binding 2 +OpDecorate %21 Block +OpMemberDecorate %21 0 Offset 0 +OpDecorate %24 BuiltIn Position +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 3 +%5 = OpTypeStruct %3 +%6 = OpTypeStruct %5 %4 +%9 = OpTypeInt 32 0 +%8 = OpConstant %9 2 +%7 = OpTypeArray %3 %8 +%10 = OpTypeStruct %7 %4 +%11 = OpTypeMatrix %3 4 +%12 = OpTypeStruct %11 %4 +%13 = OpTypeVector %4 4 +%15 = OpTypeStruct %6 +%16 = OpTypePointer Uniform %15 +%14 = OpVariable %16 Uniform +%18 = OpTypeStruct %10 +%19 = OpTypePointer Uniform %18 +%17 = OpVariable %19 Uniform +%21 = OpTypeStruct %12 +%22 = OpTypePointer Uniform %21 +%20 = OpVariable %22 Uniform +%25 = OpTypePointer Output %13 +%24 = OpVariable %25 Output +%27 = OpTypeFunction %2 +%28 = OpTypePointer Uniform %6 +%29 = OpConstant %9 0 +%31 = OpTypePointer Uniform %10 +%33 = OpTypePointer Uniform %12 +%35 = OpConstant %4 1.0 +%36 = OpConstantComposite %13 %35 %35 %35 %35 +%38 = OpTypePointer Uniform %4 +%39 = OpConstant %9 1 +%26 = OpFunction %2 None %27 +%23 = OpLabel +%30 = OpAccessChain %28 %14 %29 +%32 = OpAccessChain %31 %17 %29 +%34 = OpAccessChain %33 %20 %29 +OpBranch %37 +%37 = OpLabel +%40 = OpAccessChain %38 %30 %39 +%41 = OpLoad %4 %40 +%42 = OpVectorTimesScalar %13 %36 %41 +%43 = OpAccessChain %38 %32 %39 +%44 = OpLoad %4 %43 +%45 = OpVectorTimesScalar %13 %42 %44 +%46 = OpAccessChain %38 %34 %39 +%47 = OpLoad %4 %46 +%48 = OpVectorTimesScalar %13 %45 %47 +OpStore %24 %48 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/pointers.spvasm b/naga/tests/out/spv/pointers.spvasm new file mode 100644 index 0000000000..ae42aed2e0 --- /dev/null +++ b/naga/tests/out/spv/pointers.spvasm @@ -0,0 +1,77 @@ +; SPIR-V +; Version: 1.2 +; Generator: rspirv +; Bound: 42 +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpMemberName %7 0 "arr" +OpName %7 "DynamicArray" +OpName %8 "dynamic_array" +OpName %11 "f" +OpName %14 "v" +OpName %22 "i" +OpName %23 "v" +OpName %24 "index_unsized" +OpName %34 "i" +OpName %35 "v" +OpName %36 "index_dynamic_array" +OpDecorate %6 ArrayStride 4 +OpMemberDecorate %7 0 Offset 0 +OpDecorate %7 Block +OpDecorate %8 DescriptorSet 0 +OpDecorate %8 Binding 0 +%2 = OpTypeVoid +%4 = OpTypeInt 32 1 +%3 = OpTypeVector %4 2 +%5 = OpTypeInt 32 0 +%6 = OpTypeRuntimeArray %5 +%7 = OpTypeStruct %6 +%9 = OpTypePointer StorageBuffer %7 +%8 = OpVariable %9 StorageBuffer +%12 = OpTypeFunction %2 +%13 = OpConstant %4 10 +%15 = OpTypePointer Function %3 +%16 = OpConstantNull %3 +%18 = OpTypePointer Function %4 +%19 = OpConstant %5 0 +%25 = OpTypeFunction %2 %4 %5 +%27 = OpTypePointer StorageBuffer %6 +%28 = OpTypePointer StorageBuffer %5 +%11 = OpFunction %2 None %12 +%10 = OpLabel +%14 = OpVariable %15 Function %16 +OpBranch %17 +%17 = OpLabel +%20 = OpAccessChain %18 %14 %19 +OpStore %20 %13 +OpReturn +OpFunctionEnd +%24 = OpFunction %2 None %25 +%22 = OpFunctionParameter %4 +%23 = OpFunctionParameter %5 +%21 = OpLabel +OpBranch %26 +%26 = OpLabel +%29 = OpAccessChain %28 %8 %19 %22 +%30 = OpLoad %5 %29 +%31 = OpIAdd %5 %30 %23 +%32 = OpAccessChain %28 %8 %19 %22 +OpStore %32 %31 +OpReturn +OpFunctionEnd +%36 = OpFunction %2 None %25 +%34 = OpFunctionParameter %4 +%35 = OpFunctionParameter %5 +%33 = OpLabel +OpBranch %37 +%37 = OpLabel +%38 = OpAccessChain %28 %8 %19 %34 +%39 = OpLoad %5 %38 +%40 = OpIAdd %5 %39 %35 +%41 = OpAccessChain %28 %8 %19 %34 +OpStore %41 %40 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/policy-mix.spvasm b/naga/tests/out/spv/policy-mix.spvasm new file mode 100644 index 0000000000..a10ff1121f --- /dev/null +++ b/naga/tests/out/spv/policy-mix.spvasm @@ -0,0 +1,147 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 100 +OpCapability Shader +OpCapability ImageQuery +OpCapability Linkage +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpMemberName %8 0 "a" +OpName %8 "InStorage" +OpMemberName %11 0 "a" +OpName %11 "InUniform" +OpName %21 "in_storage" +OpName %24 "in_uniform" +OpName %27 "image_2d_array" +OpName %29 "in_workgroup" +OpName %31 "in_private" +OpName %35 "c" +OpName %36 "i" +OpName %37 "l" +OpName %38 "mock_function" +OpName %52 "in_function" +OpDecorate %5 ArrayStride 16 +OpMemberDecorate %8 0 Offset 0 +OpDecorate %9 ArrayStride 16 +OpMemberDecorate %11 0 Offset 0 +OpDecorate %13 ArrayStride 4 +OpDecorate %15 ArrayStride 4 +OpDecorate %19 ArrayStride 16 +OpDecorate %21 NonWritable +OpDecorate %21 DescriptorSet 0 +OpDecorate %21 Binding 0 +OpDecorate %22 Block +OpMemberDecorate %22 0 Offset 0 +OpDecorate %24 DescriptorSet 0 +OpDecorate %24 Binding 1 +OpDecorate %25 Block +OpMemberDecorate %25 0 Offset 0 +OpDecorate %27 DescriptorSet 0 +OpDecorate %27 Binding 2 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%7 = OpTypeInt 32 0 +%6 = OpConstant %7 10 +%5 = OpTypeArray %3 %6 +%8 = OpTypeStruct %5 +%10 = OpConstant %7 20 +%9 = OpTypeArray %3 %10 +%11 = OpTypeStruct %9 +%12 = OpTypeImage %4 2D 0 1 0 1 Unknown +%14 = OpConstant %7 30 +%13 = OpTypeArray %4 %14 +%16 = OpConstant %7 40 +%15 = OpTypeArray %4 %16 +%18 = OpTypeInt 32 1 +%17 = OpTypeVector %18 2 +%20 = OpConstant %7 2 +%19 = OpTypeArray %3 %20 +%22 = OpTypeStruct %8 +%23 = OpTypePointer StorageBuffer %22 +%21 = OpVariable %23 StorageBuffer +%25 = OpTypeStruct %11 +%26 = OpTypePointer Uniform %25 +%24 = OpVariable %26 Uniform +%28 = OpTypePointer UniformConstant %12 +%27 = OpVariable %28 UniformConstant +%30 = OpTypePointer Workgroup %13 +%29 = OpVariable %30 Workgroup +%32 = OpTypePointer Private %15 +%33 = OpConstantNull %15 +%31 = OpVariable %32 Private %33 +%39 = OpTypeFunction %3 %17 %18 %18 +%40 = OpTypePointer StorageBuffer %8 +%41 = OpConstant %7 0 +%43 = OpTypePointer Uniform %11 +%46 = OpConstant %4 0.707 +%47 = OpConstant %4 0.0 +%48 = OpConstant %4 1.0 +%49 = OpConstantComposite %3 %46 %47 %47 %48 +%50 = OpConstantComposite %3 %47 %46 %47 %48 +%51 = OpConstantComposite %19 %49 %50 +%53 = OpTypePointer Function %19 +%55 = OpTypePointer StorageBuffer %5 +%56 = OpTypePointer StorageBuffer %3 +%59 = OpTypePointer Uniform %9 +%60 = OpTypePointer Uniform %3 +%64 = OpTypeVector %18 3 +%66 = OpTypeBool +%67 = OpConstantNull %3 +%73 = OpTypeVector %66 3 +%80 = OpTypePointer Workgroup %4 +%81 = OpConstant %7 29 +%87 = OpTypePointer Private %4 +%88 = OpConstant %7 39 +%94 = OpTypePointer Function %3 +%95 = OpConstant %7 1 +%38 = OpFunction %3 None %39 +%35 = OpFunctionParameter %17 +%36 = OpFunctionParameter %18 +%37 = OpFunctionParameter %18 +%34 = OpLabel +%52 = OpVariable %53 Function %51 +%42 = OpAccessChain %40 %21 %41 +%44 = OpAccessChain %43 %24 %41 +%45 = OpLoad %12 %27 +OpBranch %54 +%54 = OpLabel +%57 = OpAccessChain %56 %42 %41 %36 +%58 = OpLoad %3 %57 +%61 = OpAccessChain %60 %44 %41 %36 +%62 = OpLoad %3 %61 +%63 = OpFAdd %3 %58 %62 +%65 = OpCompositeConstruct %64 %35 %36 +%68 = OpImageQueryLevels %18 %45 +%69 = OpULessThan %66 %37 %68 +OpSelectionMerge %70 None +OpBranchConditional %69 %71 %70 +%71 = OpLabel +%72 = OpImageQuerySizeLod %64 %45 %37 +%74 = OpULessThan %73 %65 %72 +%75 = OpAll %66 %74 +OpBranchConditional %75 %76 %70 +%76 = OpLabel +%77 = OpImageFetch %3 %45 %65 Lod %37 +OpBranch %70 +%70 = OpLabel +%78 = OpPhi %3 %67 %54 %67 %71 %77 %76 +%79 = OpFAdd %3 %63 %78 +%82 = OpExtInst %7 %1 UMin %36 %81 +%83 = OpAccessChain %80 %29 %82 +%84 = OpLoad %4 %83 +%85 = OpCompositeConstruct %3 %84 %84 %84 %84 +%86 = OpFAdd %3 %79 %85 +%89 = OpExtInst %7 %1 UMin %36 %88 +%90 = OpAccessChain %87 %31 %89 +%91 = OpLoad %4 %90 +%92 = OpCompositeConstruct %3 %91 %91 %91 %91 +%93 = OpFAdd %3 %86 %92 +%96 = OpExtInst %7 %1 UMin %36 %95 +%97 = OpAccessChain %94 %52 %96 +%98 = OpLoad %3 %97 +%99 = OpFAdd %3 %93 %98 +OpReturnValue %99 +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/quad.spvasm b/naga/tests/out/spv/quad.spvasm new file mode 100644 index 0000000000..b77ed65e07 --- /dev/null +++ b/naga/tests/out/spv/quad.spvasm @@ -0,0 +1,118 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 64 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %24 "vert_main" %15 %18 %20 %22 +OpEntryPoint Fragment %44 "frag_main" %41 %43 +OpEntryPoint Fragment %60 "fs_extra" %59 +OpExecutionMode %44 OriginUpperLeft +OpExecutionMode %60 OriginUpperLeft +OpMemberName %6 0 "uv" +OpMemberName %6 1 "position" +OpName %6 "VertexOutput" +OpName %9 "c_scale" +OpName %10 "u_texture" +OpName %12 "u_sampler" +OpName %15 "pos" +OpName %18 "uv" +OpName %20 "uv" +OpName %22 "position" +OpName %24 "vert_main" +OpName %41 "uv" +OpName %44 "frag_main" +OpName %60 "fs_extra" +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 16 +OpDecorate %10 DescriptorSet 0 +OpDecorate %10 Binding 0 +OpDecorate %12 DescriptorSet 0 +OpDecorate %12 Binding 1 +OpDecorate %15 Location 0 +OpDecorate %18 Location 1 +OpDecorate %20 Location 0 +OpDecorate %22 BuiltIn Position +OpDecorate %41 Location 0 +OpDecorate %43 Location 0 +OpDecorate %59 Location 0 +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%4 = OpTypeVector %3 2 +%5 = OpTypeVector %3 4 +%6 = OpTypeStruct %4 %5 +%7 = OpTypeImage %3 2D 0 0 0 1 Unknown +%8 = OpTypeSampler +%9 = OpConstant %3 1.2 +%11 = OpTypePointer UniformConstant %7 +%10 = OpVariable %11 UniformConstant +%13 = OpTypePointer UniformConstant %8 +%12 = OpVariable %13 UniformConstant +%16 = OpTypePointer Input %4 +%15 = OpVariable %16 Input +%18 = OpVariable %16 Input +%21 = OpTypePointer Output %4 +%20 = OpVariable %21 Output +%23 = OpTypePointer Output %5 +%22 = OpVariable %23 Output +%25 = OpTypeFunction %2 +%26 = OpConstant %3 0.0 +%27 = OpConstant %3 1.0 +%34 = OpTypePointer Output %3 +%36 = OpTypeInt 32 0 +%35 = OpConstant %36 1 +%41 = OpVariable %16 Input +%43 = OpVariable %23 Output +%48 = OpTypeSampledImage %7 +%52 = OpTypeBool +%59 = OpVariable %23 Output +%61 = OpConstant %3 0.5 +%62 = OpConstantComposite %5 %26 %61 %26 %61 +%24 = OpFunction %2 None %25 +%14 = OpLabel +%17 = OpLoad %4 %15 +%19 = OpLoad %4 %18 +OpBranch %28 +%28 = OpLabel +%29 = OpVectorTimesScalar %4 %17 %9 +%30 = OpCompositeConstruct %5 %29 %26 %27 +%31 = OpCompositeConstruct %6 %19 %30 +%32 = OpCompositeExtract %4 %31 0 +OpStore %20 %32 +%33 = OpCompositeExtract %5 %31 1 +OpStore %22 %33 +%37 = OpAccessChain %34 %22 %35 +%38 = OpLoad %3 %37 +%39 = OpFNegate %3 %38 +OpStore %37 %39 +OpReturn +OpFunctionEnd +%44 = OpFunction %2 None %25 +%40 = OpLabel +%42 = OpLoad %4 %41 +%45 = OpLoad %7 %10 +%46 = OpLoad %8 %12 +OpBranch %47 +%47 = OpLabel +%49 = OpSampledImage %48 %45 %46 +%50 = OpImageSampleImplicitLod %5 %49 %42 +%51 = OpCompositeExtract %3 %50 3 +%53 = OpFOrdEqual %52 %51 %26 +OpSelectionMerge %54 None +OpBranchConditional %53 %55 %54 +%55 = OpLabel +OpKill +%54 = OpLabel +%56 = OpCompositeExtract %3 %50 3 +%57 = OpVectorTimesScalar %5 %50 %56 +OpStore %43 %57 +OpReturn +OpFunctionEnd +%60 = OpFunction %2 None %25 +%58 = OpLabel +OpBranch %63 +%63 = OpLabel +OpStore %59 %62 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/ray-query.spvasm b/naga/tests/out/spv/ray-query.spvasm new file mode 100644 index 0000000000..23d5dd1baa --- /dev/null +++ b/naga/tests/out/spv/ray-query.spvasm @@ -0,0 +1,152 @@ +; SPIR-V +; Version: 1.4 +; Generator: rspirv +; Bound: 95 +OpCapability Shader +OpCapability RayQueryKHR +OpExtension "SPV_KHR_ray_query" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %41 "main" %15 %17 +OpExecutionMode %41 LocalSize 1 1 1 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 16 +OpMemberDecorate %11 0 Offset 0 +OpMemberDecorate %11 1 Offset 4 +OpMemberDecorate %11 2 Offset 8 +OpMemberDecorate %11 3 Offset 12 +OpMemberDecorate %11 4 Offset 16 +OpMemberDecorate %11 5 Offset 20 +OpMemberDecorate %11 6 Offset 24 +OpMemberDecorate %11 7 Offset 28 +OpMemberDecorate %11 8 Offset 36 +OpMemberDecorate %11 9 Offset 48 +OpMemberDecorate %11 9 ColMajor +OpMemberDecorate %11 9 MatrixStride 16 +OpMemberDecorate %11 10 Offset 112 +OpMemberDecorate %11 10 ColMajor +OpMemberDecorate %11 10 MatrixStride 16 +OpMemberDecorate %14 0 Offset 0 +OpMemberDecorate %14 1 Offset 4 +OpMemberDecorate %14 2 Offset 8 +OpMemberDecorate %14 3 Offset 12 +OpMemberDecorate %14 4 Offset 16 +OpMemberDecorate %14 5 Offset 32 +OpDecorate %15 DescriptorSet 0 +OpDecorate %15 Binding 0 +OpDecorate %17 DescriptorSet 0 +OpDecorate %17 Binding 1 +OpDecorate %18 Block +OpMemberDecorate %18 0 Offset 0 +%2 = OpTypeVoid +%3 = OpTypeAccelerationStructureNV +%4 = OpTypeInt 32 0 +%6 = OpTypeFloat 32 +%5 = OpTypeVector %6 3 +%7 = OpTypeStruct %4 %5 +%8 = OpTypeVector %6 2 +%9 = OpTypeBool +%10 = OpTypeMatrix %5 4 +%11 = OpTypeStruct %4 %6 %4 %4 %4 %4 %4 %8 %9 %10 %10 +%12 = OpTypeVector %6 4 +%13 = OpTypeRayQueryKHR +%14 = OpTypeStruct %4 %4 %6 %6 %5 %5 +%16 = OpTypePointer UniformConstant %3 +%15 = OpVariable %16 UniformConstant +%18 = OpTypeStruct %7 +%19 = OpTypePointer StorageBuffer %18 +%17 = OpVariable %19 StorageBuffer +%24 = OpTypeFunction %5 %5 %11 +%25 = OpConstant %6 1.0 +%26 = OpConstant %6 2.4 +%27 = OpConstant %6 0.0 +%42 = OpTypeFunction %2 +%44 = OpTypePointer StorageBuffer %7 +%45 = OpConstant %4 0 +%47 = OpConstantComposite %5 %27 %25 %27 +%48 = OpConstant %4 4 +%49 = OpConstant %4 255 +%50 = OpConstantComposite %5 %27 %27 %27 +%51 = OpConstant %6 0.1 +%52 = OpConstant %6 100.0 +%53 = OpConstantComposite %14 %48 %49 %51 %52 %50 %47 +%55 = OpTypePointer Function %13 +%72 = OpConstant %4 1 +%85 = OpTypePointer StorageBuffer %4 +%90 = OpTypePointer StorageBuffer %5 +%23 = OpFunction %5 None %24 +%21 = OpFunctionParameter %5 +%22 = OpFunctionParameter %11 +%20 = OpLabel +OpBranch %28 +%28 = OpLabel +%29 = OpCompositeExtract %10 %22 10 +%30 = OpCompositeConstruct %12 %21 %25 +%31 = OpMatrixTimesVector %5 %29 %30 +%32 = OpVectorShuffle %8 %31 %31 0 1 +%33 = OpExtInst %8 %1 Normalize %32 +%34 = OpVectorTimesScalar %8 %33 %26 +%35 = OpCompositeExtract %10 %22 9 +%36 = OpCompositeConstruct %12 %34 %27 %25 +%37 = OpMatrixTimesVector %5 %35 %36 +%38 = OpFSub %5 %21 %37 +%39 = OpExtInst %5 %1 Normalize %38 +OpReturnValue %39 +OpFunctionEnd +%41 = OpFunction %2 None %42 +%40 = OpLabel +%54 = OpVariable %55 Function +%43 = OpLoad %3 %15 +%46 = OpAccessChain %44 %17 %45 +OpBranch %56 +%56 = OpLabel +%57 = OpCompositeExtract %4 %53 0 +%58 = OpCompositeExtract %4 %53 1 +%59 = OpCompositeExtract %6 %53 2 +%60 = OpCompositeExtract %6 %53 3 +%61 = OpCompositeExtract %5 %53 4 +%62 = OpCompositeExtract %5 %53 5 +OpRayQueryInitializeKHR %54 %43 %57 %58 %61 %59 %62 %60 +OpBranch %63 +%63 = OpLabel +OpLoopMerge %64 %66 None +OpBranch %65 +%65 = OpLabel +%67 = OpRayQueryProceedKHR %9 %54 +OpSelectionMerge %68 None +OpBranchConditional %67 %68 %69 +%69 = OpLabel +OpBranch %64 +%68 = OpLabel +OpBranch %70 +%70 = OpLabel +OpBranch %71 +%71 = OpLabel +OpBranch %66 +%66 = OpLabel +OpBranch %63 +%64 = OpLabel +%73 = OpRayQueryGetIntersectionTypeKHR %4 %54 %72 +%74 = OpRayQueryGetIntersectionInstanceCustomIndexKHR %4 %54 %72 +%75 = OpRayQueryGetIntersectionInstanceIdKHR %4 %54 %72 +%76 = OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR %4 %54 %72 +%77 = OpRayQueryGetIntersectionGeometryIndexKHR %4 %54 %72 +%78 = OpRayQueryGetIntersectionPrimitiveIndexKHR %4 %54 %72 +%79 = OpRayQueryGetIntersectionTKHR %6 %54 %72 +%80 = OpRayQueryGetIntersectionBarycentricsKHR %8 %54 %72 +%81 = OpRayQueryGetIntersectionFrontFaceKHR %9 %54 %72 +%82 = OpRayQueryGetIntersectionObjectToWorldKHR %10 %54 %72 +%83 = OpRayQueryGetIntersectionWorldToObjectKHR %10 %54 %72 +%84 = OpCompositeConstruct %11 %73 %79 %74 %75 %76 %77 %78 %80 %81 %82 %83 +%86 = OpCompositeExtract %4 %84 0 +%87 = OpIEqual %9 %86 %45 +%88 = OpSelect %4 %87 %72 %45 +%89 = OpAccessChain %85 %46 %45 +OpStore %89 %88 +%91 = OpCompositeExtract %6 %84 1 +%92 = OpVectorTimesScalar %5 %47 %91 +%93 = OpFunctionCall %5 %23 %92 %84 +%94 = OpAccessChain %90 %46 %72 +OpStore %94 %93 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/runtime-array-in-unused-struct.spvasm b/naga/tests/out/spv/runtime-array-in-unused-struct.spvasm new file mode 100644 index 0000000000..ab3bd3d01b --- /dev/null +++ b/naga/tests/out/spv/runtime-array-in-unused-struct.spvasm @@ -0,0 +1,24 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 10 +OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %5 1 Offset 16 +OpDecorate %6 ArrayStride 32 +OpMemberDecorate %7 0 Offset 0 +OpDecorate %7 Block +OpDecorate %8 ArrayStride 4 +OpMemberDecorate %9 0 Offset 0 +OpDecorate %9 Block +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%4 = OpTypeVector %3 4 +%5 = OpTypeStruct %3 %4 +%6 = OpTypeRuntimeArray %5 +%7 = OpTypeStruct %6 +%8 = OpTypeRuntimeArray %3 +%9 = OpTypeStruct %8 \ No newline at end of file diff --git a/naga/tests/out/spv/separate-entry-points.compute.spvasm b/naga/tests/out/spv/separate-entry-points.compute.spvasm new file mode 100644 index 0000000000..38b7ea417e --- /dev/null +++ b/naga/tests/out/spv/separate-entry-points.compute.spvasm @@ -0,0 +1,33 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 18 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %15 "compute" +OpExecutionMode %15 LocalSize 1 1 1 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%7 = OpTypeFunction %2 +%10 = OpTypeInt 32 0 +%9 = OpConstant %10 2 +%11 = OpConstant %10 1 +%12 = OpConstant %10 72 +%13 = OpConstant %10 264 +%6 = OpFunction %2 None %7 +%5 = OpLabel +OpBranch %8 +%8 = OpLabel +OpControlBarrier %9 %11 %12 +OpControlBarrier %9 %9 %13 +OpReturn +OpFunctionEnd +%15 = OpFunction %2 None %7 +%14 = OpLabel +OpBranch %16 +%16 = OpLabel +%17 = OpFunctionCall %2 %6 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/separate-entry-points.fragment.spvasm b/naga/tests/out/spv/separate-entry-points.fragment.spvasm new file mode 100644 index 0000000000..e29ce8f15d --- /dev/null +++ b/naga/tests/out/spv/separate-entry-points.fragment.spvasm @@ -0,0 +1,35 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 20 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %16 "fragment" %14 +OpExecutionMode %16 OriginUpperLeft +OpDecorate %14 Location 0 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%7 = OpTypeFunction %2 +%8 = OpConstant %4 0.0 +%15 = OpTypePointer Output %3 +%14 = OpVariable %15 Output +%17 = OpConstantNull %3 +%6 = OpFunction %2 None %7 +%5 = OpLabel +OpBranch %9 +%9 = OpLabel +%10 = OpDPdx %4 %8 +%11 = OpDPdy %4 %8 +%12 = OpFwidth %4 %8 +OpReturn +OpFunctionEnd +%16 = OpFunction %2 None %7 +%13 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpFunctionCall %2 %6 +OpStore %14 %17 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/shadow.spvasm b/naga/tests/out/spv/shadow.spvasm new file mode 100644 index 0000000000..55bfa4fec0 --- /dev/null +++ b/naga/tests/out/spv/shadow.spvasm @@ -0,0 +1,420 @@ +; SPIR-V +; Version: 1.2 +; Generator: rspirv +; Bound: 265 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %84 "vs_main" %74 %77 %79 %81 %83 +OpEntryPoint Fragment %142 "fs_main" %133 %136 %139 %141 +OpEntryPoint Fragment %210 "fs_main_without_storage" %203 %205 %207 %209 +OpExecutionMode %142 OriginUpperLeft +OpExecutionMode %210 OriginUpperLeft +OpMemberName %8 0 "view_proj" +OpMemberName %8 1 "num_lights" +OpName %8 "Globals" +OpMemberName %9 0 "world" +OpMemberName %9 1 "color" +OpName %9 "Entity" +OpMemberName %11 0 "proj_position" +OpMemberName %11 1 "world_normal" +OpMemberName %11 2 "world_position" +OpName %11 "VertexOutput" +OpMemberName %15 0 "proj" +OpMemberName %15 1 "pos" +OpMemberName %15 2 "color" +OpName %15 "Light" +OpName %23 "c_ambient" +OpName %18 "c_max_lights" +OpName %24 "u_globals" +OpName %27 "u_entity" +OpName %30 "s_lights" +OpName %33 "u_lights" +OpName %36 "t_shadow" +OpName %38 "sampler_shadow" +OpName %41 "light_id" +OpName %42 "homogeneous_coords" +OpName %43 "fetch_shadow" +OpName %74 "position" +OpName %77 "normal" +OpName %79 "proj_position" +OpName %81 "world_normal" +OpName %83 "world_position" +OpName %84 "vs_main" +OpName %91 "out" +OpName %133 "proj_position" +OpName %136 "world_normal" +OpName %139 "world_position" +OpName %142 "fs_main" +OpName %149 "color" +OpName %150 "i" +OpName %203 "proj_position" +OpName %205 "world_normal" +OpName %207 "world_position" +OpName %210 "fs_main_without_storage" +OpName %217 "color" +OpName %218 "i" +OpMemberDecorate %8 0 Offset 0 +OpMemberDecorate %8 0 ColMajor +OpMemberDecorate %8 0 MatrixStride 16 +OpMemberDecorate %8 1 Offset 64 +OpMemberDecorate %9 0 Offset 0 +OpMemberDecorate %9 0 ColMajor +OpMemberDecorate %9 0 MatrixStride 16 +OpMemberDecorate %9 1 Offset 64 +OpMemberDecorate %11 0 Offset 0 +OpMemberDecorate %11 1 Offset 16 +OpMemberDecorate %11 2 Offset 32 +OpMemberDecorate %15 0 Offset 0 +OpMemberDecorate %15 0 ColMajor +OpMemberDecorate %15 0 MatrixStride 16 +OpMemberDecorate %15 1 Offset 64 +OpMemberDecorate %15 2 Offset 80 +OpDecorate %16 ArrayStride 96 +OpDecorate %17 ArrayStride 96 +OpDecorate %24 DescriptorSet 0 +OpDecorate %24 Binding 0 +OpDecorate %25 Block +OpMemberDecorate %25 0 Offset 0 +OpDecorate %27 DescriptorSet 1 +OpDecorate %27 Binding 0 +OpDecorate %28 Block +OpMemberDecorate %28 0 Offset 0 +OpDecorate %30 NonWritable +OpDecorate %30 DescriptorSet 0 +OpDecorate %30 Binding 1 +OpDecorate %31 Block +OpMemberDecorate %31 0 Offset 0 +OpDecorate %33 DescriptorSet 0 +OpDecorate %33 Binding 1 +OpDecorate %34 Block +OpMemberDecorate %34 0 Offset 0 +OpDecorate %36 DescriptorSet 0 +OpDecorate %36 Binding 2 +OpDecorate %38 DescriptorSet 0 +OpDecorate %38 Binding 3 +OpDecorate %74 Location 0 +OpDecorate %77 Location 1 +OpDecorate %79 BuiltIn Position +OpDecorate %81 Location 0 +OpDecorate %83 Location 1 +OpDecorate %133 BuiltIn FragCoord +OpDecorate %136 Location 0 +OpDecorate %139 Location 1 +OpDecorate %141 Location 0 +OpDecorate %203 BuiltIn FragCoord +OpDecorate %205 Location 0 +OpDecorate %207 Location 1 +OpDecorate %209 Location 0 +%2 = OpTypeVoid +%5 = OpTypeFloat 32 +%4 = OpTypeVector %5 4 +%3 = OpTypeMatrix %4 4 +%7 = OpTypeInt 32 0 +%6 = OpTypeVector %7 4 +%8 = OpTypeStruct %3 %6 +%9 = OpTypeStruct %3 %4 +%10 = OpTypeVector %5 3 +%11 = OpTypeStruct %4 %10 %4 +%13 = OpTypeInt 32 1 +%12 = OpTypeVector %13 4 +%14 = OpTypeMatrix %10 3 +%15 = OpTypeStruct %3 %4 %4 +%16 = OpTypeRuntimeArray %15 +%18 = OpConstant %7 10 +%17 = OpTypeArray %15 %18 +%19 = OpTypeImage %5 2D 1 1 0 1 Unknown +%20 = OpTypeSampler +%21 = OpTypeVector %5 2 +%22 = OpConstant %5 0.05 +%23 = OpConstantComposite %10 %22 %22 %22 +%25 = OpTypeStruct %8 +%26 = OpTypePointer Uniform %25 +%24 = OpVariable %26 Uniform +%28 = OpTypeStruct %9 +%29 = OpTypePointer Uniform %28 +%27 = OpVariable %29 Uniform +%31 = OpTypeStruct %16 +%32 = OpTypePointer StorageBuffer %31 +%30 = OpVariable %32 StorageBuffer +%34 = OpTypeStruct %17 +%35 = OpTypePointer Uniform %34 +%33 = OpVariable %35 Uniform +%37 = OpTypePointer UniformConstant %19 +%36 = OpVariable %37 UniformConstant +%39 = OpTypePointer UniformConstant %20 +%38 = OpVariable %39 UniformConstant +%44 = OpTypeFunction %5 %7 %4 +%47 = OpConstant %5 0.0 +%48 = OpConstant %5 1.0 +%49 = OpConstant %5 0.5 +%50 = OpConstant %5 -0.5 +%51 = OpConstantComposite %21 %49 %50 +%52 = OpConstantComposite %21 %49 %49 +%55 = OpTypeBool +%68 = OpTypeSampledImage %19 +%75 = OpTypePointer Input %12 +%74 = OpVariable %75 Input +%77 = OpVariable %75 Input +%80 = OpTypePointer Output %4 +%79 = OpVariable %80 Output +%82 = OpTypePointer Output %10 +%81 = OpVariable %82 Output +%83 = OpVariable %80 Output +%85 = OpTypeFunction %2 +%86 = OpTypePointer Uniform %8 +%87 = OpConstant %7 0 +%89 = OpTypePointer Uniform %9 +%92 = OpTypePointer Function %11 +%93 = OpConstantNull %11 +%95 = OpTypePointer Uniform %3 +%102 = OpTypePointer Function %10 +%110 = OpTypeVector %13 3 +%114 = OpConstant %7 1 +%116 = OpTypePointer Function %4 +%117 = OpConstant %7 2 +%125 = OpTypePointer Output %5 +%134 = OpTypePointer Input %4 +%133 = OpVariable %134 Input +%137 = OpTypePointer Input %10 +%136 = OpVariable %137 Input +%139 = OpVariable %134 Input +%141 = OpVariable %80 Output +%145 = OpTypePointer StorageBuffer %16 +%151 = OpTypePointer Function %7 +%160 = OpTypePointer Uniform %6 +%161 = OpTypePointer Uniform %7 +%171 = OpTypePointer StorageBuffer %15 +%197 = OpTypePointer Uniform %4 +%203 = OpVariable %134 Input +%205 = OpVariable %137 Input +%207 = OpVariable %134 Input +%209 = OpVariable %80 Output +%213 = OpTypePointer Uniform %17 +%236 = OpTypePointer Uniform %15 +%43 = OpFunction %5 None %44 +%41 = OpFunctionParameter %7 +%42 = OpFunctionParameter %4 +%40 = OpLabel +%45 = OpLoad %19 %36 +%46 = OpLoad %20 %38 +OpBranch %53 +%53 = OpLabel +%54 = OpCompositeExtract %5 %42 3 +%56 = OpFOrdLessThanEqual %55 %54 %47 +OpSelectionMerge %57 None +OpBranchConditional %56 %58 %57 +%58 = OpLabel +OpReturnValue %48 +%57 = OpLabel +%59 = OpCompositeExtract %5 %42 3 +%60 = OpFDiv %5 %48 %59 +%61 = OpVectorShuffle %21 %42 %42 0 1 +%62 = OpFMul %21 %61 %51 +%63 = OpVectorTimesScalar %21 %62 %60 +%64 = OpFAdd %21 %63 %52 +%65 = OpBitcast %13 %41 +%66 = OpCompositeExtract %5 %42 2 +%67 = OpFMul %5 %66 %60 +%69 = OpConvertSToF %5 %65 +%70 = OpCompositeConstruct %10 %64 %69 +%71 = OpSampledImage %68 %45 %46 +%72 = OpImageSampleDrefExplicitLod %5 %71 %70 %67 Lod %47 +OpReturnValue %72 +OpFunctionEnd +%84 = OpFunction %2 None %85 +%73 = OpLabel +%91 = OpVariable %92 Function %93 +%76 = OpLoad %12 %74 +%78 = OpLoad %12 %77 +%88 = OpAccessChain %86 %24 %87 +%90 = OpAccessChain %89 %27 %87 +OpBranch %94 +%94 = OpLabel +%96 = OpAccessChain %95 %90 %87 +%97 = OpLoad %3 %96 +%98 = OpAccessChain %95 %90 %87 +%99 = OpLoad %3 %98 +%100 = OpConvertSToF %4 %76 +%101 = OpMatrixTimesVector %4 %99 %100 +%103 = OpCompositeExtract %4 %97 0 +%104 = OpVectorShuffle %10 %103 %103 0 1 2 +%105 = OpCompositeExtract %4 %97 1 +%106 = OpVectorShuffle %10 %105 %105 0 1 2 +%107 = OpCompositeExtract %4 %97 2 +%108 = OpVectorShuffle %10 %107 %107 0 1 2 +%109 = OpCompositeConstruct %14 %104 %106 %108 +%111 = OpVectorShuffle %110 %78 %78 0 1 2 +%112 = OpConvertSToF %10 %111 +%113 = OpMatrixTimesVector %10 %109 %112 +%115 = OpAccessChain %102 %91 %114 +OpStore %115 %113 +%118 = OpAccessChain %116 %91 %117 +OpStore %118 %101 +%119 = OpAccessChain %95 %88 %87 +%120 = OpLoad %3 %119 +%121 = OpMatrixTimesVector %4 %120 %101 +%122 = OpAccessChain %116 %91 %87 +OpStore %122 %121 +%123 = OpLoad %11 %91 +%124 = OpCompositeExtract %4 %123 0 +OpStore %79 %124 +%126 = OpAccessChain %125 %79 %114 +%127 = OpLoad %5 %126 +%128 = OpFNegate %5 %127 +OpStore %126 %128 +%129 = OpCompositeExtract %10 %123 1 +OpStore %81 %129 +%130 = OpCompositeExtract %4 %123 2 +OpStore %83 %130 +OpReturn +OpFunctionEnd +%142 = OpFunction %2 None %85 +%131 = OpLabel +%149 = OpVariable %102 Function %23 +%150 = OpVariable %151 Function %87 +%135 = OpLoad %4 %133 +%138 = OpLoad %10 %136 +%140 = OpLoad %4 %139 +%132 = OpCompositeConstruct %11 %135 %138 %140 +%143 = OpAccessChain %86 %24 %87 +%144 = OpAccessChain %89 %27 %87 +%146 = OpAccessChain %145 %30 %87 +%147 = OpLoad %19 %36 +%148 = OpLoad %20 %38 +OpBranch %152 +%152 = OpLabel +%153 = OpCompositeExtract %10 %132 1 +%154 = OpExtInst %10 %1 Normalize %153 +OpBranch %155 +%155 = OpLabel +OpLoopMerge %156 %158 None +OpBranch %157 +%157 = OpLabel +%159 = OpLoad %7 %150 +%162 = OpAccessChain %161 %143 %114 %87 +%163 = OpLoad %7 %162 +%164 = OpExtInst %7 %1 UMin %163 %18 +%165 = OpULessThan %55 %159 %164 +OpSelectionMerge %166 None +OpBranchConditional %165 %166 %167 +%167 = OpLabel +OpBranch %156 +%166 = OpLabel +OpBranch %168 +%168 = OpLabel +%170 = OpLoad %7 %150 +%172 = OpAccessChain %171 %146 %170 +%173 = OpLoad %15 %172 +%174 = OpLoad %7 %150 +%175 = OpCompositeExtract %3 %173 0 +%176 = OpCompositeExtract %4 %132 2 +%177 = OpMatrixTimesVector %4 %175 %176 +%178 = OpFunctionCall %5 %43 %174 %177 +%179 = OpCompositeExtract %4 %173 1 +%180 = OpVectorShuffle %10 %179 %179 0 1 2 +%181 = OpCompositeExtract %4 %132 2 +%182 = OpVectorShuffle %10 %181 %181 0 1 2 +%183 = OpFSub %10 %180 %182 +%184 = OpExtInst %10 %1 Normalize %183 +%185 = OpDot %5 %154 %184 +%186 = OpExtInst %5 %1 FMax %47 %185 +%187 = OpFMul %5 %178 %186 +%188 = OpCompositeExtract %4 %173 2 +%189 = OpVectorShuffle %10 %188 %188 0 1 2 +%190 = OpVectorTimesScalar %10 %189 %187 +%191 = OpLoad %10 %149 +%192 = OpFAdd %10 %191 %190 +OpStore %149 %192 +OpBranch %169 +%169 = OpLabel +OpBranch %158 +%158 = OpLabel +%193 = OpLoad %7 %150 +%194 = OpIAdd %7 %193 %114 +OpStore %150 %194 +OpBranch %155 +%156 = OpLabel +%195 = OpLoad %10 %149 +%196 = OpCompositeConstruct %4 %195 %48 +%198 = OpAccessChain %197 %144 %114 +%199 = OpLoad %4 %198 +%200 = OpFMul %4 %196 %199 +OpStore %141 %200 +OpReturn +OpFunctionEnd +%210 = OpFunction %2 None %85 +%201 = OpLabel +%217 = OpVariable %102 Function %23 +%218 = OpVariable %151 Function %87 +%204 = OpLoad %4 %203 +%206 = OpLoad %10 %205 +%208 = OpLoad %4 %207 +%202 = OpCompositeConstruct %11 %204 %206 %208 +%211 = OpAccessChain %86 %24 %87 +%212 = OpAccessChain %89 %27 %87 +%214 = OpAccessChain %213 %33 %87 +%215 = OpLoad %19 %36 +%216 = OpLoad %20 %38 +OpBranch %219 +%219 = OpLabel +%220 = OpCompositeExtract %10 %202 1 +%221 = OpExtInst %10 %1 Normalize %220 +OpBranch %222 +%222 = OpLabel +OpLoopMerge %223 %225 None +OpBranch %224 +%224 = OpLabel +%226 = OpLoad %7 %218 +%227 = OpAccessChain %161 %211 %114 %87 +%228 = OpLoad %7 %227 +%229 = OpExtInst %7 %1 UMin %228 %18 +%230 = OpULessThan %55 %226 %229 +OpSelectionMerge %231 None +OpBranchConditional %230 %231 %232 +%232 = OpLabel +OpBranch %223 +%231 = OpLabel +OpBranch %233 +%233 = OpLabel +%235 = OpLoad %7 %218 +%237 = OpAccessChain %236 %214 %235 +%238 = OpLoad %15 %237 +%239 = OpLoad %7 %218 +%240 = OpCompositeExtract %3 %238 0 +%241 = OpCompositeExtract %4 %202 2 +%242 = OpMatrixTimesVector %4 %240 %241 +%243 = OpFunctionCall %5 %43 %239 %242 +%244 = OpCompositeExtract %4 %238 1 +%245 = OpVectorShuffle %10 %244 %244 0 1 2 +%246 = OpCompositeExtract %4 %202 2 +%247 = OpVectorShuffle %10 %246 %246 0 1 2 +%248 = OpFSub %10 %245 %247 +%249 = OpExtInst %10 %1 Normalize %248 +%250 = OpDot %5 %221 %249 +%251 = OpExtInst %5 %1 FMax %47 %250 +%252 = OpFMul %5 %243 %251 +%253 = OpCompositeExtract %4 %238 2 +%254 = OpVectorShuffle %10 %253 %253 0 1 2 +%255 = OpVectorTimesScalar %10 %254 %252 +%256 = OpLoad %10 %217 +%257 = OpFAdd %10 %256 %255 +OpStore %217 %257 +OpBranch %234 +%234 = OpLabel +OpBranch %225 +%225 = OpLabel +%258 = OpLoad %7 %218 +%259 = OpIAdd %7 %258 %114 +OpStore %218 %259 +OpBranch %222 +%223 = OpLabel +%260 = OpLoad %10 %217 +%261 = OpCompositeConstruct %4 %260 %48 +%262 = OpAccessChain %197 %212 %114 +%263 = OpLoad %4 %262 +%264 = OpFMul %4 %261 %263 +OpStore %209 %264 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/skybox.spvasm b/naga/tests/out/spv/skybox.spvasm new file mode 100644 index 0000000000..4d541321a9 --- /dev/null +++ b/naga/tests/out/spv/skybox.spvasm @@ -0,0 +1,139 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 98 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %29 "vs_main" %22 %25 %27 +OpEntryPoint Fragment %90 "fs_main" %83 %86 %89 +OpExecutionMode %90 OriginUpperLeft +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 16 +OpMemberDecorate %8 0 Offset 0 +OpMemberDecorate %8 0 ColMajor +OpMemberDecorate %8 0 MatrixStride 16 +OpMemberDecorate %8 1 Offset 64 +OpMemberDecorate %8 1 ColMajor +OpMemberDecorate %8 1 MatrixStride 16 +OpDecorate %14 DescriptorSet 0 +OpDecorate %14 Binding 0 +OpDecorate %15 Block +OpMemberDecorate %15 0 Offset 0 +OpDecorate %17 DescriptorSet 0 +OpDecorate %17 Binding 1 +OpDecorate %19 DescriptorSet 0 +OpDecorate %19 Binding 2 +OpDecorate %22 BuiltIn VertexIndex +OpDecorate %25 BuiltIn Position +OpDecorate %27 Location 0 +OpDecorate %83 BuiltIn FragCoord +OpDecorate %86 Location 0 +OpDecorate %89 Location 0 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeVector %4 3 +%6 = OpTypeStruct %3 %5 +%7 = OpTypeMatrix %3 4 +%8 = OpTypeStruct %7 %7 +%9 = OpTypeInt 32 0 +%10 = OpTypeInt 32 1 +%11 = OpTypeMatrix %5 3 +%12 = OpTypeImage %4 Cube 0 0 0 1 Unknown +%13 = OpTypeSampler +%15 = OpTypeStruct %8 +%16 = OpTypePointer Uniform %15 +%14 = OpVariable %16 Uniform +%18 = OpTypePointer UniformConstant %12 +%17 = OpVariable %18 UniformConstant +%20 = OpTypePointer UniformConstant %13 +%19 = OpVariable %20 UniformConstant +%23 = OpTypePointer Input %9 +%22 = OpVariable %23 Input +%26 = OpTypePointer Output %3 +%25 = OpVariable %26 Output +%28 = OpTypePointer Output %5 +%27 = OpVariable %28 Output +%30 = OpTypeFunction %2 +%31 = OpTypePointer Uniform %8 +%32 = OpConstant %9 0 +%34 = OpConstant %10 2 +%35 = OpConstant %10 1 +%36 = OpConstant %4 4.0 +%37 = OpConstant %4 1.0 +%38 = OpConstant %4 0.0 +%40 = OpTypePointer Function %10 +%41 = OpConstantNull %10 +%43 = OpConstantNull %10 +%58 = OpTypePointer Uniform %7 +%59 = OpTypePointer Uniform %3 +%60 = OpConstant %9 1 +%67 = OpConstant %9 2 +%84 = OpTypePointer Input %3 +%83 = OpVariable %84 Input +%87 = OpTypePointer Input %5 +%86 = OpVariable %87 Input +%89 = OpVariable %26 Output +%95 = OpTypeSampledImage %12 +%29 = OpFunction %2 None %30 +%21 = OpLabel +%39 = OpVariable %40 Function %41 +%42 = OpVariable %40 Function %43 +%24 = OpLoad %9 %22 +%33 = OpAccessChain %31 %14 %32 +OpBranch %44 +%44 = OpLabel +%45 = OpBitcast %10 %24 +%46 = OpSDiv %10 %45 %34 +OpStore %39 %46 +%47 = OpBitcast %10 %24 +%48 = OpBitwiseAnd %10 %47 %35 +OpStore %42 %48 +%49 = OpLoad %10 %39 +%50 = OpConvertSToF %4 %49 +%51 = OpFMul %4 %50 %36 +%52 = OpFSub %4 %51 %37 +%53 = OpLoad %10 %42 +%54 = OpConvertSToF %4 %53 +%55 = OpFMul %4 %54 %36 +%56 = OpFSub %4 %55 %37 +%57 = OpCompositeConstruct %3 %52 %56 %38 %37 +%61 = OpAccessChain %59 %33 %60 %32 +%62 = OpLoad %3 %61 +%63 = OpVectorShuffle %5 %62 %62 0 1 2 +%64 = OpAccessChain %59 %33 %60 %60 +%65 = OpLoad %3 %64 +%66 = OpVectorShuffle %5 %65 %65 0 1 2 +%68 = OpAccessChain %59 %33 %60 %67 +%69 = OpLoad %3 %68 +%70 = OpVectorShuffle %5 %69 %69 0 1 2 +%71 = OpCompositeConstruct %11 %63 %66 %70 +%72 = OpTranspose %11 %71 +%73 = OpAccessChain %58 %33 %32 +%74 = OpLoad %7 %73 +%75 = OpMatrixTimesVector %3 %74 %57 +%76 = OpVectorShuffle %5 %75 %75 0 1 2 +%77 = OpMatrixTimesVector %5 %72 %76 +%78 = OpCompositeConstruct %6 %57 %77 +%79 = OpCompositeExtract %3 %78 0 +OpStore %25 %79 +%80 = OpCompositeExtract %5 %78 1 +OpStore %27 %80 +OpReturn +OpFunctionEnd +%90 = OpFunction %2 None %30 +%81 = OpLabel +%85 = OpLoad %3 %83 +%88 = OpLoad %5 %86 +%82 = OpCompositeConstruct %6 %85 %88 +%91 = OpLoad %12 %17 +%92 = OpLoad %13 %19 +OpBranch %93 +%93 = OpLabel +%94 = OpCompositeExtract %5 %82 1 +%96 = OpSampledImage %95 %91 %92 +%97 = OpImageSampleImplicitLod %3 %96 %94 +OpStore %89 %97 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/sprite.spvasm b/naga/tests/out/spv/sprite.spvasm new file mode 100644 index 0000000000..f3a574b28a --- /dev/null +++ b/naga/tests/out/spv/sprite.spvasm @@ -0,0 +1,43 @@ +; SPIR-V +; Version: 1.4 +; Generator: rspirv +; Bound: 26 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %18 "main" %13 %16 %8 %10 +OpExecutionMode %18 OriginUpperLeft +OpDecorate %8 DescriptorSet 0 +OpDecorate %8 Binding 0 +OpDecorate %10 DescriptorSet 0 +OpDecorate %10 Binding 1 +OpDecorate %13 Location 0 +OpDecorate %16 Location 0 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeImage %4 2D 0 0 0 1 Unknown +%5 = OpTypeSampler +%6 = OpTypeVector %4 2 +%7 = OpTypeVector %4 4 +%9 = OpTypePointer UniformConstant %3 +%8 = OpVariable %9 UniformConstant +%11 = OpTypePointer UniformConstant %5 +%10 = OpVariable %11 UniformConstant +%14 = OpTypePointer Input %6 +%13 = OpVariable %14 Input +%17 = OpTypePointer Output %7 +%16 = OpVariable %17 Output +%19 = OpTypeFunction %2 +%23 = OpTypeSampledImage %3 +%18 = OpFunction %2 None %19 +%12 = OpLabel +%15 = OpLoad %6 %13 +%20 = OpLoad %3 %8 +%21 = OpLoad %5 %10 +OpBranch %22 +%22 = OpLabel +%24 = OpSampledImage %23 %20 %21 +%25 = OpImageSampleImplicitLod %7 %24 %15 +OpStore %16 %25 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/standard.spvasm b/naga/tests/out/spv/standard.spvasm new file mode 100644 index 0000000000..50026e6dcd --- /dev/null +++ b/naga/tests/out/spv/standard.spvasm @@ -0,0 +1,68 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 42 +OpCapability Shader +OpCapability DerivativeControl +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %17 "derivatives" %12 %15 +OpExecutionMode %17 OriginUpperLeft +OpDecorate %12 BuiltIn FragCoord +OpDecorate %15 Location 0 +%2 = OpTypeVoid +%3 = OpTypeBool +%5 = OpTypeFloat 32 +%4 = OpTypeVector %5 4 +%8 = OpTypeFunction %3 +%9 = OpConstantTrue %3 +%13 = OpTypePointer Input %4 +%12 = OpVariable %13 Input +%16 = OpTypePointer Output %4 +%15 = OpVariable %16 Output +%18 = OpTypeFunction %2 +%20 = OpTypePointer Function %4 +%21 = OpConstantNull %4 +%23 = OpConstantNull %4 +%25 = OpConstantNull %4 +%7 = OpFunction %3 None %8 +%6 = OpLabel +OpBranch %10 +%10 = OpLabel +OpReturnValue %9 +OpFunctionEnd +%17 = OpFunction %2 None %18 +%11 = OpLabel +%19 = OpVariable %20 Function %21 +%22 = OpVariable %20 Function %23 +%24 = OpVariable %20 Function %25 +%14 = OpLoad %4 %12 +OpBranch %26 +%26 = OpLabel +%27 = OpDPdxCoarse %4 %14 +OpStore %19 %27 +%28 = OpDPdyCoarse %4 %14 +OpStore %22 %28 +%29 = OpFwidthCoarse %4 %14 +OpStore %24 %29 +%30 = OpDPdxFine %4 %14 +OpStore %19 %30 +%31 = OpDPdyFine %4 %14 +OpStore %22 %31 +%32 = OpFwidthFine %4 %14 +OpStore %24 %32 +%33 = OpDPdx %4 %14 +OpStore %19 %33 +%34 = OpDPdy %4 %14 +OpStore %22 %34 +%35 = OpFwidth %4 %14 +OpStore %24 %35 +%36 = OpFunctionCall %3 %7 +%37 = OpLoad %4 %19 +%38 = OpLoad %4 %22 +%39 = OpFAdd %4 %37 %38 +%40 = OpLoad %4 %24 +%41 = OpFMul %4 %39 %40 +OpStore %15 %41 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/struct-layout.spvasm b/naga/tests/out/spv/struct-layout.spvasm new file mode 100644 index 0000000000..fccaef5269 --- /dev/null +++ b/naga/tests/out/spv/struct-layout.spvasm @@ -0,0 +1,169 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 92 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %30 "no_padding_frag" %22 %25 %28 +OpEntryPoint Vertex %42 "no_padding_vert" %37 %39 %41 +OpEntryPoint GLCompute %45 "no_padding_comp" +OpEntryPoint Fragment %67 "needs_padding_frag" %60 %62 %64 %66 +OpEntryPoint Vertex %78 "needs_padding_vert" %71 %73 %75 %77 +OpEntryPoint GLCompute %81 "needs_padding_comp" +OpExecutionMode %30 OriginUpperLeft +OpExecutionMode %45 LocalSize 16 1 1 +OpExecutionMode %67 OriginUpperLeft +OpExecutionMode %81 LocalSize 16 1 1 +OpMemberDecorate %5 0 Offset 0 +OpMemberDecorate %5 1 Offset 12 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 16 +OpMemberDecorate %7 2 Offset 28 +OpDecorate %8 DescriptorSet 0 +OpDecorate %8 Binding 0 +OpDecorate %9 Block +OpMemberDecorate %9 0 Offset 0 +OpDecorate %11 DescriptorSet 0 +OpDecorate %11 Binding 1 +OpDecorate %12 Block +OpMemberDecorate %12 0 Offset 0 +OpDecorate %14 DescriptorSet 0 +OpDecorate %14 Binding 2 +OpDecorate %15 Block +OpMemberDecorate %15 0 Offset 0 +OpDecorate %17 DescriptorSet 0 +OpDecorate %17 Binding 3 +OpDecorate %18 Block +OpMemberDecorate %18 0 Offset 0 +OpDecorate %22 Location 0 +OpDecorate %25 Location 1 +OpDecorate %28 Location 0 +OpDecorate %37 Location 0 +OpDecorate %39 Location 1 +OpDecorate %41 BuiltIn Position +OpDecorate %60 Location 0 +OpDecorate %62 Location 1 +OpDecorate %64 Location 2 +OpDecorate %66 Location 0 +OpDecorate %71 Location 0 +OpDecorate %73 Location 1 +OpDecorate %75 Location 2 +OpDecorate %77 BuiltIn Position +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 3 +%5 = OpTypeStruct %3 %4 +%6 = OpTypeVector %4 4 +%7 = OpTypeStruct %4 %3 %4 +%9 = OpTypeStruct %5 +%10 = OpTypePointer Uniform %9 +%8 = OpVariable %10 Uniform +%12 = OpTypeStruct %5 +%13 = OpTypePointer StorageBuffer %12 +%11 = OpVariable %13 StorageBuffer +%15 = OpTypeStruct %7 +%16 = OpTypePointer Uniform %15 +%14 = OpVariable %16 Uniform +%18 = OpTypeStruct %7 +%19 = OpTypePointer StorageBuffer %18 +%17 = OpVariable %19 StorageBuffer +%23 = OpTypePointer Input %3 +%22 = OpVariable %23 Input +%26 = OpTypePointer Input %4 +%25 = OpVariable %26 Input +%29 = OpTypePointer Output %6 +%28 = OpVariable %29 Output +%31 = OpTypeFunction %2 +%32 = OpConstant %4 0.0 +%33 = OpConstantComposite %6 %32 %32 %32 %32 +%37 = OpVariable %23 Input +%39 = OpVariable %26 Input +%41 = OpVariable %29 Output +%46 = OpTypePointer Uniform %5 +%48 = OpTypeInt 32 0 +%47 = OpConstant %48 0 +%50 = OpTypePointer StorageBuffer %5 +%53 = OpTypePointer Function %5 +%54 = OpConstantNull %5 +%60 = OpVariable %26 Input +%62 = OpVariable %23 Input +%64 = OpVariable %26 Input +%66 = OpVariable %29 Output +%71 = OpVariable %26 Input +%73 = OpVariable %23 Input +%75 = OpVariable %26 Input +%77 = OpVariable %29 Output +%82 = OpTypePointer Uniform %7 +%84 = OpTypePointer StorageBuffer %7 +%87 = OpTypePointer Function %7 +%88 = OpConstantNull %7 +%30 = OpFunction %2 None %31 +%20 = OpLabel +%24 = OpLoad %3 %22 +%27 = OpLoad %4 %25 +%21 = OpCompositeConstruct %5 %24 %27 +OpBranch %34 +%34 = OpLabel +OpStore %28 %33 +OpReturn +OpFunctionEnd +%42 = OpFunction %2 None %31 +%35 = OpLabel +%38 = OpLoad %3 %37 +%40 = OpLoad %4 %39 +%36 = OpCompositeConstruct %5 %38 %40 +OpBranch %43 +%43 = OpLabel +OpStore %41 %33 +OpReturn +OpFunctionEnd +%45 = OpFunction %2 None %31 +%44 = OpLabel +%52 = OpVariable %53 Function %54 +%49 = OpAccessChain %46 %8 %47 +%51 = OpAccessChain %50 %11 %47 +OpBranch %55 +%55 = OpLabel +%56 = OpLoad %5 %49 +OpStore %52 %56 +%57 = OpLoad %5 %51 +OpStore %52 %57 +OpReturn +OpFunctionEnd +%67 = OpFunction %2 None %31 +%58 = OpLabel +%61 = OpLoad %4 %60 +%63 = OpLoad %3 %62 +%65 = OpLoad %4 %64 +%59 = OpCompositeConstruct %7 %61 %63 %65 +OpBranch %68 +%68 = OpLabel +OpStore %66 %33 +OpReturn +OpFunctionEnd +%78 = OpFunction %2 None %31 +%69 = OpLabel +%72 = OpLoad %4 %71 +%74 = OpLoad %3 %73 +%76 = OpLoad %4 %75 +%70 = OpCompositeConstruct %7 %72 %74 %76 +OpBranch %79 +%79 = OpLabel +OpStore %77 %33 +OpReturn +OpFunctionEnd +%81 = OpFunction %2 None %31 +%80 = OpLabel +%86 = OpVariable %87 Function %88 +%83 = OpAccessChain %82 %14 %47 +%85 = OpAccessChain %84 %17 %47 +OpBranch %89 +%89 = OpLabel +%90 = OpLoad %7 %83 +OpStore %86 %90 +%91 = OpLoad %7 %85 +OpStore %86 %91 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/texture-arg.spvasm b/naga/tests/out/spv/texture-arg.spvasm new file mode 100644 index 0000000000..88832eb193 --- /dev/null +++ b/naga/tests/out/spv/texture-arg.spvasm @@ -0,0 +1,59 @@ +; SPIR-V +; Version: 1.0 +; Generator: rspirv +; Bound: 34 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %28 "main" %26 +OpExecutionMode %28 OriginUpperLeft +OpName %8 "Texture" +OpName %10 "Sampler" +OpName %13 "Passed_Texture" +OpName %15 "Passed_Sampler" +OpName %17 "test" +OpName %28 "main" +OpDecorate %8 DescriptorSet 0 +OpDecorate %8 Binding 0 +OpDecorate %10 DescriptorSet 0 +OpDecorate %10 Binding 1 +OpDecorate %26 Location 0 +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeImage %4 2D 0 0 0 1 Unknown +%5 = OpTypeSampler +%6 = OpTypeVector %4 4 +%7 = OpTypeVector %4 2 +%9 = OpTypePointer UniformConstant %3 +%8 = OpVariable %9 UniformConstant +%11 = OpTypePointer UniformConstant %5 +%10 = OpVariable %11 UniformConstant +%18 = OpTypeFunction %6 %9 %11 +%19 = OpConstant %4 0.0 +%20 = OpConstantComposite %7 %19 %19 +%22 = OpTypeSampledImage %3 +%27 = OpTypePointer Output %6 +%26 = OpVariable %27 Output +%29 = OpTypeFunction %2 +%17 = OpFunction %6 None %18 +%13 = OpFunctionParameter %9 +%15 = OpFunctionParameter %11 +%12 = OpLabel +%14 = OpLoad %3 %13 +%16 = OpLoad %5 %15 +OpBranch %21 +%21 = OpLabel +%23 = OpSampledImage %22 %14 %16 +%24 = OpImageSampleImplicitLod %6 %23 %20 +OpReturnValue %24 +OpFunctionEnd +%28 = OpFunction %2 None %29 +%25 = OpLabel +%30 = OpLoad %3 %8 +%31 = OpLoad %5 %10 +OpBranch %32 +%32 = OpLabel +%33 = OpFunctionCall %6 %17 %8 %10 +OpStore %26 %33 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/workgroup-uniform-load.spvasm b/naga/tests/out/spv/workgroup-uniform-load.spvasm new file mode 100644 index 0000000000..87f212a799 --- /dev/null +++ b/naga/tests/out/spv/workgroup-uniform-load.spvasm @@ -0,0 +1,66 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 40 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %14 "test_workgroupUniformLoad" %11 %19 +OpExecutionMode %14 LocalSize 4 1 1 +OpDecorate %5 ArrayStride 4 +OpDecorate %11 BuiltIn WorkgroupId +OpDecorate %19 BuiltIn LocalInvocationId +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeInt 32 1 +%6 = OpConstant %3 128 +%5 = OpTypeArray %4 %6 +%7 = OpTypeVector %3 3 +%9 = OpTypePointer Workgroup %5 +%8 = OpVariable %9 Workgroup +%12 = OpTypePointer Input %7 +%11 = OpVariable %12 Input +%15 = OpTypeFunction %2 +%16 = OpConstant %4 10 +%18 = OpConstantNull %5 +%20 = OpTypePointer Input %7 +%19 = OpVariable %20 Input +%22 = OpConstantNull %7 +%24 = OpTypeBool +%23 = OpTypeVector %24 3 +%29 = OpConstant %3 2 +%30 = OpConstant %3 264 +%33 = OpTypePointer Workgroup %4 +%14 = OpFunction %2 None %15 +%10 = OpLabel +%13 = OpLoad %7 %11 +OpBranch %17 +%17 = OpLabel +%21 = OpLoad %7 %19 +%25 = OpIEqual %23 %21 %22 +%26 = OpAll %24 %25 +OpSelectionMerge %27 None +OpBranchConditional %26 %28 %27 +%28 = OpLabel +OpStore %8 %18 +OpBranch %27 +%27 = OpLabel +OpControlBarrier %29 %29 %30 +OpBranch %31 +%31 = OpLabel +%32 = OpCompositeExtract %3 %13 0 +OpControlBarrier %29 %29 %30 +%34 = OpAccessChain %33 %8 %32 +%35 = OpLoad %4 %34 +OpControlBarrier %29 %29 %30 +%36 = OpSGreaterThan %24 %35 %16 +OpSelectionMerge %37 None +OpBranchConditional %36 %38 %39 +%38 = OpLabel +OpControlBarrier %29 %29 %30 +OpReturn +%39 = OpLabel +OpReturn +%37 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/workgroup-var-init.spvasm b/naga/tests/out/spv/workgroup-var-init.spvasm new file mode 100644 index 0000000000..399f63855c --- /dev/null +++ b/naga/tests/out/spv/workgroup-var-init.spvasm @@ -0,0 +1,77 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 41 +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %17 "main" %25 +OpExecutionMode %17 LocalSize 1 1 1 +OpMemberName %10 0 "arr" +OpMemberName %10 1 "atom" +OpMemberName %10 2 "atom_arr" +OpName %10 "WStruct" +OpName %11 "w_mem" +OpName %13 "output" +OpName %17 "main" +OpDecorate %4 ArrayStride 4 +OpDecorate %7 ArrayStride 4 +OpDecorate %9 ArrayStride 32 +OpMemberDecorate %10 0 Offset 0 +OpMemberDecorate %10 1 Offset 2048 +OpMemberDecorate %10 2 Offset 2052 +OpDecorate %13 DescriptorSet 0 +OpDecorate %13 Binding 0 +OpDecorate %14 Block +OpMemberDecorate %14 0 Offset 0 +OpDecorate %25 BuiltIn LocalInvocationId +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%5 = OpConstant %3 512 +%4 = OpTypeArray %3 %5 +%6 = OpTypeInt 32 1 +%8 = OpConstant %3 8 +%7 = OpTypeArray %6 %8 +%9 = OpTypeArray %7 %8 +%10 = OpTypeStruct %4 %6 %9 +%12 = OpTypePointer Workgroup %10 +%11 = OpVariable %12 Workgroup +%14 = OpTypeStruct %4 +%15 = OpTypePointer StorageBuffer %14 +%13 = OpVariable %15 StorageBuffer +%18 = OpTypeFunction %2 +%19 = OpTypePointer StorageBuffer %4 +%20 = OpConstant %3 0 +%23 = OpConstantNull %10 +%24 = OpTypeVector %3 3 +%26 = OpTypePointer Input %24 +%25 = OpVariable %26 Input +%28 = OpConstantNull %24 +%30 = OpTypeBool +%29 = OpTypeVector %30 3 +%35 = OpConstant %3 2 +%36 = OpConstant %3 264 +%38 = OpTypePointer Workgroup %4 +%17 = OpFunction %2 None %18 +%16 = OpLabel +%21 = OpAccessChain %19 %13 %20 +OpBranch %22 +%22 = OpLabel +%27 = OpLoad %24 %25 +%31 = OpIEqual %29 %27 %28 +%32 = OpAll %30 %31 +OpSelectionMerge %33 None +OpBranchConditional %32 %34 %33 +%34 = OpLabel +OpStore %11 %23 +OpBranch %33 +%33 = OpLabel +OpControlBarrier %35 %35 %36 +OpBranch %37 +%37 = OpLabel +%39 = OpAccessChain %38 %11 %20 +%40 = OpLoad %4 %39 +OpStore %21 %40 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/wgsl/210-bevy-2d-shader.frag.wgsl b/naga/tests/out/wgsl/210-bevy-2d-shader.frag.wgsl new file mode 100644 index 0000000000..a800b0f856 --- /dev/null +++ b/naga/tests/out/wgsl/210-bevy-2d-shader.frag.wgsl @@ -0,0 +1,30 @@ +struct ColorMaterial_color { + Color: vec4, +} + +struct FragmentOutput { + @location(0) o_Target: vec4, +} + +var v_Uv_1: vec2; +var o_Target: vec4; +@group(1) @binding(0) +var global: ColorMaterial_color; + +fn main_1() { + var color: vec4; + + let _e4 = global.Color; + color = _e4; + let _e6 = color; + o_Target = _e6; + return; +} + +@fragment +fn main(@location(0) v_Uv: vec2) -> FragmentOutput { + v_Uv_1 = v_Uv; + main_1(); + let _e9 = o_Target; + return FragmentOutput(_e9); +} diff --git a/naga/tests/out/wgsl/210-bevy-2d-shader.vert.wgsl b/naga/tests/out/wgsl/210-bevy-2d-shader.vert.wgsl new file mode 100644 index 0000000000..5a8e54159a --- /dev/null +++ b/naga/tests/out/wgsl/210-bevy-2d-shader.vert.wgsl @@ -0,0 +1,54 @@ +struct Camera { + ViewProj: mat4x4, +} + +struct Transform { + Model: mat4x4, +} + +struct Sprite_size { + size: vec2, +} + +struct VertexOutput { + @location(0) v_Uv: vec2, + @builtin(position) member: vec4, +} + +var Vertex_Position_1: vec3; +var Vertex_Normal_1: vec3; +var Vertex_Uv_1: vec2; +var v_Uv: vec2; +@group(0) @binding(0) +var global: Camera; +@group(2) @binding(0) +var global_1: Transform; +@group(2) @binding(1) +var global_2: Sprite_size; +var gl_Position: vec4; + +fn main_1() { + var position: vec3; + + let _e10 = Vertex_Uv_1; + v_Uv = _e10; + let _e11 = Vertex_Position_1; + let _e12 = global_2.size; + position = (_e11 * vec3(_e12.x, _e12.y, 1f)); + let _e20 = global.ViewProj; + let _e21 = global_1.Model; + let _e23 = position; + gl_Position = ((_e20 * _e21) * vec4(_e23.x, _e23.y, _e23.z, 1f)); + return; +} + +@vertex +fn main(@location(0) Vertex_Position: vec3, @location(1) Vertex_Normal: vec3, @location(2) Vertex_Uv: vec2) -> VertexOutput { + Vertex_Position_1 = Vertex_Position; + Vertex_Normal_1 = Vertex_Normal; + Vertex_Uv_1 = Vertex_Uv; + main_1(); + let _e21 = v_Uv; + let _e23 = gl_Position; + return VertexOutput(_e21, _e23); +} diff --git a/naga/tests/out/wgsl/210-bevy-shader.vert.wgsl b/naga/tests/out/wgsl/210-bevy-shader.vert.wgsl new file mode 100644 index 0000000000..f929543ebf --- /dev/null +++ b/naga/tests/out/wgsl/210-bevy-shader.vert.wgsl @@ -0,0 +1,57 @@ +struct Camera { + ViewProj: mat4x4, +} + +struct Transform { + Model: mat4x4, +} + +struct VertexOutput { + @location(0) v_Position: vec3, + @location(1) v_Normal: vec3, + @location(2) v_Uv: vec2, + @builtin(position) member: vec4, +} + +var Vertex_Position_1: vec3; +var Vertex_Normal_1: vec3; +var Vertex_Uv_1: vec2; +var v_Position: vec3; +var v_Normal: vec3; +var v_Uv: vec2; +@group(0) @binding(0) +var global: Camera; +@group(2) @binding(0) +var global_1: Transform; +var gl_Position: vec4; + +fn main_1() { + let _e10 = global_1.Model; + let _e11 = Vertex_Normal_1; + v_Normal = (_e10 * vec4(_e11.x, _e11.y, _e11.z, 1f)).xyz; + let _e19 = global_1.Model; + let _e29 = Vertex_Normal_1; + v_Normal = (mat3x3(_e19[0].xyz, _e19[1].xyz, _e19[2].xyz) * _e29); + let _e31 = global_1.Model; + let _e32 = Vertex_Position_1; + v_Position = (_e31 * vec4(_e32.x, _e32.y, _e32.z, 1f)).xyz; + let _e40 = Vertex_Uv_1; + v_Uv = _e40; + let _e42 = global.ViewProj; + let _e43 = v_Position; + gl_Position = (_e42 * vec4(_e43.x, _e43.y, _e43.z, 1f)); + return; +} + +@vertex +fn main(@location(0) Vertex_Position: vec3, @location(1) Vertex_Normal: vec3, @location(2) Vertex_Uv: vec2) -> VertexOutput { + Vertex_Position_1 = Vertex_Position; + Vertex_Normal_1 = Vertex_Normal; + Vertex_Uv_1 = Vertex_Uv; + main_1(); + let _e23 = v_Position; + let _e25 = v_Normal; + let _e27 = v_Uv; + let _e29 = gl_Position; + return VertexOutput(_e23, _e25, _e27, _e29); +} diff --git a/naga/tests/out/wgsl/246-collatz.comp.wgsl b/naga/tests/out/wgsl/246-collatz.comp.wgsl new file mode 100644 index 0000000000..cd89fd831a --- /dev/null +++ b/naga/tests/out/wgsl/246-collatz.comp.wgsl @@ -0,0 +1,60 @@ +struct PrimeIndices { + indices: array, +} + +@group(0) @binding(0) +var global: PrimeIndices; +var gl_GlobalInvocationID: vec3; + +fn collatz_iterations(n: u32) -> u32 { + var n_1: u32; + var i: u32 = 0u; + + n_1 = n; + loop { + let _e7 = n_1; + if !((_e7 != 1u)) { + break; + } + { + let _e14 = n_1; + let _e15 = f32(_e14); + if ((_e15 - (floor((_e15 / 2f)) * 2f)) == 0f) { + { + let _e25 = n_1; + n_1 = (_e25 / 2u); + } + } else { + { + let _e30 = n_1; + n_1 = ((3u * _e30) + 1u); + } + } + let _e36 = i; + i = (_e36 + 1u); + } + } + let _e39 = i; + return _e39; +} + +fn main_1() { + var index: u32; + + let _e3 = gl_GlobalInvocationID; + index = _e3.x; + let _e6 = index; + let _e8 = index; + let _e11 = index; + let _e13 = global.indices[_e11]; + let _e14 = collatz_iterations(_e13); + global.indices[_e6] = _e14; + return; +} + +@compute @workgroup_size(1, 1, 1) +fn main(@builtin(global_invocation_id) param: vec3) { + gl_GlobalInvocationID = param; + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/277-casting.frag.wgsl b/naga/tests/out/wgsl/277-casting.frag.wgsl new file mode 100644 index 0000000000..c9ef2be74a --- /dev/null +++ b/naga/tests/out/wgsl/277-casting.frag.wgsl @@ -0,0 +1,11 @@ +fn main_1() { + var a: f32 = 1f; + + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/280-matrix-cast.frag.wgsl b/naga/tests/out/wgsl/280-matrix-cast.frag.wgsl new file mode 100644 index 0000000000..6cc18567cd --- /dev/null +++ b/naga/tests/out/wgsl/280-matrix-cast.frag.wgsl @@ -0,0 +1,10 @@ +fn main_1() { + var a: mat4x4 = mat4x4(vec4(1f, 0f, 0f, 0f), vec4(0f, 1f, 0f, 0f), vec4(0f, 0f, 1f, 0f), vec4(0f, 0f, 0f, 1f)); + +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/484-preprocessor-if.frag.wgsl b/naga/tests/out/wgsl/484-preprocessor-if.frag.wgsl new file mode 100644 index 0000000000..6b3c4ac973 --- /dev/null +++ b/naga/tests/out/wgsl/484-preprocessor-if.frag.wgsl @@ -0,0 +1,9 @@ +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/800-out-of-bounds-panic.vert.wgsl b/naga/tests/out/wgsl/800-out-of-bounds-panic.vert.wgsl new file mode 100644 index 0000000000..820ab6194c --- /dev/null +++ b/naga/tests/out/wgsl/800-out-of-bounds-panic.vert.wgsl @@ -0,0 +1,43 @@ +struct Globals { + view_matrix: mat4x4, +} + +struct VertexPushConstants { + world_matrix: mat4x4, +} + +struct VertexOutput { + @location(0) frag_color: vec4, + @builtin(position) member: vec4, +} + +@group(0) @binding(0) +var global: Globals; +var global_1: VertexPushConstants; +var position_1: vec2; +var color_1: vec4; +var frag_color: vec4; +var gl_Position: vec4; + +fn main_1() { + let _e7 = color_1; + frag_color = _e7; + let _e9 = global.view_matrix; + let _e10 = global_1.world_matrix; + let _e12 = position_1; + gl_Position = ((_e9 * _e10) * vec4(_e12.x, _e12.y, 0f, 1f)); + let _e20 = gl_Position; + let _e22 = gl_Position; + gl_Position.z = ((_e20.z + _e22.w) / 2f); + return; +} + +@vertex +fn main(@location(0) position: vec2, @location(1) color: vec4) -> VertexOutput { + position_1 = position; + color_1 = color; + main_1(); + let _e15 = frag_color; + let _e17 = gl_Position; + return VertexOutput(_e15, _e17); +} diff --git a/naga/tests/out/wgsl/896-push-constant.frag.wgsl b/naga/tests/out/wgsl/896-push-constant.frag.wgsl new file mode 100644 index 0000000000..729e35a43f --- /dev/null +++ b/naga/tests/out/wgsl/896-push-constant.frag.wgsl @@ -0,0 +1,15 @@ +struct PushConstants { + example: f32, +} + +var c: PushConstants; + +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/900-implicit-conversions.frag.wgsl b/naga/tests/out/wgsl/900-implicit-conversions.frag.wgsl new file mode 100644 index 0000000000..2c468dd929 --- /dev/null +++ b/naga/tests/out/wgsl/900-implicit-conversions.frag.wgsl @@ -0,0 +1,68 @@ +fn exact(a: f32) { + var a_1: f32; + + a_1 = a; + return; +} + +fn exact_1(a_2: i32) { + var a_3: i32; + + a_3 = a_2; + return; +} + +fn implicit(a_4: f32) { + var a_5: f32; + + a_5 = a_4; + return; +} + +fn implicit_1(a_6: i32) { + var a_7: i32; + + a_7 = a_6; + return; +} + +fn implicit_dims(v: f32) { + var v_1: f32; + + v_1 = v; + return; +} + +fn implicit_dims_1(v_2: vec2) { + var v_3: vec2; + + v_3 = v_2; + return; +} + +fn implicit_dims_2(v_4: vec3) { + var v_5: vec3; + + v_5 = v_4; + return; +} + +fn implicit_dims_3(v_6: vec4) { + var v_7: vec4; + + v_7 = v_6; + return; +} + +fn main_1() { + exact_1(1i); + implicit(1f); + implicit_dims_2(vec3(1f)); + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/901-lhs-field-select.frag.wgsl b/naga/tests/out/wgsl/901-lhs-field-select.frag.wgsl new file mode 100644 index 0000000000..ae45360074 --- /dev/null +++ b/naga/tests/out/wgsl/901-lhs-field-select.frag.wgsl @@ -0,0 +1,12 @@ +fn main_1() { + var a: vec4 = vec4(1f); + + a.x = 2f; + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/931-constant-emitting.frag.wgsl b/naga/tests/out/wgsl/931-constant-emitting.frag.wgsl new file mode 100644 index 0000000000..ac59a0c3ef --- /dev/null +++ b/naga/tests/out/wgsl/931-constant-emitting.frag.wgsl @@ -0,0 +1,15 @@ +const constant: i32 = 10i; + +fn function() -> f32 { + return 0f; +} + +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/932-for-loop-if.frag.wgsl b/naga/tests/out/wgsl/932-for-loop-if.frag.wgsl new file mode 100644 index 0000000000..bf6a281b49 --- /dev/null +++ b/naga/tests/out/wgsl/932-for-loop-if.frag.wgsl @@ -0,0 +1,23 @@ +fn main_1() { + var i: i32 = 0i; + + loop { + let _e2 = i; + if !((_e2 < 1i)) { + break; + } + { + } + continuing { + let _e6 = i; + i = (_e6 + 1i); + } + } + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/abstract-types-const.wgsl b/naga/tests/out/wgsl/abstract-types-const.wgsl new file mode 100644 index 0000000000..195247bbbc --- /dev/null +++ b/naga/tests/out/wgsl/abstract-types-const.wgsl @@ -0,0 +1,55 @@ +struct S { + f: f32, + i: i32, + u: u32, +} + +const xvupaiai: vec2 = vec2(42u, 43u); +const xvfpaiai: vec2 = vec2(44f, 45f); +const xvupuai: vec2 = vec2(42u, 43u); +const xvupaiu: vec2 = vec2(42u, 43u); +const xvuuai: vec2 = vec2(42u, 43u); +const xvuaiu: vec2 = vec2(42u, 43u); +const xmfpaiaiaiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +const xmfpafaiaiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +const xmfpaiafaiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +const xmfpaiaiafai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +const xmfpaiaiaiaf: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +const imfpaiaiaiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +const imfpafaiaiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +const imfpafafafaf: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +const ivispai: vec2 = vec2(1i); +const ivfspaf: vec2 = vec2(1f); +const ivis_ai: vec2 = vec2(1i); +const ivus_ai: vec2 = vec2(1u); +const ivfs_ai: vec2 = vec2(1f); +const ivfs_af: vec2 = vec2(1f); +const iafafaf: array = array(1f, 2f); +const iafaiai: array = array(1f, 2f); +const iafpafaf: array = array(1f, 2f); +const iafpaiaf: array = array(1f, 2f); +const iafpafai: array = array(1f, 2f); +const xafpafaf: array = array(1f, 2f); +const s_f_i_u: S = S(1f, 1i, 1u); +const s_f_iai: S = S(1f, 1i, 1u); +const s_fai_u: S = S(1f, 1i, 1u); +const s_faiai: S = S(1f, 1i, 1u); +const saf_i_u: S = S(1f, 1i, 1u); +const saf_iai: S = S(1f, 1i, 1u); +const safai_u: S = S(1f, 1i, 1u); +const safaiai: S = S(1f, 1i, 1u); +const ivfr_f_f: vec3 = vec3(vec2(1f, 2f), 3f); +const ivfr_f_af: vec3 = vec3(vec2(1f, 2f), 3f); +const ivfraf_f: vec3 = vec3(vec2(1f, 2f), 3f); +const ivfraf_af: vec3 = vec3(vec2(1f, 2f), 3f); +const ivf_fr_f: vec3 = vec3(1f, vec2(2f, 3f)); +const ivf_fraf: vec3 = vec3(1f, vec2(2f, 3f)); +const ivf_afr_f: vec3 = vec3(1f, vec2(2f, 3f)); +const ivf_afraf: vec3 = vec3(1f, vec2(2f, 3f)); +const ivfr_f_ai: vec3 = vec3(vec2(1f, 2f), 3f); +const ivfrai_f: vec3 = vec3(vec2(1f, 2f), 3f); +const ivfrai_ai: vec3 = vec3(vec2(1f, 2f), 3f); +const ivf_frai: vec3 = vec3(1f, vec2(2f, 3f)); +const ivf_air_f: vec3 = vec3(1f, vec2(2f, 3f)); +const ivf_airai: vec3 = vec3(1f, vec2(2f, 3f)); + diff --git a/naga/tests/out/wgsl/abstract-types-operators.wgsl b/naga/tests/out/wgsl/abstract-types-operators.wgsl new file mode 100644 index 0000000000..de2b073074 --- /dev/null +++ b/naga/tests/out/wgsl/abstract-types-operators.wgsl @@ -0,0 +1,84 @@ +const plus_fafaf_1: f32 = 3f; +const plus_fafai_1: f32 = 3f; +const plus_faf_f_1: f32 = 3f; +const plus_faiaf_1: f32 = 3f; +const plus_faiai_1: f32 = 3f; +const plus_fai_f_1: f32 = 3f; +const plus_f_faf_1: f32 = 3f; +const plus_f_fai_1: f32 = 3f; +const plus_f_f_f_1: f32 = 3f; +const plus_iaiai_1: i32 = 3i; +const plus_iai_i_1: i32 = 3i; +const plus_i_iai_1: i32 = 3i; +const plus_i_i_i_1: i32 = 3i; +const plus_uaiai_1: u32 = 3u; +const plus_uai_u_1: u32 = 3u; +const plus_u_uai_1: u32 = 3u; +const plus_u_u_u_1: u32 = 3u; +const bitflip_u_u: u32 = 0u; +const bitflip_uai: u32 = 0u; +const least_i32_: i32 = i32(-2147483648); +const least_f32_: f32 = -340282350000000000000000000000000000000f; +const wgpu_4492_: i32 = i32(-2147483648); +const wgpu_4492_2_: i32 = i32(-2147483648); + +var a: array; + +fn runtime_values() { + var f: f32 = 42f; + var i: i32 = 43i; + var u: u32 = 44u; + var plus_fafaf: f32 = 3f; + var plus_fafai: f32 = 3f; + var plus_faf_f: f32; + var plus_faiaf: f32 = 3f; + var plus_faiai: f32 = 3f; + var plus_fai_f: f32; + var plus_f_faf: f32; + var plus_f_fai: f32; + var plus_f_f_f: f32; + var plus_iaiai: i32 = 3i; + var plus_iai_i: i32; + var plus_i_iai: i32; + var plus_i_i_i: i32; + var plus_uaiai: u32 = 3u; + var plus_uai_u: u32; + var plus_u_uai: u32; + var plus_u_u_u: u32; + + let _e8 = f; + plus_faf_f = (1f + _e8); + let _e14 = f; + plus_fai_f = (1f + _e14); + let _e18 = f; + plus_f_faf = (_e18 + 2f); + let _e22 = f; + plus_f_fai = (_e22 + 2f); + let _e26 = f; + let _e27 = f; + plus_f_f_f = (_e26 + _e27); + let _e31 = i; + plus_iai_i = (1i + _e31); + let _e35 = i; + plus_i_iai = (_e35 + 2i); + let _e39 = i; + let _e40 = i; + plus_i_i_i = (_e39 + _e40); + let _e44 = u; + plus_uai_u = (1u + _e44); + let _e48 = u; + plus_u_uai = (_e48 + 2u); + let _e52 = u; + let _e53 = u; + plus_u_u_u = (_e52 + _e53); + return; +} + +fn wgpu_4445_() { + return; +} + +fn wgpu_4435_() { + let y = a[(1i - 1i)]; +} + diff --git a/naga/tests/out/wgsl/abstract-types-var.wgsl b/naga/tests/out/wgsl/abstract-types-var.wgsl new file mode 100644 index 0000000000..0533f19442 --- /dev/null +++ b/naga/tests/out/wgsl/abstract-types-var.wgsl @@ -0,0 +1,130 @@ +var xvipaiai_1: vec2 = vec2(42i, 43i); +var xvupaiai_1: vec2 = vec2(44u, 45u); +var xvfpaiai_1: vec2 = vec2(46f, 47f); +var xvupuai_2: vec2 = vec2(42u, 43u); +var xvupaiu_2: vec2 = vec2(42u, 43u); +var xvuuai_2: vec2 = vec2(42u, 43u); +var xvuaiu_2: vec2 = vec2(42u, 43u); +var xmfpaiaiaiai_1: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +var xmfpafaiaiai_1: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +var xmfpaiafaiai_1: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +var xmfpaiaiafai_1: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +var xmfpaiaiaiaf_1: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); +var xvispai_1: vec2 = vec2(1i); +var xvfspaf_1: vec2 = vec2(1f); +var xvis_ai_1: vec2 = vec2(1i); +var xvus_ai_1: vec2 = vec2(1u); +var xvfs_ai_1: vec2 = vec2(1f); +var xvfs_af_1: vec2 = vec2(1f); +var xafafaf_1: array = array(1f, 2f); +var xafaiai_1: array = array(1f, 2f); +var xafpaiai_1: array = array(1i, 2i); +var xafpaiaf_1: array = array(1f, 2f); +var xafpafai_1: array = array(1f, 2f); +var xafpafaf_1: array = array(1f, 2f); + +fn all_constant_arguments() { + var xvipaiai: vec2 = vec2(42i, 43i); + var xvupaiai: vec2 = vec2(44u, 45u); + var xvfpaiai: vec2 = vec2(46f, 47f); + var xvupuai: vec2 = vec2(42u, 43u); + var xvupaiu: vec2 = vec2(42u, 43u); + var xvuuai: vec2 = vec2(42u, 43u); + var xvuaiu: vec2 = vec2(42u, 43u); + var xmfpaiaiaiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var xmfpafaiaiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var xmfpaiafaiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var xmfpaiaiafai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var xmfpaiaiaiaf: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var xmfp_faiaiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var xmfpai_faiai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var xmfpaiai_fai: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var xmfpaiaiai_f: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var xvispai: vec2 = vec2(1i); + var xvfspaf: vec2 = vec2(1f); + var xvis_ai: vec2 = vec2(1i); + var xvus_ai: vec2 = vec2(1u); + var xvfs_ai: vec2 = vec2(1f); + var xvfs_af: vec2 = vec2(1f); + var xafafaf: array = array(1f, 2f); + var xaf_faf: array = array(1f, 2f); + var xafaf_f: array = array(1f, 2f); + var xafaiai: array = array(1f, 2f); + var xai_iai: array = array(1i, 2i); + var xaiai_i: array = array(1i, 2i); + var xaipaiai: array = array(1i, 2i); + var xafpaiai: array = array(1f, 2f); + var xafpaiaf: array = array(1f, 2f); + var xafpafai: array = array(1f, 2f); + var xafpafaf: array = array(1f, 2f); + +} + +fn mixed_constant_and_runtime_arguments() { + var u: u32; + var i: i32; + var f: f32; + var xvupuai_1: vec2; + var xvupaiu_1: vec2; + var xvuuai_1: vec2; + var xvuaiu_1: vec2; + var xmfp_faiaiai_1: mat2x2; + var xmfpai_faiai_1: mat2x2; + var xmfpaiai_fai_1: mat2x2; + var xmfpaiaiai_f_1: mat2x2; + var xaf_faf_1: array; + var xafaf_f_1: array; + var xaf_fai: array; + var xafai_f: array; + var xai_iai_1: array; + var xaiai_i_1: array; + var xafp_faf: array; + var xafpaf_f: array; + var xafp_fai: array; + var xafpai_f: array; + var xaip_iai: array; + var xaipai_i: array; + + let _e3 = u; + xvupuai_1 = vec2(_e3, 43u); + let _e7 = u; + xvupaiu_1 = vec2(42u, _e7); + let _e11 = u; + xvuuai_1 = vec2(_e11, 43u); + let _e15 = u; + xvuaiu_1 = vec2(42u, _e15); + let _e19 = f; + xmfp_faiaiai_1 = mat2x2(vec2(_e19, 2f), vec2(3f, 4f)); + let _e27 = f; + xmfpai_faiai_1 = mat2x2(vec2(1f, _e27), vec2(3f, 4f)); + let _e35 = f; + xmfpaiai_fai_1 = mat2x2(vec2(1f, 2f), vec2(_e35, 4f)); + let _e43 = f; + xmfpaiaiai_f_1 = mat2x2(vec2(1f, 2f), vec2(3f, _e43)); + let _e51 = f; + xaf_faf_1 = array(_e51, 2f); + let _e55 = f; + xafaf_f_1 = array(1f, _e55); + let _e59 = f; + xaf_fai = array(_e59, 2f); + let _e63 = f; + xafai_f = array(1f, _e63); + let _e67 = i; + xai_iai_1 = array(_e67, 2i); + let _e71 = i; + xaiai_i_1 = array(1i, _e71); + let _e75 = f; + xafp_faf = array(_e75, 2f); + let _e79 = f; + xafpaf_f = array(1f, _e79); + let _e83 = f; + xafp_fai = array(_e83, 2f); + let _e87 = f; + xafpai_f = array(1f, _e87); + let _e91 = i; + xaip_iai = array(_e91, 2i); + let _e95 = i; + xaipai_i = array(1i, _e95); + return; +} + diff --git a/naga/tests/out/wgsl/access.wgsl b/naga/tests/out/wgsl/access.wgsl new file mode 100644 index 0000000000..1409e80b11 --- /dev/null +++ b/naga/tests/out/wgsl/access.wgsl @@ -0,0 +1,170 @@ +struct GlobalConst { + a: u32, + b: vec3, + c: i32, +} + +struct AlignedWrapper { + value: i32, +} + +struct Bar { + _matrix: mat4x3, + matrix_array: array, 2>, + atom: atomic, + atom_arr: array, 10>, + arr: array, 2>, + data: array, +} + +struct Baz { + m: mat3x2, +} + +struct MatCx2InArray { + am: array, 2>, +} + +var global_const: GlobalConst = GlobalConst(0u, vec3(0u, 0u, 0u), 0i); +@group(0) @binding(0) +var bar: Bar; +@group(0) @binding(1) +var baz: Baz; +@group(0) @binding(2) +var qux: vec2; +@group(0) @binding(3) +var nested_mat_cx2_: MatCx2InArray; + +fn test_matrix_within_struct_accesses() { + var idx: i32 = 1i; + var t: Baz = Baz(mat3x2(vec2(1f), vec2(2f), vec2(3f))); + + let _e3 = idx; + idx = (_e3 - 1i); + let l0_ = baz.m; + let l1_ = baz.m[0]; + let _e14 = idx; + let l2_ = baz.m[_e14]; + let l3_ = baz.m[0][1]; + let _e25 = idx; + let l4_ = baz.m[0][_e25]; + let _e30 = idx; + let l5_ = baz.m[_e30][1]; + let _e36 = idx; + let _e38 = idx; + let l6_ = baz.m[_e36][_e38]; + let _e51 = idx; + idx = (_e51 + 1i); + t.m = mat3x2(vec2(6f), vec2(5f), vec2(4f)); + t.m[0] = vec2(9f); + let _e66 = idx; + t.m[_e66] = vec2(90f); + t.m[0][1] = 10f; + let _e76 = idx; + t.m[0][_e76] = 20f; + let _e80 = idx; + t.m[_e80][1] = 30f; + let _e85 = idx; + let _e87 = idx; + t.m[_e85][_e87] = 40f; + return; +} + +fn test_matrix_within_array_within_struct_accesses() { + var idx_1: i32 = 1i; + var t_1: MatCx2InArray = MatCx2InArray(array, 2>()); + + let _e3 = idx_1; + idx_1 = (_e3 - 1i); + let l0_1 = nested_mat_cx2_.am; + let l1_1 = nested_mat_cx2_.am[0]; + let l2_1 = nested_mat_cx2_.am[0][0]; + let _e20 = idx_1; + let l3_1 = nested_mat_cx2_.am[0][_e20]; + let l4_1 = nested_mat_cx2_.am[0][0][1]; + let _e33 = idx_1; + let l5_1 = nested_mat_cx2_.am[0][0][_e33]; + let _e39 = idx_1; + let l6_1 = nested_mat_cx2_.am[0][_e39][1]; + let _e46 = idx_1; + let _e48 = idx_1; + let l7_ = nested_mat_cx2_.am[0][_e46][_e48]; + let _e55 = idx_1; + idx_1 = (_e55 + 1i); + t_1.am = array, 2>(); + t_1.am[0] = mat4x2(vec2(8f), vec2(7f), vec2(6f), vec2(5f)); + t_1.am[0][0] = vec2(9f); + let _e77 = idx_1; + t_1.am[0][_e77] = vec2(90f); + t_1.am[0][0][1] = 10f; + let _e89 = idx_1; + t_1.am[0][0][_e89] = 20f; + let _e94 = idx_1; + t_1.am[0][_e94][1] = 30f; + let _e100 = idx_1; + let _e102 = idx_1; + t_1.am[0][_e100][_e102] = 40f; + return; +} + +fn read_from_private(foo_1: ptr) -> f32 { + let _e1 = (*foo_1); + return _e1; +} + +fn test_arr_as_arg(a: array, 5>) -> f32 { + return a[4][9]; +} + +fn assign_through_ptr_fn(p: ptr) { + (*p) = 42u; + return; +} + +fn assign_array_through_ptr_fn(foo_2: ptr, 2>>) { + (*foo_2) = array, 2>(vec4(1f), vec4(2f)); + return; +} + +@vertex +fn foo_vert(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4 { + var foo: f32 = 0f; + var c2_: array; + + let baz_1 = foo; + foo = 1f; + test_matrix_within_struct_accesses(); + test_matrix_within_array_within_struct_accesses(); + let _matrix = bar._matrix; + let arr_1 = bar.arr; + let b = bar._matrix[3u][0]; + let a_1 = bar.data[(arrayLength((&bar.data)) - 2u)].value; + let c = qux; + let data_pointer = (&bar.data[0].value); + let _e33 = read_from_private((&foo)); + c2_ = array(a_1, i32(b), 3i, 4i, 5i); + c2_[(vi + 1u)] = 42i; + let value = c2_[vi]; + let _e47 = test_arr_as_arg(array, 5>()); + return vec4((_matrix * vec4(vec4(value))), 2f); +} + +@fragment +fn foo_frag() -> @location(0) vec4 { + bar._matrix[1][2] = 1f; + bar._matrix = mat4x3(vec3(0f), vec3(1f), vec3(2f), vec3(3f)); + bar.arr = array, 2>(vec2(0u), vec2(1u)); + bar.data[1].value = 1i; + qux = vec2(); + return vec4(0f); +} + +@compute @workgroup_size(1, 1, 1) +fn assign_through_ptr() { + var val: u32 = 33u; + var arr: array, 2> = array, 2>(vec4(6f), vec4(7f)); + + assign_through_ptr_fn((&val)); + assign_array_through_ptr_fn((&arr)); + return; +} diff --git a/naga/tests/out/wgsl/array-in-ctor.wgsl b/naga/tests/out/wgsl/array-in-ctor.wgsl new file mode 100644 index 0000000000..8c17731f0c --- /dev/null +++ b/naga/tests/out/wgsl/array-in-ctor.wgsl @@ -0,0 +1,11 @@ +struct Ah { + inner: array, +} + +@group(0) @binding(0) +var ah: Ah; + +@compute @workgroup_size(1, 1, 1) +fn cs_main() { + let ah_1 = ah; +} diff --git a/naga/tests/out/wgsl/array-in-function-return-type.wgsl b/naga/tests/out/wgsl/array-in-function-return-type.wgsl new file mode 100644 index 0000000000..2beacd3ff4 --- /dev/null +++ b/naga/tests/out/wgsl/array-in-function-return-type.wgsl @@ -0,0 +1,9 @@ +fn ret_array() -> array { + return array(1f, 2f); +} + +@fragment +fn main() -> @location(0) vec4 { + let _e0 = ret_array(); + return vec4(_e0[0], _e0[1], 0f, 1f); +} diff --git a/naga/tests/out/wgsl/atomicCompareExchange.wgsl b/naga/tests/out/wgsl/atomicCompareExchange.wgsl new file mode 100644 index 0000000000..17bd5144b1 --- /dev/null +++ b/naga/tests/out/wgsl/atomicCompareExchange.wgsl @@ -0,0 +1,90 @@ +const SIZE: u32 = 128u; + +@group(0) @binding(0) +var arr_i32_: array, 128>; +@group(0) @binding(1) +var arr_u32_: array, 128>; + +@compute @workgroup_size(1, 1, 1) +fn test_atomic_compare_exchange_i32_() { + var i: u32 = 0u; + var old: i32; + var exchanged: bool; + + loop { + let _e2 = i; + if (_e2 < SIZE) { + } else { + break; + } + { + let _e6 = i; + let _e8 = atomicLoad((&arr_i32_[_e6])); + old = _e8; + exchanged = false; + loop { + let _e12 = exchanged; + if !(_e12) { + } else { + break; + } + { + let _e14 = old; + let new_ = bitcast((bitcast(_e14) + 1f)); + let _e20 = i; + let _e22 = old; + let _e23 = atomicCompareExchangeWeak((&arr_i32_[_e20]), _e22, new_); + old = _e23.old_value; + exchanged = _e23.exchanged; + } + } + } + continuing { + let _e27 = i; + i = (_e27 + 1u); + } + } + return; +} + +@compute @workgroup_size(1, 1, 1) +fn test_atomic_compare_exchange_u32_() { + var i_1: u32 = 0u; + var old_1: u32; + var exchanged_1: bool; + + loop { + let _e2 = i_1; + if (_e2 < SIZE) { + } else { + break; + } + { + let _e6 = i_1; + let _e8 = atomicLoad((&arr_u32_[_e6])); + old_1 = _e8; + exchanged_1 = false; + loop { + let _e12 = exchanged_1; + if !(_e12) { + } else { + break; + } + { + let _e14 = old_1; + let new_1 = bitcast((bitcast(_e14) + 1f)); + let _e20 = i_1; + let _e22 = old_1; + let _e23 = atomicCompareExchangeWeak((&arr_u32_[_e20]), _e22, new_1); + old_1 = _e23.old_value; + exchanged_1 = _e23.exchanged; + } + } + } + continuing { + let _e27 = i_1; + i_1 = (_e27 + 1u); + } + } + return; +} diff --git a/naga/tests/out/wgsl/atomicOps.wgsl b/naga/tests/out/wgsl/atomicOps.wgsl new file mode 100644 index 0000000000..be102e6833 --- /dev/null +++ b/naga/tests/out/wgsl/atomicOps.wgsl @@ -0,0 +1,107 @@ +struct Struct { + atomic_scalar: atomic, + atomic_arr: array, 2>, +} + +@group(0) @binding(0) +var storage_atomic_scalar: atomic; +@group(0) @binding(1) +var storage_atomic_arr: array, 2>; +@group(0) @binding(2) +var storage_struct: Struct; +var workgroup_atomic_scalar: atomic; +var workgroup_atomic_arr: array, 2>; +var workgroup_struct: Struct; + +@compute @workgroup_size(2, 1, 1) +fn cs_main(@builtin(local_invocation_id) id: vec3) { + atomicStore((&storage_atomic_scalar), 1u); + atomicStore((&storage_atomic_arr[1]), 1i); + atomicStore((&storage_struct.atomic_scalar), 1u); + atomicStore((&storage_struct.atomic_arr[1]), 1i); + atomicStore((&workgroup_atomic_scalar), 1u); + atomicStore((&workgroup_atomic_arr[1]), 1i); + atomicStore((&workgroup_struct.atomic_scalar), 1u); + atomicStore((&workgroup_struct.atomic_arr[1]), 1i); + workgroupBarrier(); + let l0_ = atomicLoad((&storage_atomic_scalar)); + let l1_ = atomicLoad((&storage_atomic_arr[1])); + let l2_ = atomicLoad((&storage_struct.atomic_scalar)); + let l3_ = atomicLoad((&storage_struct.atomic_arr[1])); + let l4_ = atomicLoad((&workgroup_atomic_scalar)); + let l5_ = atomicLoad((&workgroup_atomic_arr[1])); + let l6_ = atomicLoad((&workgroup_struct.atomic_scalar)); + let l7_ = atomicLoad((&workgroup_struct.atomic_arr[1])); + workgroupBarrier(); + let _e51 = atomicAdd((&storage_atomic_scalar), 1u); + let _e55 = atomicAdd((&storage_atomic_arr[1]), 1i); + let _e59 = atomicAdd((&storage_struct.atomic_scalar), 1u); + let _e64 = atomicAdd((&storage_struct.atomic_arr[1]), 1i); + let _e67 = atomicAdd((&workgroup_atomic_scalar), 1u); + let _e71 = atomicAdd((&workgroup_atomic_arr[1]), 1i); + let _e75 = atomicAdd((&workgroup_struct.atomic_scalar), 1u); + let _e80 = atomicAdd((&workgroup_struct.atomic_arr[1]), 1i); + workgroupBarrier(); + let _e83 = atomicSub((&storage_atomic_scalar), 1u); + let _e87 = atomicSub((&storage_atomic_arr[1]), 1i); + let _e91 = atomicSub((&storage_struct.atomic_scalar), 1u); + let _e96 = atomicSub((&storage_struct.atomic_arr[1]), 1i); + let _e99 = atomicSub((&workgroup_atomic_scalar), 1u); + let _e103 = atomicSub((&workgroup_atomic_arr[1]), 1i); + let _e107 = atomicSub((&workgroup_struct.atomic_scalar), 1u); + let _e112 = atomicSub((&workgroup_struct.atomic_arr[1]), 1i); + workgroupBarrier(); + let _e115 = atomicMax((&storage_atomic_scalar), 1u); + let _e119 = atomicMax((&storage_atomic_arr[1]), 1i); + let _e123 = atomicMax((&storage_struct.atomic_scalar), 1u); + let _e128 = atomicMax((&storage_struct.atomic_arr[1]), 1i); + let _e131 = atomicMax((&workgroup_atomic_scalar), 1u); + let _e135 = atomicMax((&workgroup_atomic_arr[1]), 1i); + let _e139 = atomicMax((&workgroup_struct.atomic_scalar), 1u); + let _e144 = atomicMax((&workgroup_struct.atomic_arr[1]), 1i); + workgroupBarrier(); + let _e147 = atomicMin((&storage_atomic_scalar), 1u); + let _e151 = atomicMin((&storage_atomic_arr[1]), 1i); + let _e155 = atomicMin((&storage_struct.atomic_scalar), 1u); + let _e160 = atomicMin((&storage_struct.atomic_arr[1]), 1i); + let _e163 = atomicMin((&workgroup_atomic_scalar), 1u); + let _e167 = atomicMin((&workgroup_atomic_arr[1]), 1i); + let _e171 = atomicMin((&workgroup_struct.atomic_scalar), 1u); + let _e176 = atomicMin((&workgroup_struct.atomic_arr[1]), 1i); + workgroupBarrier(); + let _e179 = atomicAnd((&storage_atomic_scalar), 1u); + let _e183 = atomicAnd((&storage_atomic_arr[1]), 1i); + let _e187 = atomicAnd((&storage_struct.atomic_scalar), 1u); + let _e192 = atomicAnd((&storage_struct.atomic_arr[1]), 1i); + let _e195 = atomicAnd((&workgroup_atomic_scalar), 1u); + let _e199 = atomicAnd((&workgroup_atomic_arr[1]), 1i); + let _e203 = atomicAnd((&workgroup_struct.atomic_scalar), 1u); + let _e208 = atomicAnd((&workgroup_struct.atomic_arr[1]), 1i); + workgroupBarrier(); + let _e211 = atomicOr((&storage_atomic_scalar), 1u); + let _e215 = atomicOr((&storage_atomic_arr[1]), 1i); + let _e219 = atomicOr((&storage_struct.atomic_scalar), 1u); + let _e224 = atomicOr((&storage_struct.atomic_arr[1]), 1i); + let _e227 = atomicOr((&workgroup_atomic_scalar), 1u); + let _e231 = atomicOr((&workgroup_atomic_arr[1]), 1i); + let _e235 = atomicOr((&workgroup_struct.atomic_scalar), 1u); + let _e240 = atomicOr((&workgroup_struct.atomic_arr[1]), 1i); + workgroupBarrier(); + let _e243 = atomicXor((&storage_atomic_scalar), 1u); + let _e247 = atomicXor((&storage_atomic_arr[1]), 1i); + let _e251 = atomicXor((&storage_struct.atomic_scalar), 1u); + let _e256 = atomicXor((&storage_struct.atomic_arr[1]), 1i); + let _e259 = atomicXor((&workgroup_atomic_scalar), 1u); + let _e263 = atomicXor((&workgroup_atomic_arr[1]), 1i); + let _e267 = atomicXor((&workgroup_struct.atomic_scalar), 1u); + let _e272 = atomicXor((&workgroup_struct.atomic_arr[1]), 1i); + let _e275 = atomicExchange((&storage_atomic_scalar), 1u); + let _e279 = atomicExchange((&storage_atomic_arr[1]), 1i); + let _e283 = atomicExchange((&storage_struct.atomic_scalar), 1u); + let _e288 = atomicExchange((&storage_struct.atomic_arr[1]), 1i); + let _e291 = atomicExchange((&workgroup_atomic_scalar), 1u); + let _e295 = atomicExchange((&workgroup_atomic_arr[1]), 1i); + let _e299 = atomicExchange((&workgroup_struct.atomic_scalar), 1u); + let _e304 = atomicExchange((&workgroup_struct.atomic_arr[1]), 1i); + return; +} diff --git a/naga/tests/out/wgsl/bevy-pbr.frag.wgsl b/naga/tests/out/wgsl/bevy-pbr.frag.wgsl new file mode 100644 index 0000000000..81d69d0736 --- /dev/null +++ b/naga/tests/out/wgsl/bevy-pbr.frag.wgsl @@ -0,0 +1,933 @@ +struct PointLight { + pos: vec4, + color: vec4, + lightParams: vec4, +} + +struct DirectionalLight { + direction: vec4, + color: vec4, +} + +struct CameraViewProj { + ViewProj: mat4x4, +} + +struct CameraPosition { + CameraPos: vec4, +} + +struct Lights { + AmbientColor: vec4, + NumLights: vec4, + PointLights: array, + DirectionalLights: array, +} + +struct StandardMaterial_base_color { + base_color: vec4, +} + +struct StandardMaterial_roughness { + perceptual_roughness: f32, +} + +struct StandardMaterial_metallic { + metallic: f32, +} + +struct StandardMaterial_reflectance { + reflectance: f32, +} + +struct StandardMaterial_emissive { + emissive: vec4, +} + +struct FragmentOutput { + @location(0) o_Target: vec4, +} + +const MAX_POINT_LIGHTS: i32 = 10i; +const MAX_DIRECTIONAL_LIGHTS: i32 = 1i; +const PI: f32 = 3.1415927f; + +var v_WorldPosition_1: vec3; +var v_WorldNormal_1: vec3; +var v_Uv_1: vec2; +var v_WorldTangent_1: vec4; +var o_Target: vec4; +@group(0) @binding(0) +var global: CameraViewProj; +@group(0) @binding(1) +var global_1: CameraPosition; +@group(1) @binding(0) +var global_2: Lights; +@group(3) @binding(0) +var global_3: StandardMaterial_base_color; +@group(3) @binding(1) +var StandardMaterial_base_color_texture: texture_2d; +@group(3) @binding(2) +var StandardMaterial_base_color_texture_sampler: sampler; +@group(3) @binding(3) +var global_4: StandardMaterial_roughness; +@group(3) @binding(4) +var global_5: StandardMaterial_metallic; +@group(3) @binding(5) +var StandardMaterial_metallic_roughness_texture: texture_2d; +@group(3) @binding(6) +var StandardMaterial_metallic_roughness_texture_sampler: sampler; +@group(3) @binding(7) +var global_6: StandardMaterial_reflectance; +@group(3) @binding(8) +var StandardMaterial_normal_map: texture_2d; +@group(3) @binding(9) +var StandardMaterial_normal_map_sampler: sampler; +@group(3) @binding(10) +var StandardMaterial_occlusion_texture: texture_2d; +@group(3) @binding(11) +var StandardMaterial_occlusion_texture_sampler: sampler; +@group(3) @binding(12) +var global_7: StandardMaterial_emissive; +@group(3) @binding(13) +var StandardMaterial_emissive_texture: texture_2d; +@group(3) @binding(14) +var StandardMaterial_emissive_texture_sampler: sampler; +var gl_FrontFacing: bool; + +fn pow5_(x: f32) -> f32 { + var x_1: f32; + var x2_: f32; + + x_1 = x; + let _e42 = x_1; + let _e43 = x_1; + x2_ = (_e42 * _e43); + let _e46 = x2_; + let _e47 = x2_; + let _e49 = x_1; + return ((_e46 * _e47) * _e49); +} + +fn getDistanceAttenuation(distanceSquare: f32, inverseRangeSquared: f32) -> f32 { + var distanceSquare_1: f32; + var inverseRangeSquared_1: f32; + var factor: f32; + var smoothFactor: f32; + var attenuation: f32; + + distanceSquare_1 = distanceSquare; + inverseRangeSquared_1 = inverseRangeSquared; + let _e44 = distanceSquare_1; + let _e45 = inverseRangeSquared_1; + factor = (_e44 * _e45); + let _e49 = factor; + let _e50 = factor; + let _e56 = factor; + let _e57 = factor; + smoothFactor = clamp((1f - (_e56 * _e57)), 0f, 1f); + let _e64 = smoothFactor; + let _e65 = smoothFactor; + attenuation = (_e64 * _e65); + let _e68 = attenuation; + let _e73 = distanceSquare_1; + return ((_e68 * 1f) / max(_e73, 0.001f)); +} + +fn D_GGX(roughness: f32, NoH: f32, h: vec3) -> f32 { + var roughness_1: f32; + var NoH_1: f32; + var oneMinusNoHSquared: f32; + var a: f32; + var k: f32; + var d: f32; + + roughness_1 = roughness; + NoH_1 = NoH; + let _e46 = NoH_1; + let _e47 = NoH_1; + oneMinusNoHSquared = (1f - (_e46 * _e47)); + let _e51 = NoH_1; + let _e52 = roughness_1; + a = (_e51 * _e52); + let _e55 = roughness_1; + let _e56 = oneMinusNoHSquared; + let _e57 = a; + let _e58 = a; + k = (_e55 / (_e56 + (_e57 * _e58))); + let _e63 = k; + let _e64 = k; + d = ((_e63 * _e64) * 0.31830987f); + let _e71 = d; + return _e71; +} + +fn V_SmithGGXCorrelated(roughness_2: f32, NoV: f32, NoL: f32) -> f32 { + var roughness_3: f32; + var NoV_1: f32; + var NoL_1: f32; + var a2_: f32; + var lambdaV: f32; + var lambdaL: f32; + var v: f32; + + roughness_3 = roughness_2; + NoV_1 = NoV; + NoL_1 = NoL; + let _e46 = roughness_3; + let _e47 = roughness_3; + a2_ = (_e46 * _e47); + let _e50 = NoL_1; + let _e51 = NoV_1; + let _e52 = a2_; + let _e53 = NoV_1; + let _e56 = NoV_1; + let _e58 = a2_; + let _e60 = NoV_1; + let _e61 = a2_; + let _e62 = NoV_1; + let _e65 = NoV_1; + let _e67 = a2_; + lambdaV = (_e50 * sqrt((((_e60 - (_e61 * _e62)) * _e65) + _e67))); + let _e72 = NoV_1; + let _e73 = NoL_1; + let _e74 = a2_; + let _e75 = NoL_1; + let _e78 = NoL_1; + let _e80 = a2_; + let _e82 = NoL_1; + let _e83 = a2_; + let _e84 = NoL_1; + let _e87 = NoL_1; + let _e89 = a2_; + lambdaL = (_e72 * sqrt((((_e82 - (_e83 * _e84)) * _e87) + _e89))); + let _e95 = lambdaV; + let _e96 = lambdaL; + v = (0.5f / (_e95 + _e96)); + let _e100 = v; + return _e100; +} + +fn F_Schlick(f0_: vec3, f90_: f32, VoH: f32) -> vec3 { + var f90_1: f32; + var VoH_1: f32; + + f90_1 = f90_; + VoH_1 = VoH; + let _e45 = f90_1; + let _e49 = VoH_1; + let _e52 = VoH_1; + let _e54 = pow5_((1f - _e52)); + return (f0_ + ((vec3(_e45) - f0_) * _e54)); +} + +fn F_Schlick_1(f0_1: f32, f90_2: f32, VoH_2: f32) -> f32 { + var f0_2: f32; + var f90_3: f32; + var VoH_3: f32; + + f0_2 = f0_1; + f90_3 = f90_2; + VoH_3 = VoH_2; + let _e46 = f0_2; + let _e47 = f90_3; + let _e48 = f0_2; + let _e51 = VoH_3; + let _e54 = VoH_3; + let _e56 = pow5_((1f - _e54)); + return (_e46 + ((_e47 - _e48) * _e56)); +} + +fn fresnel(f0_3: vec3, LoH: f32) -> vec3 { + var f0_4: vec3; + var LoH_1: f32; + var f90_4: f32; + + f0_4 = f0_3; + LoH_1 = LoH; + let _e49 = f0_4; + let _e62 = f0_4; + f90_4 = clamp(dot(_e62, vec3(16.5f)), 0f, 1f); + let _e75 = f0_4; + let _e76 = f90_4; + let _e77 = LoH_1; + let _e78 = F_Schlick(_e75, _e76, _e77); + return _e78; +} + +fn specular(f0_5: vec3, roughness_4: f32, h_1: vec3, NoV_2: f32, NoL_2: f32, NoH_2: f32, LoH_2: f32, specularIntensity: f32) -> vec3 { + var f0_6: vec3; + var roughness_5: f32; + var NoV_3: f32; + var NoL_3: f32; + var NoH_3: f32; + var LoH_3: f32; + var specularIntensity_1: f32; + var D: f32; + var V: f32; + var F: vec3; + + f0_6 = f0_5; + roughness_5 = roughness_4; + NoV_3 = NoV_2; + NoL_3 = NoL_2; + NoH_3 = NoH_2; + LoH_3 = LoH_2; + specularIntensity_1 = specularIntensity; + let _e57 = roughness_5; + let _e58 = NoH_3; + let _e59 = D_GGX(_e57, _e58, h_1); + D = _e59; + let _e64 = roughness_5; + let _e65 = NoV_3; + let _e66 = NoL_3; + let _e67 = V_SmithGGXCorrelated(_e64, _e65, _e66); + V = _e67; + let _e71 = f0_6; + let _e72 = LoH_3; + let _e73 = fresnel(_e71, _e72); + F = _e73; + let _e75 = specularIntensity_1; + let _e76 = D; + let _e78 = V; + let _e80 = F; + return (((_e75 * _e76) * _e78) * _e80); +} + +fn Fd_Burley(roughness_6: f32, NoV_4: f32, NoL_4: f32, LoH_4: f32) -> f32 { + var roughness_7: f32; + var NoV_5: f32; + var NoL_5: f32; + var LoH_5: f32; + var f90_5: f32; + var lightScatter: f32; + var viewScatter: f32; + + roughness_7 = roughness_6; + NoV_5 = NoV_4; + NoL_5 = NoL_4; + LoH_5 = LoH_4; + let _e50 = roughness_7; + let _e52 = LoH_5; + let _e54 = LoH_5; + f90_5 = (0.5f + (((2f * _e50) * _e52) * _e54)); + let _e62 = f90_5; + let _e63 = NoL_5; + let _e64 = F_Schlick_1(1f, _e62, _e63); + lightScatter = _e64; + let _e70 = f90_5; + let _e71 = NoV_5; + let _e72 = F_Schlick_1(1f, _e70, _e71); + viewScatter = _e72; + let _e74 = lightScatter; + let _e75 = viewScatter; + return ((_e74 * _e75) * 0.31830987f); +} + +fn EnvBRDFApprox(f0_7: vec3, perceptual_roughness: f32, NoV_6: f32) -> vec3 { + var f0_8: vec3; + var perceptual_roughness_1: f32; + var NoV_7: f32; + var c0_: vec4 = vec4(-1f, -0.0275f, -0.572f, 0.022f); + var c1_: vec4 = vec4(1f, 0.0425f, 1.04f, -0.04f); + var r: vec4; + var a004_: f32; + var AB: vec2; + + f0_8 = f0_7; + perceptual_roughness_1 = perceptual_roughness; + NoV_7 = NoV_6; + let _e62 = perceptual_roughness_1; + let _e64 = c0_; + let _e66 = c1_; + r = ((vec4(_e62) * _e64) + _e66); + let _e69 = r; + let _e71 = r; + let _e76 = NoV_7; + let _e80 = NoV_7; + let _e83 = r; + let _e85 = r; + let _e90 = NoV_7; + let _e94 = NoV_7; + let _e98 = r; + let _e101 = r; + a004_ = ((min((_e83.x * _e85.x), exp2((-9.28f * _e94))) * _e98.x) + _e101.y); + let _e109 = a004_; + let _e112 = r; + AB = ((vec2(-1.04f, 1.04f) * vec2(_e109)) + _e112.zw); + let _e116 = f0_8; + let _e117 = AB; + let _e121 = AB; + return ((_e116 * vec3(_e117.x)) + vec3(_e121.y)); +} + +fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 { + var perceptualRoughness_1: f32; + var clampedPerceptualRoughness: f32; + + perceptualRoughness_1 = perceptualRoughness; + let _e45 = perceptualRoughness_1; + clampedPerceptualRoughness = clamp(_e45, 0.089f, 1f); + let _e50 = clampedPerceptualRoughness; + let _e51 = clampedPerceptualRoughness; + return (_e50 * _e51); +} + +fn reinhard(color: vec3) -> vec3 { + var color_1: vec3; + + color_1 = color; + let _e42 = color_1; + let _e45 = color_1; + return (_e42 / (vec3(1f) + _e45)); +} + +fn reinhard_extended(color_2: vec3, max_white: f32) -> vec3 { + var color_3: vec3; + var max_white_1: f32; + var numerator: vec3; + + color_3 = color_2; + max_white_1 = max_white; + let _e44 = color_3; + let _e47 = color_3; + let _e48 = max_white_1; + let _e49 = max_white_1; + numerator = (_e44 * (vec3(1f) + (_e47 / vec3((_e48 * _e49))))); + let _e56 = numerator; + let _e59 = color_3; + return (_e56 / (vec3(1f) + _e59)); +} + +fn luminance(v_1: vec3) -> f32 { + var v_2: vec3; + + v_2 = v_1; + let _e47 = v_2; + return dot(_e47, vec3(0.2126f, 0.7152f, 0.0722f)); +} + +fn change_luminance(c_in: vec3, l_out: f32) -> vec3 { + var c_in_1: vec3; + var l_out_1: f32; + var l_in: f32; + + c_in_1 = c_in; + l_out_1 = l_out; + let _e45 = c_in_1; + let _e46 = luminance(_e45); + l_in = _e46; + let _e48 = c_in_1; + let _e49 = l_out_1; + let _e50 = l_in; + return (_e48 * (_e49 / _e50)); +} + +fn reinhard_luminance(color_4: vec3) -> vec3 { + var color_5: vec3; + var l_old: f32; + var l_new: f32; + + color_5 = color_4; + let _e43 = color_5; + let _e44 = luminance(_e43); + l_old = _e44; + let _e46 = l_old; + let _e48 = l_old; + l_new = (_e46 / (1f + _e48)); + let _e54 = color_5; + let _e55 = l_new; + let _e56 = change_luminance(_e54, _e55); + return _e56; +} + +fn reinhard_extended_luminance(color_6: vec3, max_white_l: f32) -> vec3 { + var color_7: vec3; + var max_white_l_1: f32; + var l_old_1: f32; + var numerator_1: f32; + var l_new_1: f32; + + color_7 = color_6; + max_white_l_1 = max_white_l; + let _e45 = color_7; + let _e46 = luminance(_e45); + l_old_1 = _e46; + let _e48 = l_old_1; + let _e50 = l_old_1; + let _e51 = max_white_l_1; + let _e52 = max_white_l_1; + numerator_1 = (_e48 * (1f + (_e50 / (_e51 * _e52)))); + let _e58 = numerator_1; + let _e60 = l_old_1; + l_new_1 = (_e58 / (1f + _e60)); + let _e66 = color_7; + let _e67 = l_new_1; + let _e68 = change_luminance(_e66, _e67); + return _e68; +} + +fn point_light(light: PointLight, roughness_8: f32, NdotV: f32, N: vec3, V_1: vec3, R: vec3, F0_: vec3, diffuseColor: vec3) -> vec3 { + var light_1: PointLight; + var roughness_9: f32; + var NdotV_1: f32; + var N_1: vec3; + var V_2: vec3; + var R_1: vec3; + var F0_1: vec3; + var diffuseColor_1: vec3; + var light_to_frag: vec3; + var distance_square: f32; + var rangeAttenuation: f32; + var a_1: f32; + var radius: f32; + var centerToRay: vec3; + var closestPoint: vec3; + var LspecLengthInverse: f32; + var normalizationFactor: f32; + var specularIntensity_2: f32; + var L: vec3; + var H: vec3; + var NoL_6: f32; + var NoH_4: f32; + var LoH_6: f32; + var specular_1: vec3; + var diffuse: vec3; + + light_1 = light; + roughness_9 = roughness_8; + NdotV_1 = NdotV; + N_1 = N; + V_2 = V_1; + R_1 = R; + F0_1 = F0_; + diffuseColor_1 = diffuseColor; + let _e56 = light_1; + let _e59 = v_WorldPosition_1; + light_to_frag = (_e56.pos.xyz - _e59.xyz); + let _e65 = light_to_frag; + let _e66 = light_to_frag; + distance_square = dot(_e65, _e66); + let _e70 = light_1; + let _e73 = distance_square; + let _e74 = light_1; + let _e77 = getDistanceAttenuation(_e73, _e74.lightParams.x); + rangeAttenuation = _e77; + let _e79 = roughness_9; + a_1 = _e79; + let _e81 = light_1; + radius = _e81.lightParams.y; + let _e87 = light_to_frag; + let _e88 = R_1; + let _e90 = R_1; + let _e92 = light_to_frag; + centerToRay = ((dot(_e87, _e88) * _e90) - _e92); + let _e95 = light_to_frag; + let _e96 = centerToRay; + let _e97 = radius; + let _e100 = centerToRay; + let _e101 = centerToRay; + let _e105 = centerToRay; + let _e106 = centerToRay; + let _e112 = radius; + let _e115 = centerToRay; + let _e116 = centerToRay; + let _e120 = centerToRay; + let _e121 = centerToRay; + closestPoint = (_e95 + (_e96 * clamp((_e112 * inverseSqrt(dot(_e120, _e121))), 0f, 1f))); + let _e133 = closestPoint; + let _e134 = closestPoint; + let _e138 = closestPoint; + let _e139 = closestPoint; + LspecLengthInverse = inverseSqrt(dot(_e138, _e139)); + let _e143 = a_1; + let _e144 = a_1; + let _e145 = radius; + let _e148 = LspecLengthInverse; + let _e153 = a_1; + let _e154 = radius; + let _e157 = LspecLengthInverse; + normalizationFactor = (_e143 / clamp((_e153 + ((_e154 * 0.5f) * _e157)), 0f, 1f)); + let _e165 = normalizationFactor; + let _e166 = normalizationFactor; + specularIntensity_2 = (_e165 * _e166); + let _e169 = closestPoint; + let _e170 = LspecLengthInverse; + L = (_e169 * _e170); + let _e173 = L; + let _e174 = V_2; + let _e176 = L; + let _e177 = V_2; + H = normalize((_e176 + _e177)); + let _e183 = N_1; + let _e184 = L; + let _e190 = N_1; + let _e191 = L; + NoL_6 = clamp(dot(_e190, _e191), 0f, 1f); + let _e199 = N_1; + let _e200 = H; + let _e206 = N_1; + let _e207 = H; + NoH_4 = clamp(dot(_e206, _e207), 0f, 1f); + let _e215 = L; + let _e216 = H; + let _e222 = L; + let _e223 = H; + LoH_6 = clamp(dot(_e222, _e223), 0f, 1f); + let _e237 = F0_1; + let _e238 = roughness_9; + let _e239 = H; + let _e240 = NdotV_1; + let _e241 = NoL_6; + let _e242 = NoH_4; + let _e243 = LoH_6; + let _e244 = specularIntensity_2; + let _e245 = specular(_e237, _e238, _e239, _e240, _e241, _e242, _e243, _e244); + specular_1 = _e245; + let _e248 = light_to_frag; + L = normalize(_e248); + let _e250 = L; + let _e251 = V_2; + let _e253 = L; + let _e254 = V_2; + H = normalize((_e253 + _e254)); + let _e259 = N_1; + let _e260 = L; + let _e266 = N_1; + let _e267 = L; + NoL_6 = clamp(dot(_e266, _e267), 0f, 1f); + let _e274 = N_1; + let _e275 = H; + let _e281 = N_1; + let _e282 = H; + NoH_4 = clamp(dot(_e281, _e282), 0f, 1f); + let _e289 = L; + let _e290 = H; + let _e296 = L; + let _e297 = H; + LoH_6 = clamp(dot(_e296, _e297), 0f, 1f); + let _e302 = diffuseColor_1; + let _e307 = roughness_9; + let _e308 = NdotV_1; + let _e309 = NoL_6; + let _e310 = LoH_6; + let _e311 = Fd_Burley(_e307, _e308, _e309, _e310); + diffuse = (_e302 * _e311); + let _e314 = diffuse; + let _e315 = specular_1; + let _e317 = light_1; + let _e321 = rangeAttenuation; + let _e322 = NoL_6; + return (((_e314 + _e315) * _e317.color.xyz) * (_e321 * _e322)); +} + +fn dir_light(light_2: DirectionalLight, roughness_10: f32, NdotV_2: f32, normal: vec3, view: vec3, R_2: vec3, F0_2: vec3, diffuseColor_2: vec3) -> vec3 { + var light_3: DirectionalLight; + var roughness_11: f32; + var NdotV_3: f32; + var normal_1: vec3; + var view_1: vec3; + var R_3: vec3; + var F0_3: vec3; + var diffuseColor_3: vec3; + var incident_light: vec3; + var half_vector: vec3; + var NoL_7: f32; + var NoH_5: f32; + var LoH_7: f32; + var diffuse_1: vec3; + var specularIntensity_3: f32 = 1f; + var specular_2: vec3; + + light_3 = light_2; + roughness_11 = roughness_10; + NdotV_3 = NdotV_2; + normal_1 = normal; + view_1 = view; + R_3 = R_2; + F0_3 = F0_2; + diffuseColor_3 = diffuseColor_2; + let _e56 = light_3; + incident_light = _e56.direction.xyz; + let _e60 = incident_light; + let _e61 = view_1; + let _e63 = incident_light; + let _e64 = view_1; + half_vector = normalize((_e63 + _e64)); + let _e70 = normal_1; + let _e71 = incident_light; + let _e77 = normal_1; + let _e78 = incident_light; + NoL_7 = clamp(dot(_e77, _e78), 0f, 1f); + let _e86 = normal_1; + let _e87 = half_vector; + let _e93 = normal_1; + let _e94 = half_vector; + NoH_5 = clamp(dot(_e93, _e94), 0f, 1f); + let _e102 = incident_light; + let _e103 = half_vector; + let _e109 = incident_light; + let _e110 = half_vector; + LoH_7 = clamp(dot(_e109, _e110), 0f, 1f); + let _e116 = diffuseColor_3; + let _e121 = roughness_11; + let _e122 = NdotV_3; + let _e123 = NoL_7; + let _e124 = LoH_7; + let _e125 = Fd_Burley(_e121, _e122, _e123, _e124); + diffuse_1 = (_e116 * _e125); + let _e138 = F0_3; + let _e139 = roughness_11; + let _e140 = half_vector; + let _e141 = NdotV_3; + let _e142 = NoL_7; + let _e143 = NoH_5; + let _e144 = LoH_7; + let _e145 = specularIntensity_3; + let _e146 = specular(_e138, _e139, _e140, _e141, _e142, _e143, _e144, _e145); + specular_2 = _e146; + let _e148 = specular_2; + let _e149 = diffuse_1; + let _e151 = light_3; + let _e155 = NoL_7; + return (((_e148 + _e149) * _e151.color.xyz) * _e155); +} + +fn main_1() { + var output_color: vec4; + var metallic_roughness: vec4; + var metallic: f32; + var perceptual_roughness_2: f32; + var roughness_12: f32; + var N_2: vec3; + var T: vec3; + var B: vec3; + var local: vec3; + var local_1: vec3; + var local_2: vec3; + var TBN: mat3x3; + var occlusion: f32; + var emissive: vec4; + var V_3: vec3; + var NdotV_4: f32; + var F0_4: vec3; + var diffuseColor_4: vec3; + var R_4: vec3; + var light_accum: vec3 = vec3(0f); + var i: i32 = 0i; + var i_1: i32 = 0i; + var diffuse_ambient: vec3; + var specular_ambient: vec3; + + let _e40 = global_3.base_color; + output_color = _e40; + let _e42 = output_color; + let _e44 = v_Uv_1; + let _e45 = textureSample(StandardMaterial_base_color_texture, StandardMaterial_base_color_texture_sampler, _e44); + output_color = (_e42 * _e45); + let _e48 = v_Uv_1; + let _e49 = textureSample(StandardMaterial_metallic_roughness_texture, StandardMaterial_metallic_roughness_texture_sampler, _e48); + metallic_roughness = _e49; + let _e51 = global_5.metallic; + let _e52 = metallic_roughness; + metallic = (_e51 * _e52.z); + let _e56 = global_4.perceptual_roughness; + let _e57 = metallic_roughness; + perceptual_roughness_2 = (_e56 * _e57.y); + let _e62 = perceptual_roughness_2; + let _e63 = perceptualRoughnessToRoughness(_e62); + roughness_12 = _e63; + let _e66 = v_WorldNormal_1; + N_2 = normalize(_e66); + let _e69 = v_WorldTangent_1; + let _e71 = v_WorldTangent_1; + T = normalize(_e71.xyz); + let _e77 = N_2; + let _e78 = T; + let _e80 = v_WorldTangent_1; + B = (cross(_e77, _e78) * _e80.w); + let _e85 = gl_FrontFacing; + if _e85 { + let _e86 = N_2; + local = _e86; + } else { + let _e87 = N_2; + local = -(_e87); + } + let _e90 = local; + N_2 = _e90; + let _e91 = gl_FrontFacing; + if _e91 { + let _e92 = T; + local_1 = _e92; + } else { + let _e93 = T; + local_1 = -(_e93); + } + let _e96 = local_1; + T = _e96; + let _e97 = gl_FrontFacing; + if _e97 { + let _e98 = B; + local_2 = _e98; + } else { + let _e99 = B; + local_2 = -(_e99); + } + let _e102 = local_2; + B = _e102; + let _e103 = T; + let _e104 = B; + let _e105 = N_2; + TBN = mat3x3(vec3(_e103.x, _e103.y, _e103.z), vec3(_e104.x, _e104.y, _e104.z), vec3(_e105.x, _e105.y, _e105.z)); + let _e120 = TBN; + let _e122 = v_Uv_1; + let _e123 = textureSample(StandardMaterial_normal_map, StandardMaterial_normal_map_sampler, _e122); + let _e131 = v_Uv_1; + let _e132 = textureSample(StandardMaterial_normal_map, StandardMaterial_normal_map_sampler, _e131); + N_2 = (_e120 * normalize(((_e132.xyz * 2f) - vec3(1f)))); + let _e142 = v_Uv_1; + let _e143 = textureSample(StandardMaterial_occlusion_texture, StandardMaterial_occlusion_texture_sampler, _e142); + occlusion = _e143.x; + let _e146 = global_7.emissive; + emissive = _e146; + let _e148 = emissive; + let _e150 = emissive; + let _e153 = v_Uv_1; + let _e154 = textureSample(StandardMaterial_emissive_texture, StandardMaterial_emissive_texture_sampler, _e153); + let _e156 = (_e150.xyz * _e154.xyz); + emissive.x = _e156.x; + emissive.y = _e156.y; + emissive.z = _e156.z; + let _e163 = global_1.CameraPos; + let _e165 = v_WorldPosition_1; + let _e168 = global_1.CameraPos; + let _e170 = v_WorldPosition_1; + V_3 = normalize((_e168.xyz - _e170.xyz)); + let _e177 = N_2; + let _e178 = V_3; + let _e183 = N_2; + let _e184 = V_3; + NdotV_4 = max(dot(_e183, _e184), 0.001f); + let _e190 = global_6.reflectance; + let _e192 = global_6.reflectance; + let _e195 = metallic; + let _e199 = output_color; + let _e201 = metallic; + F0_4 = (vec3((((0.16f * _e190) * _e192) * (1f - _e195))) + (_e199.xyz * vec3(_e201))); + let _e206 = output_color; + let _e209 = metallic; + diffuseColor_4 = (_e206.xyz * vec3((1f - _e209))); + let _e214 = V_3; + let _e217 = V_3; + let _e219 = N_2; + R_4 = reflect(-(_e217), _e219); + loop { + let _e227 = i; + let _e228 = global_2.NumLights; + let _e232 = i; + if !(((_e227 < i32(_e228.x)) && (_e232 < MAX_POINT_LIGHTS))) { + break; + } + { + let _e239 = light_accum; + let _e240 = i; + let _e250 = i; + let _e252 = global_2.PointLights[_e250]; + let _e253 = roughness_12; + let _e254 = NdotV_4; + let _e255 = N_2; + let _e256 = V_3; + let _e257 = R_4; + let _e258 = F0_4; + let _e259 = diffuseColor_4; + let _e260 = point_light(_e252, _e253, _e254, _e255, _e256, _e257, _e258, _e259); + light_accum = (_e239 + _e260); + } + continuing { + let _e236 = i; + i = (_e236 + 1i); + } + } + loop { + let _e264 = i_1; + let _e265 = global_2.NumLights; + let _e269 = i_1; + if !(((_e264 < i32(_e265.y)) && (_e269 < MAX_DIRECTIONAL_LIGHTS))) { + break; + } + { + let _e276 = light_accum; + let _e277 = i_1; + let _e287 = i_1; + let _e289 = global_2.DirectionalLights[_e287]; + let _e290 = roughness_12; + let _e291 = NdotV_4; + let _e292 = N_2; + let _e293 = V_3; + let _e294 = R_4; + let _e295 = F0_4; + let _e296 = diffuseColor_4; + let _e297 = dir_light(_e289, _e290, _e291, _e292, _e293, _e294, _e295, _e296); + light_accum = (_e276 + _e297); + } + continuing { + let _e273 = i_1; + i_1 = (_e273 + 1i); + } + } + let _e302 = diffuseColor_4; + let _e304 = NdotV_4; + let _e305 = EnvBRDFApprox(_e302, 1f, _e304); + diffuse_ambient = _e305; + let _e310 = F0_4; + let _e311 = perceptual_roughness_2; + let _e312 = NdotV_4; + let _e313 = EnvBRDFApprox(_e310, _e311, _e312); + specular_ambient = _e313; + let _e315 = output_color; + let _e317 = light_accum; + output_color.x = _e317.x; + output_color.y = _e317.y; + output_color.z = _e317.z; + let _e324 = output_color; + let _e326 = output_color; + let _e328 = diffuse_ambient; + let _e329 = specular_ambient; + let _e331 = global_2.AmbientColor; + let _e334 = occlusion; + let _e336 = (_e326.xyz + (((_e328 + _e329) * _e331.xyz) * _e334)); + output_color.x = _e336.x; + output_color.y = _e336.y; + output_color.z = _e336.z; + let _e343 = output_color; + let _e345 = output_color; + let _e347 = emissive; + let _e349 = output_color; + let _e352 = (_e345.xyz + (_e347.xyz * _e349.w)); + output_color.x = _e352.x; + output_color.y = _e352.y; + output_color.z = _e352.z; + let _e359 = output_color; + let _e361 = output_color; + let _e363 = output_color; + let _e365 = reinhard_luminance(_e363.xyz); + output_color.x = _e365.x; + output_color.y = _e365.y; + output_color.z = _e365.z; + let _e372 = output_color; + o_Target = _e372; + return; +} + +@fragment +fn main(@location(0) v_WorldPosition: vec3, @location(1) v_WorldNormal: vec3, @location(2) v_Uv: vec2, @location(3) v_WorldTangent: vec4, @builtin(front_facing) param: bool) -> FragmentOutput { + v_WorldPosition_1 = v_WorldPosition; + v_WorldNormal_1 = v_WorldNormal; + v_Uv_1 = v_Uv; + v_WorldTangent_1 = v_WorldTangent; + gl_FrontFacing = param; + main_1(); + let _e69 = o_Target; + return FragmentOutput(_e69); +} diff --git a/naga/tests/out/wgsl/bevy-pbr.vert.wgsl b/naga/tests/out/wgsl/bevy-pbr.vert.wgsl new file mode 100644 index 0000000000..71cf8d2fe6 --- /dev/null +++ b/naga/tests/out/wgsl/bevy-pbr.vert.wgsl @@ -0,0 +1,68 @@ +struct CameraViewProj { + ViewProj: mat4x4, +} + +struct Transform { + Model: mat4x4, +} + +struct VertexOutput { + @location(0) v_WorldPosition: vec3, + @location(1) v_WorldNormal: vec3, + @location(2) v_Uv: vec2, + @location(3) v_WorldTangent: vec4, + @builtin(position) member: vec4, +} + +var Vertex_Position_1: vec3; +var Vertex_Normal_1: vec3; +var Vertex_Uv_1: vec2; +var Vertex_Tangent_1: vec4; +var v_WorldPosition: vec3; +var v_WorldNormal: vec3; +var v_Uv: vec2; +@group(0) @binding(0) +var global: CameraViewProj; +var v_WorldTangent: vec4; +@group(2) @binding(0) +var global_1: Transform; +var gl_Position: vec4; + +fn main_1() { + var world_position: vec4; + + let _e12 = global_1.Model; + let _e13 = Vertex_Position_1; + world_position = (_e12 * vec4(_e13.x, _e13.y, _e13.z, 1f)); + let _e21 = world_position; + v_WorldPosition = _e21.xyz; + let _e23 = global_1.Model; + let _e33 = Vertex_Normal_1; + v_WorldNormal = (mat3x3(_e23[0].xyz, _e23[1].xyz, _e23[2].xyz) * _e33); + let _e35 = Vertex_Uv_1; + v_Uv = _e35; + let _e36 = global_1.Model; + let _e46 = Vertex_Tangent_1; + let _e48 = (mat3x3(_e36[0].xyz, _e36[1].xyz, _e36[2].xyz) * _e46.xyz); + let _e49 = Vertex_Tangent_1; + v_WorldTangent = vec4(_e48.x, _e48.y, _e48.z, _e49.w); + let _e56 = global.ViewProj; + let _e57 = world_position; + gl_Position = (_e56 * _e57); + return; +} + +@vertex +fn main(@location(0) Vertex_Position: vec3, @location(1) Vertex_Normal: vec3, @location(2) Vertex_Uv: vec2, @location(3) Vertex_Tangent: vec4) -> VertexOutput { + Vertex_Position_1 = Vertex_Position; + Vertex_Normal_1 = Vertex_Normal; + Vertex_Uv_1 = Vertex_Uv; + Vertex_Tangent_1 = Vertex_Tangent; + main_1(); + let _e29 = v_WorldPosition; + let _e31 = v_WorldNormal; + let _e33 = v_Uv; + let _e35 = v_WorldTangent; + let _e37 = gl_Position; + return VertexOutput(_e29, _e31, _e33, _e35, _e37); +} diff --git a/naga/tests/out/wgsl/binding-arrays.dynamic.wgsl b/naga/tests/out/wgsl/binding-arrays.dynamic.wgsl new file mode 100644 index 0000000000..4b2f079206 --- /dev/null +++ b/naga/tests/out/wgsl/binding-arrays.dynamic.wgsl @@ -0,0 +1,18 @@ +@group(0) @binding(0) +var global: binding_array>; +@group(0) @binding(1) +var global_1: binding_array; +var global_2: vec4; + +fn function() { + let _e8 = textureSampleLevel(global[1i], global_1[1i], vec2(0.5f, 0.5f), 0f); + global_2 = _e8; + return; +} + +@fragment +fn main() -> @location(0) vec4 { + function(); + let _e1 = global_2; + return _e1; +} diff --git a/naga/tests/out/wgsl/binding-arrays.static.wgsl b/naga/tests/out/wgsl/binding-arrays.static.wgsl new file mode 100644 index 0000000000..bf87a16844 --- /dev/null +++ b/naga/tests/out/wgsl/binding-arrays.static.wgsl @@ -0,0 +1,18 @@ +@group(0) @binding(0) +var global: binding_array, 256>; +@group(0) @binding(1) +var global_1: binding_array; +var global_2: vec4; + +fn function() { + let _e8 = textureSampleLevel(global[1i], global_1[1i], vec2(0.5f, 0.5f), 0f); + global_2 = _e8; + return; +} + +@fragment +fn main() -> @location(0) vec4 { + function(); + let _e1 = global_2; + return _e1; +} diff --git a/naga/tests/out/wgsl/binding-arrays.wgsl b/naga/tests/out/wgsl/binding-arrays.wgsl new file mode 100644 index 0000000000..86bcfc1bff --- /dev/null +++ b/naga/tests/out/wgsl/binding-arrays.wgsl @@ -0,0 +1,168 @@ +struct UniformIndex { + index: u32, +} + +struct FragmentIn { + @location(0) @interpolate(flat) index: u32, +} + +@group(0) @binding(0) +var texture_array_unbounded: binding_array>; +@group(0) @binding(1) +var texture_array_bounded: binding_array, 5>; +@group(0) @binding(2) +var texture_array_2darray: binding_array, 5>; +@group(0) @binding(3) +var texture_array_multisampled: binding_array, 5>; +@group(0) @binding(4) +var texture_array_depth: binding_array; +@group(0) @binding(5) +var texture_array_storage: binding_array, 5>; +@group(0) @binding(6) +var samp: binding_array; +@group(0) @binding(7) +var samp_comp: binding_array; +@group(0) @binding(8) +var uni: UniformIndex; + +@fragment +fn main(fragment_in: FragmentIn) -> @location(0) vec4 { + var u1_: u32 = 0u; + var u2_: vec2 = vec2(0u); + var v1_: f32 = 0f; + var v4_: vec4 = vec4(0f); + + let uniform_index = uni.index; + let non_uniform_index = fragment_in.index; + let uv = vec2(0f); + let pix = vec2(0i); + let _e21 = textureDimensions(texture_array_unbounded[0]); + let _e22 = u2_; + u2_ = (_e22 + _e21); + let _e26 = textureDimensions(texture_array_unbounded[uniform_index]); + let _e27 = u2_; + u2_ = (_e27 + _e26); + let _e31 = textureDimensions(texture_array_unbounded[non_uniform_index]); + let _e32 = u2_; + u2_ = (_e32 + _e31); + let _e38 = textureGather(0, texture_array_bounded[0], samp[0], uv); + let _e39 = v4_; + v4_ = (_e39 + _e38); + let _e45 = textureGather(0, texture_array_bounded[uniform_index], samp[uniform_index], uv); + let _e46 = v4_; + v4_ = (_e46 + _e45); + let _e52 = textureGather(0, texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv); + let _e53 = v4_; + v4_ = (_e53 + _e52); + let _e60 = textureGatherCompare(texture_array_depth[0], samp_comp[0], uv, 0f); + let _e61 = v4_; + v4_ = (_e61 + _e60); + let _e68 = textureGatherCompare(texture_array_depth[uniform_index], samp_comp[uniform_index], uv, 0f); + let _e69 = v4_; + v4_ = (_e69 + _e68); + let _e76 = textureGatherCompare(texture_array_depth[non_uniform_index], samp_comp[non_uniform_index], uv, 0f); + let _e77 = v4_; + v4_ = (_e77 + _e76); + let _e82 = textureLoad(texture_array_unbounded[0], pix, 0i); + let _e83 = v4_; + v4_ = (_e83 + _e82); + let _e88 = textureLoad(texture_array_unbounded[uniform_index], pix, 0i); + let _e89 = v4_; + v4_ = (_e89 + _e88); + let _e94 = textureLoad(texture_array_unbounded[non_uniform_index], pix, 0i); + let _e95 = v4_; + v4_ = (_e95 + _e94); + let _e99 = textureNumLayers(texture_array_2darray[0]); + let _e100 = u1_; + u1_ = (_e100 + _e99); + let _e104 = textureNumLayers(texture_array_2darray[uniform_index]); + let _e105 = u1_; + u1_ = (_e105 + _e104); + let _e109 = textureNumLayers(texture_array_2darray[non_uniform_index]); + let _e110 = u1_; + u1_ = (_e110 + _e109); + let _e114 = textureNumLevels(texture_array_bounded[0]); + let _e115 = u1_; + u1_ = (_e115 + _e114); + let _e119 = textureNumLevels(texture_array_bounded[uniform_index]); + let _e120 = u1_; + u1_ = (_e120 + _e119); + let _e124 = textureNumLevels(texture_array_bounded[non_uniform_index]); + let _e125 = u1_; + u1_ = (_e125 + _e124); + let _e129 = textureNumSamples(texture_array_multisampled[0]); + let _e130 = u1_; + u1_ = (_e130 + _e129); + let _e134 = textureNumSamples(texture_array_multisampled[uniform_index]); + let _e135 = u1_; + u1_ = (_e135 + _e134); + let _e139 = textureNumSamples(texture_array_multisampled[non_uniform_index]); + let _e140 = u1_; + u1_ = (_e140 + _e139); + let _e146 = textureSample(texture_array_bounded[0], samp[0], uv); + let _e147 = v4_; + v4_ = (_e147 + _e146); + let _e153 = textureSample(texture_array_bounded[uniform_index], samp[uniform_index], uv); + let _e154 = v4_; + v4_ = (_e154 + _e153); + let _e160 = textureSample(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv); + let _e161 = v4_; + v4_ = (_e161 + _e160); + let _e168 = textureSampleBias(texture_array_bounded[0], samp[0], uv, 0f); + let _e169 = v4_; + v4_ = (_e169 + _e168); + let _e176 = textureSampleBias(texture_array_bounded[uniform_index], samp[uniform_index], uv, 0f); + let _e177 = v4_; + v4_ = (_e177 + _e176); + let _e184 = textureSampleBias(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv, 0f); + let _e185 = v4_; + v4_ = (_e185 + _e184); + let _e192 = textureSampleCompare(texture_array_depth[0], samp_comp[0], uv, 0f); + let _e193 = v1_; + v1_ = (_e193 + _e192); + let _e200 = textureSampleCompare(texture_array_depth[uniform_index], samp_comp[uniform_index], uv, 0f); + let _e201 = v1_; + v1_ = (_e201 + _e200); + let _e208 = textureSampleCompare(texture_array_depth[non_uniform_index], samp_comp[non_uniform_index], uv, 0f); + let _e209 = v1_; + v1_ = (_e209 + _e208); + let _e216 = textureSampleCompareLevel(texture_array_depth[0], samp_comp[0], uv, 0f); + let _e217 = v1_; + v1_ = (_e217 + _e216); + let _e224 = textureSampleCompareLevel(texture_array_depth[uniform_index], samp_comp[uniform_index], uv, 0f); + let _e225 = v1_; + v1_ = (_e225 + _e224); + let _e232 = textureSampleCompareLevel(texture_array_depth[non_uniform_index], samp_comp[non_uniform_index], uv, 0f); + let _e233 = v1_; + v1_ = (_e233 + _e232); + let _e239 = textureSampleGrad(texture_array_bounded[0], samp[0], uv, uv, uv); + let _e240 = v4_; + v4_ = (_e240 + _e239); + let _e246 = textureSampleGrad(texture_array_bounded[uniform_index], samp[uniform_index], uv, uv, uv); + let _e247 = v4_; + v4_ = (_e247 + _e246); + let _e253 = textureSampleGrad(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv, uv, uv); + let _e254 = v4_; + v4_ = (_e254 + _e253); + let _e261 = textureSampleLevel(texture_array_bounded[0], samp[0], uv, 0f); + let _e262 = v4_; + v4_ = (_e262 + _e261); + let _e269 = textureSampleLevel(texture_array_bounded[uniform_index], samp[uniform_index], uv, 0f); + let _e270 = v4_; + v4_ = (_e270 + _e269); + let _e277 = textureSampleLevel(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv, 0f); + let _e278 = v4_; + v4_ = (_e278 + _e277); + let _e282 = v4_; + textureStore(texture_array_storage[0], pix, _e282); + let _e285 = v4_; + textureStore(texture_array_storage[uniform_index], pix, _e285); + let _e288 = v4_; + textureStore(texture_array_storage[non_uniform_index], pix, _e288); + let _e289 = u2_; + let _e290 = u1_; + let v2_ = vec2((_e289 + vec2(_e290))); + let _e294 = v4_; + let _e301 = v1_; + return ((_e294 + vec4(v2_.x, v2_.y, v2_.x, v2_.y)) + vec4(_e301)); +} diff --git a/naga/tests/out/wgsl/binding-buffer-arrays.wgsl b/naga/tests/out/wgsl/binding-buffer-arrays.wgsl new file mode 100644 index 0000000000..317a386239 --- /dev/null +++ b/naga/tests/out/wgsl/binding-buffer-arrays.wgsl @@ -0,0 +1,35 @@ +struct UniformIndex { + index: u32, +} + +struct Foo { + x: u32, +} + +struct FragmentIn { + @location(0) @interpolate(flat) index: u32, +} + +@group(0) @binding(0) +var storage_array: binding_array; +@group(0) @binding(10) +var uni: UniformIndex; + +@fragment +fn main(fragment_in: FragmentIn) -> @location(0) @interpolate(flat) u32 { + var u1_: u32 = 0u; + + let uniform_index = uni.index; + let non_uniform_index = fragment_in.index; + let _e10 = storage_array[0].x; + let _e11 = u1_; + u1_ = (_e11 + _e10); + let _e16 = storage_array[uniform_index].x; + let _e17 = u1_; + u1_ = (_e17 + _e16); + let _e22 = storage_array[non_uniform_index].x; + let _e23 = u1_; + u1_ = (_e23 + _e22); + let _e25 = u1_; + return _e25; +} diff --git a/naga/tests/out/wgsl/bitcast.wgsl b/naga/tests/out/wgsl/bitcast.wgsl new file mode 100644 index 0000000000..1f36ce5e66 --- /dev/null +++ b/naga/tests/out/wgsl/bitcast.wgsl @@ -0,0 +1,32 @@ +@compute @workgroup_size(1, 1, 1) +fn main() { + var i2_: vec2 = vec2(0i); + var i3_: vec3 = vec3(0i); + var i4_: vec4 = vec4(0i); + var u2_: vec2 = vec2(0u); + var u3_: vec3 = vec3(0u); + var u4_: vec4 = vec4(0u); + var f2_: vec2 = vec2(0f); + var f3_: vec3 = vec3(0f); + var f4_: vec4 = vec4(0f); + + let _e27 = i2_; + u2_ = bitcast>(_e27); + let _e29 = i3_; + u3_ = bitcast>(_e29); + let _e31 = i4_; + u4_ = bitcast>(_e31); + let _e33 = u2_; + i2_ = bitcast>(_e33); + let _e35 = u3_; + i3_ = bitcast>(_e35); + let _e37 = u4_; + i4_ = bitcast>(_e37); + let _e39 = i2_; + f2_ = bitcast>(_e39); + let _e41 = i3_; + f3_ = bitcast>(_e41); + let _e43 = i4_; + f4_ = bitcast>(_e43); + return; +} diff --git a/naga/tests/out/wgsl/bits.wgsl b/naga/tests/out/wgsl/bits.wgsl new file mode 100644 index 0000000000..05915549ad --- /dev/null +++ b/naga/tests/out/wgsl/bits.wgsl @@ -0,0 +1,119 @@ +@compute @workgroup_size(1, 1, 1) +fn main() { + var i: i32 = 0i; + var i2_: vec2 = vec2(0i); + var i3_: vec3 = vec3(0i); + var i4_: vec4 = vec4(0i); + var u: u32 = 0u; + var u2_: vec2 = vec2(0u); + var u3_: vec3 = vec3(0u); + var u4_: vec4 = vec4(0u); + var f2_: vec2 = vec2(0f); + var f4_: vec4 = vec4(0f); + + let _e28 = f4_; + u = pack4x8snorm(_e28); + let _e30 = f4_; + u = pack4x8unorm(_e30); + let _e32 = f2_; + u = pack2x16snorm(_e32); + let _e34 = f2_; + u = pack2x16unorm(_e34); + let _e36 = f2_; + u = pack2x16float(_e36); + let _e38 = u; + f4_ = unpack4x8snorm(_e38); + let _e40 = u; + f4_ = unpack4x8unorm(_e40); + let _e42 = u; + f2_ = unpack2x16snorm(_e42); + let _e44 = u; + f2_ = unpack2x16unorm(_e44); + let _e46 = u; + f2_ = unpack2x16float(_e46); + let _e48 = i; + let _e49 = i; + i = insertBits(_e48, _e49, 5u, 10u); + let _e53 = i2_; + let _e54 = i2_; + i2_ = insertBits(_e53, _e54, 5u, 10u); + let _e58 = i3_; + let _e59 = i3_; + i3_ = insertBits(_e58, _e59, 5u, 10u); + let _e63 = i4_; + let _e64 = i4_; + i4_ = insertBits(_e63, _e64, 5u, 10u); + let _e68 = u; + let _e69 = u; + u = insertBits(_e68, _e69, 5u, 10u); + let _e73 = u2_; + let _e74 = u2_; + u2_ = insertBits(_e73, _e74, 5u, 10u); + let _e78 = u3_; + let _e79 = u3_; + u3_ = insertBits(_e78, _e79, 5u, 10u); + let _e83 = u4_; + let _e84 = u4_; + u4_ = insertBits(_e83, _e84, 5u, 10u); + let _e88 = i; + i = extractBits(_e88, 5u, 10u); + let _e92 = i2_; + i2_ = extractBits(_e92, 5u, 10u); + let _e96 = i3_; + i3_ = extractBits(_e96, 5u, 10u); + let _e100 = i4_; + i4_ = extractBits(_e100, 5u, 10u); + let _e104 = u; + u = extractBits(_e104, 5u, 10u); + let _e108 = u2_; + u2_ = extractBits(_e108, 5u, 10u); + let _e112 = u3_; + u3_ = extractBits(_e112, 5u, 10u); + let _e116 = u4_; + u4_ = extractBits(_e116, 5u, 10u); + let _e120 = i; + i = firstTrailingBit(_e120); + let _e122 = u2_; + u2_ = firstTrailingBit(_e122); + let _e124 = i3_; + i3_ = firstLeadingBit(_e124); + let _e126 = u3_; + u3_ = firstLeadingBit(_e126); + let _e128 = i; + i = firstLeadingBit(_e128); + let _e130 = u; + u = firstLeadingBit(_e130); + let _e132 = i; + i = countOneBits(_e132); + let _e134 = i2_; + i2_ = countOneBits(_e134); + let _e136 = i3_; + i3_ = countOneBits(_e136); + let _e138 = i4_; + i4_ = countOneBits(_e138); + let _e140 = u; + u = countOneBits(_e140); + let _e142 = u2_; + u2_ = countOneBits(_e142); + let _e144 = u3_; + u3_ = countOneBits(_e144); + let _e146 = u4_; + u4_ = countOneBits(_e146); + let _e148 = i; + i = reverseBits(_e148); + let _e150 = i2_; + i2_ = reverseBits(_e150); + let _e152 = i3_; + i3_ = reverseBits(_e152); + let _e154 = i4_; + i4_ = reverseBits(_e154); + let _e156 = u; + u = reverseBits(_e156); + let _e158 = u2_; + u2_ = reverseBits(_e158); + let _e160 = u3_; + u3_ = reverseBits(_e160); + let _e162 = u4_; + u4_ = reverseBits(_e162); + return; +} diff --git a/naga/tests/out/wgsl/bits_glsl.frag.wgsl b/naga/tests/out/wgsl/bits_glsl.frag.wgsl new file mode 100644 index 0000000000..e8365dd1ee --- /dev/null +++ b/naga/tests/out/wgsl/bits_glsl.frag.wgsl @@ -0,0 +1,112 @@ +fn main_1() { + var i: i32 = 0i; + var i2_: vec2 = vec2(0i); + var i3_: vec3 = vec3(0i); + var i4_: vec4 = vec4(0i); + var u: u32 = 0u; + var u2_: vec2 = vec2(0u); + var u3_: vec3 = vec3(0u); + var u4_: vec4 = vec4(0u); + var f2_: vec2 = vec2(0f); + var f4_: vec4 = vec4(0f); + + let _e33 = f4_; + u = pack4x8snorm(_e33); + let _e36 = f4_; + u = pack4x8unorm(_e36); + let _e39 = f2_; + u = pack2x16unorm(_e39); + let _e42 = f2_; + u = pack2x16snorm(_e42); + let _e45 = f2_; + u = pack2x16float(_e45); + let _e48 = u; + f4_ = unpack4x8snorm(_e48); + let _e51 = u; + f4_ = unpack4x8unorm(_e51); + let _e54 = u; + f2_ = unpack2x16snorm(_e54); + let _e57 = u; + f2_ = unpack2x16unorm(_e57); + let _e60 = u; + f2_ = unpack2x16float(_e60); + let _e66 = i; + let _e67 = i; + i = insertBits(_e66, _e67, 5u, 10u); + let _e77 = i2_; + let _e78 = i2_; + i2_ = insertBits(_e77, _e78, 5u, 10u); + let _e88 = i3_; + let _e89 = i3_; + i3_ = insertBits(_e88, _e89, 5u, 10u); + let _e99 = i4_; + let _e100 = i4_; + i4_ = insertBits(_e99, _e100, 5u, 10u); + let _e110 = u; + let _e111 = u; + u = insertBits(_e110, _e111, 5u, 10u); + let _e121 = u2_; + let _e122 = u2_; + u2_ = insertBits(_e121, _e122, 5u, 10u); + let _e132 = u3_; + let _e133 = u3_; + u3_ = insertBits(_e132, _e133, 5u, 10u); + let _e143 = u4_; + let _e144 = u4_; + u4_ = insertBits(_e143, _e144, 5u, 10u); + let _e153 = i; + i = extractBits(_e153, 5u, 10u); + let _e162 = i2_; + i2_ = extractBits(_e162, 5u, 10u); + let _e171 = i3_; + i3_ = extractBits(_e171, 5u, 10u); + let _e180 = i4_; + i4_ = extractBits(_e180, 5u, 10u); + let _e189 = u; + u = extractBits(_e189, 5u, 10u); + let _e198 = u2_; + u2_ = extractBits(_e198, 5u, 10u); + let _e207 = u3_; + u3_ = extractBits(_e207, 5u, 10u); + let _e216 = u4_; + u4_ = extractBits(_e216, 5u, 10u); + let _e223 = i; + i = firstTrailingBit(_e223); + let _e226 = i2_; + i2_ = firstTrailingBit(_e226); + let _e229 = i3_; + i3_ = firstTrailingBit(_e229); + let _e232 = i4_; + i4_ = firstTrailingBit(_e232); + let _e235 = u; + i = i32(firstTrailingBit(_e235)); + let _e239 = u2_; + i2_ = vec2(firstTrailingBit(_e239)); + let _e243 = u3_; + i3_ = vec3(firstTrailingBit(_e243)); + let _e247 = u4_; + i4_ = vec4(firstTrailingBit(_e247)); + let _e251 = i; + i = firstLeadingBit(_e251); + let _e254 = i2_; + i2_ = firstLeadingBit(_e254); + let _e257 = i3_; + i3_ = firstLeadingBit(_e257); + let _e260 = i4_; + i4_ = firstLeadingBit(_e260); + let _e263 = u; + i = i32(firstLeadingBit(_e263)); + let _e267 = u2_; + i2_ = vec2(firstLeadingBit(_e267)); + let _e271 = u3_; + i3_ = vec3(firstLeadingBit(_e271)); + let _e275 = u4_; + i4_ = vec4(firstLeadingBit(_e275)); + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/boids.wgsl b/naga/tests/out/wgsl/boids.wgsl new file mode 100644 index 0000000000..ee28ca6786 --- /dev/null +++ b/naga/tests/out/wgsl/boids.wgsl @@ -0,0 +1,148 @@ +struct Particle { + pos: vec2, + vel: vec2, +} + +struct SimParams { + deltaT: f32, + rule1Distance: f32, + rule2Distance: f32, + rule3Distance: f32, + rule1Scale: f32, + rule2Scale: f32, + rule3Scale: f32, +} + +struct Particles { + particles: array, +} + +const NUM_PARTICLES: u32 = 1500u; + +@group(0) @binding(0) +var params: SimParams; +@group(0) @binding(1) +var particlesSrc: Particles; +@group(0) @binding(2) +var particlesDst: Particles; + +@compute @workgroup_size(64, 1, 1) +fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { + var vPos: vec2; + var vVel: vec2; + var cMass: vec2 = vec2(0f, 0f); + var cVel: vec2 = vec2(0f, 0f); + var colVel: vec2 = vec2(0f, 0f); + var cMassCount: i32 = 0i; + var cVelCount: i32 = 0i; + var pos: vec2; + var vel: vec2; + var i: u32 = 0u; + + let index = global_invocation_id.x; + if (index >= NUM_PARTICLES) { + return; + } + let _e8 = particlesSrc.particles[index].pos; + vPos = _e8; + let _e14 = particlesSrc.particles[index].vel; + vVel = _e14; + loop { + let _e36 = i; + if (_e36 >= NUM_PARTICLES) { + break; + } + let _e39 = i; + if (_e39 == index) { + continue; + } + let _e43 = i; + let _e46 = particlesSrc.particles[_e43].pos; + pos = _e46; + let _e49 = i; + let _e52 = particlesSrc.particles[_e49].vel; + vel = _e52; + let _e53 = pos; + let _e54 = vPos; + let _e58 = params.rule1Distance; + if (distance(_e53, _e54) < _e58) { + let _e60 = cMass; + let _e61 = pos; + cMass = (_e60 + _e61); + let _e63 = cMassCount; + cMassCount = (_e63 + 1i); + } + let _e66 = pos; + let _e67 = vPos; + let _e71 = params.rule2Distance; + if (distance(_e66, _e67) < _e71) { + let _e73 = colVel; + let _e74 = pos; + let _e75 = vPos; + colVel = (_e73 - (_e74 - _e75)); + } + let _e78 = pos; + let _e79 = vPos; + let _e83 = params.rule3Distance; + if (distance(_e78, _e79) < _e83) { + let _e85 = cVel; + let _e86 = vel; + cVel = (_e85 + _e86); + let _e88 = cVelCount; + cVelCount = (_e88 + 1i); + } + continuing { + let _e91 = i; + i = (_e91 + 1u); + } + } + let _e94 = cMassCount; + if (_e94 > 0i) { + let _e97 = cMass; + let _e98 = cMassCount; + let _e102 = vPos; + cMass = ((_e97 / vec2(f32(_e98))) - _e102); + } + let _e104 = cVelCount; + if (_e104 > 0i) { + let _e107 = cVel; + let _e108 = cVelCount; + cVel = (_e107 / vec2(f32(_e108))); + } + let _e112 = vVel; + let _e113 = cMass; + let _e116 = params.rule1Scale; + let _e119 = colVel; + let _e122 = params.rule2Scale; + let _e125 = cVel; + let _e128 = params.rule3Scale; + vVel = (((_e112 + (_e113 * _e116)) + (_e119 * _e122)) + (_e125 * _e128)); + let _e131 = vVel; + let _e133 = vVel; + vVel = (normalize(_e131) * clamp(length(_e133), 0f, 0.1f)); + let _e139 = vPos; + let _e140 = vVel; + let _e143 = params.deltaT; + vPos = (_e139 + (_e140 * _e143)); + let _e147 = vPos.x; + if (_e147 < -1f) { + vPos.x = 1f; + } + let _e153 = vPos.x; + if (_e153 > 1f) { + vPos.x = -1f; + } + let _e159 = vPos.y; + if (_e159 < -1f) { + vPos.y = 1f; + } + let _e165 = vPos.y; + if (_e165 > 1f) { + vPos.y = -1f; + } + let _e174 = vPos; + particlesDst.particles[index].pos = _e174; + let _e179 = vVel; + particlesDst.particles[index].vel = _e179; + return; +} diff --git a/naga/tests/out/wgsl/bool-select.frag.wgsl b/naga/tests/out/wgsl/bool-select.frag.wgsl new file mode 100644 index 0000000000..747ff677a2 --- /dev/null +++ b/naga/tests/out/wgsl/bool-select.frag.wgsl @@ -0,0 +1,45 @@ +struct FragmentOutput { + @location(0) o_color: vec4, +} + +var o_color: vec4; + +fn TevPerCompGT(a: f32, b: f32) -> f32 { + var a_1: f32; + var b_1: f32; + + a_1 = a; + b_1 = b; + let _e5 = a_1; + let _e6 = b_1; + return select(0f, 1f, (_e5 > _e6)); +} + +fn TevPerCompGT_1(a_2: vec3, b_2: vec3) -> vec3 { + var a_3: vec3; + var b_3: vec3; + + a_3 = a_2; + b_3 = b_2; + let _e7 = a_3; + let _e8 = b_3; + return select(vec3(0f), vec3(1f), (_e7 > _e8)); +} + +fn main_1() { + let _e1 = o_color; + let _e11 = TevPerCompGT_1(vec3(3f), vec3(5f)); + o_color.x = _e11.x; + o_color.y = _e11.y; + o_color.z = _e11.z; + let _e23 = TevPerCompGT(3f, 5f); + o_color.w = _e23; + return; +} + +@fragment +fn main() -> FragmentOutput { + main_1(); + let _e3 = o_color; + return FragmentOutput(_e3); +} diff --git a/naga/tests/out/wgsl/break-if.wgsl b/naga/tests/out/wgsl/break-if.wgsl new file mode 100644 index 0000000000..c3d45a50ac --- /dev/null +++ b/naga/tests/out/wgsl/break-if.wgsl @@ -0,0 +1,59 @@ +fn breakIfEmpty() { + loop { + continuing { + break if true; + } + } + return; +} + +fn breakIfEmptyBody(a: bool) { + var b: bool; + var c: bool; + + loop { + continuing { + b = a; + let _e2 = b; + c = (a != _e2); + let _e5 = c; + break if (a == _e5); + } + } + return; +} + +fn breakIf(a_1: bool) { + var d: bool; + var e: bool; + + loop { + d = a_1; + let _e2 = d; + e = (a_1 != _e2); + continuing { + let _e5 = e; + break if (a_1 == _e5); + } + } + return; +} + +fn breakIfSeparateVariable() { + var counter: u32 = 0u; + + loop { + let _e3 = counter; + counter = (_e3 + 1u); + continuing { + let _e5 = counter; + break if (_e5 == 5u); + } + } + return; +} + +@compute @workgroup_size(1, 1, 1) +fn main() { + return; +} diff --git a/naga/tests/out/wgsl/buffer.frag.wgsl b/naga/tests/out/wgsl/buffer.frag.wgsl new file mode 100644 index 0000000000..349d062e74 --- /dev/null +++ b/naga/tests/out/wgsl/buffer.frag.wgsl @@ -0,0 +1,30 @@ +struct testBufferBlock { + data: array, +} + +struct testBufferReadOnlyBlock { + data: array, +} + +@group(0) @binding(0) +var testBuffer: testBufferBlock; +@group(0) @binding(2) +var testBufferReadOnly: testBufferReadOnlyBlock; + +fn main_1() { + var a: u32; + var b: u32; + + let _e9 = testBuffer.data[0]; + a = _e9; + testBuffer.data[1i] = 2u; + let _e19 = testBufferReadOnly.data[0]; + b = _e19; + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/clamp-splat.vert.wgsl b/naga/tests/out/wgsl/clamp-splat.vert.wgsl new file mode 100644 index 0000000000..5dcc28e979 --- /dev/null +++ b/naga/tests/out/wgsl/clamp-splat.vert.wgsl @@ -0,0 +1,21 @@ +struct VertexOutput { + @builtin(position) member: vec4, +} + +var a_pos_1: vec2; +var gl_Position: vec4; + +fn main_1() { + let _e5 = a_pos_1; + let _e10 = clamp(_e5, vec2(0f), vec2(1f)); + gl_Position = vec4(_e10.x, _e10.y, 0f, 1f); + return; +} + +@vertex +fn main(@location(0) a_pos: vec2) -> VertexOutput { + a_pos_1 = a_pos; + main_1(); + let _e5 = gl_Position; + return VertexOutput(_e5); +} diff --git a/naga/tests/out/wgsl/collatz.wgsl b/naga/tests/out/wgsl/collatz.wgsl new file mode 100644 index 0000000000..477317594f --- /dev/null +++ b/naga/tests/out/wgsl/collatz.wgsl @@ -0,0 +1,42 @@ +struct PrimeIndices { + data: array, +} + +@group(0) @binding(0) +var v_indices: PrimeIndices; + +fn collatz_iterations(n_base: u32) -> u32 { + var n: u32; + var i: u32 = 0u; + + n = n_base; + loop { + let _e4 = n; + if (_e4 > 1u) { + } else { + break; + } + { + let _e7 = n; + if ((_e7 % 2u) == 0u) { + let _e12 = n; + n = (_e12 / 2u); + } else { + let _e16 = n; + n = ((3u * _e16) + 1u); + } + let _e20 = i; + i = (_e20 + 1u); + } + } + let _e23 = i; + return _e23; +} + +@compute @workgroup_size(1, 1, 1) +fn main(@builtin(global_invocation_id) global_id: vec3) { + let _e9 = v_indices.data[global_id.x]; + let _e10 = collatz_iterations(_e9); + v_indices.data[global_id.x] = _e10; + return; +} diff --git a/naga/tests/out/wgsl/const-exprs.wgsl b/naga/tests/out/wgsl/const-exprs.wgsl new file mode 100644 index 0000000000..20a43e2044 --- /dev/null +++ b/naga/tests/out/wgsl/const-exprs.wgsl @@ -0,0 +1,92 @@ +const TWO: u32 = 2u; +const THREE: i32 = 3i; +const FOUR: i32 = 4i; +const FOUR_ALIAS: i32 = 4i; +const TEST_CONSTANT_ADDITION: i32 = 8i; +const TEST_CONSTANT_ALIAS_ADDITION: i32 = 8i; +const PI: f32 = 3.141f; +const phi_sun: f32 = 6.282f; +const DIV: vec4 = vec4(0.44444445f, 0f, 0f, 0f); +const TEXTURE_KIND_REGULAR: i32 = 0i; +const TEXTURE_KIND_WARP: i32 = 1i; +const TEXTURE_KIND_SKY: i32 = 2i; +const add_vec: vec2 = vec2(4f, 5f); +const compare_vec: vec2 = vec2(true, false); + +fn swizzle_of_compose() { + var out: vec4 = vec4(4i, 3i, 2i, 1i); + +} + +fn index_of_compose() { + var out_1: i32 = 2i; + +} + +fn compose_three_deep() { + var out_2: i32 = 6i; + +} + +fn non_constant_initializers() { + var w: i32 = 30i; + var x: i32; + var y: i32; + var z: i32 = 70i; + var out_3: vec4; + + let _e2 = w; + x = _e2; + let _e4 = x; + y = _e4; + let _e8 = w; + let _e9 = x; + let _e10 = y; + let _e11 = z; + out_3 = vec4(_e8, _e9, _e10, _e11); + return; +} + +fn splat_of_constant() { + var out_4: vec4 = vec4(-4i, -4i, -4i, -4i); + +} + +fn compose_of_constant() { + var out_5: vec4 = vec4(-4i, -4i, -4i, -4i); + +} + +fn compose_of_splat() { + var x_1: vec4 = vec4(2f, 1f, 1f, 1f); + +} + +fn map_texture_kind(texture_kind: i32) -> u32 { + switch texture_kind { + case 0: { + return 10u; + } + case 1: { + return 20u; + } + case 2: { + return 30u; + } + default: { + return 0u; + } + } +} + +@compute @workgroup_size(2, 3, 1) +fn main() { + swizzle_of_compose(); + index_of_compose(); + compose_three_deep(); + non_constant_initializers(); + splat_of_constant(); + compose_of_constant(); + compose_of_splat(); + return; +} diff --git a/naga/tests/out/wgsl/const-global-swizzle.frag.wgsl b/naga/tests/out/wgsl/const-global-swizzle.frag.wgsl new file mode 100644 index 0000000000..dee49c2463 --- /dev/null +++ b/naga/tests/out/wgsl/const-global-swizzle.frag.wgsl @@ -0,0 +1,26 @@ +struct FragmentOutput { + @location(0) o_Target: vec4, +} + +const blank: vec2 = vec2(0f, 1f); + +var v_Uv_1: vec2; +var o_Target: vec4; + +fn main_1() { + var col: vec2; + + let _e3 = v_Uv_1; + col = (_e3.xy * blank); + let _e7 = col; + o_Target = vec4(_e7.x, _e7.y, 0f, 1f); + return; +} + +@fragment +fn main(@location(0) v_Uv: vec2) -> FragmentOutput { + v_Uv_1 = v_Uv; + main_1(); + let _e9 = o_Target; + return FragmentOutput(_e9); +} diff --git a/naga/tests/out/wgsl/constant-array-size.frag.wgsl b/naga/tests/out/wgsl/constant-array-size.frag.wgsl new file mode 100644 index 0000000000..46697cb7c2 --- /dev/null +++ b/naga/tests/out/wgsl/constant-array-size.frag.wgsl @@ -0,0 +1,42 @@ +struct Data { + vecs: array, 42>, +} + +const NUM_VECS: i32 = 42i; + +@group(1) @binding(0) +var global: Data; + +fn function() -> vec4 { + var sum: vec4 = vec4(0f); + var i: i32 = 0i; + + loop { + let _e9 = i; + if !((_e9 < NUM_VECS)) { + break; + } + { + let _e15 = sum; + let _e16 = i; + let _e18 = global.vecs[_e16]; + sum = (_e15 + _e18); + } + continuing { + let _e12 = i; + i = (_e12 + 1i); + } + } + let _e20 = sum; + return _e20; +} + +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/constructors.wgsl b/naga/tests/out/wgsl/constructors.wgsl new file mode 100644 index 0000000000..a8f62dfecd --- /dev/null +++ b/naga/tests/out/wgsl/constructors.wgsl @@ -0,0 +1,32 @@ +struct Foo { + a: vec4, + b: i32, +} + +const const2_: vec3 = vec3(0f, 1f, 2f); +const const3_: mat2x2 = mat2x2(vec2(0f, 1f), vec2(2f, 3f)); +const const4_: array, 1> = array, 1>(mat2x2(vec2(0f, 1f), vec2(2f, 3f))); +const cz0_: bool = bool(); +const cz1_: i32 = i32(); +const cz2_: u32 = u32(); +const cz3_: f32 = f32(); +const cz4_: vec2 = vec2(); +const cz5_: mat2x2 = mat2x2(); +const cz6_: array = array(); +const cz7_: Foo = Foo(); +const cp3_: array = array(0i, 1i, 2i, 3i); + +@compute @workgroup_size(1, 1, 1) +fn main() { + var foo: Foo; + + foo = Foo(vec4(1f), 1i); + let m0_ = mat2x2(vec2(1f, 0f), vec2(0f, 1f)); + let m1_ = mat4x4(vec4(1f, 0f, 0f, 0f), vec4(0f, 1f, 0f, 0f), vec4(0f, 0f, 1f, 0f), vec4(0f, 0f, 0f, 1f)); + let cit0_ = vec2(0u); + let cit1_ = mat2x2(vec2(0f), vec2(0f)); + let cit2_ = array(0i, 1i, 2i, 3i); + let ic0_ = bool(bool()); + let ic4_ = vec2(0u, 0u); + let ic5_ = mat2x3(vec3(0f, 0f, 0f), vec3(0f, 0f, 0f)); +} diff --git a/naga/tests/out/wgsl/control-flow.wgsl b/naga/tests/out/wgsl/control-flow.wgsl new file mode 100644 index 0000000000..dcc3f90365 --- /dev/null +++ b/naga/tests/out/wgsl/control-flow.wgsl @@ -0,0 +1,91 @@ +fn switch_default_break(i: i32) { + switch i { + default: { + break; + } + } +} + +fn switch_case_break() { + switch 0i { + case 0: { + break; + } + default: { + } + } + return; +} + +fn loop_switch_continue(x: i32) { + loop { + switch x { + case 1: { + continue; + } + default: { + } + } + } + return; +} + +@compute @workgroup_size(1, 1, 1) +fn main(@builtin(global_invocation_id) global_id: vec3) { + var pos: i32; + + storageBarrier(); + workgroupBarrier(); + switch 1i { + default: { + pos = 1i; + } + } + let _e4 = pos; + switch _e4 { + case 1: { + pos = 0i; + break; + } + case 2: { + pos = 1i; + } + case 3, 4: { + pos = 2i; + } + case 5: { + pos = 3i; + } + case default, 6: { + pos = 4i; + } + } + switch 0u { + case 0u: { + } + default: { + } + } + let _e11 = pos; + switch _e11 { + case 1: { + pos = 0i; + break; + } + case 2: { + pos = 1i; + return; + } + case 3: { + pos = 2i; + return; + } + case 4: { + return; + } + default: { + pos = 3i; + return; + } + } +} diff --git a/naga/tests/out/wgsl/declarations.frag.wgsl b/naga/tests/out/wgsl/declarations.frag.wgsl new file mode 100644 index 0000000000..c0c0236ad6 --- /dev/null +++ b/naga/tests/out/wgsl/declarations.frag.wgsl @@ -0,0 +1,69 @@ +struct VertexData { + position: vec2, + a: vec2, +} + +struct FragmentData { + position: vec2, + a: vec2, +} + +struct TestStruct { + a: f32, + b: f32, +} + +struct LightScatteringParams { + BetaRay: f32, + BetaMie: array, + HGg: f32, + DistanceMul: array, + BlendCoeff: f32, + SunDirection: vec3, + SunColor: vec3, +} + +struct FragmentOutput { + @location(0) position: vec2, + @location(1) a: vec2, + @location(2) out_array: vec4, + @location(3) out_array_1: vec4, +} + +var vert: VertexData; +var frag: FragmentData; +var in_array_2: array, 2>; +var out_array: array, 2>; +var array_2d: array, 2>; +var array_toomanyd: array, 2>, 2>, 2>, 2>, 2>, 2>; + +fn main_1() { + var positions: array, 2> = array, 2>(vec3(-1f, 1f, 0f), vec3(-1f, -1f, 0f)); + var strct: TestStruct = TestStruct(1f, 2f); + var from_input_array: vec4; + var a_1: f32; + var b: f32; + + let _e35 = in_array_2[1]; + from_input_array = _e35; + let _e41 = array_2d[0][0]; + a_1 = _e41; + let _e57 = array_toomanyd[0][0][0][0][0][0][0]; + b = _e57; + out_array[0i] = vec4(2f); + return; +} + +@fragment +fn main(@location(0) position: vec2, @location(1) a: vec2, @location(2) in_array: vec4, @location(3) in_array_1: vec4) -> FragmentOutput { + vert.position = position; + vert.a = a; + in_array_2[0] = in_array; + in_array_2[1] = in_array_1; + main_1(); + let _e30 = frag.position; + let _e32 = frag.a; + let _e35 = out_array[0]; + let _e37 = out_array[1]; + return FragmentOutput(_e30, _e32, _e35, _e37); +} diff --git a/naga/tests/out/wgsl/do-while.wgsl b/naga/tests/out/wgsl/do-while.wgsl new file mode 100644 index 0000000000..0b7e3afbcc --- /dev/null +++ b/naga/tests/out/wgsl/do-while.wgsl @@ -0,0 +1,23 @@ +fn fb1_(cond: ptr) { + loop { + continue; + continuing { + let _e1 = (*cond); + break if !(_e1); + } + } + return; +} + +fn main_1() { + var param: bool; + + param = false; + fb1_((¶m)); + return; +} + +@fragment +fn main() { + main_1(); +} diff --git a/naga/tests/out/wgsl/double-math-functions.frag.wgsl b/naga/tests/out/wgsl/double-math-functions.frag.wgsl new file mode 100644 index 0000000000..914af92d24 --- /dev/null +++ b/naga/tests/out/wgsl/double-math-functions.frag.wgsl @@ -0,0 +1,105 @@ +fn main_1() { + var a: vec4 = vec4(1.0lf); + var b: vec4 = vec4(2.0lf); + var m: mat4x4; + var i: i32 = 5i; + var ceilOut: vec4; + var roundOut: vec4; + var floorOut: vec4; + var fractOut: vec4; + var truncOut: vec4; + var absOut: vec4; + var sqrtOut: vec4; + var inversesqrtOut: vec4; + var signOut: vec4; + var transposeOut: mat4x4; + var normalizeOut: vec4; + var lengthOut: f64; + var determinantOut: f64; + var modOut: f64; + var dotOut: f64; + var maxOut: vec4; + var minOut: vec4; + var reflectOut: vec4; + var crossOut: vec3; + var distanceOut: f64; + var stepOut: vec4; + var ldexpOut: f64; + var smoothStepScalar: f64; + var smoothStepVector: vec4; + var smoothStepMixed: vec4; + + let _e8 = a; + let _e9 = b; + let _e10 = a; + let _e11 = b; + m = mat4x4(vec4(_e8.x, _e8.y, _e8.z, _e8.w), vec4(_e9.x, _e9.y, _e9.z, _e9.w), vec4(_e10.x, _e10.y, _e10.z, _e10.w), vec4(_e11.x, _e11.y, _e11.z, _e11.w)); + let _e37 = a; + ceilOut = ceil(_e37); + let _e41 = a; + roundOut = round(_e41); + let _e45 = a; + floorOut = floor(_e45); + let _e49 = a; + fractOut = fract(_e49); + let _e53 = a; + truncOut = trunc(_e53); + let _e57 = a; + absOut = abs(_e57); + let _e61 = a; + sqrtOut = sqrt(_e61); + let _e65 = a; + inversesqrtOut = inverseSqrt(_e65); + let _e69 = a; + signOut = sign(_e69); + let _e73 = m; + transposeOut = transpose(_e73); + let _e77 = a; + normalizeOut = normalize(_e77); + let _e81 = a; + lengthOut = length(_e81); + let _e85 = m; + determinantOut = determinant(_e85); + let _e88 = a; + let _e90 = b; + let _e92 = a; + let _e94 = b; + modOut = (_e92.x - (floor((_e92.x / _e94.x)) * _e94.x)); + let _e103 = a; + let _e104 = b; + dotOut = dot(_e103, _e104); + let _e109 = a; + let _e110 = b; + maxOut = max(_e109, _e110); + let _e115 = a; + let _e116 = b; + minOut = min(_e115, _e116); + let _e121 = a; + let _e122 = b; + reflectOut = reflect(_e121, _e122); + let _e125 = a; + let _e127 = b; + let _e129 = a; + let _e131 = b; + crossOut = cross(_e129.xyz, _e131.xyz); + let _e137 = a; + let _e138 = b; + distanceOut = distance(_e137, _e138); + let _e143 = a; + let _e144 = b; + stepOut = step(_e143, _e144); + let _e147 = a; + let _e150 = a; + let _e152 = i; + ldexpOut = ldexp(_e150.x, _e152); + smoothStepScalar = f64(smoothstep(0f, 1f, 0.5f)); + smoothStepVector = smoothstep(vec4(0.0lf), vec4(1.0lf), vec4(0.5lf)); + smoothStepMixed = smoothstep(vec4(0.0lf), vec4(1.0lf), vec4(0.5lf)); + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/dualsource.wgsl b/naga/tests/out/wgsl/dualsource.wgsl new file mode 100644 index 0000000000..505159bb45 --- /dev/null +++ b/naga/tests/out/wgsl/dualsource.wgsl @@ -0,0 +1,14 @@ +struct FragmentOutput { + @location(0) color: vec4, + @location(0) @second_blend_source mask: vec4, +} + +@fragment +fn main(@builtin(position) position: vec4) -> FragmentOutput { + var color: vec4 = vec4(0.4f, 0.3f, 0.2f, 0.1f); + var mask: vec4 = vec4(0.9f, 0.8f, 0.7f, 0.6f); + + let _e13 = color; + let _e14 = mask; + return FragmentOutput(_e13, _e14); +} diff --git a/naga/tests/out/wgsl/empty-global-name.wgsl b/naga/tests/out/wgsl/empty-global-name.wgsl new file mode 100644 index 0000000000..1be54db130 --- /dev/null +++ b/naga/tests/out/wgsl/empty-global-name.wgsl @@ -0,0 +1,17 @@ +struct type_1 { + member: i32, +} + +@group(0) @binding(0) +var unnamed: type_1; + +fn function() { + let _e3 = unnamed.member; + unnamed.member = (_e3 + 1i); + return; +} + +@compute @workgroup_size(1, 1, 1) +fn main() { + function(); +} diff --git a/naga/tests/out/wgsl/empty.wgsl b/naga/tests/out/wgsl/empty.wgsl new file mode 100644 index 0000000000..9d1c6ac9d9 --- /dev/null +++ b/naga/tests/out/wgsl/empty.wgsl @@ -0,0 +1,4 @@ +@compute @workgroup_size(1, 1, 1) +fn main() { + return; +} diff --git a/naga/tests/out/wgsl/expressions.frag.wgsl b/naga/tests/out/wgsl/expressions.frag.wgsl new file mode 100644 index 0000000000..0ba5962ab2 --- /dev/null +++ b/naga/tests/out/wgsl/expressions.frag.wgsl @@ -0,0 +1,446 @@ +struct BST { + data: i32, +} + +struct a_buf { + a: array, +} + +struct TestStruct { + array_: array, 2>, +} + +struct FragmentOutput { + @location(0) o_color: vec4, +} + +const strct: TestStruct = TestStruct(array, 2>(vec4(0u), vec4(1u))); + +var global: f32; +@group(0) @binding(0) +var global_1: a_buf; +var o_color: vec4; + +fn testBinOpVecFloat(a: vec4, b: f32) { + var a_1: vec4; + var b_1: f32; + var v: vec4; + + a_1 = a; + b_1 = b; + let _e5 = a_1; + v = (_e5 * 2f); + let _e8 = a_1; + v = (_e8 / vec4(2f)); + let _e12 = a_1; + v = (_e12 + vec4(2f)); + let _e16 = a_1; + v = (_e16 - vec4(2f)); + return; +} + +fn testBinOpFloatVec(a_2: vec4, b_2: f32) { + var a_3: vec4; + var b_3: f32; + var v_1: vec4; + + a_3 = a_2; + b_3 = b_2; + let _e5 = a_3; + let _e6 = b_3; + v_1 = (_e5 * _e6); + let _e8 = a_3; + let _e9 = b_3; + v_1 = (_e8 / vec4(_e9)); + let _e12 = a_3; + let _e13 = b_3; + v_1 = (_e12 + vec4(_e13)); + let _e16 = a_3; + let _e17 = b_3; + v_1 = (_e16 - vec4(_e17)); + return; +} + +fn testBinOpIVecInt(a_4: vec4, b_4: i32) { + var a_5: vec4; + var b_5: i32; + var v_2: vec4; + + a_5 = a_4; + b_5 = b_4; + let _e5 = a_5; + let _e6 = b_5; + v_2 = (_e5 * _e6); + let _e8 = a_5; + let _e9 = b_5; + v_2 = (_e8 / vec4(_e9)); + let _e12 = a_5; + let _e13 = b_5; + v_2 = (_e12 + vec4(_e13)); + let _e16 = a_5; + let _e17 = b_5; + v_2 = (_e16 - vec4(_e17)); + let _e20 = a_5; + let _e21 = b_5; + v_2 = (_e20 & vec4(_e21)); + let _e24 = a_5; + let _e25 = b_5; + v_2 = (_e24 | vec4(_e25)); + let _e28 = a_5; + let _e29 = b_5; + v_2 = (_e28 ^ vec4(_e29)); + let _e32 = a_5; + let _e33 = b_5; + v_2 = (_e32 >> vec4(u32(_e33))); + let _e37 = a_5; + let _e38 = b_5; + v_2 = (_e37 << vec4(u32(_e38))); + return; +} + +fn testBinOpIntIVec(a_6: i32, b_6: vec4) { + var a_7: i32; + var b_7: vec4; + var v_3: vec4; + + a_7 = a_6; + b_7 = b_6; + let _e5 = a_7; + let _e6 = b_7; + v_3 = (_e5 * _e6); + let _e8 = a_7; + let _e9 = b_7; + v_3 = (vec4(_e8) + _e9); + let _e12 = a_7; + let _e13 = b_7; + v_3 = (vec4(_e12) - _e13); + let _e16 = a_7; + let _e17 = b_7; + v_3 = (vec4(_e16) & _e17); + let _e20 = a_7; + let _e21 = b_7; + v_3 = (vec4(_e20) | _e21); + let _e24 = a_7; + let _e25 = b_7; + v_3 = (vec4(_e24) ^ _e25); + return; +} + +fn testBinOpUVecUint(a_8: vec4, b_8: u32) { + var a_9: vec4; + var b_9: u32; + var v_4: vec4; + + a_9 = a_8; + b_9 = b_8; + let _e5 = a_9; + let _e6 = b_9; + v_4 = (_e5 * _e6); + let _e8 = a_9; + let _e9 = b_9; + v_4 = (_e8 / vec4(_e9)); + let _e12 = a_9; + let _e13 = b_9; + v_4 = (_e12 + vec4(_e13)); + let _e16 = a_9; + let _e17 = b_9; + v_4 = (_e16 - vec4(_e17)); + let _e20 = a_9; + let _e21 = b_9; + v_4 = (_e20 & vec4(_e21)); + let _e24 = a_9; + let _e25 = b_9; + v_4 = (_e24 | vec4(_e25)); + let _e28 = a_9; + let _e29 = b_9; + v_4 = (_e28 ^ vec4(_e29)); + let _e32 = a_9; + let _e33 = b_9; + v_4 = (_e32 >> vec4(_e33)); + let _e36 = a_9; + let _e37 = b_9; + v_4 = (_e36 << vec4(_e37)); + return; +} + +fn testBinOpUintUVec(a_10: u32, b_10: vec4) { + var a_11: u32; + var b_11: vec4; + var v_5: vec4; + + a_11 = a_10; + b_11 = b_10; + let _e5 = a_11; + let _e6 = b_11; + v_5 = (_e5 * _e6); + let _e8 = a_11; + let _e9 = b_11; + v_5 = (vec4(_e8) + _e9); + let _e12 = a_11; + let _e13 = b_11; + v_5 = (vec4(_e12) - _e13); + let _e16 = a_11; + let _e17 = b_11; + v_5 = (vec4(_e16) & _e17); + let _e20 = a_11; + let _e21 = b_11; + v_5 = (vec4(_e20) | _e21); + let _e24 = a_11; + let _e25 = b_11; + v_5 = (vec4(_e24) ^ _e25); + return; +} + +fn testBinOpMatMat(a_12: mat3x3, b_12: mat3x3) { + var a_13: mat3x3; + var b_13: mat3x3; + var v_6: mat3x3; + var c: bool; + + a_13 = a_12; + b_13 = b_12; + let _e6 = a_13; + let _e7 = b_13; + v_6 = mat3x3((_e6[0] / _e7[0]), (_e6[1] / _e7[1]), (_e6[2] / _e7[2])); + let _e18 = a_13; + let _e19 = b_13; + v_6 = (_e18 * _e19); + let _e21 = a_13; + let _e22 = b_13; + v_6 = (_e21 + _e22); + let _e24 = a_13; + let _e25 = b_13; + v_6 = (_e24 - _e25); + let _e27 = a_13; + let _e28 = b_13; + c = (all((_e27[2] == _e28[2])) && (all((_e27[1] == _e28[1])) && all((_e27[0] == _e28[0])))); + let _e43 = a_13; + let _e44 = b_13; + c = (any((_e43[2] != _e44[2])) || (any((_e43[1] != _e44[1])) || any((_e43[0] != _e44[0])))); + return; +} + +fn testBinOpMatFloat(a_14: f32, b_14: mat3x3) { + var a_15: f32; + var b_15: mat3x3; + var v_7: mat3x3; + + a_15 = a_14; + b_15 = b_14; + let _e5 = a_15; + let _e6 = b_15; + let _e7 = vec3(_e5); + v_7 = mat3x3((_e7 / _e6[0]), (_e7 / _e6[1]), (_e7 / _e6[2])); + let _e15 = a_15; + let _e16 = b_15; + v_7 = (_e15 * _e16); + let _e18 = a_15; + let _e19 = b_15; + let _e20 = vec3(_e18); + v_7 = mat3x3((_e20 + _e19[0]), (_e20 + _e19[1]), (_e20 + _e19[2])); + let _e28 = a_15; + let _e29 = b_15; + let _e30 = vec3(_e28); + v_7 = mat3x3((_e30 - _e29[0]), (_e30 - _e29[1]), (_e30 - _e29[2])); + let _e38 = b_15; + let _e39 = a_15; + let _e40 = vec3(_e39); + v_7 = mat3x3((_e38[0] / _e40), (_e38[1] / _e40), (_e38[2] / _e40)); + let _e48 = b_15; + let _e49 = a_15; + v_7 = (_e48 * _e49); + let _e51 = b_15; + let _e52 = a_15; + let _e53 = vec3(_e52); + v_7 = mat3x3((_e51[0] + _e53), (_e51[1] + _e53), (_e51[2] + _e53)); + let _e61 = b_15; + let _e62 = a_15; + let _e63 = vec3(_e62); + v_7 = mat3x3((_e61[0] - _e63), (_e61[1] - _e63), (_e61[2] - _e63)); + return; +} + +fn testUnaryOpMat(a_16: mat3x3) { + var a_17: mat3x3; + var v_8: mat3x3; + + a_17 = a_16; + let _e3 = a_17; + v_8 = -(_e3); + let _e5 = a_17; + let _e7 = vec3(1f); + let _e9 = (_e5 - mat3x3(_e7, _e7, _e7)); + a_17 = _e9; + v_8 = _e9; + let _e10 = a_17; + let _e12 = vec3(1f); + a_17 = (_e10 - mat3x3(_e12, _e12, _e12)); + v_8 = _e10; + return; +} + +fn testStructConstructor() { + var tree: BST = BST(1i); + +} + +fn testNonScalarToScalarConstructor() { + var f: f32 = 1f; + +} + +fn testArrayConstructor() { + var tree_1: array = array(0f); + +} + +fn testFreestandingConstructor() { + return; +} + +fn testNonImplicitCastVectorCast() { + var a_18: u32 = 1u; + var b_16: vec4; + + let _e3 = a_18; + b_16 = vec4(i32(_e3)); + return; +} + +fn privatePointer(a_19: ptr) { + return; +} + +fn ternary(a_20: bool) { + var a_21: bool; + var local: u32; + var b_17: u32; + var local_1: u32; + var c_1: u32; + var local_2: u32; + var local_3: u32; + var local_4: u32; + var nested: u32; + + a_21 = a_20; + let _e3 = a_21; + if _e3 { + local = 0u; + } else { + local = 1u; + } + let _e8 = local; + b_17 = _e8; + let _e10 = a_21; + if _e10 { + local_1 = 0u; + } else { + local_1 = 1u; + } + let _e15 = local_1; + c_1 = _e15; + let _e17 = a_21; + if _e17 { + let _e18 = a_21; + if _e18 { + let _e19 = a_21; + if _e19 { + local_2 = 2u; + } else { + local_2 = 3u; + } + let _e24 = local_2; + local_3 = _e24; + } else { + local_3 = 4u; + } + let _e27 = local_3; + local_4 = _e27; + } else { + local_4 = 5u; + } + let _e31 = local_4; + nested = _e31; + return; +} + +fn testMatrixMultiplication(a_22: mat4x3, b_18: mat4x4) { + var a_23: mat4x3; + var b_19: mat4x4; + var c_2: mat4x3; + + a_23 = a_22; + b_19 = b_18; + let _e5 = a_23; + let _e6 = b_19; + c_2 = (_e5 * _e6); + return; +} + +fn testLength() { + var len: i32; + + len = i32(arrayLength((&global_1.a))); + return; +} + +fn testConstantLength(a_24: array) { + var a_25: array; + var len_1: i32 = 4i; + + a_25 = a_24; +} + +fn indexConstantNonConstantIndex(i: i32) { + var i_1: i32; + var local_5: TestStruct = strct; + var a_26: vec4; + + i_1 = i; + let _e6 = i_1; + let _e11 = local_5.array_[_e6]; + a_26 = _e11; + return; +} + +fn testSwizzleWrites(a_27: vec3) { + var a_28: vec3; + + a_28 = a_27; + let _e6 = a_28; + a_28.z = 3f; + a_28.x = 4f; + let _e14 = a_28; + let _e16 = a_28; + let _e19 = (_e16.xy * 5f); + a_28.x = _e19.x; + a_28.y = _e19.y; + let _e24 = a_28; + let _e28 = (_e24.zy + vec2(1f)); + a_28.z = _e28.x; + a_28.y = _e28.y; + return; +} + +fn main_1() { + var local_6: f32; + + let _e6 = global; + local_6 = _e6; + privatePointer((&local_6)); + let _e8 = local_6; + global = _e8; + let _e9 = o_color; + o_color.x = 1f; + o_color.y = 1f; + o_color.z = 1f; + o_color.w = 1f; + return; +} + +@fragment +fn main() -> FragmentOutput { + main_1(); + let _e9 = o_color; + return FragmentOutput(_e9); +} diff --git a/naga/tests/out/wgsl/extra.wgsl b/naga/tests/out/wgsl/extra.wgsl new file mode 100644 index 0000000000..25f894e7f5 --- /dev/null +++ b/naga/tests/out/wgsl/extra.wgsl @@ -0,0 +1,21 @@ +struct PushConstants { + index: u32, + double: vec2, +} + +struct FragmentIn { + @location(0) color: vec4, + @builtin(primitive_index) primitive_index: u32, +} + +var pc: PushConstants; + +@fragment +fn main(in: FragmentIn) -> @location(0) vec4 { + let _e4 = pc.index; + if (in.primitive_index == _e4) { + return in.color; + } else { + return vec4((vec3(1f) - in.color.xyz), in.color.w); + } +} diff --git a/naga/tests/out/wgsl/f64.wgsl b/naga/tests/out/wgsl/f64.wgsl new file mode 100644 index 0000000000..65699237fe --- /dev/null +++ b/naga/tests/out/wgsl/f64.wgsl @@ -0,0 +1,17 @@ +const k: f64 = 2.0lf; + +var v: f64 = 1.0lf; + +fn f(x: f64) -> f64 { + var z: f64; + + let y = (30.0lf + 400.0lf); + z = (y + 5.0lf); + return (((x + y) + k) + 5.0lf); +} + +@compute @workgroup_size(1, 1, 1) +fn main() { + let _e1 = f(6.0lf); + return; +} diff --git a/naga/tests/out/wgsl/fma.frag.wgsl b/naga/tests/out/wgsl/fma.frag.wgsl new file mode 100644 index 0000000000..57d11c1d24 --- /dev/null +++ b/naga/tests/out/wgsl/fma.frag.wgsl @@ -0,0 +1,48 @@ +struct Mat4x3_ { + mx: vec4, + my: vec4, + mz: vec4, +} + +struct FragmentOutput { + @location(0) o_color: vec4, +} + +var o_color: vec4; + +fn Fma(d: ptr, m: Mat4x3_, s: f32) { + var m_1: Mat4x3_; + var s_1: f32; + + m_1 = m; + s_1 = s; + let _e6 = (*d); + let _e8 = m_1; + let _e10 = s_1; + (*d).mx = (_e6.mx + (_e8.mx * _e10)); + let _e14 = (*d); + let _e16 = m_1; + let _e18 = s_1; + (*d).my = (_e14.my + (_e16.my * _e18)); + let _e22 = (*d); + let _e24 = m_1; + let _e26 = s_1; + (*d).mz = (_e22.mz + (_e24.mz * _e26)); + return; +} + +fn main_1() { + let _e1 = o_color; + o_color.x = 1f; + o_color.y = 1f; + o_color.z = 1f; + o_color.w = 1f; + return; +} + +@fragment +fn main() -> FragmentOutput { + main_1(); + let _e3 = o_color; + return FragmentOutput(_e3); +} diff --git a/naga/tests/out/wgsl/fragment-output.wgsl b/naga/tests/out/wgsl/fragment-output.wgsl new file mode 100644 index 0000000000..1a17003979 --- /dev/null +++ b/naga/tests/out/wgsl/fragment-output.wgsl @@ -0,0 +1,45 @@ +struct FragmentOutputVec4Vec3_ { + @location(0) vec4f: vec4, + @location(1) @interpolate(flat) vec4i: vec4, + @location(2) @interpolate(flat) vec4u: vec4, + @location(3) vec3f: vec3, + @location(4) @interpolate(flat) vec3i: vec3, + @location(5) @interpolate(flat) vec3u: vec3, +} + +struct FragmentOutputVec2Scalar { + @location(0) vec2f: vec2, + @location(1) @interpolate(flat) vec2i: vec2, + @location(2) @interpolate(flat) vec2u: vec2, + @location(3) scalarf: f32, + @location(4) @interpolate(flat) scalari: i32, + @location(5) @interpolate(flat) scalaru: u32, +} + +@fragment +fn main_vec4vec3_() -> FragmentOutputVec4Vec3_ { + var output: FragmentOutputVec4Vec3_; + + output.vec4f = vec4(0f); + output.vec4i = vec4(0i); + output.vec4u = vec4(0u); + output.vec3f = vec3(0f); + output.vec3i = vec3(0i); + output.vec3u = vec3(0u); + let _e19 = output; + return _e19; +} + +@fragment +fn main_vec2scalar() -> FragmentOutputVec2Scalar { + var output_1: FragmentOutputVec2Scalar; + + output_1.vec2f = vec2(0f); + output_1.vec2i = vec2(0i); + output_1.vec2u = vec2(0u); + output_1.scalarf = 0f; + output_1.scalari = 0i; + output_1.scalaru = 0u; + let _e16 = output_1; + return _e16; +} diff --git a/naga/tests/out/wgsl/functions.wgsl b/naga/tests/out/wgsl/functions.wgsl new file mode 100644 index 0000000000..79f000ce22 --- /dev/null +++ b/naga/tests/out/wgsl/functions.wgsl @@ -0,0 +1,24 @@ +fn test_fma() -> vec2 { + let a = vec2(2f, 2f); + let b = vec2(0.5f, 0.5f); + let c = vec2(0.5f, 0.5f); + return fma(a, b, c); +} + +fn test_integer_dot_product() -> i32 { + let a_2_ = vec2(1i); + let b_2_ = vec2(1i); + let c_2_ = dot(a_2_, b_2_); + let a_3_ = vec3(1u); + let b_3_ = vec3(1u); + let c_3_ = dot(a_3_, b_3_); + let c_4_ = dot(vec4(4i), vec4(2i)); + return c_4_; +} + +@compute @workgroup_size(1, 1, 1) +fn main() { + let _e0 = test_fma(); + let _e1 = test_integer_dot_product(); + return; +} diff --git a/naga/tests/out/wgsl/functions_call.frag.wgsl b/naga/tests/out/wgsl/functions_call.frag.wgsl new file mode 100644 index 0000000000..62aaeb2033 --- /dev/null +++ b/naga/tests/out/wgsl/functions_call.frag.wgsl @@ -0,0 +1,63 @@ +fn swizzleCallee(a: ptr>) { + return; +} + +fn swizzleCaller(a_1: vec3) { + var a_2: vec3; + var local: vec2; + + a_2 = a_1; + let _e2 = a_2; + let _e4 = a_2; + local = _e4.xz; + swizzleCallee((&local)); + let _e11 = local.x; + a_2.x = _e11; + let _e12 = local.y; + a_2.z = _e12; + return; +} + +fn outImplicitCastCallee(a_3: ptr) { + return; +} + +fn outImplicitCastCaller(a_4: f32) { + var a_5: f32; + var local_1: u32; + + a_5 = a_4; + outImplicitCastCallee((&local_1)); + let _e5 = local_1; + a_5 = f32(_e5); + return; +} + +fn swizzleImplicitCastCallee(a_6: ptr>) { + return; +} + +fn swizzleImplicitCastCaller(a_7: vec3) { + var a_8: vec3; + var local_2: vec2; + + a_8 = a_7; + let _e2 = a_8; + let _e4 = a_8; + swizzleImplicitCastCallee((&local_2)); + let _e11 = local_2.x; + a_8.x = f32(_e11); + let _e13 = local_2.y; + a_8.z = f32(_e13); + return; +} + +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/global-constant-array.frag.wgsl b/naga/tests/out/wgsl/global-constant-array.frag.wgsl new file mode 100644 index 0000000000..bdb509dc35 --- /dev/null +++ b/naga/tests/out/wgsl/global-constant-array.frag.wgsl @@ -0,0 +1,15 @@ +const array_: array = array(1f, 2f); + +var i: u32; + +fn main_1() { + var local: array = array_; + + let _e2 = i; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/globals.wgsl b/naga/tests/out/wgsl/globals.wgsl new file mode 100644 index 0000000000..229d40ccf6 --- /dev/null +++ b/naga/tests/out/wgsl/globals.wgsl @@ -0,0 +1,71 @@ +struct FooStruct { + v3_: vec3, + v1_: f32, +} + +const Foo_1: bool = true; + +var wg: array; +var at_1: atomic; +@group(0) @binding(1) +var alignment: FooStruct; +@group(0) @binding(2) +var dummy: array>; +@group(0) @binding(3) +var float_vecs: array, 20>; +@group(0) @binding(4) +var global_vec: vec3; +@group(0) @binding(5) +var global_mat: mat3x2; +@group(0) @binding(6) +var global_nested_arrays_of_matrices_2x4_: array, 2>, 2>; +@group(0) @binding(7) +var global_nested_arrays_of_matrices_4x2_: array, 2>, 2>; + +fn test_msl_packed_vec3_as_arg(arg: vec3) { + return; +} + +fn test_msl_packed_vec3_() { + var idx: i32 = 1i; + + alignment.v3_ = vec3(1f); + alignment.v3_.x = 1f; + alignment.v3_.x = 2f; + let _e16 = idx; + alignment.v3_[_e16] = 3f; + let data = alignment; + let l0_ = data.v3_; + let l1_ = data.v3_.zx; + test_msl_packed_vec3_as_arg(data.v3_); + let mvm0_ = (data.v3_ * mat3x3()); + let mvm1_ = (mat3x3() * data.v3_); + let svm0_ = (data.v3_ * 2f); + let svm1_ = (2f * data.v3_); +} + +@compute @workgroup_size(1, 1, 1) +fn main() { + var Foo: f32 = 1f; + var at: bool = true; + + test_msl_packed_vec3_(); + let _e5 = global_nested_arrays_of_matrices_4x2_[0][0]; + let _e10 = global_nested_arrays_of_matrices_2x4_[0][0][0]; + wg[7] = (_e5 * _e10).x; + let _e16 = global_mat; + let _e18 = global_vec; + wg[6] = (_e16 * _e18).x; + let _e26 = dummy[1].y; + wg[5] = _e26; + let _e32 = float_vecs[0].w; + wg[4] = _e32; + let _e37 = alignment.v1_; + wg[3] = _e37; + let _e43 = alignment.v3_.x; + wg[2] = _e43; + alignment.v1_ = 4f; + wg[1] = f32(arrayLength((&dummy))); + atomicStore((&at_1), 2u); + return; +} diff --git a/naga/tests/out/wgsl/image.wgsl b/naga/tests/out/wgsl/image.wgsl new file mode 100644 index 0000000000..008b4c20c1 --- /dev/null +++ b/naga/tests/out/wgsl/image.wgsl @@ -0,0 +1,238 @@ +@group(0) @binding(0) +var image_mipmapped_src: texture_2d; +@group(0) @binding(3) +var image_multisampled_src: texture_multisampled_2d; +@group(0) @binding(4) +var image_depth_multisampled_src: texture_depth_multisampled_2d; +@group(0) @binding(1) +var image_storage_src: texture_storage_2d; +@group(0) @binding(5) +var image_array_src: texture_2d_array; +@group(0) @binding(6) +var image_dup_src: texture_storage_1d; +@group(0) @binding(7) +var image_1d_src: texture_1d; +@group(0) @binding(2) +var image_dst: texture_storage_1d; +@group(0) @binding(0) +var image_1d: texture_1d; +@group(0) @binding(1) +var image_2d: texture_2d; +@group(0) @binding(2) +var image_2d_u32_: texture_2d; +@group(0) @binding(3) +var image_2d_i32_: texture_2d; +@group(0) @binding(4) +var image_2d_array: texture_2d_array; +@group(0) @binding(5) +var image_cube: texture_cube; +@group(0) @binding(6) +var image_cube_array: texture_cube_array; +@group(0) @binding(7) +var image_3d: texture_3d; +@group(0) @binding(8) +var image_aa: texture_multisampled_2d; +@group(1) @binding(0) +var sampler_reg: sampler; +@group(1) @binding(1) +var sampler_cmp: sampler_comparison; +@group(1) @binding(2) +var image_2d_depth: texture_depth_2d; +@group(1) @binding(3) +var image_2d_array_depth: texture_depth_2d_array; +@group(1) @binding(4) +var image_cube_depth: texture_depth_cube; + +@compute @workgroup_size(16, 1, 1) +fn main(@builtin(local_invocation_id) local_id: vec3) { + let dim = textureDimensions(image_storage_src); + let itc = (vec2((dim * local_id.xy)) % vec2(10i, 20i)); + let value1_ = textureLoad(image_mipmapped_src, itc, i32(local_id.z)); + let value2_ = textureLoad(image_multisampled_src, itc, i32(local_id.z)); + let value4_ = textureLoad(image_storage_src, itc); + let value5_ = textureLoad(image_array_src, itc, local_id.z, (i32(local_id.z) + 1i)); + let value6_ = textureLoad(image_array_src, itc, i32(local_id.z), (i32(local_id.z) + 1i)); + let value7_ = textureLoad(image_1d_src, i32(local_id.x), i32(local_id.z)); + let value1u = textureLoad(image_mipmapped_src, vec2(itc), i32(local_id.z)); + let value2u = textureLoad(image_multisampled_src, vec2(itc), i32(local_id.z)); + let value4u = textureLoad(image_storage_src, vec2(itc)); + let value5u = textureLoad(image_array_src, vec2(itc), local_id.z, (i32(local_id.z) + 1i)); + let value6u = textureLoad(image_array_src, vec2(itc), i32(local_id.z), (i32(local_id.z) + 1i)); + let value7u = textureLoad(image_1d_src, u32(local_id.x), i32(local_id.z)); + textureStore(image_dst, itc.x, ((((value1_ + value2_) + value4_) + value5_) + value6_)); + textureStore(image_dst, u32(itc.x), ((((value1u + value2u) + value4u) + value5u) + value6u)); + return; +} + +@compute @workgroup_size(16, 1, 1) +fn depth_load(@builtin(local_invocation_id) local_id_1: vec3) { + let dim_1 = textureDimensions(image_storage_src); + let itc_1 = (vec2((dim_1 * local_id_1.xy)) % vec2(10i, 20i)); + let val = textureLoad(image_depth_multisampled_src, itc_1, i32(local_id_1.z)); + textureStore(image_dst, itc_1.x, vec4(u32(val))); + return; +} + +@vertex +fn queries() -> @builtin(position) vec4 { + let dim_1d = textureDimensions(image_1d); + let dim_1d_lod = textureDimensions(image_1d, i32(dim_1d)); + let dim_2d = textureDimensions(image_2d); + let dim_2d_lod = textureDimensions(image_2d, 1i); + let dim_2d_array = textureDimensions(image_2d_array); + let dim_2d_array_lod = textureDimensions(image_2d_array, 1i); + let dim_cube = textureDimensions(image_cube); + let dim_cube_lod = textureDimensions(image_cube, 1i); + let dim_cube_array = textureDimensions(image_cube_array); + let dim_cube_array_lod = textureDimensions(image_cube_array, 1i); + let dim_3d = textureDimensions(image_3d); + let dim_3d_lod = textureDimensions(image_3d, 1i); + let dim_2s_ms = textureDimensions(image_aa); + let sum = ((((((((((dim_1d + dim_2d.y) + dim_2d_lod.y) + dim_2d_array.y) + dim_2d_array_lod.y) + dim_cube.y) + dim_cube_lod.y) + dim_cube_array.y) + dim_cube_array_lod.y) + dim_3d.z) + dim_3d_lod.z); + return vec4(f32(sum)); +} + +@vertex +fn levels_queries() -> @builtin(position) vec4 { + let num_levels_2d = textureNumLevels(image_2d); + let num_levels_2d_array = textureNumLevels(image_2d_array); + let num_layers_2d = textureNumLayers(image_2d_array); + let num_levels_cube = textureNumLevels(image_cube); + let num_levels_cube_array = textureNumLevels(image_cube_array); + let num_layers_cube = textureNumLayers(image_cube_array); + let num_levels_3d = textureNumLevels(image_3d); + let num_samples_aa = textureNumSamples(image_aa); + let sum_1 = (((((((num_layers_2d + num_layers_cube) + num_samples_aa) + num_levels_2d) + num_levels_2d_array) + num_levels_3d) + num_levels_cube) + num_levels_cube_array); + return vec4(f32(sum_1)); +} + +@fragment +fn texture_sample() -> @location(0) vec4 { + var a: vec4; + + let tc = vec2(0.5f); + let tc3_ = vec3(0.5f); + let _e9 = textureSample(image_1d, sampler_reg, tc.x); + let _e10 = a; + a = (_e10 + _e9); + let _e14 = textureSample(image_2d, sampler_reg, tc); + let _e15 = a; + a = (_e15 + _e14); + let _e19 = textureSample(image_2d, sampler_reg, tc, vec2(3i, 1i)); + let _e20 = a; + a = (_e20 + _e19); + let _e24 = textureSampleLevel(image_2d, sampler_reg, tc, 2.3f); + let _e25 = a; + a = (_e25 + _e24); + let _e29 = textureSampleLevel(image_2d, sampler_reg, tc, 2.3f, vec2(3i, 1i)); + let _e30 = a; + a = (_e30 + _e29); + let _e35 = textureSampleBias(image_2d, sampler_reg, tc, 2f, vec2(3i, 1i)); + let _e36 = a; + a = (_e36 + _e35); + let _e41 = textureSample(image_2d_array, sampler_reg, tc, 0u); + let _e42 = a; + a = (_e42 + _e41); + let _e47 = textureSample(image_2d_array, sampler_reg, tc, 0u, vec2(3i, 1i)); + let _e48 = a; + a = (_e48 + _e47); + let _e53 = textureSampleLevel(image_2d_array, sampler_reg, tc, 0u, 2.3f); + let _e54 = a; + a = (_e54 + _e53); + let _e59 = textureSampleLevel(image_2d_array, sampler_reg, tc, 0u, 2.3f, vec2(3i, 1i)); + let _e60 = a; + a = (_e60 + _e59); + let _e66 = textureSampleBias(image_2d_array, sampler_reg, tc, 0u, 2f, vec2(3i, 1i)); + let _e67 = a; + a = (_e67 + _e66); + let _e72 = textureSample(image_2d_array, sampler_reg, tc, 0i); + let _e73 = a; + a = (_e73 + _e72); + let _e78 = textureSample(image_2d_array, sampler_reg, tc, 0i, vec2(3i, 1i)); + let _e79 = a; + a = (_e79 + _e78); + let _e84 = textureSampleLevel(image_2d_array, sampler_reg, tc, 0i, 2.3f); + let _e85 = a; + a = (_e85 + _e84); + let _e90 = textureSampleLevel(image_2d_array, sampler_reg, tc, 0i, 2.3f, vec2(3i, 1i)); + let _e91 = a; + a = (_e91 + _e90); + let _e97 = textureSampleBias(image_2d_array, sampler_reg, tc, 0i, 2f, vec2(3i, 1i)); + let _e98 = a; + a = (_e98 + _e97); + let _e103 = textureSample(image_cube_array, sampler_reg, tc3_, 0u); + let _e104 = a; + a = (_e104 + _e103); + let _e109 = textureSampleLevel(image_cube_array, sampler_reg, tc3_, 0u, 2.3f); + let _e110 = a; + a = (_e110 + _e109); + let _e116 = textureSampleBias(image_cube_array, sampler_reg, tc3_, 0u, 2f); + let _e117 = a; + a = (_e117 + _e116); + let _e122 = textureSample(image_cube_array, sampler_reg, tc3_, 0i); + let _e123 = a; + a = (_e123 + _e122); + let _e128 = textureSampleLevel(image_cube_array, sampler_reg, tc3_, 0i, 2.3f); + let _e129 = a; + a = (_e129 + _e128); + let _e135 = textureSampleBias(image_cube_array, sampler_reg, tc3_, 0i, 2f); + let _e136 = a; + a = (_e136 + _e135); + let _e138 = a; + return _e138; +} + +@fragment +fn texture_sample_comparison() -> @location(0) f32 { + var a_1: f32; + + let tc_1 = vec2(0.5f); + let tc3_1 = vec3(0.5f); + let _e8 = textureSampleCompare(image_2d_depth, sampler_cmp, tc_1, 0.5f); + let _e9 = a_1; + a_1 = (_e9 + _e8); + let _e14 = textureSampleCompare(image_2d_array_depth, sampler_cmp, tc_1, 0u, 0.5f); + let _e15 = a_1; + a_1 = (_e15 + _e14); + let _e20 = textureSampleCompare(image_2d_array_depth, sampler_cmp, tc_1, 0i, 0.5f); + let _e21 = a_1; + a_1 = (_e21 + _e20); + let _e25 = textureSampleCompare(image_cube_depth, sampler_cmp, tc3_1, 0.5f); + let _e26 = a_1; + a_1 = (_e26 + _e25); + let _e30 = textureSampleCompareLevel(image_2d_depth, sampler_cmp, tc_1, 0.5f); + let _e31 = a_1; + a_1 = (_e31 + _e30); + let _e36 = textureSampleCompareLevel(image_2d_array_depth, sampler_cmp, tc_1, 0u, 0.5f); + let _e37 = a_1; + a_1 = (_e37 + _e36); + let _e42 = textureSampleCompareLevel(image_2d_array_depth, sampler_cmp, tc_1, 0i, 0.5f); + let _e43 = a_1; + a_1 = (_e43 + _e42); + let _e47 = textureSampleCompareLevel(image_cube_depth, sampler_cmp, tc3_1, 0.5f); + let _e48 = a_1; + a_1 = (_e48 + _e47); + let _e50 = a_1; + return _e50; +} + +@fragment +fn gather() -> @location(0) vec4 { + let tc_2 = vec2(0.5f); + let s2d = textureGather(1, image_2d, sampler_reg, tc_2); + let s2d_offset = textureGather(3, image_2d, sampler_reg, tc_2, vec2(3i, 1i)); + let s2d_depth = textureGatherCompare(image_2d_depth, sampler_cmp, tc_2, 0.5f); + let s2d_depth_offset = textureGatherCompare(image_2d_depth, sampler_cmp, tc_2, 0.5f, vec2(3i, 1i)); + let u = textureGather(0, image_2d_u32_, sampler_reg, tc_2); + let i = textureGather(0, image_2d_i32_, sampler_reg, tc_2); + let f = (vec4(u) + vec4(i)); + return ((((s2d + s2d_offset) + s2d_depth) + s2d_depth_offset) + f); +} + +@fragment +fn depth_no_comparison() -> @location(0) vec4 { + let tc_3 = vec2(0.5f); + let s2d_1 = textureSample(image_2d_depth, sampler_reg, tc_3); + let s2d_gather = textureGather(image_2d_depth, sampler_reg, tc_3); + return (vec4(s2d_1) + s2d_gather); +} diff --git a/naga/tests/out/wgsl/images.frag.wgsl b/naga/tests/out/wgsl/images.frag.wgsl new file mode 100644 index 0000000000..e7eada96e8 --- /dev/null +++ b/naga/tests/out/wgsl/images.frag.wgsl @@ -0,0 +1,144 @@ +@group(0) @binding(0) +var img1D: texture_storage_1d; +@group(0) @binding(1) +var img2D: texture_storage_2d; +@group(0) @binding(2) +var img3D: texture_storage_3d; +@group(0) @binding(4) +var img1DArray: texture_storage_1d_array; +@group(0) @binding(5) +var img2DArray: texture_storage_2d_array; +@group(0) @binding(7) +var imgReadOnly: texture_storage_2d; +@group(0) @binding(8) +var imgWriteOnly: texture_storage_2d; +@group(0) @binding(9) +var imgWriteReadOnly: texture_storage_2d; + +fn testImg1D(coord: i32) { + var coord_1: i32; + var size: i32; + var c: vec4; + + coord_1 = coord; + let _e10 = textureDimensions(img1D); + size = i32(_e10); + let _e17 = coord_1; + textureStore(img1D, _e17, vec4(2f)); + let _e22 = coord_1; + let _e23 = textureLoad(img1D, _e22); + c = _e23; + return; +} + +fn testImg1DArray(coord_2: vec2) { + var coord_3: vec2; + var size_1: vec2; + var c_1: vec4; + + coord_3 = coord_2; + let _e10 = textureDimensions(img1DArray); + let _e11 = textureNumLayers(img1DArray); + size_1 = vec2(vec2(vec2(_e10, _e11))); + let _e17 = coord_3; + let _e20 = textureLoad(img1DArray, _e17.x, _e17.y); + c_1 = _e20; + let _e26 = coord_3; + textureStore(img1DArray, _e26.x, _e26.y, vec4(2f)); + return; +} + +fn testImg2D(coord_4: vec2) { + var coord_5: vec2; + var size_2: vec2; + var c_2: vec4; + + coord_5 = coord_4; + let _e10 = textureDimensions(img2D); + size_2 = vec2(vec2(_e10)); + let _e15 = coord_5; + let _e16 = textureLoad(img2D, _e15); + c_2 = _e16; + let _e22 = coord_5; + textureStore(img2D, _e22, vec4(2f)); + return; +} + +fn testImg2DArray(coord_6: vec3) { + var coord_7: vec3; + var size_3: vec3; + var c_3: vec4; + + coord_7 = coord_6; + let _e10 = textureDimensions(img2DArray); + let _e13 = textureNumLayers(img2DArray); + size_3 = vec3(vec3(vec3(_e10.x, _e10.y, _e13))); + let _e19 = coord_7; + let _e22 = textureLoad(img2DArray, _e19.xy, _e19.z); + c_3 = _e22; + let _e28 = coord_7; + textureStore(img2DArray, _e28.xy, _e28.z, vec4(2f)); + return; +} + +fn testImg3D(coord_8: vec3) { + var coord_9: vec3; + var size_4: vec3; + var c_4: vec4; + + coord_9 = coord_8; + let _e10 = textureDimensions(img3D); + size_4 = vec3(vec3(_e10)); + let _e15 = coord_9; + let _e16 = textureLoad(img3D, _e15); + c_4 = _e16; + let _e22 = coord_9; + textureStore(img3D, _e22, vec4(2f)); + return; +} + +fn testImgReadOnly(coord_10: vec2) { + var coord_11: vec2; + var size_5: vec2; + var c_5: vec4; + + coord_11 = coord_10; + let _e10 = textureDimensions(img2D); + size_5 = vec2(vec2(_e10)); + let _e15 = coord_11; + let _e16 = textureLoad(imgReadOnly, _e15); + c_5 = _e16; + return; +} + +fn testImgWriteOnly(coord_12: vec2) { + var coord_13: vec2; + var size_6: vec2; + + coord_13 = coord_12; + let _e10 = textureDimensions(img2D); + size_6 = vec2(vec2(_e10)); + let _e18 = coord_13; + textureStore(imgWriteOnly, _e18, vec4(2f)); + return; +} + +fn testImgWriteReadOnly(coord_14: vec2) { + var coord_15: vec2; + var size_7: vec2; + + coord_15 = coord_14; + let _e10 = textureDimensions(imgWriteReadOnly); + size_7 = vec2(vec2(_e10)); + return; +} + +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/interface.wgsl b/naga/tests/out/wgsl/interface.wgsl new file mode 100644 index 0000000000..1f46278ab8 --- /dev/null +++ b/naga/tests/out/wgsl/interface.wgsl @@ -0,0 +1,47 @@ +struct VertexOutput { + @builtin(position) @invariant position: vec4, + @location(1) _varying: f32, +} + +struct FragmentOutput { + @builtin(frag_depth) depth: f32, + @builtin(sample_mask) sample_mask: u32, + @location(0) color: f32, +} + +struct Input1_ { + @builtin(vertex_index) index: u32, +} + +struct Input2_ { + @builtin(instance_index) index: u32, +} + +var output: array; + +@vertex +fn vertex(@builtin(vertex_index) vertex_index: u32, @builtin(instance_index) instance_index: u32, @location(10) @interpolate(flat) color: u32) -> VertexOutput { + let tmp: u32 = ((vertex_index + instance_index) + color); + return VertexOutput(vec4(1f), f32(tmp)); +} + +@fragment +fn fragment(in: VertexOutput, @builtin(front_facing) front_facing: bool, @builtin(sample_index) sample_index: u32, @builtin(sample_mask) sample_mask: u32) -> FragmentOutput { + let mask: u32 = (sample_mask & (1u << sample_index)); + let color_1: f32 = select(0f, 1f, front_facing); + return FragmentOutput(in._varying, mask, color_1); +} + +@compute @workgroup_size(1, 1, 1) +fn compute(@builtin(global_invocation_id) global_id: vec3, @builtin(local_invocation_id) local_id: vec3, @builtin(local_invocation_index) local_index: u32, @builtin(workgroup_id) wg_id: vec3, @builtin(num_workgroups) num_wgs: vec3) { + output[0] = ((((global_id.x + local_id.x) + local_index) + wg_id.x) + num_wgs.x); + return; +} + +@vertex +fn vertex_two_structs(in1_: Input1_, in2_: Input2_) -> @builtin(position) @invariant vec4 { + var index: u32 = 2u; + + let _e8: u32 = index; + return vec4(f32(in1_.index), f32(in2_.index), f32(_e8), 0f); +} diff --git a/naga/tests/out/wgsl/interpolate.wgsl b/naga/tests/out/wgsl/interpolate.wgsl new file mode 100644 index 0000000000..402e60cef5 --- /dev/null +++ b/naga/tests/out/wgsl/interpolate.wgsl @@ -0,0 +1,31 @@ +struct FragmentInput { + @builtin(position) position: vec4, + @location(0) @interpolate(flat) _flat: u32, + @location(1) @interpolate(linear) _linear: f32, + @location(2) @interpolate(linear, centroid) linear_centroid: vec2, + @location(3) @interpolate(linear, sample) linear_sample: vec3, + @location(4) perspective: vec4, + @location(5) @interpolate(perspective, centroid) perspective_centroid: f32, + @location(6) @interpolate(perspective, sample) perspective_sample: f32, +} + +@vertex +fn vert_main() -> FragmentInput { + var out: FragmentInput; + + out.position = vec4(2f, 4f, 5f, 6f); + out._flat = 8u; + out._linear = 27f; + out.linear_centroid = vec2(64f, 125f); + out.linear_sample = vec3(216f, 343f, 512f); + out.perspective = vec4(729f, 1000f, 1331f, 1728f); + out.perspective_centroid = 2197f; + out.perspective_sample = 2744f; + let _e30 = out; + return _e30; +} + +@fragment +fn frag_main(val: FragmentInput) { + return; +} diff --git a/naga/tests/out/wgsl/inv-hyperbolic-trig-functions.wgsl b/naga/tests/out/wgsl/inv-hyperbolic-trig-functions.wgsl new file mode 100644 index 0000000000..e68413c279 --- /dev/null +++ b/naga/tests/out/wgsl/inv-hyperbolic-trig-functions.wgsl @@ -0,0 +1,20 @@ +var a: f32; + +fn main_1() { + var b: f32; + var c: f32; + var d: f32; + + let _e4 = a; + b = asinh(_e4); + let _e6 = a; + c = acosh(_e6); + let _e8 = a; + d = atanh(_e8); + return; +} + +@fragment +fn main() { + main_1(); +} diff --git a/naga/tests/out/wgsl/lexical-scopes.wgsl b/naga/tests/out/wgsl/lexical-scopes.wgsl new file mode 100644 index 0000000000..e787f96b10 --- /dev/null +++ b/naga/tests/out/wgsl/lexical-scopes.wgsl @@ -0,0 +1,65 @@ +fn blockLexicalScope(a: bool) { + { + { + return; + } + } +} + +fn ifLexicalScope(a_1: bool) { + if a_1 { + return; + } else { + return; + } +} + +fn loopLexicalScope(a_2: bool) { + loop { + } + return; +} + +fn forLexicalScope(a_3: f32) { + var a_4: i32 = 0i; + + loop { + let _e3 = a_4; + if (_e3 < 1i) { + } else { + break; + } + { + } + continuing { + let _e8 = a_4; + a_4 = (_e8 + 1i); + } + } + return; +} + +fn whileLexicalScope(a_5: i32) { + loop { + if (a_5 > 2i) { + } else { + break; + } + { + } + } + return; +} + +fn switchLexicalScope(a_6: i32) { + switch a_6 { + case 0: { + } + case 1: { + } + default: { + } + } + let test = (a_6 == 2i); +} + diff --git a/naga/tests/out/wgsl/local-var-init-in-loop.comp.wgsl b/naga/tests/out/wgsl/local-var-init-in-loop.comp.wgsl new file mode 100644 index 0000000000..a4bb55437a --- /dev/null +++ b/naga/tests/out/wgsl/local-var-init-in-loop.comp.wgsl @@ -0,0 +1,29 @@ +fn main_1() { + var sum: vec4 = vec4(0f); + var i: i32 = 0i; + var a: vec4; + + loop { + let _e6 = i; + if !((_e6 < 4i)) { + break; + } + { + a = vec4(1f); + let _e17 = sum; + let _e18 = a; + sum = (_e17 + _e18); + } + continuing { + let _e10 = i; + i = (_e10 + 1i); + } + } + return; +} + +@compute @workgroup_size(1, 1, 1) +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/long-form-matrix.frag.wgsl b/naga/tests/out/wgsl/long-form-matrix.frag.wgsl new file mode 100644 index 0000000000..c69439159a --- /dev/null +++ b/naga/tests/out/wgsl/long-form-matrix.frag.wgsl @@ -0,0 +1,17 @@ +fn main_1() { + var splat: mat2x2 = mat2x2(vec2(1f, 0f), vec2(0f, 1f)); + var normal: mat2x2 = mat2x2(vec2(1f, 1f), vec2(2f, 2f)); + var from_matrix: mat2x4 = mat2x4(vec4(1f, 0f, 0f, 0f), vec4(0f, 1f, 0f, 0f)); + var a: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var b: mat2x2 = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + var c: mat3x3 = mat3x3(vec3(1f, 2f, 3f), vec3(1f, 1f, 1f), vec3(1f, 1f, 1f)); + var d: mat3x3 = mat3x3(vec3(2f, 2f, 1f), vec3(1f, 1f, 1f), vec3(1f, 1f, 1f)); + var e: mat4x4 = mat4x4(vec4(2f, 2f, 1f, 1f), vec4(1f, 1f, 2f, 2f), vec4(1f, 1f, 1f, 1f), vec4(1f, 1f, 1f, 1f)); + +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/math-functions.frag.wgsl b/naga/tests/out/wgsl/math-functions.frag.wgsl new file mode 100644 index 0000000000..b01c3322ae --- /dev/null +++ b/naga/tests/out/wgsl/math-functions.frag.wgsl @@ -0,0 +1,168 @@ +fn main_1() { + var a: vec4 = vec4(1f); + var b: vec4 = vec4(2f); + var m: mat4x4; + var i: i32 = 5i; + var ceilOut: vec4; + var roundOut: vec4; + var floorOut: vec4; + var fractOut: vec4; + var truncOut: vec4; + var sinOut: vec4; + var absOut: vec4; + var sqrtOut: vec4; + var inversesqrtOut: vec4; + var expOut: vec4; + var exp2Out: vec4; + var signOut: vec4; + var transposeOut: mat4x4; + var normalizeOut: vec4; + var sinhOut: vec4; + var cosOut: vec4; + var coshOut: vec4; + var tanOut: vec4; + var tanhOut: vec4; + var acosOut: vec4; + var asinOut: vec4; + var logOut: vec4; + var log2Out: vec4; + var lengthOut: f32; + var determinantOut: f32; + var bitCountOut: i32; + var bitfieldReverseOut: i32; + var atanOut: f32; + var atan2Out: f32; + var modOut: f32; + var powOut: vec4; + var dotOut: f32; + var maxOut: vec4; + var minOut: vec4; + var reflectOut: vec4; + var crossOut: vec3; + var distanceOut: f32; + var stepOut: vec4; + var ldexpOut: f32; + var rad: vec4; + var deg: f32; + var smoothStepScalar: f32; + var smoothStepVector: vec4; + var smoothStepMixed: vec4; + + let _e6 = a; + let _e7 = b; + let _e8 = a; + let _e9 = b; + m = mat4x4(vec4(_e6.x, _e6.y, _e6.z, _e6.w), vec4(_e7.x, _e7.y, _e7.z, _e7.w), vec4(_e8.x, _e8.y, _e8.z, _e8.w), vec4(_e9.x, _e9.y, _e9.z, _e9.w)); + let _e35 = a; + ceilOut = ceil(_e35); + let _e39 = a; + roundOut = round(_e39); + let _e43 = a; + floorOut = floor(_e43); + let _e47 = a; + fractOut = fract(_e47); + let _e51 = a; + truncOut = trunc(_e51); + let _e55 = a; + sinOut = sin(_e55); + let _e59 = a; + absOut = abs(_e59); + let _e63 = a; + sqrtOut = sqrt(_e63); + let _e67 = a; + inversesqrtOut = inverseSqrt(_e67); + let _e71 = a; + expOut = exp(_e71); + let _e75 = a; + exp2Out = exp2(_e75); + let _e79 = a; + signOut = sign(_e79); + let _e83 = m; + transposeOut = transpose(_e83); + let _e87 = a; + normalizeOut = normalize(_e87); + let _e91 = a; + sinhOut = sinh(_e91); + let _e95 = a; + cosOut = cos(_e95); + let _e99 = a; + coshOut = cosh(_e99); + let _e103 = a; + tanOut = tan(_e103); + let _e107 = a; + tanhOut = tanh(_e107); + let _e111 = a; + acosOut = acos(_e111); + let _e115 = a; + asinOut = asin(_e115); + let _e119 = a; + logOut = log(_e119); + let _e123 = a; + log2Out = log2(_e123); + let _e127 = a; + lengthOut = length(_e127); + let _e131 = m; + determinantOut = determinant(_e131); + let _e135 = i; + bitCountOut = countOneBits(_e135); + let _e139 = i; + bitfieldReverseOut = reverseBits(_e139); + let _e142 = a; + let _e144 = a; + atanOut = atan(_e144.x); + let _e148 = a; + let _e150 = a; + let _e152 = a; + let _e154 = a; + atan2Out = atan2(_e152.x, _e154.y); + let _e158 = a; + let _e160 = b; + let _e162 = a; + let _e164 = b; + modOut = (_e162.x - (floor((_e162.x / _e164.x)) * _e164.x)); + let _e173 = a; + let _e174 = b; + powOut = pow(_e173, _e174); + let _e179 = a; + let _e180 = b; + dotOut = dot(_e179, _e180); + let _e185 = a; + let _e186 = b; + maxOut = max(_e185, _e186); + let _e191 = a; + let _e192 = b; + minOut = min(_e191, _e192); + let _e197 = a; + let _e198 = b; + reflectOut = reflect(_e197, _e198); + let _e201 = a; + let _e203 = b; + let _e205 = a; + let _e207 = b; + crossOut = cross(_e205.xyz, _e207.xyz); + let _e213 = a; + let _e214 = b; + distanceOut = distance(_e213, _e214); + let _e219 = a; + let _e220 = b; + stepOut = step(_e219, _e220); + let _e223 = a; + let _e226 = a; + let _e228 = i; + ldexpOut = ldexp(_e226.x, _e228); + let _e232 = a; + rad = radians(_e232); + let _e235 = a; + let _e237 = a; + deg = degrees(_e237.x); + smoothStepScalar = smoothstep(0f, 1f, 0.5f); + smoothStepVector = smoothstep(vec4(0f), vec4(1f), vec4(0.5f)); + smoothStepMixed = smoothstep(vec4(0f), vec4(1f), vec4(0.5f)); + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/math-functions.wgsl b/naga/tests/out/wgsl/math-functions.wgsl new file mode 100644 index 0000000000..92f391038d --- /dev/null +++ b/naga/tests/out/wgsl/math-functions.wgsl @@ -0,0 +1,47 @@ +@fragment +fn main() { + let v = vec4(0f); + let a = degrees(1f); + let b = radians(1f); + let c = degrees(v); + let d = radians(v); + let e = saturate(v); + let g = refract(v, v, 1f); + let sign_a = sign(-1i); + let sign_b = sign(vec4(-1i)); + let sign_c = sign(-1f); + let sign_d = sign(vec4(-1f)); + let const_dot = dot(vec2(), vec2()); + let first_leading_bit_abs = firstLeadingBit(abs(0u)); + let flb_a = firstLeadingBit(-1i); + let flb_b = firstLeadingBit(vec2(-1i)); + let flb_c = firstLeadingBit(vec2(1u)); + let ftb_a = firstTrailingBit(-1i); + let ftb_b = firstTrailingBit(1u); + let ftb_c = firstTrailingBit(vec2(-1i)); + let ftb_d = firstTrailingBit(vec2(1u)); + let ctz_a = countTrailingZeros(0u); + let ctz_b = countTrailingZeros(0i); + let ctz_c = countTrailingZeros(4294967295u); + let ctz_d = countTrailingZeros(-1i); + let ctz_e = countTrailingZeros(vec2(0u)); + let ctz_f = countTrailingZeros(vec2(0i)); + let ctz_g = countTrailingZeros(vec2(1u)); + let ctz_h = countTrailingZeros(vec2(1i)); + let clz_a = countLeadingZeros(-1i); + let clz_b = countLeadingZeros(1u); + let clz_c = countLeadingZeros(vec2(-1i)); + let clz_d = countLeadingZeros(vec2(1u)); + let lde_a = ldexp(1f, 2i); + let lde_b = ldexp(vec2(1f, 2f), vec2(3i, 4i)); + let modf_a = modf(1.5f); + let modf_b = modf(1.5f).fract; + let modf_c = modf(1.5f).whole; + let modf_d = modf(vec2(1.5f, 1.5f)); + let modf_e = modf(vec4(1.5f, 1.5f, 1.5f, 1.5f)).whole.x; + let modf_f = modf(vec2(1.5f, 1.5f)).fract.y; + let frexp_a = frexp(1.5f); + let frexp_b = frexp(1.5f).fract; + let frexp_c = frexp(1.5f).exp; + let frexp_d = frexp(vec4(1.5f, 1.5f, 1.5f, 1.5f)).exp.x; +} diff --git a/naga/tests/out/wgsl/module-scope.wgsl b/naga/tests/out/wgsl/module-scope.wgsl new file mode 100644 index 0000000000..48e3325e53 --- /dev/null +++ b/naga/tests/out/wgsl/module-scope.wgsl @@ -0,0 +1,25 @@ +struct S { + x: i32, +} + +const Value: i32 = 1i; + +@group(0) @binding(0) +var Texture: texture_2d; +@group(0) @binding(1) +var Sampler: sampler; + +fn statement() { + return; +} + +fn returns() -> S { + return S(1i); +} + +fn call() { + statement(); + let _e0 = returns(); + let s = textureSample(Texture, Sampler, vec2(1f)); +} + diff --git a/naga/tests/out/wgsl/multiview.wgsl b/naga/tests/out/wgsl/multiview.wgsl new file mode 100644 index 0000000000..51192d2f7a --- /dev/null +++ b/naga/tests/out/wgsl/multiview.wgsl @@ -0,0 +1,4 @@ +@fragment +fn main(@builtin(view_index) view_index: i32) { + return; +} diff --git a/naga/tests/out/wgsl/operators.wgsl b/naga/tests/out/wgsl/operators.wgsl new file mode 100644 index 0000000000..dbf39556de --- /dev/null +++ b/naga/tests/out/wgsl/operators.wgsl @@ -0,0 +1,254 @@ +const v_f32_one: vec4 = vec4(1f, 1f, 1f, 1f); +const v_f32_zero: vec4 = vec4(0f, 0f, 0f, 0f); +const v_f32_half: vec4 = vec4(0.5f, 0.5f, 0.5f, 0.5f); +const v_i32_one: vec4 = vec4(1i, 1i, 1i, 1i); + +fn builtins() -> vec4 { + let s1_ = select(0i, 1i, true); + let s2_ = select(v_f32_zero, v_f32_one, true); + let s3_ = select(v_f32_one, v_f32_zero, vec4(false, false, false, false)); + let m1_ = mix(v_f32_zero, v_f32_one, v_f32_half); + let m2_ = mix(v_f32_zero, v_f32_one, 0.1f); + let b1_ = bitcast(1i); + let b2_ = bitcast>(v_i32_one); + let v_i32_zero = vec4(0i, 0i, 0i, 0i); + return (((((vec4((vec4(s1_) + v_i32_zero)) + s2_) + m1_) + m2_) + vec4(b1_)) + b2_); +} + +fn splat(m: f32, n: i32) -> vec4 { + let a_2 = (((vec2(2f) + vec2(m)) - vec2(4f)) / vec2(8f)); + let b = (vec4(n) % vec4(2i)); + return (a_2.xyxy + vec4(b)); +} + +fn splat_assignment() -> vec2 { + var a: vec2 = vec2(2f); + + let _e4 = a; + a = (_e4 + vec2(1f)); + let _e8 = a; + a = (_e8 - vec2(3f)); + let _e12 = a; + a = (_e12 / vec2(4f)); + let _e15 = a; + return _e15; +} + +fn bool_cast(x: vec3) -> vec3 { + let y = vec3(x); + return vec3(y); +} + +fn logical() { + let neg0_ = !(true); + let neg1_ = !(vec2(true)); + let or = (true || false); + let and = (true && false); + let bitwise_or0_ = (true | false); + let bitwise_or1_ = (vec3(true) | vec3(false)); + let bitwise_and0_ = (true & false); + let bitwise_and1_ = (vec4(true) & vec4(false)); +} + +fn arithmetic() { + let neg0_1 = -(1f); + let neg1_1 = -(vec2(1i)); + let neg2_ = -(vec2(1f)); + let add0_ = (2i + 1i); + let add1_ = (2u + 1u); + let add2_ = (2f + 1f); + let add3_ = (vec2(2i) + vec2(1i)); + let add4_ = (vec3(2u) + vec3(1u)); + let add5_ = (vec4(2f) + vec4(1f)); + let sub0_ = (2i - 1i); + let sub1_ = (2u - 1u); + let sub2_ = (2f - 1f); + let sub3_ = (vec2(2i) - vec2(1i)); + let sub4_ = (vec3(2u) - vec3(1u)); + let sub5_ = (vec4(2f) - vec4(1f)); + let mul0_ = (2i * 1i); + let mul1_ = (2u * 1u); + let mul2_ = (2f * 1f); + let mul3_ = (vec2(2i) * vec2(1i)); + let mul4_ = (vec3(2u) * vec3(1u)); + let mul5_ = (vec4(2f) * vec4(1f)); + let div0_ = (2i / 1i); + let div1_ = (2u / 1u); + let div2_ = (2f / 1f); + let div3_ = (vec2(2i) / vec2(1i)); + let div4_ = (vec3(2u) / vec3(1u)); + let div5_ = (vec4(2f) / vec4(1f)); + let rem0_ = (2i % 1i); + let rem1_ = (2u % 1u); + let rem2_ = (2f % 1f); + let rem3_ = (vec2(2i) % vec2(1i)); + let rem4_ = (vec3(2u) % vec3(1u)); + let rem5_ = (vec4(2f) % vec4(1f)); + { + let add0_1 = (vec2(2i) + vec2(1i)); + let add1_1 = (vec2(2i) + vec2(1i)); + let add2_1 = (vec2(2u) + vec2(1u)); + let add3_1 = (vec2(2u) + vec2(1u)); + let add4_1 = (vec2(2f) + vec2(1f)); + let add5_1 = (vec2(2f) + vec2(1f)); + let sub0_1 = (vec2(2i) - vec2(1i)); + let sub1_1 = (vec2(2i) - vec2(1i)); + let sub2_1 = (vec2(2u) - vec2(1u)); + let sub3_1 = (vec2(2u) - vec2(1u)); + let sub4_1 = (vec2(2f) - vec2(1f)); + let sub5_1 = (vec2(2f) - vec2(1f)); + let mul0_1 = (vec2(2i) * 1i); + let mul1_1 = (2i * vec2(1i)); + let mul2_1 = (vec2(2u) * 1u); + let mul3_1 = (2u * vec2(1u)); + let mul4_1 = (vec2(2f) * 1f); + let mul5_1 = (2f * vec2(1f)); + let div0_1 = (vec2(2i) / vec2(1i)); + let div1_1 = (vec2(2i) / vec2(1i)); + let div2_1 = (vec2(2u) / vec2(1u)); + let div3_1 = (vec2(2u) / vec2(1u)); + let div4_1 = (vec2(2f) / vec2(1f)); + let div5_1 = (vec2(2f) / vec2(1f)); + let rem0_1 = (vec2(2i) % vec2(1i)); + let rem1_1 = (vec2(2i) % vec2(1i)); + let rem2_1 = (vec2(2u) % vec2(1u)); + let rem3_1 = (vec2(2u) % vec2(1u)); + let rem4_1 = (vec2(2f) % vec2(1f)); + let rem5_1 = (vec2(2f) % vec2(1f)); + } + let add = (mat3x3() + mat3x3()); + let sub = (mat3x3() - mat3x3()); + let mul_scalar0_ = (mat3x3() * 1f); + let mul_scalar1_ = (2f * mat3x3()); + let mul_vector0_ = (mat4x3() * vec4(1f)); + let mul_vector1_ = (vec3(2f) * mat4x3()); + let mul = (mat4x3() * mat3x4()); +} + +fn bit() { + let flip0_ = ~(1i); + let flip1_ = ~(1u); + let flip2_ = ~(vec2(1i)); + let flip3_ = ~(vec3(1u)); + let or0_ = (2i | 1i); + let or1_ = (2u | 1u); + let or2_ = (vec2(2i) | vec2(1i)); + let or3_ = (vec3(2u) | vec3(1u)); + let and0_ = (2i & 1i); + let and1_ = (2u & 1u); + let and2_ = (vec2(2i) & vec2(1i)); + let and3_ = (vec3(2u) & vec3(1u)); + let xor0_ = (2i ^ 1i); + let xor1_ = (2u ^ 1u); + let xor2_ = (vec2(2i) ^ vec2(1i)); + let xor3_ = (vec3(2u) ^ vec3(1u)); + let shl0_ = (2i << 1u); + let shl1_ = (2u << 1u); + let shl2_ = (vec2(2i) << vec2(1u)); + let shl3_ = (vec3(2u) << vec3(1u)); + let shr0_ = (2i >> 1u); + let shr1_ = (2u >> 1u); + let shr2_ = (vec2(2i) >> vec2(1u)); + let shr3_ = (vec3(2u) >> vec3(1u)); +} + +fn comparison() { + let eq0_ = (2i == 1i); + let eq1_ = (2u == 1u); + let eq2_ = (2f == 1f); + let eq3_ = (vec2(2i) == vec2(1i)); + let eq4_ = (vec3(2u) == vec3(1u)); + let eq5_ = (vec4(2f) == vec4(1f)); + let neq0_ = (2i != 1i); + let neq1_ = (2u != 1u); + let neq2_ = (2f != 1f); + let neq3_ = (vec2(2i) != vec2(1i)); + let neq4_ = (vec3(2u) != vec3(1u)); + let neq5_ = (vec4(2f) != vec4(1f)); + let lt0_ = (2i < 1i); + let lt1_ = (2u < 1u); + let lt2_ = (2f < 1f); + let lt3_ = (vec2(2i) < vec2(1i)); + let lt4_ = (vec3(2u) < vec3(1u)); + let lt5_ = (vec4(2f) < vec4(1f)); + let lte0_ = (2i <= 1i); + let lte1_ = (2u <= 1u); + let lte2_ = (2f <= 1f); + let lte3_ = (vec2(2i) <= vec2(1i)); + let lte4_ = (vec3(2u) <= vec3(1u)); + let lte5_ = (vec4(2f) <= vec4(1f)); + let gt0_ = (2i > 1i); + let gt1_ = (2u > 1u); + let gt2_ = (2f > 1f); + let gt3_ = (vec2(2i) > vec2(1i)); + let gt4_ = (vec3(2u) > vec3(1u)); + let gt5_ = (vec4(2f) > vec4(1f)); + let gte0_ = (2i >= 1i); + let gte1_ = (2u >= 1u); + let gte2_ = (2f >= 1f); + let gte3_ = (vec2(2i) >= vec2(1i)); + let gte4_ = (vec3(2u) >= vec3(1u)); + let gte5_ = (vec4(2f) >= vec4(1f)); +} + +fn assignment() { + var a_1: i32; + var vec0_: vec3 = vec3(); + + a_1 = 1i; + let _e5 = a_1; + a_1 = (_e5 + 1i); + let _e7 = a_1; + a_1 = (_e7 - 1i); + let _e9 = a_1; + let _e10 = a_1; + a_1 = (_e10 * _e9); + let _e12 = a_1; + let _e13 = a_1; + a_1 = (_e13 / _e12); + let _e15 = a_1; + a_1 = (_e15 % 1i); + let _e17 = a_1; + a_1 = (_e17 & 0i); + let _e19 = a_1; + a_1 = (_e19 | 0i); + let _e21 = a_1; + a_1 = (_e21 ^ 0i); + let _e23 = a_1; + a_1 = (_e23 << 2u); + let _e25 = a_1; + a_1 = (_e25 >> 1u); + let _e28 = a_1; + a_1 = (_e28 + 1i); + let _e31 = a_1; + a_1 = (_e31 - 1i); + let _e37 = vec0_[1i]; + vec0_[1i] = (_e37 + 1i); + let _e41 = vec0_[1i]; + vec0_[1i] = (_e41 - 1i); + return; +} + +fn negation_avoids_prefix_decrement() { + let p0_ = -(1i); + let p1_ = -(-(1i)); + let p2_ = -(-(1i)); + let p3_ = -(-(1i)); + let p4_ = -(-(-(1i))); + let p5_ = -(-(-(-(1i)))); + let p6_ = -(-(-(-(-(1i))))); + let p7_ = -(-(-(-(-(1i))))); +} + +@compute @workgroup_size(1, 1, 1) +fn main(@builtin(workgroup_id) id: vec3) { + let _e1 = builtins(); + let _e6 = splat(f32(id.x), i32(id.y)); + let _e11 = bool_cast(vec3(1f, 1f, 1f)); + logical(); + arithmetic(); + bit(); + comparison(); + assignment(); + return; +} diff --git a/naga/tests/out/wgsl/padding.wgsl b/naga/tests/out/wgsl/padding.wgsl new file mode 100644 index 0000000000..62a5c7d567 --- /dev/null +++ b/naga/tests/out/wgsl/padding.wgsl @@ -0,0 +1,33 @@ +struct S { + a: vec3, +} + +struct Test { + a: S, + b: f32, +} + +struct Test2_ { + a: array, 2>, + b: f32, +} + +struct Test3_ { + a: mat4x3, + b: f32, +} + +@group(0) @binding(0) +var input1_: Test; +@group(0) @binding(1) +var input2_: Test2_; +@group(0) @binding(2) +var input3_: Test3_; + +@vertex +fn vertex() -> @builtin(position) vec4 { + let _e4 = input1_.b; + let _e8 = input2_.b; + let _e12 = input3_.b; + return (((vec4(1f) * _e4) * _e8) * _e12); +} diff --git a/naga/tests/out/wgsl/pointers.wgsl b/naga/tests/out/wgsl/pointers.wgsl new file mode 100644 index 0000000000..310f53cf08 --- /dev/null +++ b/naga/tests/out/wgsl/pointers.wgsl @@ -0,0 +1,28 @@ +struct DynamicArray { + arr: array, +} + +@group(0) @binding(0) +var dynamic_array: DynamicArray; + +fn f() { + var v: vec2; + + let px = (&v.x); + (*px) = 10i; + return; +} + +fn index_unsized(i: i32, v_1: u32) { + let val = dynamic_array.arr[i]; + dynamic_array.arr[i] = (val + v_1); + return; +} + +fn index_dynamic_array(i_1: i32, v_2: u32) { + let p = (&dynamic_array.arr); + let val_1 = (*p)[i_1]; + (*p)[i_1] = (val_1 + v_2); + return; +} + diff --git a/naga/tests/out/wgsl/prepostfix.frag.wgsl b/naga/tests/out/wgsl/prepostfix.frag.wgsl new file mode 100644 index 0000000000..d2c59a0dd9 --- /dev/null +++ b/naga/tests/out/wgsl/prepostfix.frag.wgsl @@ -0,0 +1,39 @@ +fn main_1() { + var scalar_target: i32; + var scalar: i32 = 1i; + var vec_target: vec2; + var vec: vec2 = vec2(1u); + var mat_target: mat4x3; + var mat: mat4x3 = mat4x3(vec3(1f, 0f, 0f), vec3(0f, 1f, 0f), vec3(0f, 0f, 1f), vec3(0f, 0f, 0f)); + + let _e3 = scalar; + scalar = (_e3 + 1i); + scalar_target = _e3; + let _e6 = scalar; + let _e8 = (_e6 - 1i); + scalar = _e8; + scalar_target = _e8; + let _e14 = vec; + vec = (_e14 - vec2(1u)); + vec_target = _e14; + let _e18 = vec; + let _e21 = (_e18 + vec2(1u)); + vec = _e21; + vec_target = _e21; + let _e32 = mat; + let _e34 = vec3(1f); + mat = (_e32 + mat4x3(_e34, _e34, _e34, _e34)); + mat_target = _e32; + let _e37 = mat; + let _e39 = vec3(1f); + let _e41 = (_e37 - mat4x3(_e39, _e39, _e39, _e39)); + mat = _e41; + mat_target = _e41; + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/quad-vert.wgsl b/naga/tests/out/wgsl/quad-vert.wgsl new file mode 100644 index 0000000000..28a2317e91 --- /dev/null +++ b/naga/tests/out/wgsl/quad-vert.wgsl @@ -0,0 +1,34 @@ +struct gl_PerVertex { + @builtin(position) gl_Position: vec4, + gl_PointSize: f32, + gl_ClipDistance: array, + gl_CullDistance: array, +} + +struct VertexOutput { + @location(0) member: vec2, + @builtin(position) gl_Position: vec4, +} + +var v_uv: vec2; +var a_uv_1: vec2; +var perVertexStruct: gl_PerVertex = gl_PerVertex(vec4(0f, 0f, 0f, 1f), 1f, array(), array()); +var a_pos_1: vec2; + +fn main_1() { + let _e6 = a_uv_1; + v_uv = _e6; + let _e7 = a_pos_1; + perVertexStruct.gl_Position = vec4(_e7.x, _e7.y, 0f, 1f); + return; +} + +@vertex +fn main(@location(1) a_uv: vec2, @location(0) a_pos: vec2) -> VertexOutput { + a_uv_1 = a_uv; + a_pos_1 = a_pos; + main_1(); + let _e7 = v_uv; + let _e8 = perVertexStruct.gl_Position; + return VertexOutput(_e7, _e8); +} diff --git a/naga/tests/out/wgsl/quad.wgsl b/naga/tests/out/wgsl/quad.wgsl new file mode 100644 index 0000000000..05e5c0e7b9 --- /dev/null +++ b/naga/tests/out/wgsl/quad.wgsl @@ -0,0 +1,31 @@ +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +} + +const c_scale: f32 = 1.2f; + +@group(0) @binding(0) +var u_texture: texture_2d; +@group(0) @binding(1) +var u_sampler: sampler; + +@vertex +fn vert_main(@location(0) pos: vec2, @location(1) uv: vec2) -> VertexOutput { + return VertexOutput(uv, vec4((c_scale * pos), 0f, 1f)); +} + +@fragment +fn frag_main(@location(0) uv_1: vec2) -> @location(0) vec4 { + let color = textureSample(u_texture, u_sampler, uv_1); + if (color.w == 0f) { + discard; + } + let premultiplied = (color.w * color); + return premultiplied; +} + +@fragment +fn fs_extra() -> @location(0) vec4 { + return vec4(0f, 0.5f, 0f, 0.5f); +} diff --git a/naga/tests/out/wgsl/quad_glsl.frag.wgsl b/naga/tests/out/wgsl/quad_glsl.frag.wgsl new file mode 100644 index 0000000000..9de0a19c4b --- /dev/null +++ b/naga/tests/out/wgsl/quad_glsl.frag.wgsl @@ -0,0 +1,19 @@ +struct FragmentOutput { + @location(0) o_color: vec4, +} + +var v_uv_1: vec2; +var o_color: vec4; + +fn main_1() { + o_color = vec4(1f, 1f, 1f, 1f); + return; +} + +@fragment +fn main(@location(0) v_uv: vec2) -> FragmentOutput { + v_uv_1 = v_uv; + main_1(); + let _e7 = o_color; + return FragmentOutput(_e7); +} diff --git a/naga/tests/out/wgsl/quad_glsl.vert.wgsl b/naga/tests/out/wgsl/quad_glsl.vert.wgsl new file mode 100644 index 0000000000..5c5ee71041 --- /dev/null +++ b/naga/tests/out/wgsl/quad_glsl.vert.wgsl @@ -0,0 +1,30 @@ +struct VertexOutput { + @location(0) v_uv: vec2, + @builtin(position) member: vec4, +} + +const c_scale: f32 = 1.2f; + +var a_pos_1: vec2; +var a_uv_1: vec2; +var v_uv: vec2; +var gl_Position: vec4; + +fn main_1() { + let _e4 = a_uv_1; + v_uv = _e4; + let _e6 = a_pos_1; + let _e8 = (c_scale * _e6); + gl_Position = vec4(_e8.x, _e8.y, 0f, 1f); + return; +} + +@vertex +fn main(@location(0) a_pos: vec2, @location(1) a_uv: vec2) -> VertexOutput { + a_pos_1 = a_pos; + a_uv_1 = a_uv; + main_1(); + let _e13 = v_uv; + let _e15 = gl_Position; + return VertexOutput(_e13, _e15); +} diff --git a/naga/tests/out/wgsl/sampler-functions.frag.wgsl b/naga/tests/out/wgsl/sampler-functions.frag.wgsl new file mode 100644 index 0000000000..3530629088 --- /dev/null +++ b/naga/tests/out/wgsl/sampler-functions.frag.wgsl @@ -0,0 +1,39 @@ +fn CalcShadowPCF1_(T_P_t_TextureDepth: texture_depth_2d, S_P_t_TextureDepth: sampler_comparison, t_ProjCoord: vec3) -> f32 { + var t_ProjCoord_1: vec3; + var t_Res: f32 = 0f; + + t_ProjCoord_1 = t_ProjCoord; + let _e6 = t_Res; + let _e7 = t_ProjCoord_1; + let _e9 = t_ProjCoord_1; + let _e10 = _e9.xyz; + let _e13 = textureSampleCompare(T_P_t_TextureDepth, S_P_t_TextureDepth, _e10.xy, _e10.z); + t_Res = (_e6 + (_e13 * 0.2f)); + let _e19 = t_Res; + return _e19; +} + +fn CalcShadowPCF(T_P_t_TextureDepth_1: texture_depth_2d, S_P_t_TextureDepth_1: sampler_comparison, t_ProjCoord_2: vec3, t_Bias: f32) -> f32 { + var t_ProjCoord_3: vec3; + var t_Bias_1: f32; + + t_ProjCoord_3 = t_ProjCoord_2; + t_Bias_1 = t_Bias; + let _e7 = t_ProjCoord_3; + let _e9 = t_Bias_1; + t_ProjCoord_3.z = (_e7.z + _e9); + let _e11 = t_ProjCoord_3; + let _e13 = t_ProjCoord_3; + let _e15 = CalcShadowPCF1_(T_P_t_TextureDepth_1, S_P_t_TextureDepth_1, _e13.xyz); + return _e15; +} + +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/samplers.frag.wgsl b/naga/tests/out/wgsl/samplers.frag.wgsl new file mode 100644 index 0000000000..4306e6683f --- /dev/null +++ b/naga/tests/out/wgsl/samplers.frag.wgsl @@ -0,0 +1,687 @@ +@group(1) @binding(0) +var tex1D: texture_1d; +@group(1) @binding(1) +var tex1DArray: texture_1d_array; +@group(1) @binding(2) +var tex2D: texture_2d; +@group(1) @binding(3) +var tex2DArray: texture_2d_array; +@group(1) @binding(4) +var texCube: texture_cube; +@group(1) @binding(5) +var texCubeArray: texture_cube_array; +@group(1) @binding(6) +var tex3D: texture_3d; +@group(1) @binding(7) +var samp: sampler; +@group(1) @binding(12) +var tex2DShadow: texture_depth_2d; +@group(1) @binding(13) +var tex2DArrayShadow: texture_depth_2d_array; +@group(1) @binding(14) +var texCubeShadow: texture_depth_cube; +@group(1) @binding(15) +var texCubeArrayShadow: texture_depth_cube_array; +@group(1) @binding(16) +var tex3DShadow: texture_3d; +@group(1) @binding(17) +var sampShadow: sampler_comparison; +@group(0) @binding(18) +var tex2DMS: texture_multisampled_2d; +@group(0) @binding(19) +var tex2DMSArray: texture_multisampled_2d_array; + +fn testTex1D(coord: f32) { + var coord_1: f32; + var size1D: i32; + var c: vec4; + + coord_1 = coord; + let _e20 = textureDimensions(tex1D, 0i); + size1D = i32(_e20); + let _e25 = coord_1; + let _e26 = textureSample(tex1D, samp, _e25); + c = _e26; + let _e29 = coord_1; + let _e31 = textureSampleBias(tex1D, samp, _e29, 2f); + c = _e31; + let _e35 = coord_1; + let _e38 = textureSampleGrad(tex1D, samp, _e35, 4f, 4f); + c = _e38; + let _e43 = coord_1; + let _e47 = textureSampleGrad(tex1D, samp, _e43, 4f, 4f, 5i); + c = _e47; + let _e50 = coord_1; + let _e52 = textureSampleLevel(tex1D, samp, _e50, 3f); + c = _e52; + let _e56 = coord_1; + let _e59 = textureSampleLevel(tex1D, samp, _e56, 3f, 5i); + c = _e59; + let _e62 = coord_1; + let _e64 = textureSample(tex1D, samp, _e62, 5i); + c = _e64; + let _e68 = coord_1; + let _e71 = textureSampleBias(tex1D, samp, _e68, 2f, 5i); + c = _e71; + let _e72 = coord_1; + let _e75 = coord_1; + let _e77 = vec2(_e75, 6f); + let _e81 = textureSample(tex1D, samp, (_e77.x / _e77.y)); + c = _e81; + let _e82 = coord_1; + let _e87 = coord_1; + let _e91 = vec4(_e87, 0f, 0f, 6f); + let _e97 = textureSample(tex1D, samp, (_e91.xyz / vec3(_e91.w)).x); + c = _e97; + let _e98 = coord_1; + let _e102 = coord_1; + let _e104 = vec2(_e102, 6f); + let _e109 = textureSampleBias(tex1D, samp, (_e104.x / _e104.y), 2f); + c = _e109; + let _e110 = coord_1; + let _e116 = coord_1; + let _e120 = vec4(_e116, 0f, 0f, 6f); + let _e127 = textureSampleBias(tex1D, samp, (_e120.xyz / vec3(_e120.w)).x, 2f); + c = _e127; + let _e128 = coord_1; + let _e133 = coord_1; + let _e135 = vec2(_e133, 6f); + let _e141 = textureSampleGrad(tex1D, samp, (_e135.x / _e135.y), 4f, 4f); + c = _e141; + let _e142 = coord_1; + let _e149 = coord_1; + let _e153 = vec4(_e149, 0f, 0f, 6f); + let _e161 = textureSampleGrad(tex1D, samp, (_e153.xyz / vec3(_e153.w)).x, 4f, 4f); + c = _e161; + let _e162 = coord_1; + let _e168 = coord_1; + let _e170 = vec2(_e168, 6f); + let _e177 = textureSampleGrad(tex1D, samp, (_e170.x / _e170.y), 4f, 4f, 5i); + c = _e177; + let _e178 = coord_1; + let _e186 = coord_1; + let _e190 = vec4(_e186, 0f, 0f, 6f); + let _e199 = textureSampleGrad(tex1D, samp, (_e190.xyz / vec3(_e190.w)).x, 4f, 4f, 5i); + c = _e199; + let _e200 = coord_1; + let _e204 = coord_1; + let _e206 = vec2(_e204, 6f); + let _e211 = textureSampleLevel(tex1D, samp, (_e206.x / _e206.y), 3f); + c = _e211; + let _e212 = coord_1; + let _e218 = coord_1; + let _e222 = vec4(_e218, 0f, 0f, 6f); + let _e229 = textureSampleLevel(tex1D, samp, (_e222.xyz / vec3(_e222.w)).x, 3f); + c = _e229; + let _e230 = coord_1; + let _e235 = coord_1; + let _e237 = vec2(_e235, 6f); + let _e243 = textureSampleLevel(tex1D, samp, (_e237.x / _e237.y), 3f, 5i); + c = _e243; + let _e244 = coord_1; + let _e251 = coord_1; + let _e255 = vec4(_e251, 0f, 0f, 6f); + let _e263 = textureSampleLevel(tex1D, samp, (_e255.xyz / vec3(_e255.w)).x, 3f, 5i); + c = _e263; + let _e264 = coord_1; + let _e268 = coord_1; + let _e270 = vec2(_e268, 6f); + let _e275 = textureSample(tex1D, samp, (_e270.x / _e270.y), 5i); + c = _e275; + let _e276 = coord_1; + let _e282 = coord_1; + let _e286 = vec4(_e282, 0f, 0f, 6f); + let _e293 = textureSample(tex1D, samp, (_e286.xyz / vec3(_e286.w)).x, 5i); + c = _e293; + let _e294 = coord_1; + let _e299 = coord_1; + let _e301 = vec2(_e299, 6f); + let _e307 = textureSampleBias(tex1D, samp, (_e301.x / _e301.y), 2f, 5i); + c = _e307; + let _e308 = coord_1; + let _e315 = coord_1; + let _e319 = vec4(_e315, 0f, 0f, 6f); + let _e327 = textureSampleBias(tex1D, samp, (_e319.xyz / vec3(_e319.w)).x, 2f, 5i); + c = _e327; + let _e328 = coord_1; + let _e331 = coord_1; + let _e334 = textureLoad(tex1D, i32(_e331), 3i); + c = _e334; + let _e335 = coord_1; + let _e339 = coord_1; + let _e343 = textureLoad(tex1D, i32(_e339), 3i); + c = _e343; + return; +} + +fn testTex1DArray(coord_2: vec2) { + var coord_3: vec2; + var size1DArray: vec2; + var c_1: vec4; + + coord_3 = coord_2; + let _e20 = textureDimensions(tex1DArray, 0i); + let _e21 = textureNumLayers(tex1DArray); + size1DArray = vec2(vec2(_e20, _e21)); + let _e27 = coord_3; + let _e31 = textureSample(tex1DArray, samp, _e27.x, i32(_e27.y)); + c_1 = _e31; + let _e34 = coord_3; + let _e39 = textureSampleBias(tex1DArray, samp, _e34.x, i32(_e34.y), 2f); + c_1 = _e39; + let _e43 = coord_3; + let _e49 = textureSampleGrad(tex1DArray, samp, _e43.x, i32(_e43.y), 4f, 4f); + c_1 = _e49; + let _e54 = coord_3; + let _e61 = textureSampleGrad(tex1DArray, samp, _e54.x, i32(_e54.y), 4f, 4f, 5i); + c_1 = _e61; + let _e64 = coord_3; + let _e69 = textureSampleLevel(tex1DArray, samp, _e64.x, i32(_e64.y), 3f); + c_1 = _e69; + let _e73 = coord_3; + let _e79 = textureSampleLevel(tex1DArray, samp, _e73.x, i32(_e73.y), 3f, 5i); + c_1 = _e79; + let _e82 = coord_3; + let _e87 = textureSample(tex1DArray, samp, _e82.x, i32(_e82.y), 5i); + c_1 = _e87; + let _e91 = coord_3; + let _e97 = textureSampleBias(tex1DArray, samp, _e91.x, i32(_e91.y), 2f, 5i); + c_1 = _e97; + let _e98 = coord_3; + let _e101 = coord_3; + let _e102 = vec2(_e101); + let _e106 = textureLoad(tex1DArray, _e102.x, _e102.y, 3i); + c_1 = _e106; + let _e107 = coord_3; + let _e111 = coord_3; + let _e112 = vec2(_e111); + let _e117 = textureLoad(tex1DArray, _e112.x, _e112.y, 3i); + c_1 = _e117; + return; +} + +fn testTex2D(coord_4: vec2) { + var coord_5: vec2; + var size2D: vec2; + var c_2: vec4; + + coord_5 = coord_4; + let _e20 = textureDimensions(tex2D, 0i); + size2D = vec2(_e20); + let _e25 = coord_5; + let _e26 = textureSample(tex2D, samp, _e25); + c_2 = _e26; + let _e29 = coord_5; + let _e31 = textureSampleBias(tex2D, samp, _e29, 2f); + c_2 = _e31; + let _e37 = coord_5; + let _e42 = textureSampleGrad(tex2D, samp, _e37, vec2(4f), vec2(4f)); + c_2 = _e42; + let _e50 = coord_5; + let _e57 = textureSampleGrad(tex2D, samp, _e50, vec2(4f), vec2(4f), vec2(5i)); + c_2 = _e57; + let _e60 = coord_5; + let _e62 = textureSampleLevel(tex2D, samp, _e60, 3f); + c_2 = _e62; + let _e67 = coord_5; + let _e71 = textureSampleLevel(tex2D, samp, _e67, 3f, vec2(5i)); + c_2 = _e71; + let _e75 = coord_5; + let _e78 = textureSample(tex2D, samp, _e75, vec2(5i)); + c_2 = _e78; + let _e83 = coord_5; + let _e87 = textureSampleBias(tex2D, samp, _e83, 2f, vec2(5i)); + c_2 = _e87; + let _e88 = coord_5; + let _e93 = coord_5; + let _e97 = vec3(_e93.x, _e93.y, 6f); + let _e102 = textureSample(tex2D, samp, (_e97.xy / vec2(_e97.z))); + c_2 = _e102; + let _e103 = coord_5; + let _e109 = coord_5; + let _e114 = vec4(_e109.x, _e109.y, 0f, 6f); + let _e120 = textureSample(tex2D, samp, (_e114.xyz / vec3(_e114.w)).xy); + c_2 = _e120; + let _e121 = coord_5; + let _e127 = coord_5; + let _e131 = vec3(_e127.x, _e127.y, 6f); + let _e137 = textureSampleBias(tex2D, samp, (_e131.xy / vec2(_e131.z)), 2f); + c_2 = _e137; + let _e138 = coord_5; + let _e145 = coord_5; + let _e150 = vec4(_e145.x, _e145.y, 0f, 6f); + let _e157 = textureSampleBias(tex2D, samp, (_e150.xyz / vec3(_e150.w)).xy, 2f); + c_2 = _e157; + let _e158 = coord_5; + let _e167 = coord_5; + let _e171 = vec3(_e167.x, _e167.y, 6f); + let _e180 = textureSampleGrad(tex2D, samp, (_e171.xy / vec2(_e171.z)), vec2(4f), vec2(4f)); + c_2 = _e180; + let _e181 = coord_5; + let _e191 = coord_5; + let _e196 = vec4(_e191.x, _e191.y, 0f, 6f); + let _e206 = textureSampleGrad(tex2D, samp, (_e196.xyz / vec3(_e196.w)).xy, vec2(4f), vec2(4f)); + c_2 = _e206; + let _e207 = coord_5; + let _e218 = coord_5; + let _e222 = vec3(_e218.x, _e218.y, 6f); + let _e233 = textureSampleGrad(tex2D, samp, (_e222.xy / vec2(_e222.z)), vec2(4f), vec2(4f), vec2(5i)); + c_2 = _e233; + let _e234 = coord_5; + let _e246 = coord_5; + let _e251 = vec4(_e246.x, _e246.y, 0f, 6f); + let _e263 = textureSampleGrad(tex2D, samp, (_e251.xyz / vec3(_e251.w)).xy, vec2(4f), vec2(4f), vec2(5i)); + c_2 = _e263; + let _e264 = coord_5; + let _e270 = coord_5; + let _e274 = vec3(_e270.x, _e270.y, 6f); + let _e280 = textureSampleLevel(tex2D, samp, (_e274.xy / vec2(_e274.z)), 3f); + c_2 = _e280; + let _e281 = coord_5; + let _e288 = coord_5; + let _e293 = vec4(_e288.x, _e288.y, 0f, 6f); + let _e300 = textureSampleLevel(tex2D, samp, (_e293.xyz / vec3(_e293.w)).xy, 3f); + c_2 = _e300; + let _e301 = coord_5; + let _e309 = coord_5; + let _e313 = vec3(_e309.x, _e309.y, 6f); + let _e321 = textureSampleLevel(tex2D, samp, (_e313.xy / vec2(_e313.z)), 3f, vec2(5i)); + c_2 = _e321; + let _e322 = coord_5; + let _e331 = coord_5; + let _e336 = vec4(_e331.x, _e331.y, 0f, 6f); + let _e345 = textureSampleLevel(tex2D, samp, (_e336.xyz / vec3(_e336.w)).xy, 3f, vec2(5i)); + c_2 = _e345; + let _e346 = coord_5; + let _e353 = coord_5; + let _e357 = vec3(_e353.x, _e353.y, 6f); + let _e364 = textureSample(tex2D, samp, (_e357.xy / vec2(_e357.z)), vec2(5i)); + c_2 = _e364; + let _e365 = coord_5; + let _e373 = coord_5; + let _e378 = vec4(_e373.x, _e373.y, 0f, 6f); + let _e386 = textureSample(tex2D, samp, (_e378.xyz / vec3(_e378.w)).xy, vec2(5i)); + c_2 = _e386; + let _e387 = coord_5; + let _e395 = coord_5; + let _e399 = vec3(_e395.x, _e395.y, 6f); + let _e407 = textureSampleBias(tex2D, samp, (_e399.xy / vec2(_e399.z)), 2f, vec2(5i)); + c_2 = _e407; + let _e408 = coord_5; + let _e417 = coord_5; + let _e422 = vec4(_e417.x, _e417.y, 0f, 6f); + let _e431 = textureSampleBias(tex2D, samp, (_e422.xyz / vec3(_e422.w)).xy, 2f, vec2(5i)); + c_2 = _e431; + let _e432 = coord_5; + let _e435 = coord_5; + let _e438 = textureLoad(tex2D, vec2(_e435), 3i); + c_2 = _e438; + let _e439 = coord_5; + let _e444 = coord_5; + let _e449 = textureLoad(tex2D, vec2(_e444), 3i); + c_2 = _e449; + return; +} + +fn testTex2DShadow(coord_6: vec2) { + var coord_7: vec2; + var size2DShadow: vec2; + var d: f32; + + coord_7 = coord_6; + let _e20 = textureDimensions(tex2DShadow, 0i); + size2DShadow = vec2(_e20); + let _e24 = coord_7; + let _e29 = coord_7; + let _e33 = vec3(_e29.x, _e29.y, 1f); + let _e36 = textureSampleCompare(tex2DShadow, sampShadow, _e33.xy, _e33.z); + d = _e36; + let _e37 = coord_7; + let _e46 = coord_7; + let _e50 = vec3(_e46.x, _e46.y, 1f); + let _e57 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e50.xy, _e50.z); + d = _e57; + let _e58 = coord_7; + let _e69 = coord_7; + let _e73 = vec3(_e69.x, _e69.y, 1f); + let _e82 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e73.xy, _e73.z, vec2(5i)); + d = _e82; + let _e83 = coord_7; + let _e89 = coord_7; + let _e93 = vec3(_e89.x, _e89.y, 1f); + let _e97 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e93.xy, _e93.z); + d = _e97; + let _e98 = coord_7; + let _e106 = coord_7; + let _e110 = vec3(_e106.x, _e106.y, 1f); + let _e116 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e110.xy, _e110.z, vec2(5i)); + d = _e116; + let _e117 = coord_7; + let _e124 = coord_7; + let _e128 = vec3(_e124.x, _e124.y, 1f); + let _e133 = textureSampleCompare(tex2DShadow, sampShadow, _e128.xy, _e128.z, vec2(5i)); + d = _e133; + let _e134 = coord_7; + let _e140 = coord_7; + let _e145 = vec4(_e140.x, _e140.y, 1f, 6f); + let _e149 = (_e145.xyz / vec3(_e145.w)); + let _e152 = textureSampleCompare(tex2DShadow, sampShadow, _e149.xy, _e149.z); + d = _e152; + let _e153 = coord_7; + let _e163 = coord_7; + let _e168 = vec4(_e163.x, _e163.y, 1f, 6f); + let _e176 = (_e168.xyz / vec3(_e168.w)); + let _e179 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e176.xy, _e176.z); + d = _e179; + let _e180 = coord_7; + let _e192 = coord_7; + let _e197 = vec4(_e192.x, _e192.y, 1f, 6f); + let _e207 = (_e197.xyz / vec3(_e197.w)); + let _e210 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e207.xy, _e207.z, vec2(5i)); + d = _e210; + let _e211 = coord_7; + let _e218 = coord_7; + let _e223 = vec4(_e218.x, _e218.y, 1f, 6f); + let _e228 = (_e223.xyz / vec3(_e223.w)); + let _e231 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e228.xy, _e228.z); + d = _e231; + let _e232 = coord_7; + let _e241 = coord_7; + let _e246 = vec4(_e241.x, _e241.y, 1f, 6f); + let _e253 = (_e246.xyz / vec3(_e246.w)); + let _e256 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e253.xy, _e253.z, vec2(5i)); + d = _e256; + let _e257 = coord_7; + let _e265 = coord_7; + let _e270 = vec4(_e265.x, _e265.y, 1f, 6f); + let _e276 = (_e270.xyz / vec3(_e270.w)); + let _e279 = textureSampleCompare(tex2DShadow, sampShadow, _e276.xy, _e276.z, vec2(5i)); + d = _e279; + return; +} + +fn testTex2DArray(coord_8: vec3) { + var coord_9: vec3; + var size2DArray: vec3; + var c_3: vec4; + + coord_9 = coord_8; + let _e20 = textureDimensions(tex2DArray, 0i); + let _e23 = textureNumLayers(tex2DArray); + size2DArray = vec3(vec3(_e20.x, _e20.y, _e23)); + let _e29 = coord_9; + let _e33 = textureSample(tex2DArray, samp, _e29.xy, i32(_e29.z)); + c_3 = _e33; + let _e36 = coord_9; + let _e41 = textureSampleBias(tex2DArray, samp, _e36.xy, i32(_e36.z), 2f); + c_3 = _e41; + let _e47 = coord_9; + let _e55 = textureSampleGrad(tex2DArray, samp, _e47.xy, i32(_e47.z), vec2(4f), vec2(4f)); + c_3 = _e55; + let _e63 = coord_9; + let _e73 = textureSampleGrad(tex2DArray, samp, _e63.xy, i32(_e63.z), vec2(4f), vec2(4f), vec2(5i)); + c_3 = _e73; + let _e76 = coord_9; + let _e81 = textureSampleLevel(tex2DArray, samp, _e76.xy, i32(_e76.z), 3f); + c_3 = _e81; + let _e86 = coord_9; + let _e93 = textureSampleLevel(tex2DArray, samp, _e86.xy, i32(_e86.z), 3f, vec2(5i)); + c_3 = _e93; + let _e97 = coord_9; + let _e103 = textureSample(tex2DArray, samp, _e97.xy, i32(_e97.z), vec2(5i)); + c_3 = _e103; + let _e108 = coord_9; + let _e115 = textureSampleBias(tex2DArray, samp, _e108.xy, i32(_e108.z), 2f, vec2(5i)); + c_3 = _e115; + let _e116 = coord_9; + let _e119 = coord_9; + let _e120 = vec3(_e119); + let _e124 = textureLoad(tex2DArray, _e120.xy, _e120.z, 3i); + c_3 = _e124; + let _e125 = coord_9; + let _e130 = coord_9; + let _e131 = vec3(_e130); + let _e137 = textureLoad(tex2DArray, _e131.xy, _e131.z, 3i); + c_3 = _e137; + return; +} + +fn testTex2DArrayShadow(coord_10: vec3) { + var coord_11: vec3; + var size2DArrayShadow: vec3; + var d_1: f32; + + coord_11 = coord_10; + let _e20 = textureDimensions(tex2DArrayShadow, 0i); + let _e23 = textureNumLayers(tex2DArrayShadow); + size2DArrayShadow = vec3(vec3(_e20.x, _e20.y, _e23)); + let _e28 = coord_11; + let _e34 = coord_11; + let _e39 = vec4(_e34.x, _e34.y, _e34.z, 1f); + let _e44 = textureSampleCompare(tex2DArrayShadow, sampShadow, _e39.xy, i32(_e39.z), _e39.w); + d_1 = _e44; + let _e45 = coord_11; + let _e55 = coord_11; + let _e60 = vec4(_e55.x, _e55.y, _e55.z, 1f); + let _e69 = textureSampleCompareLevel(tex2DArrayShadow, sampShadow, _e60.xy, i32(_e60.z), _e60.w); + d_1 = _e69; + let _e70 = coord_11; + let _e82 = coord_11; + let _e87 = vec4(_e82.x, _e82.y, _e82.z, 1f); + let _e98 = textureSampleCompareLevel(tex2DArrayShadow, sampShadow, _e87.xy, i32(_e87.z), _e87.w, vec2(5i)); + d_1 = _e98; + let _e99 = coord_11; + let _e107 = coord_11; + let _e112 = vec4(_e107.x, _e107.y, _e107.z, 1f); + let _e119 = textureSampleCompare(tex2DArrayShadow, sampShadow, _e112.xy, i32(_e112.z), _e112.w, vec2(5i)); + d_1 = _e119; + return; +} + +fn testTexCube(coord_12: vec3) { + var coord_13: vec3; + var sizeCube: vec2; + var c_4: vec4; + + coord_13 = coord_12; + let _e20 = textureDimensions(texCube, 0i); + sizeCube = vec2(_e20); + let _e25 = coord_13; + let _e26 = textureSample(texCube, samp, _e25); + c_4 = _e26; + let _e29 = coord_13; + let _e31 = textureSampleBias(texCube, samp, _e29, 2f); + c_4 = _e31; + let _e37 = coord_13; + let _e42 = textureSampleGrad(texCube, samp, _e37, vec3(4f), vec3(4f)); + c_4 = _e42; + let _e45 = coord_13; + let _e47 = textureSampleLevel(texCube, samp, _e45, 3f); + c_4 = _e47; + return; +} + +fn testTexCubeShadow(coord_14: vec3) { + var coord_15: vec3; + var sizeCubeShadow: vec2; + var d_2: f32; + + coord_15 = coord_14; + let _e20 = textureDimensions(texCubeShadow, 0i); + sizeCubeShadow = vec2(_e20); + let _e24 = coord_15; + let _e30 = coord_15; + let _e35 = vec4(_e30.x, _e30.y, _e30.z, 1f); + let _e38 = textureSampleCompare(texCubeShadow, sampShadow, _e35.xyz, _e35.w); + d_2 = _e38; + let _e39 = coord_15; + let _e49 = coord_15; + let _e54 = vec4(_e49.x, _e49.y, _e49.z, 1f); + let _e61 = textureSampleCompareLevel(texCubeShadow, sampShadow, _e54.xyz, _e54.w); + d_2 = _e61; + return; +} + +fn testTexCubeArray(coord_16: vec4) { + var coord_17: vec4; + var sizeCubeArray: vec3; + var c_5: vec4; + + coord_17 = coord_16; + let _e20 = textureDimensions(texCubeArray, 0i); + let _e23 = textureNumLayers(texCubeArray); + sizeCubeArray = vec3(vec3(_e20.x, _e20.y, _e23)); + let _e29 = coord_17; + let _e33 = textureSample(texCubeArray, samp, _e29.xyz, i32(_e29.w)); + c_5 = _e33; + let _e36 = coord_17; + let _e41 = textureSampleBias(texCubeArray, samp, _e36.xyz, i32(_e36.w), 2f); + c_5 = _e41; + let _e47 = coord_17; + let _e55 = textureSampleGrad(texCubeArray, samp, _e47.xyz, i32(_e47.w), vec3(4f), vec3(4f)); + c_5 = _e55; + let _e58 = coord_17; + let _e63 = textureSampleLevel(texCubeArray, samp, _e58.xyz, i32(_e58.w), 3f); + c_5 = _e63; + return; +} + +fn testTexCubeArrayShadow(coord_18: vec4) { + var coord_19: vec4; + var sizeCubeArrayShadow: vec3; + var d_3: f32; + + coord_19 = coord_18; + let _e20 = textureDimensions(texCubeArrayShadow, 0i); + let _e23 = textureNumLayers(texCubeArrayShadow); + sizeCubeArrayShadow = vec3(vec3(_e20.x, _e20.y, _e23)); + let _e30 = coord_19; + let _e35 = textureSampleCompare(texCubeArrayShadow, sampShadow, _e30.xyz, i32(_e30.w), 1f); + d_3 = _e35; + return; +} + +fn testTex3D(coord_20: vec3) { + var coord_21: vec3; + var size3D: vec3; + var c_6: vec4; + + coord_21 = coord_20; + let _e20 = textureDimensions(tex3D, 0i); + size3D = vec3(_e20); + let _e25 = coord_21; + let _e26 = textureSample(tex3D, samp, _e25); + c_6 = _e26; + let _e29 = coord_21; + let _e31 = textureSampleBias(tex3D, samp, _e29, 2f); + c_6 = _e31; + let _e32 = coord_21; + let _e38 = coord_21; + let _e43 = vec4(_e38.x, _e38.y, _e38.z, 6f); + let _e48 = textureSample(tex3D, samp, (_e43.xyz / vec3(_e43.w))); + c_6 = _e48; + let _e49 = coord_21; + let _e56 = coord_21; + let _e61 = vec4(_e56.x, _e56.y, _e56.z, 6f); + let _e67 = textureSampleBias(tex3D, samp, (_e61.xyz / vec3(_e61.w)), 2f); + c_6 = _e67; + let _e68 = coord_21; + let _e76 = coord_21; + let _e81 = vec4(_e76.x, _e76.y, _e76.z, 6f); + let _e88 = textureSample(tex3D, samp, (_e81.xyz / vec3(_e81.w)), vec3(5i)); + c_6 = _e88; + let _e89 = coord_21; + let _e98 = coord_21; + let _e103 = vec4(_e98.x, _e98.y, _e98.z, 6f); + let _e111 = textureSampleBias(tex3D, samp, (_e103.xyz / vec3(_e103.w)), 2f, vec3(5i)); + c_6 = _e111; + let _e112 = coord_21; + let _e119 = coord_21; + let _e124 = vec4(_e119.x, _e119.y, _e119.z, 6f); + let _e130 = textureSampleLevel(tex3D, samp, (_e124.xyz / vec3(_e124.w)), 3f); + c_6 = _e130; + let _e131 = coord_21; + let _e140 = coord_21; + let _e145 = vec4(_e140.x, _e140.y, _e140.z, 6f); + let _e153 = textureSampleLevel(tex3D, samp, (_e145.xyz / vec3(_e145.w)), 3f, vec3(5i)); + c_6 = _e153; + let _e154 = coord_21; + let _e164 = coord_21; + let _e169 = vec4(_e164.x, _e164.y, _e164.z, 6f); + let _e178 = textureSampleGrad(tex3D, samp, (_e169.xyz / vec3(_e169.w)), vec3(4f), vec3(4f)); + c_6 = _e178; + let _e179 = coord_21; + let _e191 = coord_21; + let _e196 = vec4(_e191.x, _e191.y, _e191.z, 6f); + let _e207 = textureSampleGrad(tex3D, samp, (_e196.xyz / vec3(_e196.w)), vec3(4f), vec3(4f), vec3(5i)); + c_6 = _e207; + let _e213 = coord_21; + let _e218 = textureSampleGrad(tex3D, samp, _e213, vec3(4f), vec3(4f)); + c_6 = _e218; + let _e226 = coord_21; + let _e233 = textureSampleGrad(tex3D, samp, _e226, vec3(4f), vec3(4f), vec3(5i)); + c_6 = _e233; + let _e236 = coord_21; + let _e238 = textureSampleLevel(tex3D, samp, _e236, 3f); + c_6 = _e238; + let _e243 = coord_21; + let _e247 = textureSampleLevel(tex3D, samp, _e243, 3f, vec3(5i)); + c_6 = _e247; + let _e251 = coord_21; + let _e254 = textureSample(tex3D, samp, _e251, vec3(5i)); + c_6 = _e254; + let _e259 = coord_21; + let _e263 = textureSampleBias(tex3D, samp, _e259, 2f, vec3(5i)); + c_6 = _e263; + let _e264 = coord_21; + let _e267 = coord_21; + let _e270 = textureLoad(tex3D, vec3(_e267), 3i); + c_6 = _e270; + let _e271 = coord_21; + let _e276 = coord_21; + let _e281 = textureLoad(tex3D, vec3(_e276), 3i); + c_6 = _e281; + return; +} + +fn testTex2DMS(coord_22: vec2) { + var coord_23: vec2; + var size2DMS: vec2; + var c_7: vec4; + + coord_23 = coord_22; + let _e18 = textureDimensions(tex2DMS); + size2DMS = vec2(_e18); + let _e22 = coord_23; + let _e25 = coord_23; + let _e28 = textureLoad(tex2DMS, vec2(_e25), 3i); + c_7 = _e28; + return; +} + +fn testTex2DMSArray(coord_24: vec3) { + var coord_25: vec3; + var size2DMSArray: vec3; + var c_8: vec4; + + coord_25 = coord_24; + let _e18 = textureDimensions(tex2DMSArray); + let _e21 = textureNumLayers(tex2DMSArray); + size2DMSArray = vec3(vec3(_e18.x, _e18.y, _e21)); + let _e26 = coord_25; + let _e29 = coord_25; + let _e30 = vec3(_e29); + let _e34 = textureLoad(tex2DMSArray, _e30.xy, _e30.z, 3i); + c_8 = _e34; + return; +} + +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/shadow.wgsl b/naga/tests/out/wgsl/shadow.wgsl new file mode 100644 index 0000000000..e9d5bbf1be --- /dev/null +++ b/naga/tests/out/wgsl/shadow.wgsl @@ -0,0 +1,129 @@ +struct Globals { + view_proj: mat4x4, + num_lights: vec4, +} + +struct Entity { + world: mat4x4, + color: vec4, +} + +struct VertexOutput { + @builtin(position) proj_position: vec4, + @location(0) world_normal: vec3, + @location(1) world_position: vec4, +} + +struct Light { + proj: mat4x4, + pos: vec4, + color: vec4, +} + +const c_ambient: vec3 = vec3(0.05f, 0.05f, 0.05f); +const c_max_lights: u32 = 10u; + +@group(0) @binding(0) +var u_globals: Globals; +@group(1) @binding(0) +var u_entity: Entity; +@group(0) @binding(1) +var s_lights: array; +@group(0) @binding(1) +var u_lights: array; +@group(0) @binding(2) +var t_shadow: texture_depth_2d_array; +@group(0) @binding(3) +var sampler_shadow: sampler_comparison; + +fn fetch_shadow(light_id: u32, homogeneous_coords: vec4) -> f32 { + if (homogeneous_coords.w <= 0f) { + return 1f; + } + let flip_correction = vec2(0.5f, -0.5f); + let proj_correction = (1f / homogeneous_coords.w); + let light_local = (((homogeneous_coords.xy * flip_correction) * proj_correction) + vec2(0.5f, 0.5f)); + let _e24 = textureSampleCompareLevel(t_shadow, sampler_shadow, light_local, i32(light_id), (homogeneous_coords.z * proj_correction)); + return _e24; +} + +@vertex +fn vs_main(@location(0) @interpolate(flat) position: vec4, @location(1) @interpolate(flat) normal: vec4) -> VertexOutput { + var out: VertexOutput; + + let w = u_entity.world; + let _e7 = u_entity.world; + let world_pos = (_e7 * vec4(position)); + out.world_normal = (mat3x3(w[0].xyz, w[1].xyz, w[2].xyz) * vec3(normal.xyz)); + out.world_position = world_pos; + let _e26 = u_globals.view_proj; + out.proj_position = (_e26 * world_pos); + let _e28 = out; + return _e28; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + var color: vec3 = c_ambient; + var i: u32 = 0u; + + let normal_1 = normalize(in.world_normal); + loop { + let _e7 = i; + let _e11 = u_globals.num_lights.x; + if (_e7 < min(_e11, c_max_lights)) { + } else { + break; + } + { + let _e16 = i; + let light = s_lights[_e16]; + let _e19 = i; + let _e23 = fetch_shadow(_e19, (light.proj * in.world_position)); + let light_dir = normalize((light.pos.xyz - in.world_position.xyz)); + let diffuse = max(0f, dot(normal_1, light_dir)); + let _e37 = color; + color = (_e37 + ((_e23 * diffuse) * light.color.xyz)); + } + continuing { + let _e40 = i; + i = (_e40 + 1u); + } + } + let _e42 = color; + let _e47 = u_entity.color; + return (vec4(_e42, 1f) * _e47); +} + +@fragment +fn fs_main_without_storage(in_1: VertexOutput) -> @location(0) vec4 { + var color_1: vec3 = c_ambient; + var i_1: u32 = 0u; + + let normal_2 = normalize(in_1.world_normal); + loop { + let _e7 = i_1; + let _e11 = u_globals.num_lights.x; + if (_e7 < min(_e11, c_max_lights)) { + } else { + break; + } + { + let _e16 = i_1; + let light_1 = u_lights[_e16]; + let _e19 = i_1; + let _e23 = fetch_shadow(_e19, (light_1.proj * in_1.world_position)); + let light_dir_1 = normalize((light_1.pos.xyz - in_1.world_position.xyz)); + let diffuse_1 = max(0f, dot(normal_2, light_dir_1)); + let _e37 = color_1; + color_1 = (_e37 + ((_e23 * diffuse_1) * light_1.color.xyz)); + } + continuing { + let _e40 = i_1; + i_1 = (_e40 + 1u); + } + } + let _e42 = color_1; + let _e47 = u_entity.color; + return (vec4(_e42, 1f) * _e47); +} diff --git a/naga/tests/out/wgsl/skybox.wgsl b/naga/tests/out/wgsl/skybox.wgsl new file mode 100644 index 0000000000..c10801d0e7 --- /dev/null +++ b/naga/tests/out/wgsl/skybox.wgsl @@ -0,0 +1,41 @@ +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) uv: vec3, +} + +struct Data { + proj_inv: mat4x4, + view: mat4x4, +} + +@group(0) @binding(0) +var r_data: Data; +@group(0) @binding(1) +var r_texture: texture_cube; +@group(0) @binding(2) +var r_sampler: sampler; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { + var tmp1_: i32; + var tmp2_: i32; + + tmp1_ = (i32(vertex_index) / 2i); + tmp2_ = (i32(vertex_index) & 1i); + let _e9 = tmp1_; + let _e15 = tmp2_; + let pos = vec4(((f32(_e9) * 4f) - 1f), ((f32(_e15) * 4f) - 1f), 0f, 1f); + let _e27 = r_data.view[0]; + let _e32 = r_data.view[1]; + let _e37 = r_data.view[2]; + let inv_model_view = transpose(mat3x3(_e27.xyz, _e32.xyz, _e37.xyz)); + let _e43 = r_data.proj_inv; + let unprojected = (_e43 * pos); + return VertexOutput(pos, (inv_model_view * unprojected.xyz)); +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let _e4 = textureSample(r_texture, r_sampler, in.uv); + return _e4; +} diff --git a/naga/tests/out/wgsl/standard.wgsl b/naga/tests/out/wgsl/standard.wgsl new file mode 100644 index 0000000000..886cf09193 --- /dev/null +++ b/naga/tests/out/wgsl/standard.wgsl @@ -0,0 +1,34 @@ +fn test_any_and_all_for_bool() -> bool { + return true; +} + +@fragment +fn derivatives(@builtin(position) foo: vec4) -> @location(0) vec4 { + var x: vec4; + var y: vec4; + var z: vec4; + + let _e1 = dpdxCoarse(foo); + x = _e1; + let _e3 = dpdyCoarse(foo); + y = _e3; + let _e5 = fwidthCoarse(foo); + z = _e5; + let _e7 = dpdxFine(foo); + x = _e7; + let _e8 = dpdyFine(foo); + y = _e8; + let _e9 = fwidthFine(foo); + z = _e9; + let _e10 = dpdx(foo); + x = _e10; + let _e11 = dpdy(foo); + y = _e11; + let _e12 = fwidth(foo); + z = _e12; + let _e13 = test_any_and_all_for_bool(); + let _e14 = x; + let _e15 = y; + let _e17 = z; + return ((_e14 + _e15) * _e17); +} diff --git a/naga/tests/out/wgsl/statements.frag.wgsl b/naga/tests/out/wgsl/statements.frag.wgsl new file mode 100644 index 0000000000..bc36eb2075 --- /dev/null +++ b/naga/tests/out/wgsl/statements.frag.wgsl @@ -0,0 +1,64 @@ +fn switchEmpty(a: i32) { + var a_1: i32; + + a_1 = a; + let _e2 = a_1; + switch _e2 { + default: { + } + } + return; +} + +fn switchNoDefault(a_2: i32) { + var a_3: i32; + + a_3 = a_2; + let _e2 = a_3; + switch _e2 { + case 0: { + } + default: { + } + } + return; +} + +fn switchCaseImplConv(a_4: u32) { + var a_5: u32; + + a_5 = a_4; + let _e2 = a_5; + switch _e2 { + case 0u: { + } + default: { + } + } + return; +} + +fn switchNoLastBreak(a_6: i32) { + var a_7: i32; + var b: i32; + + a_7 = a_6; + let _e2 = a_7; + switch _e2 { + default: { + let _e3 = a_7; + b = _e3; + } + } + return; +} + +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/struct-layout.wgsl b/naga/tests/out/wgsl/struct-layout.wgsl new file mode 100644 index 0000000000..b1569bde3e --- /dev/null +++ b/naga/tests/out/wgsl/struct-layout.wgsl @@ -0,0 +1,61 @@ +struct NoPadding { + @location(0) v3_: vec3, + @location(1) f3_: f32, +} + +struct NeedsPadding { + @location(0) f3_forces_padding: f32, + @location(1) v3_needs_padding: vec3, + @location(2) f3_: f32, +} + +@group(0) @binding(0) +var no_padding_uniform: NoPadding; +@group(0) @binding(1) +var no_padding_storage: NoPadding; +@group(0) @binding(2) +var needs_padding_uniform: NeedsPadding; +@group(0) @binding(3) +var needs_padding_storage: NeedsPadding; + +@fragment +fn no_padding_frag(input: NoPadding) -> @location(0) vec4 { + return vec4(0f); +} + +@vertex +fn no_padding_vert(input_1: NoPadding) -> @builtin(position) vec4 { + return vec4(0f); +} + +@compute @workgroup_size(16, 1, 1) +fn no_padding_comp() { + var x: NoPadding; + + let _e2 = no_padding_uniform; + x = _e2; + let _e4 = no_padding_storage; + x = _e4; + return; +} + +@fragment +fn needs_padding_frag(input_2: NeedsPadding) -> @location(0) vec4 { + return vec4(0f); +} + +@vertex +fn needs_padding_vert(input_3: NeedsPadding) -> @builtin(position) vec4 { + return vec4(0f); +} + +@compute @workgroup_size(16, 1, 1) +fn needs_padding_comp() { + var x_1: NeedsPadding; + + let _e2 = needs_padding_uniform; + x_1 = _e2; + let _e4 = needs_padding_storage; + x_1 = _e4; + return; +} diff --git a/naga/tests/out/wgsl/texture-arg.wgsl b/naga/tests/out/wgsl/texture-arg.wgsl new file mode 100644 index 0000000000..0ad02e43f5 --- /dev/null +++ b/naga/tests/out/wgsl/texture-arg.wgsl @@ -0,0 +1,15 @@ +@group(0) @binding(0) +var Texture: texture_2d; +@group(0) @binding(1) +var Sampler: sampler; + +fn test(Passed_Texture: texture_2d, Passed_Sampler: sampler) -> vec4 { + let _e5 = textureSample(Passed_Texture, Passed_Sampler, vec2(0f, 0f)); + return _e5; +} + +@fragment +fn main() -> @location(0) vec4 { + let _e2 = test(Texture, Sampler); + return _e2; +} diff --git a/naga/tests/out/wgsl/type-alias.wgsl b/naga/tests/out/wgsl/type-alias.wgsl new file mode 100644 index 0000000000..fe3cf79037 --- /dev/null +++ b/naga/tests/out/wgsl/type-alias.wgsl @@ -0,0 +1,10 @@ +fn main() { + let a = vec3(0f, 0f, 0f); + let c = vec3(0f); + let b = vec3(vec2(0f), 0f); + let d = vec3(vec2(0f), 0f); + let e = vec3(d); + let f = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + let g = mat3x3(a, a, a); +} + diff --git a/naga/tests/out/wgsl/vector-functions.frag.wgsl b/naga/tests/out/wgsl/vector-functions.frag.wgsl new file mode 100644 index 0000000000..90b35f3837 --- /dev/null +++ b/naga/tests/out/wgsl/vector-functions.frag.wgsl @@ -0,0 +1,167 @@ +fn ftest(a: vec4, b: vec4) { + var a_1: vec4; + var b_1: vec4; + var c: vec4; + var d: vec4; + var e: vec4; + var f: vec4; + var g: vec4; + var h: vec4; + + a_1 = a; + b_1 = b; + let _e6 = a_1; + let _e7 = b_1; + c = (_e6 < _e7); + let _e12 = a_1; + let _e13 = b_1; + d = (_e12 <= _e13); + let _e18 = a_1; + let _e19 = b_1; + e = (_e18 > _e19); + let _e24 = a_1; + let _e25 = b_1; + f = (_e24 >= _e25); + let _e30 = a_1; + let _e31 = b_1; + g = (_e30 == _e31); + let _e36 = a_1; + let _e37 = b_1; + h = (_e36 != _e37); + return; +} + +fn dtest(a_2: vec4, b_2: vec4) { + var a_3: vec4; + var b_3: vec4; + var c_1: vec4; + var d_1: vec4; + var e_1: vec4; + var f_1: vec4; + var g_1: vec4; + var h_1: vec4; + + a_3 = a_2; + b_3 = b_2; + let _e6 = a_3; + let _e7 = b_3; + c_1 = (_e6 < _e7); + let _e12 = a_3; + let _e13 = b_3; + d_1 = (_e12 <= _e13); + let _e18 = a_3; + let _e19 = b_3; + e_1 = (_e18 > _e19); + let _e24 = a_3; + let _e25 = b_3; + f_1 = (_e24 >= _e25); + let _e30 = a_3; + let _e31 = b_3; + g_1 = (_e30 == _e31); + let _e36 = a_3; + let _e37 = b_3; + h_1 = (_e36 != _e37); + return; +} + +fn itest(a_4: vec4, b_4: vec4) { + var a_5: vec4; + var b_5: vec4; + var c_2: vec4; + var d_2: vec4; + var e_2: vec4; + var f_2: vec4; + var g_2: vec4; + var h_2: vec4; + + a_5 = a_4; + b_5 = b_4; + let _e6 = a_5; + let _e7 = b_5; + c_2 = (_e6 < _e7); + let _e12 = a_5; + let _e13 = b_5; + d_2 = (_e12 <= _e13); + let _e18 = a_5; + let _e19 = b_5; + e_2 = (_e18 > _e19); + let _e24 = a_5; + let _e25 = b_5; + f_2 = (_e24 >= _e25); + let _e30 = a_5; + let _e31 = b_5; + g_2 = (_e30 == _e31); + let _e36 = a_5; + let _e37 = b_5; + h_2 = (_e36 != _e37); + return; +} + +fn utest(a_6: vec4, b_6: vec4) { + var a_7: vec4; + var b_7: vec4; + var c_3: vec4; + var d_3: vec4; + var e_3: vec4; + var f_3: vec4; + var g_3: vec4; + var h_3: vec4; + + a_7 = a_6; + b_7 = b_6; + let _e6 = a_7; + let _e7 = b_7; + c_3 = (_e6 < _e7); + let _e12 = a_7; + let _e13 = b_7; + d_3 = (_e12 <= _e13); + let _e18 = a_7; + let _e19 = b_7; + e_3 = (_e18 > _e19); + let _e24 = a_7; + let _e25 = b_7; + f_3 = (_e24 >= _e25); + let _e30 = a_7; + let _e31 = b_7; + g_3 = (_e30 == _e31); + let _e36 = a_7; + let _e37 = b_7; + h_3 = (_e36 != _e37); + return; +} + +fn btest(a_8: vec4, b_8: vec4) { + var a_9: vec4; + var b_9: vec4; + var c_4: vec4; + var d_4: vec4; + var e_4: bool; + var f_4: bool; + var g_4: vec4; + + a_9 = a_8; + b_9 = b_8; + let _e6 = a_9; + let _e7 = b_9; + c_4 = (_e6 == _e7); + let _e12 = a_9; + let _e13 = b_9; + d_4 = (_e12 != _e13); + let _e17 = a_9; + e_4 = any(_e17); + let _e21 = a_9; + f_4 = all(_e21); + let _e25 = a_9; + g_4 = !(_e25); + return; +} + +fn main_1() { + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/workgroup-uniform-load.wgsl b/naga/tests/out/wgsl/workgroup-uniform-load.wgsl new file mode 100644 index 0000000000..be85b033b9 --- /dev/null +++ b/naga/tests/out/wgsl/workgroup-uniform-load.wgsl @@ -0,0 +1,15 @@ +const SIZE: u32 = 128u; + +var arr_i32_: array; + +@compute @workgroup_size(4, 1, 1) +fn test_workgroupUniformLoad(@builtin(workgroup_id) workgroup_id: vec3) { + let x = (&arr_i32_[workgroup_id.x]); + let _e4 = workgroupUniformLoad(x); + if (_e4 > 10i) { + workgroupBarrier(); + return; + } else { + return; + } +} diff --git a/naga/tests/out/wgsl/workgroup-var-init.wgsl b/naga/tests/out/wgsl/workgroup-var-init.wgsl new file mode 100644 index 0000000000..fdad0477d6 --- /dev/null +++ b/naga/tests/out/wgsl/workgroup-var-init.wgsl @@ -0,0 +1,16 @@ +struct WStruct { + arr: array, + atom: atomic, + atom_arr: array, 8>, 8>, +} + +var w_mem: WStruct; +@group(0) @binding(0) +var output: array; + +@compute @workgroup_size(1, 1, 1) +fn main() { + let _e3 = w_mem.arr; + output = _e3; + return; +} diff --git a/naga/tests/root.rs b/naga/tests/root.rs new file mode 100644 index 0000000000..fece3ddd7e --- /dev/null +++ b/naga/tests/root.rs @@ -0,0 +1,4 @@ +mod example_wgsl; +mod snapshots; +mod spirv_capabilities; +mod wgsl_errors; diff --git a/naga/tests/snapshots.rs b/naga/tests/snapshots.rs new file mode 100644 index 0000000000..6b934de55b --- /dev/null +++ b/naga/tests/snapshots.rs @@ -0,0 +1,949 @@ +// A lot of the code can be unused based on configuration flags, +// the corresponding warnings aren't helpful. +#![allow(dead_code, unused_imports)] + +use std::{ + fs, + path::{Path, PathBuf}, +}; + +const CRATE_ROOT: &str = env!("CARGO_MANIFEST_DIR"); +const BASE_DIR_IN: &str = "tests/in"; +const BASE_DIR_OUT: &str = "tests/out"; + +bitflags::bitflags! { + #[derive(Clone, Copy)] + struct Targets: u32 { + const IR = 0x1; + const ANALYSIS = 0x2; + const SPIRV = 0x4; + const METAL = 0x8; + const GLSL = 0x10; + const DOT = 0x20; + const HLSL = 0x40; + const WGSL = 0x80; + } +} + +#[derive(serde::Deserialize)] +struct SpvOutVersion(u8, u8); +impl Default for SpvOutVersion { + fn default() -> Self { + SpvOutVersion(1, 1) + } +} + +#[derive(Default, serde::Deserialize)] +struct SpirvOutParameters { + version: SpvOutVersion, + #[serde(default)] + capabilities: naga::FastHashSet, + #[serde(default)] + debug: bool, + #[serde(default)] + adjust_coordinate_space: bool, + #[serde(default)] + force_point_size: bool, + #[serde(default)] + clamp_frag_depth: bool, + #[serde(default)] + separate_entry_points: bool, + #[serde(default)] + #[cfg(all(feature = "deserialize", feature = "spv-out"))] + binding_map: naga::back::spv::BindingMap, +} + +#[derive(Default, serde::Deserialize)] +struct WgslOutParameters { + #[serde(default)] + explicit_types: bool, +} + +#[derive(Default, serde::Deserialize)] +struct Parameters { + #[serde(default)] + god_mode: bool, + #[cfg(feature = "deserialize")] + #[serde(default)] + bounds_check_policies: naga::proc::BoundsCheckPolicies, + #[serde(default)] + spv: SpirvOutParameters, + #[cfg(all(feature = "deserialize", feature = "msl-out"))] + #[serde(default)] + msl: naga::back::msl::Options, + #[cfg(all(feature = "deserialize", feature = "msl-out"))] + #[serde(default)] + msl_pipeline: naga::back::msl::PipelineOptions, + #[cfg(all(feature = "deserialize", feature = "glsl-out"))] + #[serde(default)] + glsl: naga::back::glsl::Options, + #[serde(default)] + glsl_exclude_list: naga::FastHashSet, + #[cfg(all(feature = "deserialize", feature = "hlsl-out"))] + #[serde(default)] + hlsl: naga::back::hlsl::Options, + #[serde(default)] + wgsl: WgslOutParameters, + #[cfg(all(feature = "deserialize", feature = "glsl-out"))] + #[serde(default)] + glsl_multiview: Option, +} + +/// Information about a shader input file. +#[derive(Debug)] +struct Input { + /// The subdirectory of `tests/in` to which this input belongs, if any. + /// + /// If the subdirectory is omitted, we assume that the output goes + /// to "wgsl". + subdirectory: Option, + + /// The input filename name, without a directory. + file_name: PathBuf, + + /// True if output filenames should add the output extension on top of + /// `file_name`'s existing extension, rather than replacing it. + /// + /// This is used by `convert_glsl_folder`, which wants to take input files + /// like `210-bevy-2d-shader.frag` and just add `.wgsl` to it, producing + /// `210-bevy-2d-shader.frag.wgsl`. + keep_input_extension: bool, +} + +impl Input { + /// Read an input file and its corresponding parameters file. + /// + /// Given `input`, the relative path of a shader input file, return + /// a `Source` value containing its path, code, and parameters. + /// + /// The `input` path is interpreted relative to the `BASE_DIR_IN` + /// subdirectory of the directory given by the `CARGO_MANIFEST_DIR` + /// environment variable. + fn new(subdirectory: Option<&str>, name: &str, extension: &str) -> Input { + Input { + subdirectory: subdirectory.map(PathBuf::from), + // Don't wipe out any extensions on `name`, as + // `with_extension` would do. + file_name: PathBuf::from(format!("{name}.{extension}")), + keep_input_extension: false, + } + } + + /// Return an iterator that produces an `Input` for each entry in `subdirectory`. + fn files_in_dir(subdirectory: &str) -> impl Iterator + 'static { + let subdirectory = subdirectory.to_string(); + let mut input_directory = Path::new(env!("CARGO_MANIFEST_DIR")).join(BASE_DIR_IN); + input_directory.push(&subdirectory); + match std::fs::read_dir(&input_directory) { + Ok(entries) => entries.map(move |result| { + let entry = result.expect("error reading directory"); + let file_name = PathBuf::from(entry.file_name()); + let extension = file_name + .extension() + .expect("all files in snapshot input directory should have extensions"); + let input = Input::new( + Some(&subdirectory), + file_name.file_stem().unwrap().to_str().unwrap(), + extension.to_str().unwrap(), + ); + input + }), + Err(err) => { + panic!( + "Error opening directory '{}': {}", + input_directory.display(), + err + ); + } + } + } + + /// Return the path to the input directory. + fn input_directory(&self) -> PathBuf { + let mut dir = Path::new(CRATE_ROOT).join(BASE_DIR_IN); + if let Some(ref subdirectory) = self.subdirectory { + dir.push(subdirectory); + } + dir + } + + /// Return the path to the output directory. + fn output_directory(&self, subdirectory: &str) -> PathBuf { + let mut dir = Path::new(CRATE_ROOT).join(BASE_DIR_OUT); + dir.push(subdirectory); + dir + } + + /// Return the path to the input file. + fn input_path(&self) -> PathBuf { + let mut input = self.input_directory(); + input.push(&self.file_name); + input + } + + fn output_path(&self, subdirectory: &str, extension: &str) -> PathBuf { + let mut output = self.output_directory(subdirectory); + if self.keep_input_extension { + let mut file_name = self.file_name.as_os_str().to_owned(); + file_name.push("."); + file_name.push(extension); + output.push(&file_name); + } else { + output.push(&self.file_name); + output.set_extension(extension); + } + output + } + + /// Return the contents of the input file as a string. + fn read_source(&self) -> String { + println!("Processing '{}'", self.file_name.display()); + let input_path = self.input_path(); + match fs::read_to_string(&input_path) { + Ok(source) => source, + Err(err) => { + panic!( + "Couldn't read shader input file `{}`: {}", + input_path.display(), + err + ); + } + } + } + + /// Return the contents of the input file as a vector of bytes. + fn read_bytes(&self) -> Vec { + println!("Processing '{}'", self.file_name.display()); + let input_path = self.input_path(); + match fs::read(&input_path) { + Ok(bytes) => bytes, + Err(err) => { + panic!( + "Couldn't read shader input file `{}`: {}", + input_path.display(), + err + ); + } + } + } + + /// Return this input's parameter file, parsed. + fn read_parameters(&self) -> Parameters { + let mut param_path = self.input_path(); + param_path.set_extension("param.ron"); + match fs::read_to_string(¶m_path) { + Ok(string) => ron::de::from_str(&string) + .unwrap_or_else(|_| panic!("Couldn't parse param file: {}", param_path.display())), + Err(_) => Parameters::default(), + } + } + + /// Write `data` to a file corresponding to this input file in + /// `subdirectory`, with `extension`. + fn write_output_file(&self, subdirectory: &str, extension: &str, data: impl AsRef<[u8]>) { + let output_path = self.output_path(subdirectory, extension); + if let Err(err) = fs::write(&output_path, data) { + panic!("Error writing {}: {}", output_path.display(), err); + } + } +} + +#[allow(unused_variables)] +fn check_targets( + input: &Input, + module: &mut naga::Module, + targets: Targets, + source_code: Option<&str>, +) { + let params = input.read_parameters(); + let name = &input.file_name; + + let capabilities = if params.god_mode { + naga::valid::Capabilities::all() + } else { + naga::valid::Capabilities::default() + }; + + #[cfg(feature = "serialize")] + { + if targets.contains(Targets::IR) { + let config = ron::ser::PrettyConfig::default().new_line("\n".to_string()); + let string = ron::ser::to_string_pretty(module, config).unwrap(); + input.write_output_file("ir", "ron", string); + } + } + + let info = naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities) + .validate(module) + .unwrap_or_else(|err| { + panic!( + "Naga module validation failed on test `{}`:\n{:?}", + name.display(), + err + ); + }); + + #[cfg(feature = "compact")] + let info = { + naga::compact::compact(module); + + #[cfg(feature = "serialize")] + { + if targets.contains(Targets::IR) { + let config = ron::ser::PrettyConfig::default().new_line("\n".to_string()); + let string = ron::ser::to_string_pretty(module, config).unwrap(); + input.write_output_file("ir", "compact.ron", string); + } + } + + naga::valid::Validator::new(naga::valid::ValidationFlags::all(), capabilities) + .validate(module) + .unwrap_or_else(|err| { + panic!( + "Post-compaction module validation failed on test '{}':\n<{:?}", + name.display(), + err, + ) + }) + }; + + #[cfg(feature = "serialize")] + { + if targets.contains(Targets::ANALYSIS) { + let config = ron::ser::PrettyConfig::default().new_line("\n".to_string()); + let string = ron::ser::to_string_pretty(&info, config).unwrap(); + input.write_output_file("analysis", "info.ron", string); + } + } + + #[cfg(all(feature = "deserialize", feature = "spv-out"))] + { + let debug_info = source_code.map(|code| naga::back::spv::DebugInfo { + source_code: code, + file_name: name.as_ref(), + }); + + if targets.contains(Targets::SPIRV) { + write_output_spv( + input, + module, + &info, + debug_info, + ¶ms.spv, + params.bounds_check_policies, + ); + } + } + #[cfg(all(feature = "deserialize", feature = "msl-out"))] + { + if targets.contains(Targets::METAL) { + write_output_msl( + input, + module, + &info, + ¶ms.msl, + ¶ms.msl_pipeline, + params.bounds_check_policies, + ); + } + } + #[cfg(all(feature = "deserialize", feature = "glsl-out"))] + { + if targets.contains(Targets::GLSL) { + for ep in module.entry_points.iter() { + if params.glsl_exclude_list.contains(&ep.name) { + continue; + } + write_output_glsl( + input, + module, + &info, + ep.stage, + &ep.name, + ¶ms.glsl, + params.bounds_check_policies, + params.glsl_multiview, + ); + } + } + } + #[cfg(feature = "dot-out")] + { + if targets.contains(Targets::DOT) { + let string = naga::back::dot::write(module, Some(&info), Default::default()).unwrap(); + input.write_output_file("dot", "dot", string); + } + } + #[cfg(all(feature = "deserialize", feature = "hlsl-out"))] + { + if targets.contains(Targets::HLSL) { + write_output_hlsl(input, module, &info, ¶ms.hlsl); + } + } + #[cfg(all(feature = "deserialize", feature = "wgsl-out"))] + { + if targets.contains(Targets::WGSL) { + write_output_wgsl(input, module, &info, ¶ms.wgsl); + } + } +} + +#[cfg(feature = "spv-out")] +fn write_output_spv( + input: &Input, + module: &naga::Module, + info: &naga::valid::ModuleInfo, + debug_info: Option, + params: &SpirvOutParameters, + bounds_check_policies: naga::proc::BoundsCheckPolicies, +) { + use naga::back::spv; + use rspirv::binary::Disassemble; + + let mut flags = spv::WriterFlags::LABEL_VARYINGS; + flags.set(spv::WriterFlags::DEBUG, params.debug); + flags.set( + spv::WriterFlags::ADJUST_COORDINATE_SPACE, + params.adjust_coordinate_space, + ); + flags.set(spv::WriterFlags::FORCE_POINT_SIZE, params.force_point_size); + flags.set(spv::WriterFlags::CLAMP_FRAG_DEPTH, params.clamp_frag_depth); + + let options = spv::Options { + lang_version: (params.version.0, params.version.1), + flags, + capabilities: if params.capabilities.is_empty() { + None + } else { + Some(params.capabilities.clone()) + }, + bounds_check_policies, + binding_map: params.binding_map.clone(), + zero_initialize_workgroup_memory: spv::ZeroInitializeWorkgroupMemoryMode::Polyfill, + debug_info, + }; + + if params.separate_entry_points { + for ep in module.entry_points.iter() { + let pipeline_options = spv::PipelineOptions { + entry_point: ep.name.clone(), + shader_stage: ep.stage, + }; + write_output_spv_inner( + input, + module, + info, + &options, + Some(&pipeline_options), + &format!("{}.spvasm", ep.name), + ); + } + } else { + write_output_spv_inner(input, module, info, &options, None, "spvasm"); + } +} + +#[cfg(feature = "spv-out")] +fn write_output_spv_inner( + input: &Input, + module: &naga::Module, + info: &naga::valid::ModuleInfo, + options: &naga::back::spv::Options<'_>, + pipeline_options: Option<&naga::back::spv::PipelineOptions>, + extension: &str, +) { + use naga::back::spv; + use rspirv::binary::Disassemble; + println!("Generating SPIR-V for {:?}", input.file_name); + let spv = spv::write_vec(module, info, options, pipeline_options).unwrap(); + let dis = rspirv::dr::load_words(spv) + .expect("Produced invalid SPIR-V") + .disassemble(); + // HACK escape CR/LF if source code is in side. + let dis = if options.debug_info.is_some() { + let dis = dis.replace("\\r", "\r"); + dis.replace("\\n", "\n") + } else { + dis + }; + input.write_output_file("spv", extension, dis); +} + +#[cfg(feature = "msl-out")] +fn write_output_msl( + input: &Input, + module: &naga::Module, + info: &naga::valid::ModuleInfo, + options: &naga::back::msl::Options, + pipeline_options: &naga::back::msl::PipelineOptions, + bounds_check_policies: naga::proc::BoundsCheckPolicies, +) { + use naga::back::msl; + + println!("generating MSL"); + + let mut options = options.clone(); + options.bounds_check_policies = bounds_check_policies; + let (string, tr_info) = msl::write_string(module, info, &options, pipeline_options) + .unwrap_or_else(|err| panic!("Metal write failed: {err}")); + + for (ep, result) in module.entry_points.iter().zip(tr_info.entry_point_names) { + if let Err(error) = result { + panic!("Failed to translate '{}': {}", ep.name, error); + } + } + + input.write_output_file("msl", "msl", string); +} + +#[cfg(feature = "glsl-out")] +#[allow(clippy::too_many_arguments)] +fn write_output_glsl( + input: &Input, + module: &naga::Module, + info: &naga::valid::ModuleInfo, + stage: naga::ShaderStage, + ep_name: &str, + options: &naga::back::glsl::Options, + bounds_check_policies: naga::proc::BoundsCheckPolicies, + multiview: Option, +) { + use naga::back::glsl; + + println!("generating GLSL"); + + let pipeline_options = glsl::PipelineOptions { + shader_stage: stage, + entry_point: ep_name.to_string(), + multiview, + }; + + let mut buffer = String::new(); + let mut writer = glsl::Writer::new( + &mut buffer, + module, + info, + options, + &pipeline_options, + bounds_check_policies, + ) + .expect("GLSL init failed"); + writer.write().expect("GLSL write failed"); + + let extension = format!("{ep_name}.{stage:?}.glsl"); + input.write_output_file("glsl", &extension, buffer); +} + +#[cfg(feature = "hlsl-out")] +fn write_output_hlsl( + input: &Input, + module: &naga::Module, + info: &naga::valid::ModuleInfo, + options: &naga::back::hlsl::Options, +) { + use naga::back::hlsl; + use std::fmt::Write as _; + + println!("generating HLSL"); + + let mut buffer = String::new(); + let mut writer = hlsl::Writer::new(&mut buffer, options); + let reflection_info = writer.write(module, info).expect("HLSL write failed"); + + input.write_output_file("hlsl", "hlsl", buffer); + + // We need a config file for validation script + // This file contains an info about profiles (shader stages) contains inside generated shader + // This info will be passed to dxc + let mut config = hlsl_snapshots::Config::empty(); + for (index, ep) in module.entry_points.iter().enumerate() { + let name = match reflection_info.entry_point_names[index] { + Ok(ref name) => name, + Err(_) => continue, + }; + match ep.stage { + naga::ShaderStage::Vertex => &mut config.vertex, + naga::ShaderStage::Fragment => &mut config.fragment, + naga::ShaderStage::Compute => &mut config.compute, + } + .push(hlsl_snapshots::ConfigItem { + entry_point: name.clone(), + target_profile: format!( + "{}_{}", + ep.stage.to_hlsl_str(), + options.shader_model.to_str() + ), + }); + } + + config.to_file(input.output_path("hlsl", "ron")).unwrap(); +} + +#[cfg(feature = "wgsl-out")] +fn write_output_wgsl( + input: &Input, + module: &naga::Module, + info: &naga::valid::ModuleInfo, + params: &WgslOutParameters, +) { + use naga::back::wgsl; + + println!("generating WGSL"); + + let mut flags = wgsl::WriterFlags::empty(); + flags.set(wgsl::WriterFlags::EXPLICIT_TYPES, params.explicit_types); + + let string = wgsl::write_string(module, info, flags).expect("WGSL write failed"); + + input.write_output_file("wgsl", "wgsl", string); +} + +#[cfg(feature = "wgsl-in")] +#[test] +fn convert_wgsl() { + let _ = env_logger::try_init(); + + let inputs = [ + // TODO: merge array-in-ctor and array-in-function-return-type tests after fix HLSL issue https://github.com/gfx-rs/naga/issues/1930 + ( + "array-in-ctor", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "array-in-function-return-type", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::WGSL, + ), + ( + "empty", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "quad", + Targets::SPIRV + | Targets::METAL + | Targets::GLSL + | Targets::DOT + | Targets::HLSL + | Targets::WGSL, + ), + ( + "bits", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "bitcast", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "boids", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "skybox", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "collatz", + Targets::SPIRV + | Targets::METAL + | Targets::IR + | Targets::ANALYSIS + | Targets::HLSL + | Targets::WGSL, + ), + ( + "shadow", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "image", + Targets::SPIRV | Targets::METAL | Targets::HLSL | Targets::WGSL | Targets::GLSL, + ), + ("extra", Targets::SPIRV | Targets::METAL | Targets::WGSL), + ("push-constants", Targets::GLSL | Targets::HLSL), + ( + "operators", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "functions", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "fragment-output", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "dualsource", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ("functions-webgl", Targets::GLSL), + ( + "interpolate", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "access", + Targets::SPIRV + | Targets::METAL + | Targets::GLSL + | Targets::HLSL + | Targets::WGSL + | Targets::IR + | Targets::ANALYSIS, + ), + ( + "atomicOps", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ("atomicCompareExchange", Targets::SPIRV | Targets::WGSL), + ( + "padding", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ("pointers", Targets::SPIRV | Targets::WGSL), + ( + "control-flow", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "standard", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + //TODO: GLSL https://github.com/gfx-rs/naga/issues/874 + ( + "interface", + Targets::SPIRV | Targets::METAL | Targets::HLSL | Targets::WGSL, + ), + ( + "globals", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ("bounds-check-zero", Targets::SPIRV | Targets::METAL), + ("bounds-check-zero-atomic", Targets::METAL), + ("bounds-check-restrict", Targets::SPIRV | Targets::METAL), + ( + "bounds-check-image-restrict", + Targets::SPIRV | Targets::METAL | Targets::GLSL, + ), + ( + "bounds-check-image-rzsw", + Targets::SPIRV | Targets::METAL | Targets::GLSL, + ), + ("policy-mix", Targets::SPIRV | Targets::METAL), + ( + "texture-arg", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ("cubeArrayShadow", Targets::GLSL), + ( + "math-functions", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "binding-arrays", + Targets::WGSL | Targets::HLSL | Targets::METAL | Targets::SPIRV, + ), + ( + "binding-buffer-arrays", + Targets::WGSL | Targets::SPIRV, //TODO: more backends, eventually merge into "binding-arrays" + ), + ("resource-binding-map", Targets::METAL), + ("multiview", Targets::SPIRV | Targets::GLSL | Targets::WGSL), + ("multiview_webgl", Targets::GLSL), + ( + "break-if", + Targets::WGSL | Targets::GLSL | Targets::SPIRV | Targets::HLSL | Targets::METAL, + ), + ("lexical-scopes", Targets::WGSL), + ("type-alias", Targets::WGSL), + ("module-scope", Targets::WGSL), + ( + "workgroup-var-init", + Targets::WGSL | Targets::GLSL | Targets::SPIRV | Targets::HLSL | Targets::METAL, + ), + ( + "workgroup-uniform-load", + Targets::WGSL | Targets::GLSL | Targets::SPIRV | Targets::HLSL | Targets::METAL, + ), + ("runtime-array-in-unused-struct", Targets::SPIRV), + ("sprite", Targets::SPIRV), + ("force_point_size_vertex_shader_webgl", Targets::GLSL), + ("invariant", Targets::GLSL), + ("ray-query", Targets::SPIRV | Targets::METAL), + ("hlsl-keyword", Targets::HLSL), + ( + "constructors", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ("msl-varyings", Targets::METAL), + ( + "const-exprs", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ("separate-entry-points", Targets::SPIRV | Targets::GLSL), + ( + "struct-layout", + Targets::WGSL | Targets::GLSL | Targets::SPIRV | Targets::HLSL | Targets::METAL, + ), + ( + "f64", + Targets::SPIRV | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), + ( + "abstract-types-const", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::WGSL, + ), + ( + "abstract-types-var", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::WGSL, + ), + ( + "abstract-types-operators", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::WGSL, + ), + ]; + + for &(name, targets) in inputs.iter() { + // WGSL shaders lives in root dir as a privileged. + let input = Input::new(None, name, "wgsl"); + let source = input.read_source(); + match naga::front::wgsl::parse_str(&source) { + Ok(mut module) => check_targets(&input, &mut module, targets, None), + Err(e) => panic!( + "{}", + e.emit_to_string_with_path(&source, input.input_path()) + ), + } + } + + { + let inputs = [ + ("debug-symbol-simple", Targets::SPIRV), + ("debug-symbol-terrain", Targets::SPIRV), + ]; + for &(name, targets) in inputs.iter() { + // WGSL shaders lives in root dir as a privileged. + let input = Input::new(None, name, "wgsl"); + let source = input.read_source(); + match naga::front::wgsl::parse_str(&source) { + Ok(mut module) => check_targets(&input, &mut module, targets, Some(&source)), + Err(e) => panic!( + "{}", + e.emit_to_string_with_path(&source, input.input_path()) + ), + } + } + } +} + +#[cfg(feature = "spv-in")] +fn convert_spv(name: &str, adjust_coordinate_space: bool, targets: Targets) { + let _ = env_logger::try_init(); + + let input = Input::new(Some("spv"), name, "spv"); + let mut module = naga::front::spv::parse_u8_slice( + &input.read_bytes(), + &naga::front::spv::Options { + adjust_coordinate_space, + strict_capabilities: false, + block_ctx_dump_prefix: None, + }, + ) + .unwrap(); + check_targets(&input, &mut module, targets, None); +} + +#[cfg(feature = "spv-in")] +#[test] +fn convert_spv_all() { + convert_spv( + "quad-vert", + false, + Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ); + convert_spv("shadow", true, Targets::IR | Targets::ANALYSIS); + convert_spv( + "inv-hyperbolic-trig-functions", + true, + Targets::HLSL | Targets::WGSL, + ); + convert_spv( + "empty-global-name", + true, + Targets::HLSL | Targets::WGSL | Targets::METAL, + ); + convert_spv("degrees", false, Targets::empty()); + convert_spv("binding-arrays.dynamic", true, Targets::WGSL); + convert_spv("binding-arrays.static", true, Targets::WGSL); + convert_spv( + "do-while", + true, + Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ); +} + +#[cfg(feature = "glsl-in")] +#[test] +fn convert_glsl_variations_check() { + let input = Input::new(None, "variations", "glsl"); + let source = input.read_source(); + let mut parser = naga::front::glsl::Frontend::default(); + let mut module = parser + .parse( + &naga::front::glsl::Options { + stage: naga::ShaderStage::Fragment, + defines: Default::default(), + }, + &source, + ) + .unwrap(); + check_targets(&input, &mut module, Targets::GLSL, None); +} + +#[cfg(feature = "glsl-in")] +#[allow(unused_variables)] +#[test] +fn convert_glsl_folder() { + let _ = env_logger::try_init(); + + for input in Input::files_in_dir("glsl") { + let input = Input { + keep_input_extension: true, + ..input + }; + let file_name = &input.file_name; + if file_name.ends_with(".ron") { + // No needed to validate ron files + continue; + } + + let mut parser = naga::front::glsl::Frontend::default(); + let module = parser + .parse( + &naga::front::glsl::Options { + stage: match file_name.extension().and_then(|s| s.to_str()).unwrap() { + "vert" => naga::ShaderStage::Vertex, + "frag" => naga::ShaderStage::Fragment, + "comp" => naga::ShaderStage::Compute, + ext => panic!("Unknown extension for glsl file {ext}"), + }, + defines: Default::default(), + }, + &input.read_source(), + ) + .unwrap(); + + let info = naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::all(), + ) + .validate(&module) + .unwrap(); + + #[cfg(feature = "wgsl-out")] + { + write_output_wgsl(&input, &module, &info, &WgslOutParameters::default()); + } + } +} diff --git a/naga/tests/spirv_capabilities.rs b/naga/tests/spirv_capabilities.rs new file mode 100644 index 0000000000..35f24b7d69 --- /dev/null +++ b/naga/tests/spirv_capabilities.rs @@ -0,0 +1,178 @@ +/*! +Test SPIR-V backend capability checks. +*/ + +#![cfg(all(feature = "wgsl-in", feature = "spv-out"))] + +use spirv::Capability as Ca; + +fn capabilities_used(source: &str) -> naga::FastIndexSet { + use naga::back::spv; + use naga::valid; + + let module = naga::front::wgsl::parse_str(source).unwrap_or_else(|e| { + panic!( + "expected WGSL to parse successfully:\n{}", + e.emit_to_string(source) + ); + }); + + let info = valid::Validator::new(valid::ValidationFlags::all(), valid::Capabilities::all()) + .validate(&module) + .expect("validation failed"); + + let mut words = vec![]; + let mut writer = spv::Writer::new(&spv::Options::default()).unwrap(); + writer + .write(&module, &info, None, &None, &mut words) + .unwrap(); + writer.get_capabilities_used().clone() +} + +fn require(capabilities: &[Ca], source: &str) { + require_and_forbid(capabilities, &[], source); +} + +fn require_and_forbid(required: &[Ca], forbidden: &[Ca], source: &str) { + let caps_used = capabilities_used(source); + + let missing_caps: Vec<_> = required + .iter() + .filter(|&cap| !caps_used.contains(cap)) + .cloned() + .collect(); + if !missing_caps.is_empty() { + panic!("shader code should have requested these caps: {missing_caps:?}\n\n{source}"); + } + + let forbidden_caps: Vec<_> = forbidden + .iter() + .filter(|&cap| caps_used.contains(cap)) + .cloned() + .collect(); + if !forbidden_caps.is_empty() { + panic!("shader code should not have requested these caps: {forbidden_caps:?}\n\n{source}"); + } +} + +#[test] +fn sampler1d() { + require( + &[Ca::Sampled1D], + r#" + @group(0) @binding(0) + var image_1d: texture_1d; + "#, + ); +} + +#[test] +fn storage1d() { + require( + &[Ca::Image1D], + r#" + @group(0) @binding(0) + var image_1d: texture_storage_1d; + "#, + ); +} + +#[test] +fn cube_array() { + // ImageCubeArray is only for storage cube array images, which WGSL doesn't + // support + require_and_forbid( + &[Ca::SampledCubeArray], + &[Ca::ImageCubeArray], + r#" + @group(0) @binding(0) + var image_cube: texture_cube_array; + "#, + ); +} + +#[test] +fn image_queries() { + require( + &[Ca::ImageQuery], + r#" + fn f(i: texture_2d) -> vec2 { + return textureDimensions(i); + } + "#, + ); + require( + &[Ca::ImageQuery], + r#" + fn f(i: texture_2d_array) -> u32 { + return textureNumLayers(i); + } + "#, + ); + require( + &[Ca::ImageQuery], + r#" + fn f(i: texture_2d) -> u32 { + return textureNumLevels(i); + } + "#, + ); + require( + &[Ca::ImageQuery], + r#" + fn f(i: texture_multisampled_2d) -> u32 { + return textureNumSamples(i); + } + "#, + ); +} + +#[test] +fn sample_rate_shading() { + require( + &[Ca::SampleRateShading], + r#" + @fragment + fn f(@location(0) @interpolate(perspective, sample) x: f32) { } + "#, + ); + + require( + &[Ca::SampleRateShading], + r#" + @fragment + fn f(@builtin(sample_index) x: u32) { } + "#, + ); +} + +#[test] +fn geometry() { + require( + &[Ca::Geometry], + r#" + @fragment + fn f(@builtin(primitive_index) x: u32) { } + "#, + ); +} + +#[test] +fn storage_image_formats() { + require_and_forbid( + &[Ca::Shader], + &[Ca::StorageImageExtendedFormats], + r#" + @group(0) @binding(0) + var image_rg32f: texture_storage_2d; + "#, + ); + + require( + &[Ca::StorageImageExtendedFormats], + r#" + @group(0) @binding(0) + var image_rg32f: texture_storage_2d; + "#, + ); +} diff --git a/naga/tests/wgsl_errors.rs b/naga/tests/wgsl_errors.rs new file mode 100644 index 0000000000..2f62491b3f --- /dev/null +++ b/naga/tests/wgsl_errors.rs @@ -0,0 +1,2100 @@ +/*! +Tests for the WGSL front end. +*/ +#![cfg(feature = "wgsl-in")] + +fn check(input: &str, snapshot: &str) { + let output = naga::front::wgsl::parse_str(input) + .expect_err("expected parser error") + .emit_to_string(input); + if output != snapshot { + for diff in diff::lines(snapshot, &output) { + match diff { + diff::Result::Left(l) => println!("-{l}"), + diff::Result::Both(l, _) => println!(" {l}"), + diff::Result::Right(r) => println!("+{r}"), + } + } + panic!("Error snapshot failed"); + } +} + +#[test] +fn very_negative_integers() { + // wgpu#4492 + check( + "const i32min = -0x80000000i;", + r###"error: numeric literal not representable by target type: `0x80000000i` + ┌─ wgsl:1:17 + │ +1 │ const i32min = -0x80000000i; + │ ^^^^^^^^^^^ numeric literal not representable by target type + +"###, + ); +} + +#[test] +fn reserved_identifier_prefix() { + check( + "var __bad;", + r###"error: Identifier starts with a reserved prefix: '__bad' + ┌─ wgsl:1:5 + │ +1 │ var __bad; + │ ^^^^^ invalid identifier + +"###, + ); +} + +#[test] +fn function_without_identifier() { + check( + "fn () {}", + r###"error: expected identifier, found '(' + ┌─ wgsl:1:4 + │ +1 │ fn () {} + │ ^ expected identifier + +"###, + ); +} + +#[test] +fn invalid_integer() { + check( + "fn foo([location(1.)] x: i32) {}", + r###"error: expected identifier, found '[' + ┌─ wgsl:1:8 + │ +1 │ fn foo([location(1.)] x: i32) {} + │ ^ expected identifier + +"###, + ); +} + +#[test] +fn invalid_float() { + check( + "const scale: f32 = 1.1.;", + r###"error: expected identifier, found ';' + ┌─ wgsl:1:24 + │ +1 │ const scale: f32 = 1.1.; + │ ^ expected identifier + +"###, + ); +} + +#[test] +fn invalid_texture_sample_type() { + check( + "const x: texture_2d;", + r###"error: texture sample type must be one of f32, i32 or u32, but found bool + ┌─ wgsl:1:21 + │ +1 │ const x: texture_2d; + │ ^^^^ must be one of f32, i32 or u32 + +"###, + ); +} + +#[test] +fn unknown_identifier() { + check( + r###" + fn f(x: f32) -> f32 { + return x * schmoo; + } + "###, + r###"error: no definition in scope for identifier: 'schmoo' + ┌─ wgsl:3:30 + │ +3 │ return x * schmoo; + │ ^^^^^^ unknown identifier + +"###, + ); +} + +#[test] +fn bad_texture() { + check( + r#" + @group(0) @binding(0) var sampler1 : sampler; + + @fragment + fn main() -> @location(0) vec4 { + let a = 3; + return textureSample(a, sampler1, vec2(0.0)); + } + "#, + r#"error: expected an image, but found 'a' which is not an image + ┌─ wgsl:7:38 + │ +7 │ return textureSample(a, sampler1, vec2(0.0)); + │ ^ not an image + +"#, + ); +} + +#[test] +fn bad_type_cast() { + check( + r#" + fn x() -> i32 { + return i32(vec2(0.0)); + } + "#, + r#"error: cannot cast a vec2 to a i32 + ┌─ wgsl:3:28 + │ +3 │ return i32(vec2(0.0)); + │ ^^^^^^^^^^^^^^ cannot cast a vec2 to a i32 + +"#, + ); +} + +#[test] +fn type_not_constructible() { + check( + r#" + fn x() { + _ = atomic(0); + } + "#, + r#"error: type `atomic` is not constructible + ┌─ wgsl:3:21 + │ +3 │ _ = atomic(0); + │ ^^^^^^ type is not constructible + +"#, + ); +} + +#[test] +fn type_not_inferrable() { + check( + r#" + fn x() { + _ = vec2(); + } + "#, + r#"error: type can't be inferred + ┌─ wgsl:3:21 + │ +3 │ _ = vec2(); + │ ^^^^ type can't be inferred + +"#, + ); +} + +#[test] +fn unexpected_constructor_parameters() { + check( + r#" + fn x() { + _ = i32(0, 1); + } + "#, + r#"error: unexpected components + ┌─ wgsl:3:28 + │ +3 │ _ = i32(0, 1); + │ ^ unexpected components + +"#, + ); +} + +#[test] +fn constructor_parameter_type_mismatch() { + check( + r#" + fn x() { + _ = mat2x2(array(0, 1), vec2(2, 3)); + } + "#, + r#"error: automatic conversions cannot convert `array<{AbstractInt}, 2>` to `vec2` + ┌─ wgsl:3:21 + │ +3 │ _ = mat2x2(array(0, 1), vec2(2, 3)); + │ ^^^^^^^^^^^ ^^^^^^^^^^^ this expression has type array<{AbstractInt}, 2> + │ │ + │ a value of type vec2 is required here + +"#, + ); +} + +#[test] +fn bad_texture_sample_type() { + check( + r#" + @group(0) @binding(0) var sampler1 : sampler; + @group(0) @binding(1) var texture : texture_2d; + + @fragment + fn main() -> @location(0) vec4 { + return textureSample(texture, sampler1, vec2(0.0)); + } + "#, + r#"error: texture sample type must be one of f32, i32 or u32, but found bool + ┌─ wgsl:3:60 + │ +3 │ @group(0) @binding(1) var texture : texture_2d; + │ ^^^^ must be one of f32, i32 or u32 + +"#, + ); +} + +#[test] +fn bad_for_initializer() { + check( + r#" + fn x() { + for ({};;) {} + } + "#, + r#"error: for(;;) initializer is not an assignment or a function call: '{}' + ┌─ wgsl:3:22 + │ +3 │ for ({};;) {} + │ ^^ not an assignment or function call + +"#, + ); +} + +#[test] +fn unknown_storage_class() { + check( + r#" + @group(0) @binding(0) var texture: texture_2d; + "#, + r#"error: unknown address space: 'bad' + ┌─ wgsl:2:39 + │ +2 │ @group(0) @binding(0) var texture: texture_2d; + │ ^^^ unknown address space + +"#, + ); +} + +#[test] +fn unknown_attribute() { + check( + r#" + @a + fn x() {} + "#, + r#"error: unknown attribute: 'a' + ┌─ wgsl:2:14 + │ +2 │ @a + │ ^ unknown attribute + +"#, + ); +} + +#[test] +fn unknown_built_in() { + check( + r#" + fn x(@builtin(unknown_built_in) y: u32) {} + "#, + r#"error: unknown builtin: 'unknown_built_in' + ┌─ wgsl:2:27 + │ +2 │ fn x(@builtin(unknown_built_in) y: u32) {} + │ ^^^^^^^^^^^^^^^^ unknown builtin + +"#, + ); +} + +#[test] +fn unknown_access() { + check( + r#" + var x: array; + "#, + r#"error: unknown access: 'unknown_access' + ┌─ wgsl:2:25 + │ +2 │ var x: array; + │ ^^^^^^^^^^^^^^ unknown access + +"#, + ); +} + +#[test] +fn unknown_ident() { + check( + r#" + fn main() { + let a = b; + } + "#, + r#"error: no definition in scope for identifier: 'b' + ┌─ wgsl:3:25 + │ +3 │ let a = b; + │ ^ unknown identifier + +"#, + ); +} + +#[test] +fn unknown_scalar_type() { + check( + r#" + const a: vec2; + "#, + r#"error: unknown scalar type: 'something' + ┌─ wgsl:2:27 + │ +2 │ const a: vec2; + │ ^^^^^^^^^ unknown scalar type + │ + = note: Valid scalar types are f32, f64, i32, u32, bool + +"#, + ); +} + +#[test] +fn unknown_type() { + check( + r#" + const a: Vec = 10; + "#, + r#"error: unknown type: 'Vec' + ┌─ wgsl:2:22 + │ +2 │ const a: Vec = 10; + │ ^^^ unknown type + +"#, + ); +} + +#[test] +fn unknown_storage_format() { + check( + r#" + const storage1: texture_storage_1d; + "#, + r#"error: unknown storage format: 'rgba' + ┌─ wgsl:2:48 + │ +2 │ const storage1: texture_storage_1d; + │ ^^^^ unknown storage format + +"#, + ); +} + +#[test] +fn unknown_conservative_depth() { + check( + r#" + @early_depth_test(abc) fn main() {} + "#, + r#"error: unknown conservative depth: 'abc' + ┌─ wgsl:2:31 + │ +2 │ @early_depth_test(abc) fn main() {} + │ ^^^ unknown conservative depth + +"#, + ); +} + +#[test] +fn struct_member_size_too_low() { + check( + r#" + struct Bar { + @size(0) data: array + } + "#, + r#"error: struct member size must be at least 4 + ┌─ wgsl:3:23 + │ +3 │ @size(0) data: array + │ ^ must be at least 4 + +"#, + ); +} + +#[test] +fn struct_member_align_too_low() { + check( + r#" + struct Bar { + @align(8) data: vec3 + } + "#, + r#"error: struct member alignment must be at least 16 + ┌─ wgsl:3:24 + │ +3 │ @align(8) data: vec3 + │ ^ must be at least 16 + +"#, + ); +} + +#[test] +fn struct_member_non_po2_align() { + check( + r#" + struct Bar { + @align(7) data: array + } + "#, + r#"error: struct member alignment must be a power of 2 + ┌─ wgsl:3:24 + │ +3 │ @align(7) data: array + │ ^ must be a power of 2 + +"#, + ); +} + +#[test] +fn inconsistent_binding() { + check( + r#" + fn foo(@builtin(vertex_index) @location(0) x: u32) {} + "#, + r#"error: input/output binding is not consistent + ┌─ wgsl:2:16 + │ +2 │ fn foo(@builtin(vertex_index) @location(0) x: u32) {} + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ input/output binding is not consistent + +"#, + ); +} + +#[test] +fn unknown_local_function() { + check( + r#" + fn x() { + for (a();;) {} + } + "#, + r#"error: no definition in scope for identifier: 'a' + ┌─ wgsl:3:22 + │ +3 │ for (a();;) {} + │ ^ unknown identifier + +"#, + ); +} + +#[test] +fn let_type_mismatch() { + check( + r#" + const x: i32 = 1.0; + "#, + r#"error: the type of `x` is expected to be `i32`, but got `{AbstractFloat}` + ┌─ wgsl:2:19 + │ +2 │ const x: i32 = 1.0; + │ ^ definition of `x` + +"#, + ); + + check( + r#" + fn foo() { + let x: f32 = true; + } + "#, + r#"error: the type of `x` is expected to be `f32`, but got `bool` + ┌─ wgsl:3:21 + │ +3 │ let x: f32 = true; + │ ^ definition of `x` + +"#, + ); +} + +#[test] +fn var_type_mismatch() { + check( + r#" + fn foo() { + var x: f32 = 1u; + } + "#, + r#"error: the type of `x` is expected to be `f32`, but got `u32` + ┌─ wgsl:3:21 + │ +3 │ var x: f32 = 1u; + │ ^ definition of `x` + +"#, + ); +} + +#[test] +fn local_var_missing_type() { + check( + r#" + fn foo() { + var x; + } + "#, + r#"error: variable `x` needs a type + ┌─ wgsl:3:21 + │ +3 │ var x; + │ ^ definition of `x` + +"#, + ); +} + +#[test] +fn postfix_pointers() { + check( + r#" + fn main() { + var v: vec4 = vec4(1.0, 1.0, 1.0, 1.0); + let pv = &v; + let a = *pv[3]; // Problematic line + } + "#, + r#"error: the value indexed by a `[]` subscripting expression must not be a pointer + ┌─ wgsl:5:26 + │ +5 │ let a = *pv[3]; // Problematic line + │ ^^ expression is a pointer + +"#, + ); + + check( + r#" + struct S { m: i32 }; + fn main() { + var s: S = S(42); + let ps = &s; + let a = *ps.m; // Problematic line + } + "#, + r#"error: the value accessed by a `.member` expression must not be a pointer + ┌─ wgsl:6:26 + │ +6 │ let a = *ps.m; // Problematic line + │ ^^ expression is a pointer + +"#, + ); +} + +#[test] +fn reserved_keyword() { + // global var + check( + r#" + var bool: bool = true; + "#, + r###"error: name `bool` is a reserved keyword + ┌─ wgsl:2:17 + │ +2 │ var bool: bool = true; + │ ^^^^ definition of `bool` + +"###, + ); + + // global constant + check( + r#" + const break: bool = true; + fn foo() { + var foo = break; + } + "#, + r###"error: name `break` is a reserved keyword + ┌─ wgsl:2:19 + │ +2 │ const break: bool = true; + │ ^^^^^ definition of `break` + +"###, + ); + + // local let + check( + r#" + fn foo() { + let atomic: f32 = 1.0; + } + "#, + r###"error: name `atomic` is a reserved keyword + ┌─ wgsl:3:21 + │ +3 │ let atomic: f32 = 1.0; + │ ^^^^^^ definition of `atomic` + +"###, + ); + + // local var + check( + r#" + fn foo() { + var sampler: f32 = 1.0; + } + "#, + r###"error: name `sampler` is a reserved keyword + ┌─ wgsl:3:21 + │ +3 │ var sampler: f32 = 1.0; + │ ^^^^^^^ definition of `sampler` + +"###, + ); + + // fn name + check( + r#" + fn break() {} + "#, + r###"error: name `break` is a reserved keyword + ┌─ wgsl:2:16 + │ +2 │ fn break() {} + │ ^^^^^ definition of `break` + +"###, + ); + + // struct + check( + r#" + struct array {} + "#, + r###"error: name `array` is a reserved keyword + ┌─ wgsl:2:20 + │ +2 │ struct array {} + │ ^^^^^ definition of `array` + +"###, + ); + + // struct member + check( + r#" + struct Foo { sampler: f32 } + "#, + r###"error: name `sampler` is a reserved keyword + ┌─ wgsl:2:26 + │ +2 │ struct Foo { sampler: f32 } + │ ^^^^^^^ definition of `sampler` + +"###, + ); +} + +#[test] +fn module_scope_identifier_redefinition() { + // const + check( + r#" + const foo: bool = true; + const foo: bool = true; + "#, + r###"error: redefinition of `foo` + ┌─ wgsl:2:19 + │ +2 │ const foo: bool = true; + │ ^^^ previous definition of `foo` +3 │ const foo: bool = true; + │ ^^^ redefinition of `foo` + +"###, + ); + // var + check( + r#" + var foo: bool = true; + var foo: bool = true; + "#, + r###"error: redefinition of `foo` + ┌─ wgsl:2:17 + │ +2 │ var foo: bool = true; + │ ^^^ previous definition of `foo` +3 │ var foo: bool = true; + │ ^^^ redefinition of `foo` + +"###, + ); + + // let and var + check( + r#" + var foo: bool = true; + const foo: bool = true; + "#, + r###"error: redefinition of `foo` + ┌─ wgsl:2:17 + │ +2 │ var foo: bool = true; + │ ^^^ previous definition of `foo` +3 │ const foo: bool = true; + │ ^^^ redefinition of `foo` + +"###, + ); + + // function + check( + r#"fn foo() {} + fn bar() {} + fn foo() {}"#, + r###"error: redefinition of `foo` + ┌─ wgsl:1:4 + │ +1 │ fn foo() {} + │ ^^^ previous definition of `foo` +2 │ fn bar() {} +3 │ fn foo() {} + │ ^^^ redefinition of `foo` + +"###, + ); + + // let and function + check( + r#" + const foo: bool = true; + fn foo() {} + "#, + r###"error: redefinition of `foo` + ┌─ wgsl:2:19 + │ +2 │ const foo: bool = true; + │ ^^^ previous definition of `foo` +3 │ fn foo() {} + │ ^^^ redefinition of `foo` + +"###, + ); +} + +#[test] +fn matrix_with_bad_type() { + check( + r#" + fn main() { + let m = mat2x2(); + } + "#, + r#"error: matrix scalar type must be floating-point, but found `i32` + ┌─ wgsl:3:32 + │ +3 │ let m = mat2x2(); + │ ^^^ must be floating-point (e.g. `f32`) + +"#, + ); + + check( + r#" + fn main() { + let m: mat3x3; + } + "#, + r#"error: matrix scalar type must be floating-point, but found `i32` + ┌─ wgsl:3:31 + │ +3 │ let m: mat3x3; + │ ^^^ must be floating-point (e.g. `f32`) + +"#, + ); +} + +#[test] +fn matrix_constructor_inferred() { + check( + r#" + const m: mat2x2 = mat2x2(vec2(0), vec2(1)); + "#, + r#"error: the type of `m` is expected to be `mat2x2`, but got `mat2x2` + ┌─ wgsl:2:19 + │ +2 │ const m: mat2x2 = mat2x2(vec2(0), vec2(1)); + │ ^ definition of `m` + +"#, + ); +} + +/// Check the result of validating a WGSL program against a pattern. +/// +/// Unless you are generating code programmatically, the +/// `check_validation_error` macro will probably be more convenient to +/// use. +macro_rules! check_one_validation { + ( $source:expr, $pattern:pat $( if $guard:expr )? ) => { + let source = $source; + let error = validation_error($source); + if ! matches!(&error, $pattern $( if $guard )? ) { + eprintln!("validation error does not match pattern:\n\ + source code: {}\n\ + \n\ + actual result:\n\ + {:#?}\n\ + \n\ + expected match for pattern:\n\ + {}", + &source, + error, + stringify!($pattern)); + $( eprintln!("if {}", stringify!($guard)); )? + panic!("validation error does not match pattern"); + } + } +} + +macro_rules! check_validation { + // We want to support an optional guard expression after the pattern, so + // that we can check values we can't match against, like strings. + // Unfortunately, we can't simply include `$( if $guard:expr )?` in the + // pattern, because Rust treats `?` as a repetition operator, and its count + // (0 or 1) will not necessarily match `$source`. + ( $( $source:literal ),* : $pattern:pat ) => { + $( + check_one_validation!($source, $pattern); + )* + }; + ( $( $source:literal ),* : $pattern:pat if $guard:expr ) => { + $( + check_one_validation!($source, $pattern if $guard); + )* + } +} + +fn validation_error(source: &str) -> Result { + let module = match naga::front::wgsl::parse_str(source) { + Ok(module) => module, + Err(err) => { + eprintln!("WGSL parse failed:"); + panic!("{}", err.emit_to_string(source)); + } + }; + naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::default(), + ) + .validate(&module) + .map_err(|e| e.into_inner()) // TODO: Add tests for spans, too? +} + +#[test] +fn invalid_arrays() { + check_validation! { + "alias Bad = array, 4>;", + "alias Bad = array;", + "alias Bad = array, 4>;": + Err(naga::valid::ValidationError::Type { + source: naga::valid::TypeError::InvalidArrayBaseType(_), + .. + }) + } + + check_validation! { + r#" + fn main() -> f32 { + let a = array(0., 1., 2.); + return a[-1]; + } + "#: + Err( + naga::valid::ValidationError::Function { + name, + source: naga::valid::FunctionError::Expression { + source: naga::valid::ExpressionError::NegativeIndex(_), + .. + }, + .. + } + ) + if name == "main" + } + + check( + "alias Bad = array;", + r###"error: must be a const-expression that resolves to a concrete integer scalar (u32 or i32) + ┌─ wgsl:1:24 + │ +1 │ alias Bad = array; + │ ^^^^ must resolve to u32 or i32 + +"###, + ); + + check( + r#" + const length: f32 = 2.718; + alias Bad = array; + "#, + r###"error: must be a const-expression that resolves to a concrete integer scalar (u32 or i32) + ┌─ wgsl:3:36 + │ +3 │ alias Bad = array; + │ ^^^^^^ must resolve to u32 or i32 + +"###, + ); + + check( + "alias Bad = array;", + r###"error: array element count must be positive (> 0) + ┌─ wgsl:1:24 + │ +1 │ alias Bad = array; + │ ^ must be positive + +"###, + ); + + check( + "alias Bad = array;", + r###"error: array element count must be positive (> 0) + ┌─ wgsl:1:24 + │ +1 │ alias Bad = array; + │ ^^ must be positive + +"###, + ); +} + +#[test] +fn discard_in_wrong_stage() { + check_validation! { + "@compute @workgroup_size(1) +fn main(@builtin(global_invocation_id) global_id: vec3) { + if global_id.x == 3u { + discard; + } +}": + Err(naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Compute, + source: naga::valid::EntryPointError::ForbiddenStageOperations, + .. + }) + } + + check_validation! { + "@vertex +fn main() -> @builtin(position) vec4 { + if true { + discard; + } + return vec4(); +}": + Err(naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Vertex, + source: naga::valid::EntryPointError::ForbiddenStageOperations, + .. + }) + } +} + +#[test] +fn invalid_structs() { + check_validation! { + "struct Bad { data: sampler }", + "struct Bad { data: texture_2d }": + Err(naga::valid::ValidationError::Type { + source: naga::valid::TypeError::InvalidData(_), + .. + }) + } + + check_validation! { + "struct Bad { data: array, other: f32, }": + Err(naga::valid::ValidationError::Type { + source: naga::valid::TypeError::InvalidDynamicArray(_, _), + .. + }) + } + + check_validation! { + "struct Empty {}": + Err(naga::valid::ValidationError::Type { + source: naga::valid::TypeError::EmptyStruct, + .. + }) + } +} + +#[test] +fn invalid_functions() { + check_validation! { + "fn unacceptable_unsized(arg: array) { }", + " + struct Unsized { data: array } + fn unacceptable_unsized(arg: Unsized) { } + ": + Err(naga::valid::ValidationError::Function { + name: function_name, + source: naga::valid::FunctionError::InvalidArgumentType { + index: 0, + name: argument_name, + }, + .. + }) + if function_name == "unacceptable_unsized" && argument_name == "arg" + } + + // Pointer's address space cannot hold unsized data. + check_validation! { + "fn unacceptable_unsized(arg: ptr>) { }", + " + struct Unsized { data: array } + fn unacceptable_unsized(arg: ptr) { } + ": + Err(naga::valid::ValidationError::Type { + source: naga::valid::TypeError::InvalidPointerToUnsized { + base: _, + space: naga::AddressSpace::WorkGroup { .. }, + }, + .. + }) + } + + // Pointers of these address spaces cannot be passed as arguments. + check_validation! { + "fn unacceptable_ptr_space(arg: ptr>) { }": + Err(naga::valid::ValidationError::Function { + name: function_name, + source: naga::valid::FunctionError::InvalidArgumentPointerSpace { + index: 0, + name: argument_name, + space: naga::AddressSpace::Storage { .. }, + }, + .. + }) + if function_name == "unacceptable_ptr_space" && argument_name == "arg" + } + check_validation! { + "fn unacceptable_ptr_space(arg: ptr) { }": + Err(naga::valid::ValidationError::Function { + name: function_name, + source: naga::valid::FunctionError::InvalidArgumentPointerSpace { + index: 0, + name: argument_name, + space: naga::AddressSpace::Uniform, + }, + .. + }) + if function_name == "unacceptable_ptr_space" && argument_name == "arg" + } + check_validation! { + "fn unacceptable_ptr_space(arg: ptr) { }": + Err(naga::valid::ValidationError::Function { + name: function_name, + source: naga::valid::FunctionError::InvalidArgumentPointerSpace { + index: 0, + name: argument_name, + space: naga::AddressSpace::WorkGroup, + }, + .. + }) + if function_name == "unacceptable_ptr_space" && argument_name == "arg" + } + + check_validation! { + " + struct AFloat { + said_float: f32 + }; + @group(0) @binding(0) + var float: AFloat; + + fn return_pointer() -> ptr { + return &float.said_float; + } + ": + Err(naga::valid::ValidationError::Function { + name: function_name, + source: naga::valid::FunctionError::NonConstructibleReturnType, + .. + }) + if function_name == "return_pointer" + } + + check_validation! { + " + @group(0) @binding(0) + var atom: atomic; + + fn return_atomic() -> atomic { + return atom; + } + ": + Err(naga::valid::ValidationError::Function { + name: function_name, + source: naga::valid::FunctionError::NonConstructibleReturnType, + .. + }) + if function_name == "return_atomic" + } +} + +#[test] +fn pointer_type_equivalence() { + check_validation! { + r#" + fn f(pv: ptr>, pf: ptr) { } + + fn g() { + var m: mat2x2; + let pv: ptr> = &m.x; + let pf: ptr = &m.x.x; + + f(pv, pf); + } + "#: + Ok(_) + } +} + +#[test] +fn missing_bindings() { + check_validation! { + " + @fragment + fn fragment(_input: vec4) -> @location(0) vec4 { + return _input; + } + ": + Err(naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Fragment, + source: naga::valid::EntryPointError::Argument( + 0, + naga::valid::VaryingError::MissingBinding, + ), + .. + }) + } + + check_validation! { + " + @fragment + fn fragment(@location(0) _input: vec4, more_input: f32) -> @location(0) vec4 { + return _input + more_input; + } + ": + Err(naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Fragment, + source: naga::valid::EntryPointError::Argument( + 1, + naga::valid::VaryingError::MissingBinding, + ), + .. + }) + } + + check_validation! { + " + @fragment + fn fragment(@location(0) _input: vec4) -> vec4 { + return _input; + } + ": + Err(naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Fragment, + source: naga::valid::EntryPointError::Result( + naga::valid::VaryingError::MissingBinding, + ), + .. + }) + } + + check_validation! { + " + struct FragmentIn { + @location(0) pos: vec4, + uv: vec2 + } + + @fragment + fn fragment(_input: FragmentIn) -> @location(0) vec4 { + return _input.pos; + } + ": + Err(naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Fragment, + source: naga::valid::EntryPointError::Argument( + 0, + naga::valid::VaryingError::MemberMissingBinding(1), + ), + .. + }) + } +} + +#[test] +fn missing_bindings2() { + check_validation! { + " + @vertex + fn vertex() {} + ": + Err(naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Vertex, + source: naga::valid::EntryPointError::MissingVertexOutputPosition, + .. + }) + } + + check_validation! { + " + struct VertexOut { + @location(0) a: vec4, + } + + @vertex + fn vertex() -> VertexOut { + return VertexOut(vec4()); + } + ": + Err(naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Vertex, + source: naga::valid::EntryPointError::MissingVertexOutputPosition, + .. + }) + } +} + +#[test] +fn invalid_access() { + check_validation! { + " + fn array_by_value(a: array, i: i32) -> i32 { + return a[i]; + } + ", + " + fn matrix_by_value(m: mat4x4, i: i32) -> vec4 { + return m[i]; + } + ": + Err(naga::valid::ValidationError::Function { + source: naga::valid::FunctionError::Expression { + source: naga::valid::ExpressionError::IndexMustBeConstant(_), + .. + }, + .. + }) + } + + check_validation! { + r#" + fn main() -> f32 { + let a = array(0., 1., 2.); + return a[3]; + } + "#: + Err(naga::valid::ValidationError::Function { + source: naga::valid::FunctionError::Expression { + source: naga::valid::ExpressionError::IndexOutOfBounds(_, _), + .. + }, + .. + }) + } +} + +#[test] +fn valid_access() { + check_validation! { + " + fn vector_by_value(v: vec4, i: i32) -> i32 { + return v[i]; + } + ", + " + fn matrix_dynamic(m: mat4x4, i: i32, j: i32) -> f32 { + var temp: mat4x4 = m; + // Dynamically indexing the column vector applies + // `Access` to a `ValuePointer`. + return temp[i][j]; + } + ", + " + fn main() { + var v: vec4 = vec4(1.0, 1.0, 1.0, 1.0); + let pv = &v; + let a = (*pv)[3]; + } + ": + Ok(_) + } +} + +#[test] +fn invalid_local_vars() { + check_validation! { + " + struct Unsized { data: array } + fn local_ptr_dynamic_array(okay: ptr) { + var not_okay: ptr> = &(*okay).data; + } + ": + Err(naga::valid::ValidationError::Function { + source: naga::valid::FunctionError::LocalVariable { + name: local_var_name, + source: naga::valid::LocalVariableError::InvalidType(_), + .. + }, + .. + }) + if local_var_name == "not_okay" + } + + check_validation! { + " + fn f() { + var x: atomic; + } + ": + Err(naga::valid::ValidationError::Function { + source: naga::valid::FunctionError::LocalVariable { + name: local_var_name, + source: naga::valid::LocalVariableError::InvalidType(_), + .. + }, + .. + }) + if local_var_name == "x" + } +} + +#[test] +fn dead_code() { + check_validation! { + " + fn dead_code_after_if(condition: bool) -> i32 { + if (condition) { + return 1; + } else { + return 2; + } + return 3; + } + ": + Ok(_) + } + check_validation! { + " + fn dead_code_after_block() -> i32 { + { + return 1; + } + return 2; + } + ": + Err(naga::valid::ValidationError::Function { + source: naga::valid::FunctionError::InstructionsAfterReturn, + .. + }) + } +} + +#[test] +fn invalid_runtime_sized_arrays() { + // You can't have structs whose last member is an unsized struct. An unsized + // array may only appear as the last member of a struct used directly as a + // variable's store type. + check_validation! { + " + struct Unsized { + arr: array + } + + struct Outer { + legit: i32, + _unsized: Unsized + } + + @group(0) @binding(0) var outer: Outer; + + fn fetch(i: i32) -> f32 { + return outer._unsized.arr[i]; + } + ": + Err(naga::valid::ValidationError::Type { + name: struct_name, + source: naga::valid::TypeError::InvalidDynamicArray(member_name, _), + .. + }) + if struct_name == "Outer" && member_name == "_unsized" + } +} + +#[test] +fn select() { + check_validation! { + " + fn select_pointers(which: bool) -> i32 { + var x: i32 = 1; + var y: i32 = 2; + let p = select(&x, &y, which); + return *p; + } + ", + " + fn select_arrays(which: bool) -> i32 { + var x: array; + var y: array; + let s = select(x, y, which); + return s[0]; + } + ", + " + struct S { member: i32 } + fn select_structs(which: bool) -> S { + var x: S = S(1); + var y: S = S(2); + let s = select(x, y, which); + return s; + } + ": + Err( + naga::valid::ValidationError::Function { + name, + source: naga::valid::FunctionError::Expression { + source: naga::valid::ExpressionError::InvalidSelectTypes, + .. + }, + .. + }, + ) + if name.starts_with("select_") + } +} + +#[test] +fn missing_default_case() { + check_validation! { + " + fn test_missing_default_case() { + switch(0) { + case 0: {} + } + } + ": + Err( + naga::valid::ValidationError::Function { + source: naga::valid::FunctionError::MissingDefaultCase, + .. + }, + ) + } +} + +#[test] +fn wrong_access_mode() { + // The assignments to `global.i` should be forbidden, because they are in + // variables whose access mode is `read`, not `read_write`. + check_validation! { + " + struct Globals { + i: i32 + } + + @group(0) @binding(0) + var globals: Globals; + + fn store(v: i32) { + globals.i = v; + } + ", + " + struct Globals { + i: i32 + } + + @group(0) @binding(0) + var globals: Globals; + + fn store(v: i32) { + globals.i = v; + } + ": + Err( + naga::valid::ValidationError::Function { + name, + source: naga::valid::FunctionError::InvalidStorePointer(_), + .. + }, + ) + if name == "store" + } +} + +#[test] +fn io_shareable_types() { + for numeric in "i32 u32 f32".split_whitespace() { + let types = format!("{numeric} vec2<{numeric}> vec3<{numeric}> vec4<{numeric}>"); + for ty in types.split_whitespace() { + check_one_validation! { + &format!("@vertex + fn f(@location(0) arg: {ty}) -> @builtin(position) vec4 + {{ return vec4(0.0); }}"), + Ok(_module) + } + } + } + + for ty in "bool + vec2 vec3 vec4 + array + mat2x2 + ptr" + .split_whitespace() + { + check_one_validation! { + &format!("@vertex + fn f(@location(0) arg: {ty}) -> @builtin(position) vec4 + {{ return vec4(0.0); }}"), + Err( + naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Vertex, + name, + source: naga::valid::EntryPointError::Argument( + 0, + naga::valid::VaryingError::NotIOShareableType( + _, + ), + ), + }, + ) + if name == "f" + } + } +} + +#[test] +fn host_shareable_types() { + // Host-shareable, constructible types. + let types = "i32 u32 f32 + vec2 vec3 vec4 + mat4x4 + array,4> + AStruct"; + for ty in types.split_whitespace() { + check_one_validation! { + &format!("struct AStruct {{ member: array, 8> }}; + @group(0) @binding(0) var ubuf: {ty}; + @group(0) @binding(1) var sbuf: {ty};"), + Ok(_module) + } + } + + // Host-shareable but not constructible types. + let types = "atomic atomic + array,4> + array + AStruct"; + for ty in types.split_whitespace() { + check_one_validation! { + &format!("struct AStruct {{ member: array, 8> }}; + @group(0) @binding(1) var sbuf: {ty};"), + Ok(_module) + } + } + + // Types that are neither host-shareable nor constructible. + for ty in "bool ptr".split_whitespace() { + check_one_validation! { + &format!("@group(0) @binding(0) var sbuf: {ty};"), + Err( + naga::valid::ValidationError::GlobalVariable { + name, + handle: _, + source: naga::valid::GlobalVariableError::MissingTypeFlags { .. }, + }, + ) + if name == "sbuf" + } + + check_one_validation! { + &format!("@group(0) @binding(0) var ubuf: {ty};"), + Err(naga::valid::ValidationError::GlobalVariable { + name, + handle: _, + source: naga::valid::GlobalVariableError::MissingTypeFlags { .. }, + }, + ) + if name == "ubuf" + } + } +} + +#[test] +fn var_init() { + check_validation! { + " + var initialized: u32 = 0u; + ": + Err( + naga::valid::ValidationError::GlobalVariable { + source: naga::valid::GlobalVariableError::InitializerNotAllowed(naga::AddressSpace::WorkGroup), + .. + }, + ) + } +} + +#[test] +fn misplaced_break_if() { + check( + " + fn test_misplaced_break_if() { + loop { + break if true; + } + } + ", + r###"error: A break if is only allowed in a continuing block + ┌─ wgsl:4:17 + │ +4 │ break if true; + │ ^^^^^^^^ not in a continuing block + +"###, + ); +} + +#[test] +fn break_if_bad_condition() { + check_validation! { + " + fn test_break_if_bad_condition() { + loop { + continuing { + break if 1; + } + } + } + ": + Err( + naga::valid::ValidationError::Function { + source: naga::valid::FunctionError::InvalidIfType(_), + .. + }, + ) + } +} + +#[test] +fn swizzle_assignment() { + check( + " + fn f() { + var v = vec2(0); + v.xy = vec2(1); + } + ", + r###"error: invalid left-hand side of assignment + ┌─ wgsl:4:13 + │ +4 │ v.xy = vec2(1); + │ ^^^^ cannot assign to this expression + │ + = note: WGSL does not support assignments to swizzles + = note: consider assigning each component individually + +"###, + ); +} + +#[test] +fn binary_statement() { + check( + " + fn f() { + 3 + 5; + } + ", + r###"error: expected assignment or increment/decrement, found ';' + ┌─ wgsl:3:18 + │ +3 │ 3 + 5; + │ ^ expected assignment or increment/decrement + +"###, + ); +} + +#[test] +fn assign_to_expr() { + check( + " + fn f() { + 3 + 5 = 10; + } + ", + r###"error: invalid left-hand side of assignment + ┌─ wgsl:3:13 + │ +3 │ 3 + 5 = 10; + │ ^^^^^ cannot assign to this expression + +"###, + ); +} + +#[test] +fn assign_to_let() { + check( + " + fn f() { + let a = 10; + a = 20; + } + ", + r###"error: invalid left-hand side of assignment + ┌─ wgsl:3:17 + │ +3 │ let a = 10; + │ ^ this is an immutable binding +4 │ a = 20; + │ ^ cannot assign to this expression + │ + = note: consider declaring 'a' with `var` instead of `let` + +"###, + ); + + check( + " + fn f() { + let a = array(1, 2); + a[0] = 1; + } + ", + r###"error: invalid left-hand side of assignment + ┌─ wgsl:3:17 + │ +3 │ let a = array(1, 2); + │ ^ this is an immutable binding +4 │ a[0] = 1; + │ ^^^^ cannot assign to this expression + │ + = note: consider declaring 'a' with `var` instead of `let` + +"###, + ); + + check( + " + struct S { a: i32 } + + fn f() { + let a = S(10); + a.a = 20; + } + ", + r###"error: invalid left-hand side of assignment + ┌─ wgsl:5:17 + │ +5 │ let a = S(10); + │ ^ this is an immutable binding +6 │ a.a = 20; + │ ^^^ cannot assign to this expression + │ + = note: consider declaring 'a' with `var` instead of `let` + +"###, + ); +} + +#[test] +fn recursive_function() { + check( + " + fn f() { + f(); + } + ", + r###"error: declaration of `f` is recursive + ┌─ wgsl:2:12 + │ +2 │ fn f() { + │ ^ +3 │ f(); + │ ^ uses itself here + +"###, + ); +} + +#[test] +fn cyclic_function() { + check( + " + fn f() { + g(); + } + fn g() { + f(); + } + ", + r###"error: declaration of `f` is cyclic + ┌─ wgsl:2:12 + │ +2 │ fn f() { + │ ^ +3 │ g(); + │ ^ uses `g` +4 │ } +5 │ fn g() { + │ ^ +6 │ f(); + │ ^ ending the cycle + +"###, + ); +} + +#[test] +fn switch_signed_unsigned_mismatch() { + check( + " + fn x(y: u32) { + switch y { + case 1: {} + } + } + ", + r###"error: invalid switch value + ┌─ wgsl:4:16 + │ +4 │ case 1: {} + │ ^ expected unsigned integer + │ + = note: suffix the integer with a `u`: '1u' + +"###, + ); + + check( + " + fn x(y: i32) { + switch y { + case 1u: {} + } + } + ", + r###"error: invalid switch value + ┌─ wgsl:4:16 + │ +4 │ case 1u: {} + │ ^^ expected signed integer + │ + = note: remove the `u` suffix: '1' + +"###, + ); +} + +#[test] +fn function_returns_void() { + check( + " + fn x() { + let a = vec2(1.0, 2.0); + } + + fn b() { + let a = x(); + } + ", + r###"error: function does not return any value + ┌─ wgsl:7:18 + │ +7 │ let a = x(); + │ ^ + │ + = note: perhaps you meant to call the function in a separate statement? + +"###, + ) +} + +#[test] +fn function_param_redefinition_as_param() { + check( + " + fn x(a: f32, a: vec2) {} + ", + r###"error: redefinition of `a` + ┌─ wgsl:2:14 + │ +2 │ fn x(a: f32, a: vec2) {} + │ ^ ^ redefinition of `a` + │ │ + │ previous definition of `a` + +"###, + ) +} + +#[test] +fn function_param_redefinition_as_local() { + check( + " + fn x(a: f32) { + let a = 0.0; + } + ", + r###"error: redefinition of `a` + ┌─ wgsl:2:14 + │ +2 │ fn x(a: f32) { + │ ^ previous definition of `a` +3 │ let a = 0.0; + │ ^ redefinition of `a` + +"###, + ) +} + +#[test] +fn constructor_type_error_span() { + check( + " + fn unfortunate() { + var i: i32; + var a: array = array(i); + } + ", + r###"error: automatic conversions cannot convert `i32` to `f32` + ┌─ wgsl:4:36 + │ +4 │ var a: array = array(i); + │ ^^^^^^^^^^^^^ a value of type f32 is required here + +"###, + ) +} + +#[test] +fn global_initialization_type_mismatch() { + check( + " + var a: vec2 = vec2(1i, 2i); + ", + r###"error: the type of `a` is expected to be `vec2`, but got `vec2` + ┌─ wgsl:2:22 + │ +2 │ var a: vec2 = vec2(1i, 2i); + │ ^ definition of `a` + +"###, + ) +} + +#[test] +fn binding_array_local() { + check_validation! { + "fn f() { var x: binding_array; }": + Err(_) + } +} + +#[test] +fn binding_array_private() { + check_validation! { + "var x: binding_array;": + Err(_) + } +} + +#[test] +fn binding_array_non_struct() { + check_validation! { + "var x: binding_array;": + Err(naga::valid::ValidationError::Type { + source: naga::valid::TypeError::BindingArrayBaseTypeNotStruct(_), + .. + }) + } +} + +#[test] +fn compaction_preserves_spans() { + let source = r#" + fn f() { + var a: i32 = -(-(-(-42i))); + var x: i32; + x = 42u; + } + "#; // ^^^ correct error span: 95..98 + let mut module = naga::front::wgsl::parse_str(source).expect("source ought to parse"); + naga::compact::compact(&mut module); + let err = naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::default(), + ) + .validate(&module) + .expect_err("source ought to fail validation"); + + // Ideally this would all just be a `matches!` with a big pattern, + // but the `Span` API is full of opaque structs. + let mut spans = err.spans(); + + // The first span is the whole function. + let _ = spans.next().expect("error should have at least one span"); + + // The second span is the assignment destination. + let dest_span = spans + .next() + .expect("error should have at least two spans") + .0; + if !matches!( + dest_span.to_range(), + Some(std::ops::Range { start: 95, end: 98 }) + ) { + panic!("Error message has wrong span:\n\n{err:#?}"); + } +} diff --git a/naga/xtask/.gitignore b/naga/xtask/.gitignore new file mode 100644 index 0000000000..c7ee281fad --- /dev/null +++ b/naga/xtask/.gitignore @@ -0,0 +1,2 @@ +!Cargo.lock +target/ diff --git a/naga/xtask/Cargo.lock b/naga/xtask/Cargo.lock new file mode 100644 index 0000000000..a1727a8970 --- /dev/null +++ b/naga/xtask/Cargo.lock @@ -0,0 +1,276 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "log", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hlsl-snapshots" +version = "0.1.0" +dependencies = [ + "anyhow", + "nanoserde", +] + +[[package]] +name = "indicatif" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "nanoserde" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755e7965536bc54d7c9fba2df5ada5bf835b0443fd613f0a53fa199a301839d3" +dependencies = [ + "nanoserde-derive", +] + +[[package]] +name = "nanoserde-derive" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7a94da6c6181c35d043fc61c43ac96d3a5d739e7b8027f77650ba41504d6ab" + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger", + "glob", + "hlsl-snapshots", + "indicatif", + "jobserver", + "log", + "num_cpus", + "pico-args", + "shell-words", + "which", +] diff --git a/naga/xtask/Cargo.toml b/naga/xtask/Cargo.toml new file mode 100644 index 0000000000..159e54a586 --- /dev/null +++ b/naga/xtask/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +env_logger = { version = "0.10.0", default-features = false } +glob = "0.3.1" +hlsl-snapshots = { path = "../hlsl-snapshots"} +indicatif = "0.17" +jobserver = "0.1" +log = "0.4.17" +num_cpus = "1.16" +pico-args = "0.5.0" +shell-words = "1.1.0" +which = "4.4.0" + +[workspace] diff --git a/naga/xtask/src/cli.rs b/naga/xtask/src/cli.rs new file mode 100644 index 0000000000..d41b86ba2f --- /dev/null +++ b/naga/xtask/src/cli.rs @@ -0,0 +1,181 @@ +use std::process::exit; + +use anyhow::{anyhow, bail, ensure, Context}; +use pico_args::Arguments; + +const HELP: &str = "\ +Usage: xtask + +Commands: + all + bench [--clean] + validate + dot + glsl + hlsl + dxc + fxc + msl + spv + wgsl + +Options: + -h, --help Print help +"; + +#[derive(Debug)] +pub(crate) struct Args { + pub subcommand: Subcommand, +} + +impl Args { + pub fn parse() -> Self { + let mut args = Arguments::from_env(); + log::debug!("parsing args: {args:?}"); + if args.contains("--help") { + eprint!("{HELP}"); + exit(101); + } + match Subcommand::parse(args).map(|subcommand| Self { subcommand }) { + Ok(this) => this, + Err(e) => { + eprintln!("{:?}", anyhow!(e)); + exit(1) + } + } + } +} + +#[derive(Debug)] +pub(crate) enum Subcommand { + All, + Bench { clean: bool }, + Validate(ValidateSubcommand), +} + +impl Subcommand { + fn parse(mut args: Arguments) -> anyhow::Result { + let subcmd = args + .subcommand() + .context("failed to parse subcommand")? + .context("no subcommand specified; see `--help` for more details")?; + match &*subcmd { + "all" => { + ensure_remaining_args_empty(args)?; + Ok(Self::All) + } + "bench" => { + let clean = args.contains("--clean"); + ensure_remaining_args_empty(args)?; + Ok(Self::Bench { clean }) + } + "validate" => Ok(Self::Validate(ValidateSubcommand::parse(args)?)), + other => { + bail!("unrecognized subcommand {other:?}; see `--help` for more details") + } + } + } +} + +// If you add a new validation subcommand, be sure to update the code +// that processes `All`. +#[derive(Debug)] +pub(crate) enum ValidateSubcommand { + Spirv, + Metal, + Glsl, + Dot, + Wgsl, + Hlsl(ValidateHlslCommand), + All, +} + +impl ValidateSubcommand { + fn parse(mut args: Arguments) -> Result { + let subcmd = args + .subcommand() + .context("failed to parse `validate` subcommand")? + .context("no `validate` subcommand specified; see `--help` for more details")?; + match &*subcmd { + "spv" => { + ensure_remaining_args_empty(args)?; + Ok(Self::Spirv) + } + "msl" => { + ensure_remaining_args_empty(args)?; + Ok(Self::Metal) + } + "glsl" => { + ensure_remaining_args_empty(args)?; + Ok(Self::Glsl) + } + "dot" => { + ensure_remaining_args_empty(args)?; + Ok(Self::Dot) + } + "wgsl" => { + ensure_remaining_args_empty(args)?; + Ok(Self::Wgsl) + } + "hlsl" => Ok(Self::Hlsl(ValidateHlslCommand::parse(args)?)), + "all" => { + ensure_remaining_args_empty(args)?; + Ok(Self::All) + } + other => { + bail!("unrecognized `validate` subcommand {other:?}; see `--help` for more details") + } + } + } + + pub(crate) fn all() -> impl Iterator { + [ + Self::Spirv, + Self::Metal, + Self::Glsl, + Self::Dot, + Self::Wgsl, + Self::Hlsl(ValidateHlslCommand::Dxc), + Self::Hlsl(ValidateHlslCommand::Fxc), + ] + .into_iter() + } +} + +#[derive(Debug)] +pub(crate) enum ValidateHlslCommand { + Dxc, + Fxc, +} + +impl ValidateHlslCommand { + fn parse(mut args: Arguments) -> anyhow::Result { + let subcmd = args + .subcommand() + .context("failed to parse `hlsl` subcommand")? + .context("no `hlsl` subcommand specified; see `--help` for more details")?; + match &*subcmd { + "dxc" => { + ensure_remaining_args_empty(args)?; + Ok(Self::Dxc) + } + "fxc" => { + ensure_remaining_args_empty(args)?; + Ok(Self::Fxc) + } + other => { + bail!("unrecognized `hlsl` subcommand {other:?}; see `--help` for more details") + } + } + } +} + +fn ensure_remaining_args_empty(args: Arguments) -> anyhow::Result<()> { + let remaining_args = args.finish(); + ensure!( + remaining_args.is_empty(), + "not all arguments were parsed (remaining: {remaining_args:?}); fix your invocation, \ + please!" + ); + Ok(()) +} diff --git a/naga/xtask/src/fs.rs b/naga/xtask/src/fs.rs new file mode 100644 index 0000000000..98870fc9f9 --- /dev/null +++ b/naga/xtask/src/fs.rs @@ -0,0 +1,10 @@ +use std::{fs::File, path::Path}; + +use anyhow::Context; + +pub(crate) use std::fs::*; + +pub(crate) fn open_file(path: impl AsRef) -> anyhow::Result { + let path = path.as_ref(); + File::open(path).with_context(|| format!("failed to open {path:?}")) +} diff --git a/naga/xtask/src/glob.rs b/naga/xtask/src/glob.rs new file mode 100644 index 0000000000..4d2e1daf80 --- /dev/null +++ b/naga/xtask/src/glob.rs @@ -0,0 +1,34 @@ +use std::path::{Path, PathBuf}; + +use anyhow::Context; +use glob::glob; + +/// Apply `f` to each file matching `pattern` in `top_dir`. +/// +/// Pass files as `anyhow::Result` values, to carry errors from +/// directory iteration and metadata checking. +pub(crate) fn for_each_file( + top_dir: impl AsRef, + pattern: impl AsRef, + mut f: impl FnMut(anyhow::Result), +) { + fn filter_files(glob: &str, result: glob::GlobResult) -> anyhow::Result> { + let path = result.with_context(|| format!("error while iterating over glob {glob:?}"))?; + let metadata = path + .metadata() + .with_context(|| format!("failed to fetch metadata for {path:?}"))?; + Ok(metadata.is_file().then_some(path)) + } + + let pattern_in_dir = top_dir.as_ref().join(pattern.as_ref()); + let pattern_in_dir = pattern_in_dir.to_str().unwrap(); + + glob(pattern_in_dir) + .context("glob pattern {path:?} is invalid") + .unwrap() + .for_each(|result| { + if let Some(result) = filter_files(pattern_in_dir, result).transpose() { + f(result); + } + }); +} diff --git a/naga/xtask/src/jobserver.rs b/naga/xtask/src/jobserver.rs new file mode 100644 index 0000000000..f035f81b75 --- /dev/null +++ b/naga/xtask/src/jobserver.rs @@ -0,0 +1,34 @@ +//! Running jobs in parallel, with a controlled degree of concurrency. + +use std::sync::OnceLock; + +use jobserver::Client; + +static JOB_SERVER: OnceLock = OnceLock::new(); + +pub fn init() { + JOB_SERVER.get_or_init(|| { + // Try to connect to a jobserver inherited from our parent. + if let Some(client) = unsafe { Client::from_env() } { + log::debug!("connected to inherited jobserver client"); + client + } else { + // Otherwise, start our own jobserver. + log::debug!("no inherited jobserver client; creating a new jobserver"); + Client::new(num_cpus::get()).expect("failed to create jobserver") + } + }); +} + +/// Wait until it is okay to start a new job, and then spawn a thread running `body`. +pub fn start_job_thread(body: F) -> anyhow::Result<()> +where + F: FnOnce() + Send + 'static, +{ + let acquired = JOB_SERVER.get().unwrap().acquire()?; + std::thread::spawn(move || { + body(); + drop(acquired); + }); + Ok(()) +} diff --git a/naga/xtask/src/main.rs b/naga/xtask/src/main.rs new file mode 100644 index 0000000000..aed7f48c71 --- /dev/null +++ b/naga/xtask/src/main.rs @@ -0,0 +1,76 @@ +use std::process::ExitCode; + +use anyhow::Context; +use cli::Args; + +use crate::{ + cli::Subcommand, + fs::remove_dir_all, + path::join_path, + process::{which, EasyCommand}, +}; + +mod cli; +mod fs; +mod glob; +mod jobserver; +mod path; +mod process; +mod result; +mod validate; + +fn main() -> ExitCode { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .parse_default_env() + .format_indent(Some(0)) + .init(); + + jobserver::init(); + + let args = Args::parse(); + + match run(args) { + Ok(()) => ExitCode::SUCCESS, + Err(e) => { + log::error!("{e:?}"); + ExitCode::FAILURE + } + } +} + +fn run(args: Args) -> anyhow::Result<()> { + let Args { subcommand } = args; + + assert!(which("cargo").is_ok()); + + match subcommand { + Subcommand::All => { + EasyCommand::simple("cargo", ["fmt"]).success()?; + EasyCommand::simple("cargo", ["test", "--all-features", "--workspace"]).success()?; + EasyCommand::simple( + "cargo", + [ + "clippy", + "--all-features", + "--workspace", + "--", + "-D", + "warnings", + ], + ) + .success()?; + Ok(()) + } + Subcommand::Bench { clean } => { + if clean { + let criterion_artifact_dir = join_path(["target", "criterion"]); + log::info!("removing {}", criterion_artifact_dir.display()); + remove_dir_all(&criterion_artifact_dir) + .with_context(|| format!("failed to remove {criterion_artifact_dir:?}"))?; + } + EasyCommand::simple("cargo", ["bench"]).success() + } + Subcommand::Validate(cmd) => validate::validate(cmd), + } +} diff --git a/naga/xtask/src/path.rs b/naga/xtask/src/path.rs new file mode 100644 index 0000000000..4ba3a0ba4a --- /dev/null +++ b/naga/xtask/src/path.rs @@ -0,0 +1,11 @@ +use std::path::{Path, PathBuf}; + +pub(crate) fn join_path(iter: I) -> PathBuf +where + P: AsRef, + I: IntoIterator, +{ + let mut path = PathBuf::new(); + path.extend(iter); + path +} diff --git a/naga/xtask/src/process.rs b/naga/xtask/src/process.rs new file mode 100644 index 0000000000..b70504f768 --- /dev/null +++ b/naga/xtask/src/process.rs @@ -0,0 +1,80 @@ +use std::{ + ffi::{OsStr, OsString}, + fmt::{self, Display}, + iter::once, + ops::{Deref, DerefMut}, + process::Command, +}; + +use anyhow::{ensure, Context}; + +#[derive(Debug)] +pub(crate) struct EasyCommand { + inner: Command, +} + +impl EasyCommand { + pub fn new(cmd: C, config: impl FnOnce(&mut Command) -> &mut Command) -> Self + where + C: AsRef, + { + let mut inner = Command::new(cmd); + config(&mut inner); + Self { inner } + } + + pub fn simple(cmd: C, args: I) -> Self + where + C: AsRef, + A: AsRef, + I: IntoIterator, + { + Self::new(cmd, |cmd| cmd.args(args)) + } + + pub fn success(&mut self) -> anyhow::Result<()> { + let Self { inner } = self; + log::debug!("running {inner:?}"); + let output = inner + .output() + .with_context(|| format!("failed to run {self}"))?; + ensure!( + output.status.success(), + "{self} failed to run; exit code: {:?}\nstdout:\n{}\nstderr:\n{}", + output.status.code(), + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + Ok(()) + } +} + +impl Deref for EasyCommand { + type Target = Command; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for EasyCommand { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +pub(crate) fn which(binary_name: &str) -> anyhow::Result { + ::which::which(binary_name) + .with_context(|| format!("unable to find `{binary_name}` binary")) + .map(|buf| buf.file_name().unwrap().to_owned()) +} + +impl Display for EasyCommand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { inner } = self; + let prog = inner.get_program().to_string_lossy(); + let args = inner.get_args().map(|a| a.to_string_lossy()); + let shell_words = shell_words::join(once(prog).chain(args)); + write!(f, "`{shell_words}`") + } +} diff --git a/naga/xtask/src/result.rs b/naga/xtask/src/result.rs new file mode 100644 index 0000000000..e351ebf4cf --- /dev/null +++ b/naga/xtask/src/result.rs @@ -0,0 +1,33 @@ +#[derive(Clone, Copy, Debug)] +pub(crate) enum ErrorStatus { + NoFailuresFound, + OneOrMoreFailuresFound, +} + +impl ErrorStatus { + pub(crate) fn merge(self, other: Self) -> Self { + match (self, other) { + (Self::OneOrMoreFailuresFound, _) | (_, Self::OneOrMoreFailuresFound) => { + Self::OneOrMoreFailuresFound + } + (Self::NoFailuresFound, Self::NoFailuresFound) => Self::NoFailuresFound, + } + } +} + +pub(crate) trait LogIfError { + fn log_if_err_found(self, status: &mut ErrorStatus) -> Option; +} + +impl LogIfError for anyhow::Result { + fn log_if_err_found(self, status: &mut ErrorStatus) -> Option { + match self { + Ok(t) => Some(t), + Err(e) => { + log::error!("{e:?}"); + *status = status.merge(ErrorStatus::OneOrMoreFailuresFound); + None + } + } + } +} diff --git a/naga/xtask/src/validate.rs b/naga/xtask/src/validate.rs new file mode 100644 index 0000000000..394b7b00d4 --- /dev/null +++ b/naga/xtask/src/validate.rs @@ -0,0 +1,390 @@ +use std::{ + io::{BufRead, BufReader}, + path::Path, + process::Stdio, +}; + +use anyhow::{bail, Context}; + +use crate::{ + cli::{ValidateHlslCommand, ValidateSubcommand}, + fs::open_file, + path::join_path, + process::{which, EasyCommand}, +}; + +type Job = Box anyhow::Result<()> + Send + std::panic::UnwindSafe + 'static>; + +pub(crate) fn validate(cmd: ValidateSubcommand) -> anyhow::Result<()> { + let mut jobs = vec![]; + collect_validation_jobs(&mut jobs, cmd)?; + + let progress_bar = indicatif::ProgressBar::new(jobs.len() as u64); + + let (tx_results, rx_results) = std::sync::mpsc::channel(); + let enqueuing_thread = std::thread::spawn(move || -> anyhow::Result<()> { + for job in jobs { + let tx_results = tx_results.clone(); + crate::jobserver::start_job_thread(move || { + let result = match std::panic::catch_unwind(|| job()) { + Ok(result) => result, + Err(payload) => Err(match payload.downcast_ref::<&str>() { + Some(message) => { + anyhow::anyhow!("Validation job thread panicked: {}", message) + } + None => anyhow::anyhow!("Validation job thread panicked"), + }), + }; + tx_results.send(result).unwrap(); + })?; + } + Ok(()) + }); + + let mut all_good = true; + for result in rx_results { + if let Err(error) = result { + all_good = false; + progress_bar.suspend(|| { + log::error!("{:#}", error); + }); + } + progress_bar.inc(1); + } + + progress_bar.finish_and_clear(); + + anyhow::ensure!( + all_good, + "failed to validate one or more files, see above output for more details" + ); + + if let Err(error) = enqueuing_thread.join().unwrap() { + bail!("Error enqueuing jobs:\n{:#}", error); + } + + Ok(()) +} + +fn collect_validation_jobs(jobs: &mut Vec, cmd: ValidateSubcommand) -> anyhow::Result<()> { + let snapshots_base_out = join_path(["tests", "out"]); + match cmd { + ValidateSubcommand::Spirv => { + let spirv_as = "spirv-as"; + which(spirv_as)?; + + let spirv_val = "spirv-val"; + which(spirv_val)?; + + push_job_for_each_file(snapshots_base_out, "spv/*.spvasm", jobs, |path| { + validate_spirv(&path, spirv_as, spirv_val) + }); + } + ValidateSubcommand::Metal => { + let xcrun = "xcrun"; + which(xcrun)?; + push_job_for_each_file(snapshots_base_out, "msl/*.msl", jobs, |path| { + validate_metal(&path, xcrun) + }); + } + ValidateSubcommand::Glsl => { + let glslang_validator = "glslangValidator"; + which(glslang_validator)?; + for (glob, type_arg) in [ + ("glsl/*.Vertex.glsl", "vert"), + ("glsl/*.Fragment.glsl", "frag"), + ("glsl/*.Compute.glsl", "comp"), + ] { + push_job_for_each_file(&snapshots_base_out, glob, jobs, |path| { + validate_glsl(&path, type_arg, glslang_validator) + }); + } + } + ValidateSubcommand::Dot => { + let dot = "dot"; + which(dot)?; + push_job_for_each_file(snapshots_base_out, "dot/*.dot", jobs, |path| { + validate_dot(&path, dot) + }); + } + ValidateSubcommand::Wgsl => { + let mut paths = vec![]; + crate::glob::for_each_file(snapshots_base_out, "wgsl/*.wgsl", |path_result| { + try_push_job(jobs, |_| { + paths.push(path_result?.to_owned()); + Ok(()) + }) + }); + if !paths.is_empty() { + jobs.push(Box::new(move || validate_wgsl(&paths))); + } + } + ValidateSubcommand::Hlsl(cmd) => { + let bin; + let validator: fn(&Path, hlsl_snapshots::ConfigItem, &str) -> anyhow::Result<()>; + match cmd { + ValidateHlslCommand::Dxc => { + bin = "dxc"; + which(bin)?; + validator = validate_hlsl_with_dxc; + } + ValidateHlslCommand::Fxc => { + bin = "fxc"; + which(bin)?; + validator = validate_hlsl_with_fxc; + } + } + + crate::glob::for_each_file(snapshots_base_out, "hlsl/*.hlsl", |path_result| { + try_push_job(jobs, |jobs| { + let path = path_result?; + push_job_for_each_hlsl_config_item(&path, bin, jobs, validator)?; + Ok(()) + }) + }); + } + ValidateSubcommand::All => { + for subcmd in ValidateSubcommand::all() { + let should_validate = match &subcmd { + ValidateSubcommand::Wgsl + | ValidateSubcommand::Spirv + | ValidateSubcommand::Glsl + | ValidateSubcommand::Dot => true, + ValidateSubcommand::Metal => cfg!(any(target_os = "macos", target_os = "ios")), + // The FXC compiler is only available on Windows. + // + // The DXC compiler can be built and run on any platform, + // but they don't make Linux releases and it's not clear + // what Git commit actually works on Linux, so restrict + // that to Windows as well. + ValidateSubcommand::Hlsl( + ValidateHlslCommand::Dxc | ValidateHlslCommand::Fxc, + ) => cfg!(os = "windows"), + ValidateSubcommand::All => continue, + }; + if should_validate { + collect_validation_jobs(jobs, subcmd)?; + } + } + } + }; + + Ok(()) +} + +fn push_job_for_each_file( + top_dir: impl AsRef, + pattern: impl AsRef, + jobs: &mut Vec, + f: impl FnOnce(std::path::PathBuf) -> anyhow::Result<()> + + Clone + + Send + + std::panic::UnwindSafe + + 'static, +) { + crate::glob::for_each_file(top_dir, pattern, move |path_result| { + // Let each job closure stand on its own. + let f = f.clone(); + jobs.push(Box::new(|| f(path_result?))); + }); +} + +/// Call `f` to extend `jobs`, but if `f` itself fails, push a job that reports that. +fn try_push_job(jobs: &mut Vec, f: impl FnOnce(&mut Vec) -> anyhow::Result<()>) { + if let Err(error) = f(jobs) { + jobs.push(Box::new(|| Err(error))); + } +} + +fn validate_spirv(path: &Path, spirv_as: &str, spirv_val: &str) -> anyhow::Result<()> { + let second_line = { + let mut file = BufReader::new(open_file(path)?); + let mut buf = String::new(); + file.read_line(&mut buf) + .with_context(|| format!("failed to read first line from {path:?}"))?; + buf.clear(); + file.read_line(&mut buf) + .with_context(|| format!("failed to read second line from {path:?}"))?; + buf + }; + let expected_header_prefix = "; Version: "; + let Some(version) = + second_line.strip_prefix(expected_header_prefix) else { + bail!( + "no {expected_header_prefix:?} header found in {path:?}" + ); + }; + let file = open_file(path)?; + let mut spirv_as_cmd = EasyCommand::new(spirv_as, |cmd| { + cmd.stdin(Stdio::from(file)) + .stdout(Stdio::piped()) + .arg("--target-env") + .arg(format!("spv{version}")) + .args(["-", "-o", "-"]) + }); + let child = spirv_as_cmd + .spawn() + .with_context(|| format!("failed to spawn {spirv_as_cmd:?}"))?; + EasyCommand::new(spirv_val, |cmd| cmd.stdin(child.stdout.unwrap())).success() +} + +fn validate_metal(path: &Path, xcrun: &str) -> anyhow::Result<()> { + let first_line = { + let mut file = BufReader::new(open_file(path)?); + let mut buf = String::new(); + file.read_line(&mut buf) + .with_context(|| format!("failed to read header from {path:?}"))?; + buf + }; + let expected_header_prefix = "// language: "; + let Some(language) = + first_line.strip_prefix(expected_header_prefix) else { + bail!( + "no {expected_header_prefix:?} header found in {path:?}" + ); + }; + let language = language.strip_suffix('\n').unwrap_or(language); + + let file = open_file(path)?; + EasyCommand::new(xcrun, |cmd| { + cmd.stdin(Stdio::from(file)) + .args(["-sdk", "macosx", "metal", "-mmacosx-version-min=10.11"]) + .arg(format!("-std=macos-{language}")) + .args(["-x", "metal", "-", "-o", "/dev/null"]) + }) + .success() +} + +fn validate_glsl(path: &Path, type_arg: &str, glslang_validator: &str) -> anyhow::Result<()> { + let file = open_file(path)?; + EasyCommand::new(glslang_validator, |cmd| { + cmd.stdin(Stdio::from(file)) + .args(["--stdin", "-S"]) + .arg(type_arg) + }) + .success() +} + +fn validate_dot(path: &Path, dot: &str) -> anyhow::Result<()> { + let file = open_file(path)?; + EasyCommand::new(dot, |cmd| { + cmd.stdin(Stdio::from(file)).stdout(Stdio::null()) + }) + .success() +} + +fn validate_wgsl(paths: &[std::path::PathBuf]) -> anyhow::Result<()> { + EasyCommand::new("cargo", |cmd| { + cmd.args(["run", "-p", "naga-cli", "--", "--bulk-validate"]) + .args(paths) + }) + .success() +} + +fn push_job_for_each_hlsl_config_item( + path: &Path, + bin: &str, + jobs: &mut Vec, + validator: impl FnMut(&Path, hlsl_snapshots::ConfigItem, &str) -> anyhow::Result<()> + + Clone + + Send + + std::panic::UnwindSafe + + 'static, +) -> anyhow::Result<()> { + let hlsl_snapshots::Config { + vertex, + fragment, + compute, + } = hlsl_snapshots::Config::from_path(path.with_extension("ron"))?; + for shader in [vertex, fragment, compute].into_iter().flatten() { + // Let each job closure stand on its own. + let mut validator = validator.clone(); + let path = path.to_owned(); + let bin = bin.to_owned(); + jobs.push(Box::new(move || validator(&path, shader, &bin))); + } + Ok(()) +} + +fn validate_hlsl_with_dxc( + file: &Path, + config_item: hlsl_snapshots::ConfigItem, + dxc: &str, +) -> anyhow::Result<()> { + // Reference: + // . + validate_hlsl( + file, + dxc, + config_item, + &[ + "-Wno-parentheses-equality", + "-Zi", + "-Qembed_debug", + "-Od", + "-HV", + "2018", + ], + ) +} + +fn validate_hlsl_with_fxc( + file: &Path, + config_item: hlsl_snapshots::ConfigItem, + fxc: &str, +) -> anyhow::Result<()> { + let Some(Ok(shader_model_major_version)) = config_item + .target_profile + .split('_') + .nth(1) + .map(|segment| segment.parse::()) else { + bail!( + "expected target profile of the form \ + `{{model}}_{{major}}_{{minor}}`, found invalid target \ + profile {:?} in file {}", + config_item.target_profile, + file.display() + ) + }; + // NOTE: This isn't implemented by `fxc.exe`; see + // . + if shader_model_major_version < 6 { + // Reference: + // . + validate_hlsl(file, fxc, config_item, &["-Zi", "-Od"]) + } else { + log::debug!( + "skipping config. item {config_item:?} because the \ + shader model major version is > 6" + ); + Ok(()) + } +} + +fn validate_hlsl( + file: &Path, + bin: &str, + config_item: hlsl_snapshots::ConfigItem, + params: &[&str], +) -> anyhow::Result<()> { + let hlsl_snapshots::ConfigItem { + entry_point, + target_profile, + } = config_item; + EasyCommand::new(bin, |cmd| { + cmd.arg(file) + .arg("-T") + .arg(&target_profile) + .arg("-E") + .arg(&entry_point) + .args(params) + .stdout(Stdio::null()) + }) + .success() + .with_context(|| { + format!( + "failed to validate entry point {entry_point:?} with profile \ + {target_profile:?}" + ) + }) +} diff --git a/player/Cargo.toml b/player/Cargo.toml index 5a6ea2bd7d..63493a4ee2 100644 --- a/player/Cargo.toml +++ b/player/Cargo.toml @@ -10,9 +10,15 @@ keywords.workspace = true license.workspace = true publish = false -[features] -angle = ["wgc/angle"] -vulkan-portability = ["wgc/vulkan"] +[lib] +name = "player" +path = "src/lib.rs" +test = false + +[[bin]] +name = "play" +path = "src/bin/play.rs" +test = false [dependencies] env_logger.workspace = true @@ -27,7 +33,7 @@ features = ["replay"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgc] workspace = true -features = ["replay", "raw-window-handle", "strict_asserts", "wgsl", "metal", "dx11", "dx12", "vulkan", "gles"] +features = ["replay", "raw-window-handle", "strict_asserts", "wgsl", "metal", "dx12", "vulkan", "gles"] [dev-dependencies] serde.workspace = true diff --git a/player/README.md b/player/README.md index 859e079c7e..c816dd57e7 100644 --- a/player/README.md +++ b/player/README.md @@ -1,13 +1,13 @@ # wgpu player This is an application that allows replaying the `wgpu` workloads recorded elsewhere. It requires the player to be built from -the same revision as an application was linking to, or otherwise the data may fail to load. +the same revision as an application was linking to, or otherwise, the data may fail to load. Launch as: ```rust play ``` -When built with "winit" feature, it's able to replay the workloads that operate on a swapchain. It renders each frame sequentially, then waits for the user to close the window. When built without "winit", it launches in console mode and can replay any trace that doesn't use swapchains. +When built with "winit" feature, it's able to replay the workloads that operate on a swapchain. It renders each frame sequentially and then waits for the user to close the window. When built without "winit", it launches in console mode and can replay any trace that doesn't use swapchains. -Note: replaying is currently restricted to the same backend, as one used for recording a trace. It is straightforward, however, to just replace the backend in RON, since it's serialized as plain text. Valid values are: Vulkan, Metal, Dx12, and Dx11. +Note: replaying is currently restricted to the same backend as one used for recording a trace. It is straightforward, however, to just replace the backend in RON since it's serialized as plain text. Valid values are: Vulkan, Metal, and Dx12. diff --git a/player/src/bin/play.rs b/player/src/bin/play.rs index a9a47ce5ff..f91b1b6615 100644 --- a/player/src/bin/play.rs +++ b/player/src/bin/play.rs @@ -12,9 +12,14 @@ fn main() { }; #[cfg(feature = "winit")] - use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; #[cfg(feature = "winit")] - use winit::{event_loop::EventLoop, window::WindowBuilder}; + use winit::{ + event::KeyEvent, + event_loop::EventLoop, + keyboard::{Key, NamedKey}, + window::WindowBuilder, + }; env_logger::init(); @@ -35,7 +40,7 @@ fn main() { #[cfg(feature = "winit")] let event_loop = { log::info!("Creating a window"); - EventLoop::new() + EventLoop::new().unwrap() }; #[cfg(feature = "winit")] let window = WindowBuilder::new() @@ -49,14 +54,17 @@ fn main() { IdentityPassThroughFactory, wgt::InstanceDescriptor::default(), ); - let mut command_buffer_id_manager = wgc::identity::IdentityManager::default(); + let mut command_buffer_id_manager = wgc::identity::IdentityManager::new(); #[cfg(feature = "winit")] - let surface = global.instance_create_surface( - window.raw_display_handle(), - window.raw_window_handle(), - wgc::id::TypedId::zip(0, 1, wgt::Backend::Empty), - ); + let surface = unsafe { + global.instance_create_surface( + window.display_handle().unwrap().into(), + window.window_handle().unwrap().into(), + wgc::id::TypedId::zip(0, 1, wgt::Backend::Empty), + ) + } + .unwrap(); let device = match actions.pop() { Some(trace::Action::Init { desc, backend }) => { @@ -81,10 +89,11 @@ fn main() { let info = gfx_select!(adapter => global.adapter_get_info(adapter)).unwrap(); log::info!("Picked '{}'", info.name); let id = wgc::id::TypedId::zip(1, 0, backend); - let (_, error) = gfx_select!(adapter => global.adapter_request_device( + let (_, _, error) = gfx_select!(adapter => global.adapter_request_device( adapter, &desc, None, + id, id )); if let Some(e) = error { @@ -105,37 +114,37 @@ fn main() { } gfx_select!(device => global.device_stop_capture(device)); - gfx_select!(device => global.device_poll(device, wgt::Maintain::Wait)).unwrap(); + gfx_select!(device => global.device_poll(device, wgt::Maintain::wait())).unwrap(); } #[cfg(feature = "winit")] { use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, WindowEvent}, event_loop::ControlFlow, }; let mut resize_config = None; let mut frame_count = 0; let mut done = false; - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Poll; + event_loop.run(move |event, target| { + target.set_control_flow(ControlFlow::Poll); + match event { - Event::MainEventsCleared => { - window.request_redraw(); - } - Event::RedrawRequested(_) if resize_config.is_none() => loop { + Event::WindowEvent { event, .. } => match event { + WindowEvent::RedrawRequested if resize_config.is_none() => { + match actions.pop() { Some(trace::Action::ConfigureSurface(_device_id, config)) => { log::info!("Configuring the surface"); let current_size: (u32, u32) = window.inner_size().into(); let size = (config.width, config.height); if current_size != size { - window.set_inner_size(winit::dpi::PhysicalSize::new( + let _ = window.request_inner_size(winit::dpi::PhysicalSize::new( config.width, config.height, )); resize_config = Some(config); - break; + target.exit(); } else { let error = gfx_select!(device => global.surface_configure(surface, device, &config)); if let Some(e) = error { @@ -147,12 +156,12 @@ fn main() { frame_count += 1; log::debug!("Presenting frame {}", frame_count); gfx_select!(device => global.surface_present(id)).unwrap(); - break; + target.exit(); } Some(trace::Action::DiscardSurfaceTexture(id)) => { log::debug!("Discarding frame {}", frame_count); gfx_select!(device => global.surface_texture_discard(id)).unwrap(); - break; + target.exit(); } Some(action) => { gfx_select!(device => global.process(device, action, &dir, &mut command_buffer_id_manager)); @@ -162,11 +171,10 @@ fn main() { println!("Finished the end at frame {}", frame_count); done = true; } - break; + target.exit(); } } - }, - Event::WindowEvent { event, .. } => match event { + }, WindowEvent::Resized(_) => { if let Some(config) = resize_config.take() { let error = gfx_select!(device => global.surface_configure(surface, device, &config)); @@ -176,26 +184,23 @@ fn main() { } } WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Escape), - state: ElementState::Pressed, - .. - }, + event: KeyEvent { + logical_key: Key::Named(NamedKey::Escape), + state: ElementState::Pressed, + .. + }, .. } - | WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } + | WindowEvent::CloseRequested => target.exit(), _ => {} }, - Event::LoopDestroyed => { + Event::LoopExiting => { log::info!("Closing"); - gfx_select!(device => global.device_poll(device, wgt::Maintain::Wait)).unwrap(); + gfx_select!(device => global.device_poll(device, wgt::Maintain::wait())).unwrap(); } _ => {} } - }); + }).unwrap(); } } diff --git a/player/src/lib.rs b/player/src/lib.rs index c7626e5ce7..bbaa7ad5e7 100644 --- a/player/src/lib.rs +++ b/player/src/lib.rs @@ -10,37 +10,22 @@ use wgc::device::trace; -use std::{borrow::Cow, fmt::Debug, fs, marker::PhantomData, path::Path}; +use std::{borrow::Cow, fs, path::Path}; -#[derive(Debug)] -pub struct IdentityPassThrough(PhantomData); +pub struct IdentityPassThroughFactory; -impl wgc::identity::IdentityHandler - for IdentityPassThrough -{ +impl wgc::identity::IdentityHandlerFactory for IdentityPassThroughFactory { type Input = I; - fn process(&self, id: I, backend: wgt::Backend) -> I { - let (index, epoch, _backend) = id.unzip(); - I::zip(index, epoch, backend) - } - fn free(&self, _id: I) {} -} -pub struct IdentityPassThroughFactory; - -impl wgc::identity::IdentityHandlerFactory - for IdentityPassThroughFactory -{ - type Filter = IdentityPassThrough; - fn spawn(&self) -> Self::Filter { - IdentityPassThrough(PhantomData) + fn input_to_id(id_in: Self::Input) -> I { + id_in } -} -impl wgc::identity::GlobalIdentityHandlerFactory for IdentityPassThroughFactory { - fn ids_are_generated_in_wgpu() -> bool { + + fn autogenerate_ids() -> bool { false } } +impl wgc::identity::GlobalIdentityHandlerFactory for IdentityPassThroughFactory {} pub trait GlobalPlay { fn encode_commands( @@ -53,7 +38,7 @@ pub trait GlobalPlay { device: wgc::id::DeviceId, action: trace::Action, dir: &Path, - comb_manager: &mut wgc::identity::IdentityManager, + comb_manager: &mut wgc::identity::IdentityManager, ); } @@ -246,7 +231,7 @@ impl GlobalPlay for wgc::global::Global { let (cmd_buf, error) = self .command_encoder_finish::(encoder, &wgt::CommandBufferDescriptor { label: None }); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } cmd_buf } @@ -256,10 +241,10 @@ impl GlobalPlay for wgc::global::Global { device: wgc::id::DeviceId, action: trace::Action, dir: &Path, - comb_manager: &mut wgc::identity::IdentityManager, + comb_manager: &mut wgc::identity::IdentityManager, ) { use wgc::device::trace::Action; - log::info!("action {:?}", action); + log::debug!("action {:?}", action); //TODO: find a way to force ID perishing without excessive `maintain()` calls. match action { Action::Init { .. } => { @@ -274,7 +259,7 @@ impl GlobalPlay for wgc::global::Global { self.device_maintain_ids::(device).unwrap(); let (_, error) = self.device_create_buffer::(device, &desc, id); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::FreeBuffer(id) => { @@ -287,7 +272,7 @@ impl GlobalPlay for wgc::global::Global { self.device_maintain_ids::(device).unwrap(); let (_, error) = self.device_create_texture::(device, &desc, id); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::FreeTexture(id) => { @@ -304,7 +289,7 @@ impl GlobalPlay for wgc::global::Global { self.device_maintain_ids::(device).unwrap(); let (_, error) = self.texture_create_view::(parent_id, &desc, id); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::DestroyTextureView(id) => { @@ -314,7 +299,7 @@ impl GlobalPlay for wgc::global::Global { self.device_maintain_ids::(device).unwrap(); let (_, error) = self.device_create_sampler::(device, &desc, id); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::DestroySampler(id) => { @@ -330,7 +315,7 @@ impl GlobalPlay for wgc::global::Global { Action::CreateBindGroupLayout(id, desc) => { let (_, error) = self.device_create_bind_group_layout::(device, &desc, id); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::DestroyBindGroupLayout(id) => { @@ -340,7 +325,7 @@ impl GlobalPlay for wgc::global::Global { self.device_maintain_ids::(device).unwrap(); let (_, error) = self.device_create_pipeline_layout::(device, &desc, id); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::DestroyPipelineLayout(id) => { @@ -350,17 +335,17 @@ impl GlobalPlay for wgc::global::Global { self.device_maintain_ids::(device).unwrap(); let (_, error) = self.device_create_bind_group::(device, &desc, id); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::DestroyBindGroup(id) => { self.bind_group_drop::(id); } Action::CreateShaderModule { id, desc, data } => { - log::info!("Creating shader from {}", data); + log::debug!("Creating shader from {}", data); let code = fs::read_to_string(dir.join(&data)).unwrap(); let source = if data.ends_with(".wgsl") { - wgc::pipeline::ShaderModuleSource::Wgsl(Cow::Owned(code)) + wgc::pipeline::ShaderModuleSource::Wgsl(Cow::Owned(code.clone())) } else if data.ends_with(".ron") { let module = ron::de::from_str(&code).unwrap(); wgc::pipeline::ShaderModuleSource::Naga(module) @@ -369,7 +354,7 @@ impl GlobalPlay for wgc::global::Global { }; let (_, error) = self.device_create_shader_module::(device, &desc, source, id); if let Some(e) = error { - panic!("{:?}", e); + println!("shader compilation error:\n---{code}\n---\n{e}"); } } Action::DestroyShaderModule(id) => { @@ -391,7 +376,7 @@ impl GlobalPlay for wgc::global::Global { let (_, error) = self.device_create_compute_pipeline::(device, &desc, id, implicit_ids); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::DestroyComputePipeline(id) => { @@ -413,7 +398,7 @@ impl GlobalPlay for wgc::global::Global { let (_, error) = self.device_create_render_pipeline::(device, &desc, id, implicit_ids); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::DestroyRenderPipeline(id) => { @@ -428,7 +413,7 @@ impl GlobalPlay for wgc::global::Global { id, ); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::DestroyRenderBundle(id) => { @@ -438,7 +423,7 @@ impl GlobalPlay for wgc::global::Global { self.device_maintain_ids::(device).unwrap(); let (_, error) = self.device_create_query_set::(device, &desc, id); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } } Action::DestroyQuerySet(id) => { @@ -478,10 +463,10 @@ impl GlobalPlay for wgc::global::Global { let (encoder, error) = self.device_create_command_encoder::( device, &wgt::CommandEncoderDescriptor { label: None }, - comb_manager.alloc(device.backend()), + comb_manager.process(device.backend()), ); if let Some(e) = error { - panic!("{:?}", e); + panic!("{e}"); } let cmdbuf = self.encode_commands::(encoder, commands); self.queue_submit::(device, &[cmdbuf]).unwrap(); diff --git a/player/tests/data/bind-group.ron b/player/tests/data/bind-group.ron index 00ecf0b20c..7b89d456bc 100644 --- a/player/tests/data/bind-group.ron +++ b/player/tests/data/bind-group.ron @@ -2,30 +2,6 @@ features: 0x0, expectations: [], //not crash! actions: [ - CreatePipelineLayout(Id(0, 1, Empty), ( - label: Some("empty"), - bind_group_layouts: [], - push_constant_ranges: [], - )), - CreateShaderModule( - id: Id(0, 1, Empty), - desc: ( - label: None, - flags: (bits: 3), - ), - data: "empty.wgsl", - ), - CreateComputePipeline( - id: Id(0, 1, Empty), - desc: ( - label: None, - layout: Some(Id(0, 1, Empty)), - stage: ( - module: Id(0, 1, Empty), - entry_point: "main", - ), - ), - ), CreateBuffer(Id(0, 1, Empty), ( label: None, size: 16, @@ -58,16 +34,42 @@ ) ], )), + CreatePipelineLayout(Id(0, 1, Empty), ( + label: Some("empty"), + bind_group_layouts: [ + Id(0, 1, Empty), + ], + push_constant_ranges: [], + )), + CreateShaderModule( + id: Id(0, 1, Empty), + desc: ( + label: None, + flags: (bits: 3), + ), + data: "empty.wgsl", + ), + CreateComputePipeline( + id: Id(0, 1, Empty), + desc: ( + label: None, + layout: Some(Id(0, 1, Empty)), + stage: ( + module: Id(0, 1, Empty), + entry_point: "main", + ), + ), + ), Submit(1, [ RunComputePass( base: ( commands: [ - SetPipeline(Id(0, 1, Empty)), SetBindGroup( index: 0, num_dynamic_offsets: 0, bind_group_id: Id(0, 1, Empty), ), + SetPipeline(Id(0, 1, Empty)), ], dynamic_offsets: [], string_data: [], diff --git a/player/tests/data/buffer-copy.ron b/player/tests/data/buffer-copy.ron index 58cce3b1c2..05c3a94334 100644 --- a/player/tests/data/buffer-copy.ron +++ b/player/tests/data/buffer-copy.ron @@ -1,5 +1,5 @@ ( - features: 0x1_0000, + features: 0x0000_0004_0000_0000, // MAPPABLE_PRIMARY_BUFFERS expectations: [ ( name: "basic", diff --git a/player/tests/data/clear-buffer-texture.ron b/player/tests/data/clear-buffer-texture.ron index c6879e31da..592e141c9f 100644 --- a/player/tests/data/clear-buffer-texture.ron +++ b/player/tests/data/clear-buffer-texture.ron @@ -1,5 +1,5 @@ ( - features: 0x0000_0004_0001_0000, + features: 0x0004_0004_0000_0000, // MAPPABLE_PRIMARY_BUFFERS | CLEAR_TEXTURE expectations: [ ( name: "Quad", @@ -85,10 +85,10 @@ dst: Id(0, 1, Empty), subresource_range: ImageSubresourceRange( aspect: all, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: None, + baseMipLevel: 0, + mipLevelCount: None, + baseArrayLayer: 0, + arrayLayerCount: None, ), ), CopyTextureToBuffer( diff --git a/player/tests/data/zero-init-buffer.ron b/player/tests/data/zero-init-buffer.ron index ca75c658fc..b93f65c736 100644 --- a/player/tests/data/zero-init-buffer.ron +++ b/player/tests/data/zero-init-buffer.ron @@ -1,5 +1,5 @@ ( - features: 0x1_0000, + features: 0x0000_0004_0000_0000, // MAPPABLE_PRIMARY_BUFFERS expectations: [ // Ensuring that mapping zero-inits buffers. ( diff --git a/player/tests/data/zero-init-texture-binding.ron b/player/tests/data/zero-init-texture-binding.ron index e94255cfc3..17aa3b4279 100644 --- a/player/tests/data/zero-init-texture-binding.ron +++ b/player/tests/data/zero-init-texture-binding.ron @@ -56,7 +56,7 @@ format: "rgba8unorm", usage: 9, // STORAGE + COPY_SRC view_formats: [], - )), + )), CreateTextureView( id: Id(1, 1, Empty), parent_id: Id(1, 1, Empty), diff --git a/player/tests/test.rs b/player/tests/test.rs index cd1302777e..196777d0b2 100644 --- a/player/tests/test.rs +++ b/player/tests/test.rs @@ -68,7 +68,6 @@ impl Test<'_> { wgt::Backend::Vulkan => "Vulkan", wgt::Backend::Metal => "Metal", wgt::Backend::Dx12 => "Dx12", - wgt::Backend::Dx11 => "Dx11", wgt::Backend::Gl => "Gl", _ => unreachable!(), }; @@ -84,50 +83,52 @@ impl Test<'_> { test_num: u32, ) { let backend = adapter.backend(); - let device = wgc::id::TypedId::zip(test_num, 0, backend); - let (_, error) = wgc::gfx_select!(adapter => global.adapter_request_device( + let device_id = wgc::id::TypedId::zip(test_num, 0, backend); + let (_, _, error) = wgc::gfx_select!(adapter => global.adapter_request_device( adapter, &wgt::DeviceDescriptor { label: None, - features: self.features, - limits: wgt::Limits::default(), + required_features: self.features, + required_limits: wgt::Limits::default(), }, None, - device + device_id, + device_id )); if let Some(e) = error { panic!("{:?}", e); } - let mut command_buffer_id_manager = wgc::identity::IdentityManager::default(); + let mut command_buffer_id_manager = wgc::identity::IdentityManager::new(); println!("\t\t\tRunning..."); for action in self.actions { - wgc::gfx_select!(device => global.process(device, action, dir, &mut command_buffer_id_manager)); + wgc::gfx_select!(device_id => global.process(device_id, action, dir, &mut command_buffer_id_manager)); } println!("\t\t\tMapping..."); for expect in &self.expectations { let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend); - wgc::gfx_select!(device => global.buffer_map_async( + wgc::gfx_select!(device_id => global.buffer_map_async( buffer, expect.offset .. expect.offset+expect.data.len() as wgt::BufferAddress, wgc::resource::BufferMapOperation { host: wgc::device::HostMap::Read, - callback: wgc::resource::BufferMapCallback::from_rust( + callback: Some(wgc::resource::BufferMapCallback::from_rust( Box::new(map_callback) - ), + )), } )) .unwrap(); } println!("\t\t\tWaiting..."); - wgc::gfx_select!(device => global.device_poll(device, wgt::Maintain::Wait)).unwrap(); + wgc::gfx_select!(device_id => global.device_poll(device_id, wgt::Maintain::wait())) + .unwrap(); for expect in self.expectations { println!("\t\t\tChecking {}", expect.name); let buffer = wgc::id::TypedId::zip(expect.buffer.index, expect.buffer.epoch, backend); let (ptr, size) = - wgc::gfx_select!(device => global.buffer_get_mapped_range(buffer, expect.offset, Some(expect.data.len() as wgt::BufferAddress))) + wgc::gfx_select!(device_id => global.buffer_get_mapped_range(buffer, expect.offset, Some(expect.data.len() as wgt::BufferAddress))) .unwrap(); let contents = unsafe { slice::from_raw_parts(ptr, size as usize) }; let expected_data = match expect.data { @@ -155,7 +156,7 @@ impl Test<'_> { } } - wgc::gfx_select!(device => global.clear_backend(())); + wgc::gfx_select!(device_id => global.clear_backend(())); } } @@ -169,7 +170,6 @@ const BACKENDS: &[wgt::Backend] = &[ wgt::Backend::Vulkan, wgt::Backend::Metal, wgt::Backend::Dx12, - wgt::Backend::Dx11, wgt::Backend::Gl, ]; @@ -184,6 +184,7 @@ impl Corpus { IdentityPassThroughFactory, wgt::InstanceDescriptor { backends: corpus.backends, + flags: wgt::InstanceFlags::debugging(), dx12_shader_compiler: wgt::Dx12Compiler::Fxc, gles_minor_version: wgt::Gles3MinorVersion::default(), }, diff --git a/rust-toolchain b/rust-toolchain.toml similarity index 84% rename from rust-toolchain rename to rust-toolchain.toml index 0f2697d74a..dde5f402a2 100644 --- a/rust-toolchain +++ b/rust-toolchain.toml @@ -5,6 +5,6 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "1.65" +channel = "1.71" # Needed for deno & cts_runner. Firefox's MSRV is 1.70 components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/tests/Cargo.toml b/tests/Cargo.toml index ed2dc8d57d..7e2084566d 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -12,27 +12,39 @@ autotests = false publish = false [[test]] -name = "wgpu-tests" +name = "wgpu-test" path = "tests/root.rs" +harness = false [features] webgl = ["wgpu/webgl"] [dependencies] +anyhow.workspace = true +arrayvec.workspace = true bitflags.workspace = true bytemuck.workspace = true cfg-if.workspace = true -env_logger.workspace = true +ctor.workspace = true +futures-lite.workspace = true +heck.workspace = true +libtest-mimic.workspace = true log.workspace = true parking_lot.workspace = true png.workspace = true pollster.workspace = true +profiling.workspace = true +serde_json.workspace = true +serde.workspace = true +wgpu-macros.workspace = true wgpu.workspace = true -wgt.workspace = true +wgt = { workspace = true, features = ["replay"] } glam.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +env_logger.workspace = true nv-flip.workspace = true +parking_lot = { workspace = true, features = ["deadlock_detection"] } [target.'cfg(target_arch = "wasm32")'.dependencies] console_log.workspace = true @@ -42,11 +54,11 @@ web-sys = { workspace = true } [dev-dependencies] naga = { workspace = true, features = ["wgsl-in"] } -wasm-bindgen-test.workspace = true [target.'cfg(target_arch = "wasm32")'.dev-dependencies] image.workspace = true js-sys.workspace = true wasm-bindgen-futures.workspace = true +wasm-bindgen-test.workspace = true wasm-bindgen.workspace = true web-sys = { workspace = true, features = ["CanvasRenderingContext2d", "Blob"] } diff --git a/tests/src/config.rs b/tests/src/config.rs new file mode 100644 index 0000000000..fa96adbc1d --- /dev/null +++ b/tests/src/config.rs @@ -0,0 +1,117 @@ +use std::{future::Future, pin::Pin, sync::Arc}; + +use crate::{TestParameters, TestingContext}; + +cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + pub type RunTestAsync = Arc Pin>>>; + + // We can't use WasmNonSend and WasmNonSync here, as we need these to not require Send/Sync + // even with the `fragile-send-sync-non-atomic-wasm` enabled. + pub trait RunTestSendSync {} + impl RunTestSendSync for T {} + pub trait RunTestSend {} + impl RunTestSend for T {} + } else { + pub type RunTestAsync = Arc Pin + Send>> + Send + Sync>; + + pub trait RunTestSend: Send {} + impl RunTestSend for T where T: Send {} + pub trait RunTestSendSync: Send + Sync {} + impl RunTestSendSync for T where T: Send + Sync {} + } +} + +/// Configuration for a GPU test. +#[derive(Clone)] +pub struct GpuTestConfiguration { + pub(crate) name: String, + pub(crate) params: TestParameters, + pub(crate) test: Option, +} + +impl GpuTestConfiguration { + pub fn new() -> Self { + Self { + name: String::new(), + params: TestParameters::default(), + test: None, + } + } + + /// Set the name of the test. Must be unique across all tests in the binary. + pub fn name(self, name: &str) -> Self { + Self { + name: String::from(name), + ..self + } + } + + #[doc(hidden)] + /// Derives the name from a `struct S` in the function initializing the test. + /// + /// Does not overwrite a given name if a name has already been set + pub fn name_from_init_function_typename(self, name: &'static str) -> Self { + if !self.name.is_empty() { + return self; + } + let type_name = std::any::type_name::(); + + // We end up with a string like: + // + // module::path::we::want::test_name_initializer::S + // + // So we reverse search for the 4th colon from the end, and take everything before that. + let mut colons = 0; + let mut colon_4_index = type_name.len(); + for i in (0..type_name.len()).rev() { + if type_name.as_bytes()[i] == b':' { + colons += 1; + } + if colons == 4 { + colon_4_index = i; + break; + } + } + + let full = format!("{}::{}", &type_name[..colon_4_index], name); + Self { name: full, ..self } + } + + /// Set the parameters that the test needs to succeed. + pub fn parameters(self, parameters: TestParameters) -> Self { + Self { + params: parameters, + ..self + } + } + + /// Make the test function an synchronous function. + pub fn run_sync( + self, + test: impl Fn(TestingContext) + Copy + RunTestSendSync + 'static, + ) -> Self { + Self { + test: Some(Arc::new(move |ctx| Box::pin(async move { test(ctx) }))), + ..self + } + } + + /// Make the test function an asynchronous function/future. + pub fn run_async(self, test: F) -> Self + where + F: Fn(TestingContext) -> R + RunTestSendSync + 'static, + R: Future + RunTestSend + 'static, + { + Self { + test: Some(Arc::new(move |ctx| Box::pin(test(ctx)))), + ..self + } + } +} + +impl Default for GpuTestConfiguration { + fn default() -> Self { + Self::new() + } +} diff --git a/tests/src/expectations.rs b/tests/src/expectations.rs new file mode 100644 index 0000000000..ee48e83aa8 --- /dev/null +++ b/tests/src/expectations.rs @@ -0,0 +1,562 @@ +/// Conditions under which a test should fail or be skipped. +/// +/// By passing a `FailureCase` to [`TestParameters::expect_fail`][expect_fail], you can +/// mark a test as expected to fail under the indicated conditions. By +/// passing it to [`TestParameters::skip`][skip], you can request that the +/// test be skipped altogether. +/// +/// If a field is `None`, then that field does not restrict matches. For +/// example: +/// +/// ``` +/// # use wgpu_test::*; +/// FailureCase { +/// backends: Some(wgpu::Backends::DX12), +/// vendor: None, +/// adapter: Some("RTX"), +/// driver: None, +/// reasons: vec![FailureReason::ValidationError(Some("Some error substring"))], +/// behavior: FailureBehavior::AssertFailure, +/// } +/// # ; +/// ``` +/// +/// This applies to all cards with `"RTX'` in their name on either +/// Direct3D backend, no matter the vendor ID or driver name. +/// +/// The strings given here need only appear as a substring in the +/// corresponding [`AdapterInfo`] fields. The comparison is +/// case-insensitive. +/// +/// The default value of `FailureCase` applies to any test case. That +/// is, there are no criteria to constrain the match. +/// +/// [skip]: super::TestParameters::skip +/// [expect_fail]: super::TestParameters::expect_fail +/// [`AdapterInfo`]: wgt::AdapterInfo +#[derive(Default, Clone)] +pub struct FailureCase { + /// Backends expected to fail, or `None` for any backend. + /// + /// If this is `None`, or if the test is using one of the backends + /// in `backends`, then this `FailureCase` applies. + pub backends: Option, + + /// Vendor expected to fail, or `None` for any vendor. + /// + /// If `Some`, this must match [`AdapterInfo::device`], which is + /// usually the PCI device id. Otherwise, this `FailureCase` + /// applies regardless of vendor. + /// + /// [`AdapterInfo::device`]: wgt::AdapterInfo::device + pub vendor: Option, + + /// Name of adaper expected to fail, or `None` for any adapter name. + /// + /// If this is `Some(s)` and `s` is a substring of + /// [`AdapterInfo::name`], then this `FailureCase` applies. If + /// this is `None`, the adapter name isn't considered. + /// + /// [`AdapterInfo::name`]: wgt::AdapterInfo::name + pub adapter: Option<&'static str>, + + /// Name of driver expected to fail, or `None` for any driver name. + /// + /// If this is `Some(s)` and `s` is a substring of + /// [`AdapterInfo::driver`], then this `FailureCase` applies. If + /// this is `None`, the driver name isn't considered. + /// + /// [`AdapterInfo::driver`]: wgt::AdapterInfo::driver + pub driver: Option<&'static str>, + + /// Reason why the test is expected to fail. + /// + /// If this does not match, the failure will not match this case. + /// + /// If no reasons are pushed, will match any failure. + pub reasons: Vec, + + /// Behavior after this case matches a failure. + pub behavior: FailureBehavior, +} + +impl FailureCase { + /// Create a new failure case. + pub fn new() -> Self { + Self::default() + } + + /// This case applies to all tests. + pub fn always() -> Self { + FailureCase::default() + } + + /// This case applies to no tests. + pub fn never() -> Self { + FailureCase { + backends: Some(wgpu::Backends::empty()), + ..FailureCase::default() + } + } + + /// Tests running on any of the given backends. + pub fn backend(backends: wgpu::Backends) -> Self { + FailureCase { + backends: Some(backends), + ..FailureCase::default() + } + } + + /// Tests running on `adapter`. + /// + /// For this case to apply, the `adapter` string must appear as a substring + /// of the adapter's [`AdapterInfo::name`]. The comparison is + /// case-insensitive. + /// + /// [`AdapterInfo::name`]: wgt::AdapterInfo::name + pub fn adapter(adapter: &'static str) -> Self { + FailureCase { + adapter: Some(adapter), + ..FailureCase::default() + } + } + + /// Tests running on `backend` and `adapter`. + /// + /// For this case to apply, the test must be using an adapter for one of the + /// given `backend` bits, and `adapter` string must appear as a substring of + /// the adapter's [`AdapterInfo::name`]. The string comparison is + /// case-insensitive. + /// + /// [`AdapterInfo::name`]: wgt::AdapterInfo::name + pub fn backend_adapter(backends: wgpu::Backends, adapter: &'static str) -> Self { + FailureCase { + backends: Some(backends), + adapter: Some(adapter), + ..FailureCase::default() + } + } + + /// Tests running under WebGL. + pub fn webgl2() -> Self { + #[cfg(target_arch = "wasm32")] + let case = FailureCase::backend(wgpu::Backends::GL); + #[cfg(not(target_arch = "wasm32"))] + let case = FailureCase::never(); + case + } + + /// Tests running on the MoltenVK Vulkan driver on macOS. + pub fn molten_vk() -> Self { + FailureCase { + backends: Some(wgpu::Backends::VULKAN), + driver: Some("MoltenVK"), + ..FailureCase::default() + } + } + + /// Return the reasons why this case should fail. + pub fn reasons(&self) -> &[FailureReason] { + if self.reasons.is_empty() { + std::array::from_ref(&FailureReason::Any) + } else { + &self.reasons + } + } + + /// Matches this failure case against the given validation error substring. + /// + /// Substrings are matched case-insensitively. + /// + /// If multiple reasons are pushed, will match any of them. + pub fn validation_error(mut self, msg: &'static str) -> Self { + self.reasons.push(FailureReason::ValidationError(Some(msg))); + self + } + + /// Matches this failure case against the given panic substring. + /// + /// Substrings are matched case-insensitively. + /// + /// If multiple reasons are pushed, will match any of them. + pub fn panic(mut self, msg: &'static str) -> Self { + self.reasons.push(FailureReason::Panic(Some(msg))); + self + } + + /// Test is flaky with the given configuration. Do not assert failure. + /// + /// Use this _very_ sparyingly, and match as tightly as you can, including giving a specific failure message. + pub fn flaky(self) -> Self { + FailureCase { + behavior: FailureBehavior::Ignore, + ..self + } + } + + /// Test whether `self` applies to `info`. + /// + /// If it does, return a `FailureReasons` whose set bits indicate + /// why. If it doesn't, return `None`. + /// + /// The caller is responsible for converting the string-valued + /// fields of `info` to lower case, to ensure case-insensitive + /// matching. + pub(crate) fn applies_to_adapter( + &self, + info: &wgt::AdapterInfo, + ) -> Option { + let mut reasons = FailureApplicationReasons::empty(); + + if let Some(backends) = self.backends { + if !backends.contains(wgpu::Backends::from(info.backend)) { + return None; + } + reasons.set(FailureApplicationReasons::BACKEND, true); + } + if let Some(vendor) = self.vendor { + if vendor != info.vendor { + return None; + } + reasons.set(FailureApplicationReasons::VENDOR, true); + } + if let Some(adapter) = self.adapter { + let adapter = adapter.to_lowercase(); + if !info.name.contains(&adapter) { + return None; + } + reasons.set(FailureApplicationReasons::ADAPTER, true); + } + if let Some(driver) = self.driver { + let driver = driver.to_lowercase(); + if !info.driver.contains(&driver) { + return None; + } + reasons.set(FailureApplicationReasons::DRIVER, true); + } + + // If we got this far but no specific reasons were triggered, then this + // must be a wildcard. + if reasons.is_empty() { + Some(FailureApplicationReasons::ALWAYS) + } else { + Some(reasons) + } + } + + /// Returns true if the given failure "satisfies" this failure case. + pub(crate) fn matches_failure(&self, failure: &FailureResult) -> bool { + for reason in self.reasons() { + let result = match (reason, failure) { + (FailureReason::Any, _) => { + log::error!("Matched failure case: Wildcard"); + true + } + (FailureReason::ValidationError(None), FailureResult::ValidationError(_)) => { + log::error!("Matched failure case: Any Validation Error"); + true + } + ( + FailureReason::ValidationError(Some(expected)), + FailureResult::ValidationError(Some(actual)), + ) => { + let result = actual.to_lowercase().contains(&expected.to_lowercase()); + if result { + log::error!( + "Matched failure case: Validation Error containing \"{}\"", + expected + ); + } + result + } + (FailureReason::Panic(None), FailureResult::Panic(_)) => { + log::error!("Matched failure case: Any Panic"); + true + } + (FailureReason::Panic(Some(expected)), FailureResult::Panic(Some(actual))) => { + let result = actual.to_lowercase().contains(&expected.to_lowercase()); + if result { + log::error!("Matched failure case: Panic containing \"{}\"", expected); + } + result + } + _ => false, + }; + + if result { + return true; + } + } + + false + } +} + +bitflags::bitflags! { + /// Reason why a test matches a given failure case. + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct FailureApplicationReasons: u8 { + const BACKEND = 1 << 0; + const VENDOR = 1 << 1; + const ADAPTER = 1 << 2; + const DRIVER = 1 << 3; + const ALWAYS = 1 << 4; + } +} + +/// Reason why a test is expected to fail. +/// +/// If the test fails for a different reason, the given FailureCase will be ignored. +#[derive(Default, Debug, Clone, PartialEq)] +pub enum FailureReason { + /// Matches any failure. + #[default] + Any, + /// Matches validation errors raised from the backend validation. + /// + /// If a string is provided, matches only validation errors that contain the string. + ValidationError(Option<&'static str>), + /// A panic was raised. + /// + /// If a string is provided, matches only panics that contain the string. + Panic(Option<&'static str>), +} + +#[derive(Default, Clone)] +pub enum FailureBehavior { + /// Assert that the test fails for the given reason. + /// + /// If the test passes, the test harness will panic. + #[default] + AssertFailure, + /// Ignore the matching failure. + /// + /// This is useful for tests that flake in a very specific way, + /// but sometimes succeed, so we can't assert that they always fail. + Ignore, +} + +#[derive(Debug)] +pub(crate) enum FailureResult { + #[allow(dead_code)] // Not constructed on wasm + ValidationError(Option), + Panic(Option), +} + +#[derive(PartialEq, Clone, Copy, Debug)] +pub(crate) enum ExpectationMatchResult { + Panic, + Complete, +} + +/// Compares if the actual failures match the expected failures. +pub(crate) fn expectations_match_failures( + expectations: &[FailureCase], + mut actual: Vec, +) -> ExpectationMatchResult { + // Start with the assumption that we will pass. + let mut result = ExpectationMatchResult::Complete; + + // Run through all expected failures. + for expected_failure in expectations { + // If any of the failures match. + let mut matched = false; + + // Iterate through the failures. + // + // In reverse, to be able to use swap_remove. + actual.retain(|failure| { + // If the failure matches, remove it from the list of failures, as we expected it. + let matches = expected_failure.matches_failure(failure); + + if matches { + matched = true; + } + + // Retain removes on false, so flip the bool so we remove on failure. + !matches + }); + + // If we didn't match our expected failure against any of the actual failures, + // and this failure is not flaky, then we need to panic, as we got an unexpected success. + if !matched && matches!(expected_failure.behavior, FailureBehavior::AssertFailure) { + result = ExpectationMatchResult::Panic; + log::error!( + "Expected to fail due to {:?}, but did not fail", + expected_failure.reasons() + ); + } + } + + // If we have any failures left, then we got an unexpected failure + // and we need to panic. + if !actual.is_empty() { + result = ExpectationMatchResult::Panic; + for failure in actual { + log::error!("Unexpected failure due to: {:?}", failure); + } + } + + result +} + +#[cfg(test)] +mod test { + use crate::{ + expectations::{ExpectationMatchResult, FailureResult}, + init::init_logger, + FailureCase, + }; + + fn validation_err(msg: &'static str) -> FailureResult { + FailureResult::ValidationError(Some(String::from(msg))) + } + + fn panic(msg: &'static str) -> FailureResult { + FailureResult::Panic(Some(String::from(msg))) + } + + #[test] + fn simple_match() { + init_logger(); + + // -- Unexpected failure -- + + let expectation = vec![]; + let actual = vec![FailureResult::ValidationError(None)]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Panic + ); + + // -- Missing expected failure -- + + let expectation = vec![FailureCase::always()]; + let actual = vec![]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Panic + ); + + // -- Expected failure (validation) -- + + let expectation = vec![FailureCase::always()]; + let actual = vec![FailureResult::ValidationError(None)]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Complete + ); + + // -- Expected failure (panic) -- + + let expectation = vec![FailureCase::always()]; + let actual = vec![FailureResult::Panic(None)]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Complete + ); + } + + #[test] + fn substring_match() { + init_logger(); + + // -- Matching Substring -- + + let expectation: Vec = + vec![FailureCase::always().validation_error("Some StrIng")]; + let actual = vec![FailureResult::ValidationError(Some(String::from( + "a very long string that contains sOmE sTrInG of different capitalization", + )))]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Complete + ); + + // -- Non-Matching Substring -- + + let expectation = vec![FailureCase::always().validation_error("Some String")]; + let actual = vec![validation_err("a very long string that doesn't contain it")]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Panic + ); + } + + #[test] + fn ignore_flaky() { + init_logger(); + + let expectation = vec![FailureCase::always().validation_error("blah").flaky()]; + let actual = vec![validation_err("some blah")]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Complete + ); + + let expectation = vec![FailureCase::always().validation_error("blah").flaky()]; + let actual = vec![]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Complete + ); + } + + #[test] + fn matches_multiple_errors() { + init_logger(); + + // -- matches all matching errors -- + + let expectation = vec![FailureCase::always().validation_error("blah")]; + let actual = vec![ + validation_err("some blah"), + validation_err("some other blah"), + ]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Complete + ); + + // -- but not all errors -- + + let expectation = vec![FailureCase::always().validation_error("blah")]; + let actual = vec![ + validation_err("some blah"), + validation_err("some other blah"), + validation_err("something else"), + ]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Panic + ); + } + + #[test] + fn multi_reason_error() { + init_logger(); + + let expectation = vec![FailureCase::default() + .validation_error("blah") + .panic("panik")]; + let actual = vec![ + validation_err("my blah blah validation error"), + panic("my panik"), + ]; + + assert_eq!( + super::expectations_match_failures(&expectation, actual), + ExpectationMatchResult::Complete + ); + } +} diff --git a/tests/src/image.rs b/tests/src/image.rs index e50fd43e7f..08e30ae2ef 100644 --- a/tests/src/image.rs +++ b/tests/src/image.rs @@ -1,9 +1,14 @@ -use std::{borrow::Cow, ffi::OsStr, io, path::Path}; +//! Image comparison utilities + +use std::{borrow::Cow, ffi::OsStr, path::Path}; use wgpu::util::{align_to, DeviceExt}; use wgpu::*; -fn read_png(path: impl AsRef, width: u32, height: u32) -> Option> { +use crate::TestingContext; + +#[cfg(not(target_arch = "wasm32"))] +async fn read_png(path: impl AsRef, width: u32, height: u32) -> Option> { let data = match std::fs::read(&path) { Ok(f) => f, Err(e) => { @@ -15,7 +20,7 @@ fn read_png(path: impl AsRef, width: u32, height: u32) -> Option> return None; } }; - let decoder = png::Decoder::new(io::Cursor::new(data)); + let decoder = png::Decoder::new(std::io::Cursor::new(data)); let mut reader = decoder.read_info().ok()?; let mut buffer = vec![0; reader.output_buffer_size()]; @@ -40,32 +45,26 @@ fn read_png(path: impl AsRef, width: u32, height: u32) -> Option> Some(buffer) } -#[allow(unused_variables)] -fn write_png( +#[cfg(not(target_arch = "wasm32"))] +async fn write_png( path: impl AsRef, width: u32, height: u32, data: &[u8], compression: png::Compression, ) { - #[cfg(not(target_arch = "wasm32"))] - { - let file = io::BufWriter::new(std::fs::File::create(path).unwrap()); + let file = std::io::BufWriter::new(std::fs::File::create(path).unwrap()); - let mut encoder = png::Encoder::new(file, width, height); - encoder.set_color(png::ColorType::Rgba); - encoder.set_depth(png::BitDepth::Eight); - encoder.set_compression(compression); - let mut writer = encoder.write_header().unwrap(); + let mut encoder = png::Encoder::new(file, width, height); + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Eight); + encoder.set_compression(compression); + let mut writer = encoder.write_header().unwrap(); - writer.write_image_data(data).unwrap(); - } -} - -pub fn calc_difference(lhs: u8, rhs: u8) -> u8 { - (lhs as i16 - rhs as i16).unsigned_abs() as u8 + writer.write_image_data(data).unwrap(); } +#[cfg_attr(target_arch = "wasm32", allow(unused))] fn add_alpha(input: &[u8]) -> Vec { input .chunks_exact(3) @@ -73,6 +72,7 @@ fn add_alpha(input: &[u8]) -> Vec { .collect() } +#[cfg_attr(target_arch = "wasm32", allow(unused))] fn remove_alpha(input: &[u8]) -> Vec { input .chunks_exact(4) @@ -148,7 +148,8 @@ impl ComparisonType { } } -pub fn compare_image_output( +#[cfg(not(target_arch = "wasm32"))] +pub async fn compare_image_output( path: impl AsRef + AsRef, adapter_info: &wgt::AdapterInfo, width: u32, @@ -156,29 +157,47 @@ pub fn compare_image_output( test_with_alpha: &[u8], checks: &[ComparisonType], ) { - #[cfg(not(target_arch = "wasm32"))] - { - use std::{ffi::OsString, str::FromStr}; - - let reference_with_alpha = read_png(&path, width, height); - - let reference = match reference_with_alpha { - Some(v) => remove_alpha(&v), - None => { - write_png( - &path, - width, - height, - test_with_alpha, - png::Compression::Best, - ); - return; - } - }; - let test = remove_alpha(test_with_alpha); + use std::{ffi::OsString, str::FromStr}; + + let reference_path = Path::new(&path); + let reference_with_alpha = read_png(&path, width, height).await; + + let reference = match reference_with_alpha { + Some(v) => remove_alpha(&v), + None => { + write_png( + &path, + width, + height, + test_with_alpha, + png::Compression::Best, + ) + .await; + return; + } + }; + let test = remove_alpha(test_with_alpha); - assert_eq!(reference.len(), test.len()); + assert_eq!(reference.len(), test.len()); + let file_stem = reference_path.file_stem().unwrap().to_string_lossy(); + let renderer = format!( + "{}-{}-{}", + adapter_info.backend.to_str(), + sanitize_for_path(&adapter_info.name), + sanitize_for_path(&adapter_info.driver) + ); + // Determine the paths to write out the various intermediate files + let actual_path = Path::new(&path).with_file_name( + OsString::from_str(&format!("{}-{}-actual.png", file_stem, renderer)).unwrap(), + ); + let difference_path = Path::new(&path).with_file_name( + OsString::from_str(&format!("{}-{}-difference.png", file_stem, renderer,)).unwrap(), + ); + + let mut all_passed; + let magma_image_with_alpha; + { let reference_flip = nv_flip::FlipImageRgb8::with_data(width, height, &reference); let test_flip = nv_flip::FlipImageRgb8::with_data(width, height, &test); @@ -189,7 +208,6 @@ pub fn compare_image_output( ); let mut pool = nv_flip::FlipPool::from_image(&error_map_flip); - let reference_path = Path::new(&path); println!( "Starting image comparison test with reference image \"{}\"", reference_path.display() @@ -198,59 +216,57 @@ pub fn compare_image_output( print_flip(&mut pool); // If there are no checks, we want to fail the test. - let mut all_passed = !checks.is_empty(); + all_passed = !checks.is_empty(); // We always iterate all of these, as the call to check prints for check in checks { all_passed &= check.check(&mut pool); } - let file_stem = reference_path.file_stem().unwrap().to_string_lossy(); - let renderer = format!( - "{}-{}-{}", - adapter_info.backend.to_str(), - sanitize_for_path(&adapter_info.name), - sanitize_for_path(&adapter_info.driver) - ); - // Determine the paths to write out the various intermediate files - let actual_path = Path::new(&path).with_file_name( - OsString::from_str(&format!("{}-{}-actual.png", file_stem, renderer)).unwrap(), - ); - let difference_path = Path::new(&path).with_file_name( - OsString::from_str(&format!("{}-{}-difference.png", file_stem, renderer,)).unwrap(), - ); - // Convert the error values to a false color reprensentation let magma_image = error_map_flip .apply_color_lut(&nv_flip::magma_lut()) .to_vec(); - let magma_image_with_alpha = add_alpha(&magma_image); - - write_png( - actual_path, - width, - height, - test_with_alpha, - png::Compression::Fast, - ); - write_png( - difference_path, - width, - height, - &magma_image_with_alpha, - png::Compression::Fast, - ); + magma_image_with_alpha = add_alpha(&magma_image); + } - if !all_passed { - panic!("Image data mismatch!") - } + write_png( + actual_path, + width, + height, + test_with_alpha, + png::Compression::Fast, + ) + .await; + write_png( + difference_path, + width, + height, + &magma_image_with_alpha, + png::Compression::Fast, + ) + .await; + + if !all_passed { + panic!("Image data mismatch!") } +} +#[cfg(target_arch = "wasm32")] +pub async fn compare_image_output( + path: impl AsRef + AsRef, + adapter_info: &wgt::AdapterInfo, + width: u32, + height: u32, + test_with_alpha: &[u8], + checks: &[ComparisonType], +) { #[cfg(target_arch = "wasm32")] { let _ = (path, adapter_info, width, height, test_with_alpha, checks); } } +#[cfg_attr(target_arch = "wasm32", allow(unused))] fn sanitize_for_path(s: &str) -> String { s.chars() .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' }) @@ -374,7 +390,7 @@ fn copy_texture_to_buffer_with_aspect( aspect: TextureAspect, ) { let (block_width, block_height) = texture.format().block_dimensions(); - let block_size = texture.format().block_size(Some(aspect)).unwrap(); + let block_size = texture.format().block_copy_size(Some(aspect)).unwrap(); let bytes_per_row = align_to( (texture.width() / block_width) * block_size, COPY_BYTES_PER_ROW_ALIGNMENT, @@ -417,13 +433,6 @@ fn copy_texture_to_buffer( } TextureFormat::Depth24PlusStencil8 => { copy_via_compute(device, encoder, texture, buffer, TextureAspect::DepthOnly); - // copy_via_compute( - // device, - // encoder, - // texture, - // buffer_stencil.as_ref().unwrap(), - // TextureAspect::StencilOnly, - // ); copy_texture_to_buffer_with_aspect( encoder, texture, @@ -487,7 +496,7 @@ impl ReadbackBuffers { let mut buffer_depth_bytes_per_row = (texture.width() / block_width) * texture .format() - .block_size(Some(TextureAspect::DepthOnly)) + .block_copy_size(Some(TextureAspect::DepthOnly)) .unwrap_or(4); if should_align_buffer_size { buffer_depth_bytes_per_row = @@ -501,7 +510,7 @@ impl ReadbackBuffers { (texture.width() / block_width) * texture .format() - .block_size(Some(TextureAspect::StencilOnly)) + .block_copy_size(Some(TextureAspect::StencilOnly)) .unwrap_or(4), COPY_BYTES_PER_ROW_ALIGNMENT, ); @@ -528,8 +537,8 @@ impl ReadbackBuffers { buffer_stencil: Some(buffer_stencil), } } else { - let mut bytes_per_row = - (texture.width() / block_width) * texture.format().block_size(None).unwrap_or(4); + let mut bytes_per_row = (texture.width() / block_width) + * texture.format().block_copy_size(None).unwrap_or(4); if should_align_buffer_size { bytes_per_row = align_to(bytes_per_row, COPY_BYTES_PER_ROW_ALIGNMENT); } @@ -556,18 +565,18 @@ impl ReadbackBuffers { copy_texture_to_buffer(device, encoder, texture, &self.buffer, &self.buffer_stencil); } - fn retrieve_buffer( + async fn retrieve_buffer( &self, - device: &Device, + ctx: &TestingContext, buffer: &Buffer, aspect: Option, ) -> Vec { let buffer_slice = buffer.slice(..); buffer_slice.map_async(MapMode::Read, |_| ()); - device.poll(Maintain::Wait); + ctx.async_poll(Maintain::wait()).await.panic_on_timeout(); let (block_width, block_height) = self.texture_format.block_dimensions(); let expected_bytes_per_row = (self.texture_width / block_width) - * self.texture_format.block_size(aspect).unwrap_or(4); + * self.texture_format.block_copy_size(aspect).unwrap_or(4); let expected_buffer_size = expected_bytes_per_row * (self.texture_height / block_height) * self.texture_depth_or_array_layers; @@ -593,30 +602,44 @@ impl ReadbackBuffers { } } - pub fn are_zero(&self, device: &Device) -> bool { - let is_zero = |device: &Device, buffer: &Buffer, aspect: Option| -> bool { - let is_zero = self - .retrieve_buffer(device, buffer, aspect) - .iter() - .all(|b| *b == 0); - buffer.unmap(); - is_zero - }; + async fn is_zero( + &self, + ctx: &TestingContext, + buffer: &Buffer, + aspect: Option, + ) -> bool { + let is_zero = self + .retrieve_buffer(ctx, buffer, aspect) + .await + .iter() + .all(|b| *b == 0); + buffer.unmap(); + is_zero + } - let buffer_zero = is_zero(device, &self.buffer, self.buffer_aspect()); + pub async fn are_zero(&self, ctx: &TestingContext) -> bool { + let buffer_zero = self.is_zero(ctx, &self.buffer, self.buffer_aspect()).await; let mut stencil_buffer_zero = true; if let Some(buffer) = &self.buffer_stencil { - stencil_buffer_zero = is_zero(device, buffer, Some(TextureAspect::StencilOnly)); + stencil_buffer_zero = self + .is_zero(ctx, buffer, Some(TextureAspect::StencilOnly)) + .await; }; buffer_zero && stencil_buffer_zero } - pub fn check_buffer_contents(&self, device: &Device, expected_data: &[u8]) -> bool { - let result = self - .retrieve_buffer(device, &self.buffer, self.buffer_aspect()) - .iter() - .eq(expected_data.iter()); + pub async fn assert_buffer_contents(&self, ctx: &TestingContext, expected_data: &[u8]) { + let result_buffer = self + .retrieve_buffer(ctx, &self.buffer, self.buffer_aspect()) + .await; + assert!( + result_buffer.len() >= expected_data.len(), + "Result buffer ({}) smaller than expected buffer ({})", + result_buffer.len(), + expected_data.len() + ); + let result_buffer = &result_buffer[..expected_data.len()]; + assert_eq!(result_buffer, expected_data); self.buffer.unmap(); - result } } diff --git a/tests/src/init.rs b/tests/src/init.rs new file mode 100644 index 0000000000..9a21c98471 --- /dev/null +++ b/tests/src/init.rs @@ -0,0 +1,148 @@ +use wgpu::{Adapter, Device, Instance, Queue}; +use wgt::{Backends, Features, Limits}; + +/// Initialize the logger for the test runner. +pub fn init_logger() { + // We don't actually care if it fails + #[cfg(not(target_arch = "wasm32"))] + let _ = env_logger::try_init(); + #[cfg(target_arch = "wasm32")] + let _ = console_log::init_with_level(log::Level::Info); +} + +/// Initialize a wgpu instance with the options from the environment. +pub fn initialize_instance() -> Instance { + // We ignore `WGPU_BACKEND` for now, merely using test filtering to only run a single backend's tests. + // + // We can potentially work support back into the test runner in the future, but as the adapters are matched up + // based on adapter index, removing some backends messes up the indexes in annoying ways. + // + // WORKAROUND for https://github.com/rust-lang/cargo/issues/7160: + // `--no-default-features` is not passed through correctly to the test runner. + // We use it whenever we want to explicitly run with webgl instead of webgpu. + // To "disable" webgpu regardless, we do this by removing the webgpu backend whenever we see + // the webgl feature. + let backends = if cfg!(feature = "webgl") { + Backends::all() - Backends::BROWSER_WEBGPU + } else { + Backends::all() + }; + let dx12_shader_compiler = wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(); + let gles_minor_version = wgpu::util::gles_minor_version_from_env().unwrap_or_default(); + Instance::new(wgpu::InstanceDescriptor { + backends, + flags: wgpu::InstanceFlags::debugging().with_env(), + dx12_shader_compiler, + gles_minor_version, + }) +} + +/// Initialize a wgpu adapter, taking the `n`th adapter from the instance. +pub async fn initialize_adapter(adapter_index: usize) -> (Instance, Adapter, Option) { + let instance = initialize_instance(); + #[allow(unused_variables)] + let _surface: wgpu::Surface; + let surface_guard: Option; + + // Create a canvas iff we need a WebGL2RenderingContext to have a working device. + #[cfg(not(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "webgl") + )))] + { + surface_guard = None; + } + #[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "webgl") + ))] + { + // On wasm, append a canvas to the document body for initializing the adapter + let canvas = initialize_html_canvas(); + + _surface = instance + .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) + .expect("could not create surface from canvas"); + + surface_guard = Some(SurfaceGuard { canvas }); + } + + cfg_if::cfg_if! { + if #[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))] { + let adapter_iter = instance.enumerate_adapters(wgpu::Backends::all()); + let adapter_count = adapter_iter.len(); + let adapter = adapter_iter.into_iter() + .nth(adapter_index) + .unwrap_or_else(|| panic!("Tried to get index {adapter_index} adapter, but adapter list was only {adapter_count} long. Is .gpuconfig out of date?")); + } else { + assert_eq!(adapter_index, 0); + let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions::default()).await.unwrap(); + } + } + + log::info!("Testing using adapter: {:#?}", adapter.get_info()); + + (instance, adapter, surface_guard) +} + +/// Initialize a wgpu device from a given adapter. +pub async fn initialize_device( + adapter: &Adapter, + features: Features, + limits: Limits, +) -> (Device, Queue) { + let bundle = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: features, + required_limits: limits, + }, + None, + ) + .await; + + match bundle { + Ok(b) => b, + Err(e) => panic!("Failed to initialize device: {e}"), + } +} + +/// Create a canvas for testing. +#[cfg(target_arch = "wasm32")] +pub fn initialize_html_canvas() -> web_sys::HtmlCanvasElement { + use wasm_bindgen::JsCast; + + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| { + let canvas = doc.create_element("Canvas").unwrap(); + canvas.dyn_into::().ok() + }) + .expect("couldn't create canvas") +} + +pub struct SurfaceGuard { + #[cfg(target_arch = "wasm32")] + #[allow(unused)] + canvas: web_sys::HtmlCanvasElement, +} + +impl SurfaceGuard { + #[cfg(all( + target_arch = "wasm32", + any(target_os = "emscripten", feature = "webgl") + ))] + pub(crate) fn check_for_unreported_errors(&self) -> bool { + use wasm_bindgen::JsCast; + + self.canvas + .get_context("webgl2") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap() + .get_error() + != web_sys::WebGl2RenderingContext::NO_ERROR + } +} diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 236b353386..08f464e5aa 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,594 +1,30 @@ -//! This module contains common test-only code that needs to be shared between the examples and the tests. -#![allow(dead_code)] // This module is used in a lot of contexts and only parts of it will be used - -use std::panic::{catch_unwind, AssertUnwindSafe}; - -use wgpu::{Adapter, Device, DownlevelFlags, Instance, Queue, Surface}; -use wgt::{Backends, DeviceDescriptor, DownlevelCapabilities, Features, Limits}; +//! Test utilities for the wgpu repository. +mod config; +mod expectations; pub mod image; +mod init; mod isolation; - -pub use self::image::ComparisonType; - -const CANVAS_ID: &str = "test-canvas"; - -async fn initialize_device( - adapter: &Adapter, - features: Features, - limits: Limits, -) -> (Device, Queue) { - let bundle = adapter - .request_device( - &DeviceDescriptor { - label: None, - features, - limits, - }, - None, - ) - .await; - - match bundle { - Ok(b) => b, - Err(e) => panic!("Failed to initialize device: {e}"), - } -} - -pub struct TestingContext { - pub adapter: Adapter, - pub adapter_info: wgt::AdapterInfo, - pub adapter_downlevel_capabilities: wgt::DownlevelCapabilities, - pub device: Device, - pub device_features: wgt::Features, - pub device_limits: wgt::Limits, - pub queue: Queue, -} - -fn lowest_downlevel_properties() -> DownlevelCapabilities { - DownlevelCapabilities { - flags: wgt::DownlevelFlags::empty(), - limits: wgt::DownlevelLimits {}, - shader_model: wgt::ShaderModel::Sm2, - } -} - -/// Conditions under which a test should fail or be skipped. -/// -/// By passing a `FailureCase` to [`TestParameters::expect_fail`], you can -/// mark a test as expected to fail under the indicated conditions. By -/// passing it to [`TestParameters::skip`], you can request that the -/// test be skipped altogether. -/// -/// If a field is `None`, then that field does not restrict matches. For -/// example: -/// -/// ``` -/// # use wgpu_test::FailureCase; -/// FailureCase { -/// backends: Some(wgpu::Backends::DX11 | wgpu::Backends::DX12), -/// vendor: None, -/// adapter: Some("RTX"), -/// driver: None, -/// } -/// # ; -/// ``` -/// -/// This applies to all cards with `"RTX'` in their name on either -/// Direct3D backend, no matter the vendor ID or driver name. -/// -/// The strings given here need only appear as a substring in the -/// corresponding [`AdapterInfo`] fields. The comparison is -/// case-insensitive. -/// -/// The default value of `FailureCase` applies to any test case. That -/// is, there are no criteria to constrain the match. -/// -/// [`AdapterInfo`]: wgt::AdapterInfo -#[derive(Default)] -pub struct FailureCase { - /// Backends expected to fail, or `None` for any backend. - /// - /// If this is `None`, or if the test is using one of the backends - /// in `backends`, then this `FailureCase` applies. - pub backends: Option, - - /// Vendor expected to fail, or `None` for any vendor. - /// - /// If `Some`, this must match [`AdapterInfo::device`], which is - /// usually the PCI device id. Otherwise, this `FailureCase` - /// applies regardless of vendor. - /// - /// [`AdapterInfo::device`]: wgt::AdapterInfo::device - pub vendor: Option, - - /// Name of adaper expected to fail, or `None` for any adapter name. - /// - /// If this is `Some(s)` and `s` is a substring of - /// [`AdapterInfo::name`], then this `FailureCase` applies. If - /// this is `None`, the adapter name isn't considered. - /// - /// [`AdapterInfo::name`]: wgt::AdapterInfo::name - pub adapter: Option<&'static str>, - - /// Name of driver expected to fail, or `None` for any driver name. - /// - /// If this is `Some(s)` and `s` is a substring of - /// [`AdapterInfo::driver`], then this `FailureCase` applies. If - /// this is `None`, the driver name isn't considered. - /// - /// [`AdapterInfo::driver`]: wgt::AdapterInfo::driver - pub driver: Option<&'static str>, -} - -impl FailureCase { - /// This case applies to all tests. - pub fn always() -> Self { - FailureCase::default() - } - - /// This case applies to no tests. - pub fn never() -> Self { - FailureCase { - backends: Some(wgpu::Backends::empty()), - ..FailureCase::default() - } - } - - /// Tests running on any of the given backends. - pub fn backend(backends: wgpu::Backends) -> Self { - FailureCase { - backends: Some(backends), - ..FailureCase::default() - } - } - - /// Tests running on `adapter`. - /// - /// For this case to apply, the `adapter` string must appear as a substring - /// of the adapter's [`AdapterInfo::name`]. The comparison is - /// case-insensitive. - /// - /// [`AdapterInfo::name`]: wgt::AdapterInfo::name - pub fn adapter(adapter: &'static str) -> Self { - FailureCase { - adapter: Some(adapter), - ..FailureCase::default() - } - } - - /// Tests running on `backend` and `adapter`. - /// - /// For this case to apply, the test must be using an adapter for one of the - /// given `backend` bits, and `adapter` string must appear as a substring of - /// the adapter's [`AdapterInfo::name`]. The string comparison is - /// case-insensitive. - /// - /// [`AdapterInfo::name`]: wgt::AdapterInfo::name - pub fn backend_adapter(backends: wgpu::Backends, adapter: &'static str) -> Self { - FailureCase { - backends: Some(backends), - adapter: Some(adapter), - ..FailureCase::default() - } - } - - /// Tests running under WebGL. - /// - /// Because of wasm's limited ability to recover from errors, we - /// usually need to skip the test altogether if it's not - /// supported, so this should be usually used with - /// [`TestParameters::skip`]. - pub fn webgl2() -> Self { - #[cfg(target_arch = "wasm32")] - let case = FailureCase::backend(wgpu::Backends::GL); - #[cfg(not(target_arch = "wasm32"))] - let case = FailureCase::never(); - case - } - - /// Tests running on the MoltenVK Vulkan driver on macOS. - pub fn molten_vk() -> Self { - FailureCase { - backends: Some(wgpu::Backends::VULKAN), - driver: Some("MoltenVK"), - ..FailureCase::default() - } - } - - /// Test whether `self` applies to `info`. - /// - /// If it does, return a `FailureReasons` whose set bits indicate - /// why. If it doesn't, return `None`. - /// - /// The caller is responsible for converting the string-valued - /// fields of `info` to lower case, to ensure case-insensitive - /// matching. - fn applies_to(&self, info: &wgt::AdapterInfo) -> Option { - let mut reasons = FailureReasons::empty(); - - if let Some(backends) = self.backends { - if !backends.contains(wgpu::Backends::from(info.backend)) { - return None; - } - reasons.set(FailureReasons::BACKEND, true); - } - if let Some(vendor) = self.vendor { - if vendor != info.vendor { - return None; - } - reasons.set(FailureReasons::VENDOR, true); - } - if let Some(adapter) = self.adapter { - let adapter = adapter.to_lowercase(); - if !info.name.contains(&adapter) { - return None; - } - reasons.set(FailureReasons::ADAPTER, true); - } - if let Some(driver) = self.driver { - let driver = driver.to_lowercase(); - if !info.driver.contains(&driver) { - return None; - } - reasons.set(FailureReasons::DRIVER, true); - } - - // If we got this far but no specific reasons were triggered, then this - // must be a wildcard. - if reasons.is_empty() { - Some(FailureReasons::ALWAYS) - } else { - Some(reasons) - } - } -} - -// This information determines if a test should run. -pub struct TestParameters { - pub required_features: Features, - pub required_downlevel_properties: DownlevelCapabilities, - pub required_limits: Limits, - - /// Conditions under which this test should be skipped. - pub skips: Vec, - - /// Conditions under which this test should be run, but is expected to fail. - pub failures: Vec, -} - -impl Default for TestParameters { - fn default() -> Self { - Self { - required_features: Features::empty(), - required_downlevel_properties: lowest_downlevel_properties(), - required_limits: Limits::downlevel_webgl2_defaults(), - skips: Vec::new(), - failures: Vec::new(), - } - } -} - -bitflags::bitflags! { - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct FailureReasons: u8 { - const BACKEND = 1 << 0; - const VENDOR = 1 << 1; - const ADAPTER = 1 << 2; - const DRIVER = 1 << 3; - const ALWAYS = 1 << 4; - } -} - -// Builder pattern to make it easier -impl TestParameters { - /// Set of common features that most internal tests require for readback. - pub fn test_features_limits(self) -> Self { - self.features(Features::MAPPABLE_PRIMARY_BUFFERS | Features::VERTEX_WRITABLE_STORAGE) - .limits(wgpu::Limits::downlevel_defaults()) - } - - /// Set the list of features this test requires. - pub fn features(mut self, features: Features) -> Self { - self.required_features |= features; - self - } - - pub fn downlevel_flags(mut self, downlevel_flags: DownlevelFlags) -> Self { - self.required_downlevel_properties.flags |= downlevel_flags; - self - } - - /// Set the limits needed for the test. - pub fn limits(mut self, limits: Limits) -> Self { - self.required_limits = limits; - self - } - - /// Mark the test as always failing, but not to be skipped. - pub fn expect_fail(mut self, when: FailureCase) -> Self { - self.failures.push(when); - self - } - - /// Mark the test as always failing, and needing to be skipped. - pub fn skip(mut self, when: FailureCase) -> Self { - self.skips.push(when); - self - } -} - -pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(TestingContext)) { - // We don't actually care if it fails - #[cfg(not(target_arch = "wasm32"))] - let _ = env_logger::try_init(); - #[cfg(target_arch = "wasm32")] - let _ = console_log::init_with_level(log::Level::Info); - - let _test_guard = isolation::OneTestPerProcessGuard::new(); - - let (adapter, _surface_guard) = initialize_adapter(); - - let adapter_info = adapter.get_info(); - - // Produce a lower-case version of the adapter info, for comparison against - // `parameters.skips` and `parameters.failures`. - let adapter_lowercase_info = wgt::AdapterInfo { - name: adapter_info.name.to_lowercase(), - driver: adapter_info.driver.to_lowercase(), - ..adapter_info.clone() - }; - - let adapter_features = adapter.features(); - let adapter_limits = adapter.limits(); - let adapter_downlevel_capabilities = adapter.get_downlevel_capabilities(); - - let missing_features = parameters.required_features - adapter_features; - if !missing_features.is_empty() { - log::info!("TEST SKIPPED: MISSING FEATURES {:?}", missing_features); - return; - } - - if !parameters.required_limits.check_limits(&adapter_limits) { - log::info!("TEST SKIPPED: LIMIT TOO LOW"); - return; - } - - let missing_downlevel_flags = - parameters.required_downlevel_properties.flags - adapter_downlevel_capabilities.flags; - if !missing_downlevel_flags.is_empty() { - log::info!( - "TEST SKIPPED: MISSING DOWNLEVEL FLAGS {:?}", - missing_downlevel_flags - ); - return; - } - - if adapter_downlevel_capabilities.shader_model - < parameters.required_downlevel_properties.shader_model - { - log::info!( - "TEST SKIPPED: LOW SHADER MODEL {:?}", - adapter_downlevel_capabilities.shader_model - ); - return; - } - - let (device, queue) = pollster::block_on(initialize_device( - &adapter, - parameters.required_features, - parameters.required_limits.clone(), - )); - - let context = TestingContext { - adapter, - adapter_info, - adapter_downlevel_capabilities, - device, - device_features: parameters.required_features, - device_limits: parameters.required_limits, - queue, - }; - - // Check if we should skip the test altogether. - if let Some(skip_reason) = parameters - .skips - .iter() - .find_map(|case| case.applies_to(&adapter_lowercase_info)) - { - log::info!("EXPECTED TEST FAILURE SKIPPED: {:?}", skip_reason); - return; - } - - // Determine if we expect this test to fail, and if so, why. - let expected_failure_reason = parameters - .failures - .iter() - .find_map(|case| case.applies_to(&adapter_lowercase_info)); - - // Run the test, and catch panics (possibly due to failed assertions). - let panicked = catch_unwind(AssertUnwindSafe(|| test_function(context))).is_err(); - - // Check whether any validation errors were reported during the test run. - cfg_if::cfg_if!( - if #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] { - let canary_set = wgpu::hal::VALIDATION_CANARY.get_and_reset(); - } else { - let canary_set = _surface_guard.unwrap().check_for_unreported_errors(); - } - ); - - // Summarize reasons for actual failure, if any. - let failure_cause = match (panicked, canary_set) { - (true, true) => Some("PANIC AND VALIDATION ERROR"), - (true, false) => Some("PANIC"), - (false, true) => Some("VALIDATION ERROR"), - (false, false) => None, - }; - - // Compare actual results against expectations. - match (failure_cause, expected_failure_reason) { - // The test passed, as expected. - (None, None) => {} - // The test failed unexpectedly. - (Some(cause), None) => { - panic!("UNEXPECTED TEST FAILURE DUE TO {cause}") - } - // The test passed unexpectedly. - (None, Some(reason)) => { - panic!("UNEXPECTED TEST PASS: {reason:?}"); - } - // The test failed, as expected. - (Some(cause), Some(reason_expected)) => { - log::info!( - "EXPECTED FAILURE DUE TO {} (expected because of {:?})", - cause, - reason_expected - ); - } - } -} - -fn initialize_adapter() -> (Adapter, Option) { - let instance = initialize_instance(); - let surface_guard: Option; - let compatible_surface; - - // Create a canvas iff we need a WebGL2RenderingContext to have a working device. - #[cfg(not(all( - target_arch = "wasm32", - any(target_os = "emscripten", feature = "webgl") - )))] - { - surface_guard = None; - compatible_surface = None; - } - #[cfg(all( - target_arch = "wasm32", - any(target_os = "emscripten", feature = "webgl") - ))] - { - // On wasm, append a canvas to the document body for initializing the adapter - let canvas = create_html_canvas(); - - // We use raw_window_handle here, as create_surface_from_canvas is not implemented on emscripten. - struct WindowHandle; - unsafe impl raw_window_handle::HasRawWindowHandle for WindowHandle { - fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { - raw_window_handle::RawWindowHandle::Web({ - let mut handle = raw_window_handle::WebWindowHandle::empty(); - handle.id = 1; - handle - }) - } - } - unsafe impl raw_window_handle::HasRawDisplayHandle for WindowHandle { - fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { - raw_window_handle::RawDisplayHandle::Web( - raw_window_handle::WebDisplayHandle::empty(), - ) - } - } - - let surface = unsafe { - instance - .create_surface(&WindowHandle) - .expect("could not create surface from canvas") - }; - - surface_guard = Some(SurfaceGuard { canvas }); - - compatible_surface = Some(surface); - } - - let compatible_surface: Option<&Surface> = compatible_surface.as_ref(); - let adapter = pollster::block_on(wgpu::util::initialize_adapter_from_env_or_default( - &instance, - compatible_surface, - )) - .expect("could not find suitable adapter on the system"); - - (adapter, surface_guard) -} - -pub fn initialize_instance() -> Instance { - let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(Backends::all); - let dx12_shader_compiler = wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(); - let gles_minor_version = wgpu::util::gles_minor_version_from_env().unwrap_or_default(); - Instance::new(wgpu::InstanceDescriptor { - backends, - dx12_shader_compiler, - gles_minor_version, - }) -} - -// Public because it is used by tests of interacting with canvas -pub struct SurfaceGuard { - #[cfg(target_arch = "wasm32")] - pub canvas: web_sys::HtmlCanvasElement, -} - -impl SurfaceGuard { - fn check_for_unreported_errors(&self) -> bool { - cfg_if::cfg_if! { - if #[cfg(all(target_arch = "wasm32", any(target_os = "emscripten", feature = "webgl")))] { - use wasm_bindgen::JsCast; - - self.canvas - .get_context("webgl2") - .unwrap() - .unwrap() - .dyn_into::() - .unwrap() - .get_error() - != web_sys::WebGl2RenderingContext::NO_ERROR - } else { - false - } - } - } -} - -#[cfg(all( - target_arch = "wasm32", - any(target_os = "emscripten", feature = "webgl") -))] -impl Drop for SurfaceGuard { - fn drop(&mut self) { - delete_html_canvas(); - } -} +pub mod native; +mod params; +mod poll; +mod report; +mod run; #[cfg(target_arch = "wasm32")] -pub fn create_html_canvas() -> web_sys::HtmlCanvasElement { - use wasm_bindgen::JsCast; - - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| { - let body = doc.body().unwrap(); - let canvas = doc.create_element("Canvas").unwrap(); - canvas.set_attribute("data-raw-handle", "1").unwrap(); - canvas.set_id(CANVAS_ID); - body.append_child(&canvas).unwrap(); - canvas.dyn_into::().ok() - }) - .expect("couldn't append canvas to document body") -} - -#[cfg(all( - target_arch = "wasm32", - any(target_os = "emscripten", feature = "webgl") -))] -fn delete_html_canvas() { - if let Some(document) = web_sys::window().and_then(|win| win.document()) { - if let Some(element) = document.get_element_by_id(CANVAS_ID) { - element.remove(); - } - }; -} +pub use init::initialize_html_canvas; -// Run some code in an error scope and assert that validation fails. +pub use self::image::ComparisonType; +pub use config::GpuTestConfiguration; +#[doc(hidden)] +pub use ctor::ctor; +pub use expectations::{FailureApplicationReasons, FailureBehavior, FailureCase, FailureReason}; +pub use init::{initialize_adapter, initialize_device, initialize_instance}; +pub use params::TestParameters; +pub use run::{execute_test, TestingContext}; +pub use wgpu_macros::gpu_test; + +/// Run some code in an error scope and assert that validation fails. pub fn fail(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T { device.push_error_scope(wgpu::ErrorFilter::Validation); let result = callback(); @@ -597,7 +33,7 @@ pub fn fail(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T { result } -// Run some code in an error scope and assert that validation succeeds. +/// Run some code in an error scope and assert that validation succeeds. pub fn valid(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T { device.push_error_scope(wgpu::ErrorFilter::Validation); let result = callback(); @@ -606,8 +42,8 @@ pub fn valid(device: &wgpu::Device, callback: impl FnOnce() -> T) -> T { result } -// Run some code in an error scope and assert that validation succeeds or fails depending on the -// provided `should_fail` boolean. +/// Run some code in an error scope and assert that validation succeeds or fails depending on the +/// provided `should_fail` boolean. pub fn fail_if(device: &wgpu::Device, should_fail: bool, callback: impl FnOnce() -> T) -> T { if should_fail { fail(device, callback) @@ -615,3 +51,19 @@ pub fn fail_if(device: &wgpu::Device, should_fail: bool, callback: impl FnOnc valid(device, callback) } } + +/// Adds the necissary main function for our gpu test harness. +#[macro_export] +macro_rules! gpu_test_main { + () => { + #[cfg(target_arch = "wasm32")] + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + #[cfg(target_arch = "wasm32")] + fn main() {} + + #[cfg(not(target_arch = "wasm32"))] + fn main() -> $crate::native::MainResult { + $crate::native::main() + } + }; +} diff --git a/tests/src/native.rs b/tests/src/native.rs new file mode 100644 index 0000000000..3d328b4ff1 --- /dev/null +++ b/tests/src/native.rs @@ -0,0 +1,110 @@ +#![cfg(not(target_arch = "wasm32"))] +//! Infrastructure for the native, `cargo-nextest` based harness. +//! +//! This is largly used by [`gpu_test_main`](crate::gpu_test_main) and [`gpu_test`](crate::gpu_test). + +use std::{future::Future, pin::Pin}; + +use parking_lot::Mutex; + +use crate::{ + config::GpuTestConfiguration, params::TestInfo, report::AdapterReport, run::execute_test, +}; + +type NativeTestFuture = Pin + Send>>; + +struct NativeTest { + name: String, + future: NativeTestFuture, +} + +impl NativeTest { + fn from_configuration( + config: GpuTestConfiguration, + adapter: &AdapterReport, + adapter_index: usize, + ) -> Self { + let backend = adapter.info.backend; + let device_name = &adapter.info.name; + + let test_info = TestInfo::from_configuration(&config, adapter); + + let full_name = format!( + "[{running_msg}] [{backend:?}/{device_name}/{adapter_index}] {base_name}", + running_msg = test_info.running_msg, + base_name = config.name, + ); + Self { + name: full_name, + future: Box::pin(async move { + // Enable metal validation layers if we're running on metal. + // + // This is a process-wide setting as it's via environment variable, but all + // tests are run in separate processes. + // + // We don't do this in the instance initializer as we don't want to enable + // validation layers for the entire process, or other instances. + // + // We do not enable metal validation when running on moltenvk. + let metal_validation = backend == wgpu::Backend::Metal; + + let env_value = if metal_validation { "1" } else { "0" }; + std::env::set_var("MTL_DEBUG_LAYER", env_value); + // Metal Shader Validation is entirely broken in the paravirtualized CI environment. + // std::env::set_var("MTL_SHADER_VALIDATION", env_value); + + execute_test(config, Some(test_info), adapter_index).await; + }), + } + } + + pub fn into_trial(self) -> libtest_mimic::Trial { + libtest_mimic::Trial::test(self.name, || { + pollster::block_on(self.future); + Ok(()) + }) + } +} + +#[doc(hidden)] +pub static TEST_LIST: Mutex> = Mutex::new(Vec::new()); + +/// Return value for the main function. +pub type MainResult = anyhow::Result<()>; + +/// Main function that runs every gpu function once for every adapter on the system. +pub fn main() -> MainResult { + use anyhow::Context; + + use crate::report::GpuReport; + + let config_text = { + profiling::scope!("Reading .gpuconfig"); + &std::fs::read_to_string(format!("{}/../.gpuconfig", env!("CARGO_MANIFEST_DIR"))) + .context("Failed to read .gpuconfig, did you run the tests via `cargo xtask test`?")? + }; + let report = GpuReport::from_json(config_text).context("Could not parse .gpuconfig JSON")?; + + let mut test_guard = TEST_LIST.lock(); + execute_native(test_guard.drain(..).flat_map(|test| { + report + .devices + .iter() + .enumerate() + .map(move |(adapter_index, adapter)| { + NativeTest::from_configuration(test.clone(), adapter, adapter_index) + }) + })); + + Ok(()) +} + +fn execute_native(tests: impl IntoIterator) { + let args = libtest_mimic::Arguments::from_args(); + let trials = { + profiling::scope!("collecting tests"); + tests.into_iter().map(NativeTest::into_trial).collect() + }; + + libtest_mimic::run(&args, trials).exit_if_failed(); +} diff --git a/tests/src/params.rs b/tests/src/params.rs new file mode 100644 index 0000000000..2f54e65bbb --- /dev/null +++ b/tests/src/params.rs @@ -0,0 +1,171 @@ +use arrayvec::ArrayVec; +use wgt::{DownlevelCapabilities, DownlevelFlags, Features, Limits}; + +use crate::{ + report::AdapterReport, FailureApplicationReasons, FailureBehavior, FailureCase, + GpuTestConfiguration, +}; + +const LOWEST_DOWNLEVEL_PROPERTIES: wgpu::DownlevelCapabilities = DownlevelCapabilities { + flags: wgt::DownlevelFlags::empty(), + limits: wgt::DownlevelLimits {}, + shader_model: wgt::ShaderModel::Sm2, +}; + +/// This information determines if a test should run. +#[derive(Clone)] +pub struct TestParameters { + pub required_features: Features, + pub required_downlevel_caps: DownlevelCapabilities, + pub required_limits: Limits, + + /// Conditions under which this test should be skipped. + pub skips: Vec, + + /// Conditions under which this test should be run, but is expected to fail. + pub failures: Vec, +} + +impl Default for TestParameters { + fn default() -> Self { + Self { + required_features: Features::empty(), + required_downlevel_caps: LOWEST_DOWNLEVEL_PROPERTIES, + required_limits: Limits::downlevel_webgl2_defaults(), + skips: Vec::new(), + failures: Vec::new(), + } + } +} + +// Builder pattern to make it easier +impl TestParameters { + /// Set of common features that most internal tests require for compute and readback. + pub fn test_features_limits(self) -> Self { + self.downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) + .limits(wgpu::Limits::downlevel_defaults()) + } + + /// Set the list of features this test requires. + pub fn features(mut self, features: Features) -> Self { + self.required_features |= features; + self + } + + pub fn downlevel_flags(mut self, downlevel_flags: DownlevelFlags) -> Self { + self.required_downlevel_caps.flags |= downlevel_flags; + self + } + + /// Set the limits needed for the test. + pub fn limits(mut self, limits: Limits) -> Self { + self.required_limits = limits; + self + } + + /// Mark the test as always failing, but not to be skipped. + pub fn expect_fail(mut self, when: FailureCase) -> Self { + self.failures.push(when); + self + } + + /// Mark the test as always failing, and needing to be skipped. + pub fn skip(mut self, when: FailureCase) -> Self { + self.skips.push(when); + self + } +} + +/// Information about a test, including if if it should be skipped. +pub struct TestInfo { + pub skip: bool, + pub failure_application_reasons: FailureApplicationReasons, + pub failures: Vec, + pub running_msg: String, +} + +impl TestInfo { + pub(crate) fn from_configuration(test: &GpuTestConfiguration, adapter: &AdapterReport) -> Self { + // Figure out if a test is unsupported, and why. + let mut unsupported_reasons: ArrayVec<_, 4> = ArrayVec::new(); + let missing_features = test.params.required_features - adapter.features; + if !missing_features.is_empty() { + unsupported_reasons.push("Features"); + } + + if !test.params.required_limits.check_limits(&adapter.limits) { + unsupported_reasons.push("Limits"); + } + + let missing_downlevel_flags = + test.params.required_downlevel_caps.flags - adapter.downlevel_caps.flags; + if !missing_downlevel_flags.is_empty() { + unsupported_reasons.push("Downlevel Flags"); + } + + if test.params.required_downlevel_caps.shader_model > adapter.downlevel_caps.shader_model { + unsupported_reasons.push("Shader Model"); + } + + // Produce a lower-case version of the adapter info, for comparison against + // `parameters.skips` and `parameters.failures`. + let adapter_lowercase_info = wgt::AdapterInfo { + name: adapter.info.name.to_lowercase(), + driver: adapter.info.driver.to_lowercase(), + ..adapter.info.clone() + }; + + // Check if we should skip the test altogether. + let skip_application_reason = test + .params + .skips + .iter() + .find_map(|case| case.applies_to_adapter(&adapter_lowercase_info)); + + let mut applicable_cases = Vec::with_capacity(test.params.failures.len()); + let mut failure_application_reasons = FailureApplicationReasons::empty(); + let mut flaky = false; + for failure in &test.params.failures { + if let Some(reasons) = failure.applies_to_adapter(&adapter_lowercase_info) { + failure_application_reasons.insert(reasons); + applicable_cases.push(failure.clone()); + flaky |= matches!(failure.behavior, FailureBehavior::Ignore); + } + } + + let mut skip = false; + let running_msg = if let Some(reasons) = skip_application_reason { + skip = true; + + let names: ArrayVec<_, 4> = reasons.iter_names().map(|(name, _)| name).collect(); + let names_text = names.join(" | "); + + format!("Skipped Failure: {}", names_text) + } else if !unsupported_reasons.is_empty() { + skip = true; + format!("Unsupported: {}", unsupported_reasons.join(" | ")) + } else if !failure_application_reasons.is_empty() { + if cfg!(target_arch = "wasm32") { + skip = true; + } + + let names: ArrayVec<_, 4> = failure_application_reasons + .iter_names() + .map(|(name, _)| name) + .collect(); + let names_text = names.join(" & "); + let flaky_text = if flaky { " Flaky " } else { " " }; + + format!("Executed{flaky_text}Failure: {names_text}") + } else { + String::from("Executed") + }; + + Self { + skip, + failure_application_reasons, + failures: applicable_cases, + running_msg, + } + } +} diff --git a/tests/src/poll.rs b/tests/src/poll.rs new file mode 100644 index 0000000000..399cb71393 --- /dev/null +++ b/tests/src/poll.rs @@ -0,0 +1,8 @@ +use crate::TestingContext; + +impl TestingContext { + /// Utility to allow future asynchronous polling. + pub async fn async_poll(&self, maintain: wgpu::Maintain) -> wgpu::MaintainResult { + self.device.poll(maintain) + } +} diff --git a/tests/src/report.rs b/tests/src/report.rs new file mode 100644 index 0000000000..42633e72ac --- /dev/null +++ b/tests/src/report.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; + +use serde::Deserialize; +use wgpu::{ + AdapterInfo, DownlevelCapabilities, Features, Limits, TextureFormat, TextureFormatFeatures, +}; + +/// Report specifying the capabilities of the GPUs on the system. +/// +/// Must be synchronized with the definition on wgpu-info/src/report.rs. +#[derive(Deserialize)] +pub(crate) struct GpuReport { + #[cfg_attr(target_arch = "wasm32", allow(unused))] + pub devices: Vec, +} + +impl GpuReport { + #[cfg_attr(target_arch = "wasm32", allow(unused))] + pub(crate) fn from_json(file: &str) -> serde_json::Result { + profiling::scope!("Parsing .gpuconfig"); + serde_json::from_str(file) + } +} + +/// A single report of the capabilities of an Adapter. +/// +/// Must be synchronized with the definition on wgpu-info/src/report.rs. +#[derive(Deserialize)] +pub(crate) struct AdapterReport { + pub info: AdapterInfo, + pub features: Features, + pub limits: Limits, + pub downlevel_caps: DownlevelCapabilities, + #[allow(unused)] + pub texture_format_features: HashMap, +} + +impl AdapterReport { + pub(crate) fn from_adapter(adapter: &wgpu::Adapter) -> Self { + let info = adapter.get_info(); + let features = adapter.features(); + let limits = adapter.limits(); + let downlevel_caps = adapter.get_downlevel_capabilities(); + + Self { + info, + features, + limits, + downlevel_caps, + texture_format_features: HashMap::new(), // todo + } + } +} diff --git a/tests/src/run.rs b/tests/src/run.rs new file mode 100644 index 0000000000..e19615bdb2 --- /dev/null +++ b/tests/src/run.rs @@ -0,0 +1,117 @@ +use std::{panic::AssertUnwindSafe, sync::Arc}; + +use futures_lite::FutureExt; +use wgpu::{Adapter, Device, Instance, Queue}; + +use crate::{ + expectations::{expectations_match_failures, ExpectationMatchResult, FailureResult}, + init::{init_logger, initialize_adapter, initialize_device}, + isolation, + params::TestInfo, + report::AdapterReport, + GpuTestConfiguration, +}; + +/// Parameters and resources hadned to the test function. +pub struct TestingContext { + pub instance: Instance, + pub adapter: Adapter, + pub adapter_info: wgpu::AdapterInfo, + pub adapter_downlevel_capabilities: wgpu::DownlevelCapabilities, + pub device: Arc, + pub device_features: wgpu::Features, + pub device_limits: wgpu::Limits, + pub queue: Queue, +} + +/// Execute the given test configuration with the given adapter index. +/// +/// If test_info is specified, will use the information whether to skip the test. +/// If it is not, we'll create the test info from the adapter itself. +pub async fn execute_test( + config: GpuTestConfiguration, + test_info: Option, + adapter_index: usize, +) { + // If we get information externally, skip based on that information before we do anything. + if let Some(TestInfo { skip: true, .. }) = test_info { + return; + } + + init_logger(); + + let _test_guard = isolation::OneTestPerProcessGuard::new(); + + let (instance, adapter, _surface_guard) = initialize_adapter(adapter_index).await; + + let adapter_info = adapter.get_info(); + let adapter_downlevel_capabilities = adapter.get_downlevel_capabilities(); + + let test_info = test_info.unwrap_or_else(|| { + let adapter_report = AdapterReport::from_adapter(&adapter); + TestInfo::from_configuration(&config, &adapter_report) + }); + + // We are now guaranteed to have information about this test, so skip if we need to. + if test_info.skip { + log::info!("TEST RESULT: SKIPPED"); + return; + } + + // Print the name of the test. + log::info!("TEST: {}", config.name); + + let (device, queue) = pollster::block_on(initialize_device( + &adapter, + config.params.required_features, + config.params.required_limits.clone(), + )); + + let context = TestingContext { + instance, + adapter, + adapter_info, + adapter_downlevel_capabilities, + device: Arc::new(device), + device_features: config.params.required_features, + device_limits: config.params.required_limits.clone(), + queue, + }; + + let mut failures = Vec::new(); + + // Run the test, and catch panics (possibly due to failed assertions). + let panic_res = AssertUnwindSafe((config.test.as_ref().unwrap())(context)) + .catch_unwind() + .await; + + if let Err(panic) = panic_res { + let panic_str = panic.downcast_ref::<&'static str>(); + let panic_string = if let Some(&panic_str) = panic_str { + Some(panic_str.to_string()) + } else { + panic.downcast_ref::().cloned() + }; + + failures.push(FailureResult::Panic(panic_string)) + } + + // Check whether any validation errors were reported during the test run. + cfg_if::cfg_if!( + if #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] { + failures.extend(wgpu::hal::VALIDATION_CANARY.get_and_reset().into_iter().map(|msg| FailureResult::ValidationError(Some(msg)))); + } else if #[cfg(all(target_arch = "wasm32", feature = "webgl"))] { + if _surface_guard.unwrap().check_for_unreported_errors() { + failures.push(FailureResult::ValidationError(None)); + } + } else { + } + ); + + // The call to matches_failure will log. + if expectations_match_failures(&test_info.failures, failures) == ExpectationMatchResult::Panic { + panic!(); + } + // Print the name of the test. + log::info!("TEST FINISHED: {}", config.name); +} diff --git a/tests/tests/bgra8unorm_storage.rs b/tests/tests/bgra8unorm_storage.rs new file mode 100644 index 0000000000..b1ca3b8362 --- /dev/null +++ b/tests/tests/bgra8unorm_storage.rs @@ -0,0 +1,158 @@ +//! Tests for BGRA8UNORM_STORAGE feature + +use std::borrow::Cow; + +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; + +const SHADER_SRC: &str = " +@group(0) @binding(0) var tex: texture_storage_2d; +@compute @workgroup_size(256) +fn main(@builtin(workgroup_id) wgid: vec3) { + var texel = vec4f(0.0, 0.0, 1.0, 1.0); + textureStore(tex, wgid.xy, texel); +} +"; + +#[gpu_test] +static BGRA8_UNORM_STORAGE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .limits(wgpu::Limits { + max_storage_textures_per_shader_stage: 1, + ..Default::default() + }) + .features(wgpu::Features::BGRA8UNORM_STORAGE), + ) + .run_async(|ctx| async move { + let device = &ctx.device; + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8Unorm, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label: None, + format: None, + dimension: None, + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + base_array_layer: 0, + mip_level_count: Some(1), + array_layer_count: Some(1), + }); + + let readback_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256 * 256 * 4, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::WriteOnly, + format: wgpu::TextureFormat::Bgra8Unorm, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }], + }); + + let bg = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&view), + }], + }); + + let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bgl], + push_constant_ranges: &[], + }); + + let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(SHADER_SRC)), + }); + + let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pl), + entry_point: "main", + module: &module, + }); + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + { + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + + pass.set_bind_group(0, &bg, &[]); + pass.set_pipeline(&pipeline); + pass.dispatch_workgroups(256, 256, 1); + } + + encoder.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &readback_buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(256 * 4), + rows_per_image: Some(256), + }, + }, + wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + ); + + ctx.queue.submit(Some(encoder.finish())); + + let buffer_slice = readback_buffer.slice(..); + buffer_slice.map_async(wgpu::MapMode::Read, Result::unwrap); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + + { + let texels = buffer_slice.get_mapped_range(); + assert_eq!(texels.len(), 256 * 256 * 4); + for texel in texels.chunks(4) { + assert_eq!(texel[0], 255); // b + assert_eq!(texel[1], 0); // g + assert_eq!(texel[2], 0); // r + assert_eq!(texel[3], 255); // a + } + } + + readback_buffer.unmap(); + }); diff --git a/tests/tests/bind_group_layout_dedup.rs b/tests/tests/bind_group_layout_dedup.rs index 03bc1f1c5a..8da284b41b 100644 --- a/tests/tests/bind_group_layout_dedup.rs +++ b/tests/tests/bind_group_layout_dedup.rs @@ -1,21 +1,45 @@ -use wgpu_test::{initialize_test, TestParameters}; +use std::num::NonZeroU64; -#[test] -fn bind_group_layout_deduplication() { - initialize_test(TestParameters::default(), |ctx| { - let entries_1 = &[]; +use wgpu_test::{ + fail, gpu_test, FailureCase, GpuTestConfiguration, TestParameters, TestingContext, +}; +use wgt::Backends; - let entries_2 = &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }]; +const SHADER_SRC: &str = " +@group(0) @binding(0) +var buffer : f32; + +@compute @workgroup_size(1, 1, 1) fn no_resources() {} +@compute @workgroup_size(1, 1, 1) fn resources() { + // Just need a static use. + let _value = buffer; +} +"; + +const ENTRY: wgpu::BindGroupLayoutEntry = wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + // Should be Some(.unwrap()) but unwrap is not const. + min_binding_size: NonZeroU64::new(4), + }, + count: None, +}; + +#[gpu_test] +static BIND_GROUP_LAYOUT_DEDUPLICATION: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().test_features_limits()) + .run_async(bgl_dedupe); +async fn bgl_dedupe(ctx: TestingContext) { + let entries_1 = &[]; + + let entries_2 = &[ENTRY]; + + // Block so we can force all resource to die. + { let bgl_1a = ctx .device .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -23,7 +47,7 @@ fn bind_group_layout_deduplication() { entries: entries_1, }); - let _bgl_2 = ctx + let bgl_2 = ctx .device .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, @@ -64,81 +88,359 @@ fn bind_group_layout_deduplication() { source: wgpu::ShaderSource::Wgsl(SHADER_SRC.into()), }); - let targets = &[Some(wgpu::ColorTargetState { - format: wgpu::TextureFormat::Rgba8Unorm, - blend: None, - write_mask: Default::default(), - })]; - - let desc = wgpu::RenderPipelineDescriptor { + let desc = wgpu::ComputePipelineDescriptor { label: None, layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &module, - entry_point: "vs_main", - buffers: &[], - }, - fragment: Some(wgpu::FragmentState { - module: &module, - entry_point: "fs_main", - targets, - }), - primitive: wgpu::PrimitiveState::default(), - depth_stencil: None, - multiview: None, - multisample: wgpu::MultisampleState::default(), + module: &module, + entry_point: "no_resources", }; - let pipeline = ctx.device.create_render_pipeline(&desc); + let pipeline = ctx.device.create_compute_pipeline(&desc); - let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + let mut encoder = ctx.device.create_command_encoder(&Default::default()); + + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None, - dimension: wgpu::TextureDimension::D2, - size: wgpu::Extent3d { - width: 32, - height: 32, - depth_or_array_layers: 1, - }, - sample_count: 1, - mip_level_count: 1, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - view_formats: &[], + timestamp_writes: None, }); - let texture_view = texture.create_view(&Default::default()); + pass.set_bind_group(0, &bg_1b, &[]); + pass.set_pipeline(&pipeline); + pass.dispatch_workgroups(1, 1, 1); - let mut encoder = ctx.device.create_command_encoder(&Default::default()); + pass.set_bind_group(0, &bg_1a, &[]); + pass.dispatch_workgroups(1, 1, 1); - { - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &texture_view, - resolve_target: None, - ops: Default::default(), - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); + drop(pass); - pass.set_bind_group(0, &bg_1b, &[]); + ctx.queue.submit(Some(encoder.finish())); - pass.set_pipeline(&pipeline); + // Abuse the fact that global_id is really just the bitpacked ids when targeting wgpu-core. + if ctx.adapter_info.backend != wgt::Backend::BrowserWebGpu { + let bgl_1a_idx = bgl_1a.global_id().inner() & 0xFFFF_FFFF; + assert_eq!(bgl_1a_idx, 0); + let bgl_2_idx = bgl_2.global_id().inner() & 0xFFFF_FFFF; + assert_eq!(bgl_2_idx, 1); + let bgl_1b_idx = bgl_1b.global_id().inner() & 0xFFFF_FFFF; + assert_eq!(bgl_1b_idx, 2); + } + } - pass.draw(0..6, 0..1); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); - pass.set_bind_group(0, &bg_1a, &[]); + if ctx.adapter_info.backend != wgt::Backend::BrowserWebGpu { + // Now all of the BGL ids should be dead, so we should get the same ids again. + for i in 0..=2 { + let test_bgl = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: entries_1, + }); - pass.draw(0..6, 0..1); + let test_bgl_idx = test_bgl.global_id().inner() & 0xFFFF_FFFF; + + // https://github.com/gfx-rs/wgpu/issues/4912 + // + // ID 2 is the deduplicated ID, which is never properly recycled. + if i == 2 { + assert_eq!(test_bgl_idx, 3); + } else { + assert_eq!(test_bgl_idx, i); + } } + } +} - ctx.queue.submit(Some(encoder.finish())); - }) +#[gpu_test] +static BIND_GROUP_LAYOUT_DEDUPLICATION_WITH_DROPPED_USER_HANDLE: GpuTestConfiguration = + GpuTestConfiguration::new() + .parameters(TestParameters::default().test_features_limits()) + .run_sync(bgl_dedupe_with_dropped_user_handle); + +// https://github.com/gfx-rs/wgpu/issues/4824 +fn bgl_dedupe_with_dropped_user_handle(ctx: TestingContext) { + let bgl_1 = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ENTRY], + }); + + let pipeline_layout = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bgl_1], + push_constant_ranges: &[], + }); + + // We drop bgl_1 here. As bgl_1 is still alive, referenced by the pipeline layout, + // the deduplication should work as expected. Previously this did not work. + drop(bgl_1); + + let bgl_2 = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ENTRY], + }); + + let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 4, + usage: wgpu::BufferUsages::UNIFORM, + mapped_at_creation: false, + }); + + let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bgl_2, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(SHADER_SRC.into()), + }); + + let pipeline = ctx + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &module, + entry_point: "no_resources", + }); + + let mut encoder = ctx.device.create_command_encoder(&Default::default()); + + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + + pass.set_bind_group(0, &bg, &[]); + pass.set_pipeline(&pipeline); + pass.dispatch_workgroups(1, 1, 1); + + drop(pass); + + ctx.queue.submit(Some(encoder.finish())); } -const SHADER_SRC: &str = " -@vertex fn vs_main() -> @builtin(position) vec4 { return vec4(1.0); } -@fragment fn fs_main() -> @location(0) vec4 { return vec4(1.0); } -"; +#[gpu_test] +static BIND_GROUP_LAYOUT_DEDUPLICATION_DERIVED: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().test_features_limits()) + .run_sync(bgl_dedupe_derived); + +fn bgl_dedupe_derived(ctx: TestingContext) { + let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 4, + usage: wgpu::BufferUsages::UNIFORM, + mapped_at_creation: false, + }); + + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(SHADER_SRC.into()), + }); + + let pipeline = ctx + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: None, + module: &module, + entry_point: "resources", + }); + + // We create two bind groups, pulling the bind_group_layout from the pipeline each time. + // + // This ensures a derived BGLs are properly deduplicated despite multiple external + // references. + let bg1 = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &pipeline.get_bind_group_layout(0), + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + let bg2 = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &pipeline.get_bind_group_layout(0), + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + let mut encoder = ctx.device.create_command_encoder(&Default::default()); + + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + + pass.set_pipeline(&pipeline); + + pass.set_bind_group(0, &bg1, &[]); + pass.dispatch_workgroups(1, 1, 1); + + pass.set_bind_group(0, &bg2, &[]); + pass.dispatch_workgroups(1, 1, 1); + + drop(pass); + + ctx.queue.submit(Some(encoder.finish())); +} + +const DX12_VALIDATION_ERROR: &str = "The command allocator cannot be reset because a command list is currently being recorded with the allocator."; + +#[gpu_test] +static SEPARATE_PROGRAMS_HAVE_INCOMPATIBLE_DERIVED_BGLS: GpuTestConfiguration = + GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .expect_fail( + FailureCase::backend(Backends::DX12).validation_error(DX12_VALIDATION_ERROR), + ), + ) + .run_sync(separate_programs_have_incompatible_derived_bgls); + +fn separate_programs_have_incompatible_derived_bgls(ctx: TestingContext) { + let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 4, + usage: wgpu::BufferUsages::UNIFORM, + mapped_at_creation: false, + }); + + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(SHADER_SRC.into()), + }); + + let desc = wgpu::ComputePipelineDescriptor { + label: None, + layout: None, + module: &module, + entry_point: "resources", + }; + // Create two pipelines, creating a BG from the second. + let pipeline1 = ctx.device.create_compute_pipeline(&desc); + let pipeline2 = ctx.device.create_compute_pipeline(&desc); + + let bg2 = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &pipeline2.get_bind_group_layout(0), + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + let mut encoder = ctx.device.create_command_encoder(&Default::default()); + + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + + pass.set_pipeline(&pipeline1); + + // We use the wrong bind group for this pipeline here. This should fail. + pass.set_bind_group(0, &bg2, &[]); + pass.dispatch_workgroups(1, 1, 1); + + fail(&ctx.device, || { + drop(pass); + }); +} + +#[gpu_test] +static DERIVED_BGLS_INCOMPATIBLE_WITH_REGULAR_BGLS: GpuTestConfiguration = + GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .expect_fail( + FailureCase::backend(Backends::DX12).validation_error(DX12_VALIDATION_ERROR), + ), + ) + .run_sync(derived_bgls_incompatible_with_regular_bgls); + +fn derived_bgls_incompatible_with_regular_bgls(ctx: TestingContext) { + let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 4, + usage: wgpu::BufferUsages::UNIFORM, + mapped_at_creation: false, + }); + + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(SHADER_SRC.into()), + }); + + // Create a pipeline. + let pipeline = ctx + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: None, + module: &module, + entry_point: "resources", + }); + + // Create a matching BGL + let bgl = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ENTRY], + }); + + // Create a bind group from the explicit BGL. This should be incompatible with the derived BGL used by the pipeline. + let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + let mut encoder = ctx.device.create_command_encoder(&Default::default()); + + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + + pass.set_pipeline(&pipeline); + + pass.set_bind_group(0, &bg, &[]); + pass.dispatch_workgroups(1, 1, 1); + + fail(&ctx.device, || { + drop(pass); + }) +} diff --git a/tests/tests/buffer.rs b/tests/tests/buffer.rs index 1e7445d89e..c3b1dbea58 100644 --- a/tests/tests/buffer.rs +++ b/tests/tests/buffer.rs @@ -1,6 +1,6 @@ -use wgpu_test::{initialize_test, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters, TestingContext}; -fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) { +async fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) { let r = wgpu::BufferUsages::MAP_READ; let rw = wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::MAP_WRITE; for usage in [r, rw] { @@ -14,7 +14,9 @@ fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) b0.slice(0..0) .map_async(wgpu::MapMode::Read, Result::unwrap); - ctx.device.poll(wgpu::MaintainBase::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); { let view = b0.slice(0..0).get_mapped_range(); @@ -48,7 +50,9 @@ fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) b0.slice(0..0) .map_async(wgpu::MapMode::Write, Result::unwrap); - ctx.device.poll(wgpu::MaintainBase::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); //{ // let view = b0.slice(0..0).get_mapped_range_mut(); @@ -77,85 +81,86 @@ fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) b1.unmap(); - ctx.device.poll(wgpu::MaintainBase::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); } -#[test] -#[ignore] -fn empty_buffer() { - // TODO: Currently wgpu does not accept empty buffer slices, which - // is what test is about. - initialize_test(TestParameters::default(), |ctx| { - test_empty_buffer_range(&ctx, 2048, "regular buffer"); - test_empty_buffer_range(&ctx, 0, "zero-sized buffer"); - }) -} +#[gpu_test] +static EMPTY_BUFFER: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::always())) + .run_async(|ctx| async move { + test_empty_buffer_range(&ctx, 2048, "regular buffer").await; + test_empty_buffer_range(&ctx, 0, "zero-sized buffer").await; + }); -#[test] -fn test_map_offset() { - initialize_test(TestParameters::default(), |ctx| { - // This test writes 16 bytes at the beginning of buffer mapped mapped with - // an offset of 32 bytes. Then the buffer is copied into another buffer that - // is read back and we check that the written bytes are correctly placed at - // offset 32..48. - // The goal is to check that get_mapped_range did not accidentally double-count - // the mapped offset. - - let write_buf = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 256, - usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, - mapped_at_creation: false, - }); - let read_buf = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 256, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); +#[gpu_test] +static MAP_OFFSET: GpuTestConfiguration = GpuTestConfiguration::new().run_async(|ctx| async move { + // This test writes 16 bytes at the beginning of buffer mapped mapped with + // an offset of 32 bytes. Then the buffer is copied into another buffer that + // is read back and we check that the written bytes are correctly placed at + // offset 32..48. + // The goal is to check that get_mapped_range did not accidentally double-count + // the mapped offset. + + let write_buf = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + let read_buf = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); - write_buf - .slice(32..) - .map_async(wgpu::MapMode::Write, move |result| { - result.unwrap(); - }); + write_buf + .slice(32..) + .map_async(wgpu::MapMode::Write, move |result| { + result.unwrap(); + }); - ctx.device.poll(wgpu::MaintainBase::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); - { - let slice = write_buf.slice(32..48); - let mut view = slice.get_mapped_range_mut(); - for byte in &mut view[..] { - *byte = 2; - } + { + let slice = write_buf.slice(32..48); + let mut view = slice.get_mapped_range_mut(); + for byte in &mut view[..] { + *byte = 2; } + } - write_buf.unmap(); + write_buf.unmap(); - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - encoder.copy_buffer_to_buffer(&write_buf, 0, &read_buf, 0, 256); + encoder.copy_buffer_to_buffer(&write_buf, 0, &read_buf, 0, 256); - ctx.queue.submit(Some(encoder.finish())); + ctx.queue.submit(Some(encoder.finish())); - read_buf - .slice(..) - .map_async(wgpu::MapMode::Read, Result::unwrap); + read_buf + .slice(..) + .map_async(wgpu::MapMode::Read, Result::unwrap); - ctx.device.poll(wgpu::MaintainBase::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); - let slice = read_buf.slice(..); - let view = slice.get_mapped_range(); - for byte in &view[0..32] { - assert_eq!(*byte, 0); - } - for byte in &view[32..48] { - assert_eq!(*byte, 2); - } - for byte in &view[48..] { - assert_eq!(*byte, 0); - } - }); -} + let slice = read_buf.slice(..); + let view = slice.get_mapped_range(); + for byte in &view[0..32] { + assert_eq!(*byte, 0); + } + for byte in &view[32..48] { + assert_eq!(*byte, 2); + } + for byte in &view[48..] { + assert_eq!(*byte, 0); + } +}); diff --git a/tests/tests/buffer_copy.rs b/tests/tests/buffer_copy.rs index 5fcafe68f0..9733255ba6 100644 --- a/tests/tests/buffer_copy.rs +++ b/tests/tests/buffer_copy.rs @@ -1,35 +1,36 @@ //! Tests for buffer copy validation. -use wasm_bindgen_test::wasm_bindgen_test; use wgt::BufferAddress; -use wgpu_test::{fail_if, initialize_test, TestParameters}; +use wgpu_test::{fail_if, gpu_test, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn copy_alignment() { - fn try_copy(offset: BufferAddress, size: BufferAddress, should_fail: bool) { - initialize_test(TestParameters::default(), |ctx| { - let buffer = ctx.device.create_buffer(&BUFFER_DESCRIPTOR); - let data = vec![255; size as usize]; - fail_if(&ctx.device, should_fail, || { - ctx.queue.write_buffer(&buffer, offset, &data) - }); - }); - } +fn try_copy( + ctx: &wgpu_test::TestingContext, + offset: BufferAddress, + size: BufferAddress, + should_fail: bool, +) { + let buffer = ctx.device.create_buffer(&BUFFER_DESCRIPTOR); + let data = vec![255; size as usize]; + fail_if(&ctx.device, should_fail, || { + ctx.queue.write_buffer(&buffer, offset, &data) + }); +} - try_copy(0, 0, false); - try_copy(4, 16 + 1, true); - try_copy(64, 20 + 2, true); - try_copy(256, 44 + 3, true); - try_copy(1024, 8 + 4, false); +#[gpu_test] +static COPY_ALIGNMENT: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + try_copy(&ctx, 0, 0, false); + try_copy(&ctx, 4, 16 + 1, true); + try_copy(&ctx, 64, 20 + 2, true); + try_copy(&ctx, 256, 44 + 3, true); + try_copy(&ctx, 1024, 8 + 4, false); - try_copy(0, 4, false); - try_copy(4 + 1, 8, true); - try_copy(64 + 2, 12, true); - try_copy(256 + 3, 16, true); - try_copy(1024 + 4, 4, false); -} + try_copy(&ctx, 0, 4, false); + try_copy(&ctx, 4 + 1, 8, true); + try_copy(&ctx, 64 + 2, 12, true); + try_copy(&ctx, 256 + 3, 16, true); + try_copy(&ctx, 1024 + 4, 4, false); +}); const BUFFER_SIZE: BufferAddress = 1234; diff --git a/tests/tests/buffer_usages.rs b/tests/tests/buffer_usages.rs index e0f5164f6e..d002b8f074 100644 --- a/tests/tests/buffer_usages.rs +++ b/tests/tests/buffer_usages.rs @@ -1,74 +1,172 @@ //! Tests for buffer usages validation. -use wasm_bindgen_test::*; -use wgpu_test::{fail_if, initialize_test, TestParameters}; +use wgpu::{BufferUsages as Bu, MapMode as Ma}; +use wgpu_test::{fail_if, gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; use wgt::BufferAddress; const BUFFER_SIZE: BufferAddress = 1234; -#[test] -#[wasm_bindgen_test] -fn buffer_usage() { - fn try_create(enable_mappable_primary_buffers: bool, usages: &[(bool, &[wgpu::BufferUsages])]) { - let mut parameters = TestParameters::default(); - if enable_mappable_primary_buffers { - parameters = parameters.features(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS); - } +const ALWAYS_VALID: &[Bu; 4] = &[ + Bu::MAP_READ, + Bu::MAP_WRITE, + Bu::MAP_READ.union(Bu::COPY_DST), + Bu::MAP_WRITE.union(Bu::COPY_SRC), +]; +// MAP_READ can only be paired with COPY_DST and MAP_WRITE can only be paired with COPY_SRC +// (unless Features::MAPPABlE_PRIMARY_BUFFERS is enabled). +const NEEDS_MAPPABLE_PRIMARY_BUFFERS: &[Bu; 7] = &[ + Bu::MAP_READ.union(Bu::COPY_DST.union(Bu::COPY_SRC)), + Bu::MAP_WRITE.union(Bu::COPY_SRC.union(Bu::COPY_DST)), + Bu::MAP_READ.union(Bu::MAP_WRITE), + Bu::MAP_WRITE.union(Bu::MAP_READ), + Bu::MAP_READ.union(Bu::COPY_DST.union(Bu::STORAGE)), + Bu::MAP_WRITE.union(Bu::COPY_SRC.union(Bu::STORAGE)), + Bu::all(), +]; +const INVALID_BITS: Bu = Bu::from_bits_retain(0b1111111111111); +const ALWAYS_FAIL: &[Bu; 2] = &[Bu::empty(), INVALID_BITS]; - initialize_test(parameters, |ctx| { - for (expect_validation_error, usage) in - usages.iter().flat_map(|&(expect_error, usages)| { - usages.iter().copied().map(move |u| (expect_error, u)) - }) - { - fail_if(&ctx.device, expect_validation_error, || { - let _buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: BUFFER_SIZE, - usage, - mapped_at_creation: false, - }); - }); - } +fn try_create(ctx: TestingContext, usages: &[(bool, &[wgpu::BufferUsages])]) { + for (expect_validation_error, usage) in usages + .iter() + .flat_map(|&(expect_error, usages)| usages.iter().copied().map(move |u| (expect_error, u))) + { + fail_if(&ctx.device, expect_validation_error, || { + let _buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: BUFFER_SIZE, + usage, + mapped_at_creation: false, + }); }); } +} - use wgpu::BufferUsages as Bu; - - let always_valid = &[ - Bu::MAP_READ, - Bu::MAP_WRITE, - Bu::MAP_READ | Bu::COPY_DST, - Bu::MAP_WRITE | Bu::COPY_SRC, - ]; - // MAP_READ can only be paired with COPY_DST and MAP_WRITE can only be paired with COPY_SRC - // (unless Features::MAPPABlE_PRIMARY_BUFFERS is enabled). - let needs_mappable_primary_buffers = &[ - Bu::MAP_READ | Bu::COPY_DST | Bu::COPY_SRC, - Bu::MAP_WRITE | Bu::COPY_SRC | Bu::COPY_DST, - Bu::MAP_READ | Bu::MAP_WRITE, - Bu::MAP_WRITE | Bu::MAP_READ, - Bu::MAP_READ | Bu::COPY_DST | Bu::STORAGE, - Bu::MAP_WRITE | Bu::COPY_SRC | Bu::STORAGE, - Bu::all(), - ]; - let invalid_bits = Bu::from_bits_retain(0b1111111111111); - let always_fail = &[Bu::empty(), invalid_bits]; - +#[gpu_test] +static BUFFER_USAGE: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { try_create( - false, + ctx, &[ - (false, always_valid), - (true, needs_mappable_primary_buffers), - (true, always_fail), - ], - ); - try_create( - true, // enable Features::MAPPABLE_PRIMARY_BUFFERS - &[ - (false, always_valid), - (false, needs_mappable_primary_buffers), - (true, always_fail), + (false, ALWAYS_VALID), + (true, NEEDS_MAPPABLE_PRIMARY_BUFFERS), + (true, ALWAYS_FAIL), ], ); +}); + +#[gpu_test] +static BUFFER_USAGE_MAPPABLE_PRIMARY_BUFFERS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS)) + .run_sync(|ctx| { + try_create( + ctx, + &[ + (false, ALWAYS_VALID), + (false, NEEDS_MAPPABLE_PRIMARY_BUFFERS), + (true, ALWAYS_FAIL), + ], + ); + }); + +async fn map_test( + ctx: &TestingContext, + usage_type: &str, + map_mode_type: Ma, + before_unmap: bool, + before_destroy: bool, + after_unmap: bool, + after_destroy: bool, +) { + log::info!("map_test usage_type:{usage_type} map_mode_type:{:?} before_unmap:{before_unmap} before_destroy:{before_destroy} after_unmap:{after_unmap} after_destroy:{after_destroy}", map_mode_type); + + let size = 8; + let usage = match usage_type { + "read" => Bu::COPY_DST | Bu::MAP_READ, + "write" => Bu::COPY_SRC | Bu::MAP_WRITE, + _ => Bu::from_bits(0).unwrap(), + }; + let buffer_creation_validation_error = usage.is_empty(); + + let mut buffer = None; + + fail_if(&ctx.device, buffer_creation_validation_error, || { + buffer = Some(ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size, + usage, + mapped_at_creation: false, + })); + }); + if buffer_creation_validation_error { + return; + } + + let buffer = buffer.unwrap(); + + let map_async_validation_error = buffer_creation_validation_error + || (map_mode_type == Ma::Read && !usage.contains(Bu::MAP_READ)) + || (map_mode_type == Ma::Write && !usage.contains(Bu::MAP_WRITE)); + + fail_if(&ctx.device, map_async_validation_error, || { + buffer.slice(0..size).map_async(map_mode_type, |_| {}); + }); + + if map_async_validation_error { + return; + } + + if before_unmap { + buffer.unmap(); + } + + if before_destroy { + buffer.destroy(); + } + + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + + if !before_unmap && !before_destroy { + { + let view = buffer.slice(0..size).get_mapped_range(); + assert!(!view.is_empty()); + } + + if after_unmap { + buffer.unmap(); + } + + if after_destroy { + buffer.destroy(); + } + } } + +#[gpu_test] +static BUFFER_MAP_ASYNC_MAP_STATE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS)) + .run_async(move |ctx| async move { + for usage_type in ["invalid", "read", "write"] { + for map_mode_type in [Ma::Read, Ma::Write] { + for before_unmap in [false, true] { + for before_destroy in [false, true] { + for after_unmap in [false, true] { + for after_destroy in [false, true] { + map_test( + &ctx, + usage_type, + map_mode_type, + before_unmap, + before_destroy, + after_unmap, + after_destroy, + ) + .await + } + } + } + } + } + } + }); diff --git a/tests/tests/clear_texture.rs b/tests/tests/clear_texture.rs index 36f48af359..3e7484ab56 100644 --- a/tests/tests/clear_texture.rs +++ b/tests/tests/clear_texture.rs @@ -1,6 +1,6 @@ -use wasm_bindgen_test::*; use wgpu_test::{ - image::ReadbackBuffers, initialize_test, FailureCase, TestParameters, TestingContext, + gpu_test, image::ReadbackBuffers, FailureCase, GpuTestConfiguration, TestParameters, + TestingContext, }; static TEXTURE_FORMATS_UNCOMPRESSED_GLES_COMPAT: &[wgpu::TextureFormat] = &[ @@ -24,6 +24,7 @@ static TEXTURE_FORMATS_UNCOMPRESSED_GLES_COMPAT: &[wgpu::TextureFormat] = &[ wgpu::TextureFormat::Rgba8Sint, wgpu::TextureFormat::Bgra8Unorm, wgpu::TextureFormat::Bgra8UnormSrgb, + wgpu::TextureFormat::Rgb10a2Uint, wgpu::TextureFormat::Rgb10a2Unorm, wgpu::TextureFormat::Rg11b10Float, wgpu::TextureFormat::Rg32Uint, @@ -202,7 +203,7 @@ static TEXTURE_FORMATS_ASTC: &[wgpu::TextureFormat] = &[ }, ]; -fn single_texture_clear_test( +async fn single_texture_clear_test( ctx: &TestingContext, format: wgpu::TextureFormat, size: wgpu::Extent3d, @@ -258,12 +259,12 @@ fn single_texture_clear_test( ctx.queue.submit([encoder.finish()]); assert!( - readback_buffers.are_zero(&ctx.device), + readback_buffers.are_zero(ctx).await, "texture with format {format:?} was not fully cleared" ); } -fn clear_texture_tests(ctx: &TestingContext, formats: &[wgpu::TextureFormat]) { +async fn clear_texture_tests(ctx: TestingContext, formats: &'static [wgpu::TextureFormat]) { for &format in formats { let (block_width, block_height) = format.block_dimensions(); let rounded_width = block_width * wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; @@ -277,7 +278,7 @@ fn clear_texture_tests(ctx: &TestingContext, formats: &[wgpu::TextureFormat]) { // 1D texture if supports_1d { single_texture_clear_test( - ctx, + &ctx, format, wgpu::Extent3d { width: rounded_width, @@ -285,11 +286,12 @@ fn clear_texture_tests(ctx: &TestingContext, formats: &[wgpu::TextureFormat]) { depth_or_array_layers: 1, }, wgpu::TextureDimension::D1, - ); + ) + .await; } // 2D texture single_texture_clear_test( - ctx, + &ctx, format, wgpu::Extent3d { width: rounded_width, @@ -297,10 +299,11 @@ fn clear_texture_tests(ctx: &TestingContext, formats: &[wgpu::TextureFormat]) { depth_or_array_layers: 1, }, wgpu::TextureDimension::D2, - ); + ) + .await; // 2D array texture single_texture_clear_test( - ctx, + &ctx, format, wgpu::Extent3d { width: rounded_width, @@ -308,11 +311,12 @@ fn clear_texture_tests(ctx: &TestingContext, formats: &[wgpu::TextureFormat]) { depth_or_array_layers: 4, }, wgpu::TextureDimension::D2, - ); + ) + .await; if supports_3d { // volume texture single_texture_clear_test( - ctx, + &ctx, format, wgpu::Extent3d { width: rounded_width, @@ -320,86 +324,77 @@ fn clear_texture_tests(ctx: &TestingContext, formats: &[wgpu::TextureFormat]) { depth_or_array_layers: 16, }, wgpu::TextureDimension::D3, - ); + ) + .await; } } } -#[test] -#[wasm_bindgen_test] -fn clear_texture_uncompressed_gles_compat() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_UNCOMPRESSED_GLES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() - .skip(FailureCase::webgl2()) - .features(wgpu::Features::CLEAR_TEXTURE), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_UNCOMPRESSED_GLES_COMPAT); - }, + .features(wgpu::Features::CLEAR_TEXTURE) + .skip(FailureCase::webgl2()), ) -} + .run_async(|ctx| clear_texture_tests(ctx, TEXTURE_FORMATS_UNCOMPRESSED_GLES_COMPAT)); -#[test] -#[wasm_bindgen_test] -fn clear_texture_uncompressed() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_UNCOMPRESSED: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() - .skip(FailureCase::webgl2()) - .expect_fail(FailureCase::backend(wgpu::Backends::GL)) + .expect_fail( + FailureCase::backend(wgpu::Backends::GL) + .panic("texture with format Rg8Snorm was not fully cleared") + .panic("texture with format Rgb9e5Ufloat was not fully cleared") + .validation_error("GL_INVALID_FRAMEBUFFER_OPERATION") + .validation_error("GL_INVALID_OPERATION"), + ) .features(wgpu::Features::CLEAR_TEXTURE), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_UNCOMPRESSED); - }, ) -} + .run_async(|ctx| clear_texture_tests(ctx, TEXTURE_FORMATS_UNCOMPRESSED)); -#[test] -#[wasm_bindgen_test] -fn clear_texture_depth() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_DEPTH: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() - .skip(FailureCase::webgl2()) .downlevel_flags( wgpu::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES | wgpu::DownlevelFlags::COMPUTE_SHADERS, ) + // https://github.com/gfx-rs/wgpu/issues/5016 + .skip(FailureCase::adapter("Apple Paravirtual device")) + .skip(FailureCase::webgl2()) .limits(wgpu::Limits::downlevel_defaults()) .features(wgpu::Features::CLEAR_TEXTURE), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_DEPTH); - }, ) -} + .run_async(|ctx| clear_texture_tests(ctx, TEXTURE_FORMATS_DEPTH)); -#[test] -#[wasm_bindgen_test] -fn clear_texture_d32_s8() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_DEPTH32_STENCIL8: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() - .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::DEPTH32FLOAT_STENCIL8), - |ctx| { - clear_texture_tests(&ctx, &[wgpu::TextureFormat::Depth32FloatStencil8]); - }, + .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::DEPTH32FLOAT_STENCIL8) + // https://github.com/gfx-rs/wgpu/issues/5016 + .skip(FailureCase::adapter("Apple Paravirtual device")), ) -} + .run_async(|ctx| clear_texture_tests(ctx, &[wgpu::TextureFormat::Depth32FloatStencil8])); -#[test] -fn clear_texture_bc() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_COMPRESSED_BCN: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_BC) // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 .expect_fail(FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE")) // compressed texture copy to buffer not yet implemented .expect_fail(FailureCase::backend(wgpu::Backends::GL)), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_BC); - }, ) -} + .run_async(|ctx| clear_texture_tests(ctx, TEXTURE_FORMATS_BC)); -#[test] -fn clear_texture_astc() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_COMPRESSED_ASTC: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_ASTC) .limits(wgpu::Limits { @@ -410,23 +405,17 @@ fn clear_texture_astc() { .expect_fail(FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE")) // compressed texture copy to buffer not yet implemented .expect_fail(FailureCase::backend(wgpu::Backends::GL)), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_ASTC); - }, ) -} + .run_async(|ctx| clear_texture_tests(ctx, TEXTURE_FORMATS_ASTC)); -#[test] -fn clear_texture_etc2() { - initialize_test( +#[gpu_test] +static CLEAR_TEXTURE_COMPRESSED_ETC2: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(wgpu::Features::CLEAR_TEXTURE | wgpu::Features::TEXTURE_COMPRESSION_ETC2) // https://bugs.chromium.org/p/angleproject/issues/detail?id=7056 .expect_fail(FailureCase::backend_adapter(wgpu::Backends::GL, "ANGLE")) // compressed texture copy to buffer not yet implemented .expect_fail(FailureCase::backend(wgpu::Backends::GL)), - |ctx| { - clear_texture_tests(&ctx, TEXTURE_FORMATS_ETC2); - }, ) -} + .run_async(|ctx| clear_texture_tests(ctx, TEXTURE_FORMATS_ETC2)); diff --git a/tests/tests/create_surface_error.rs b/tests/tests/create_surface_error.rs index f8962697ce..87aeb15726 100644 --- a/tests/tests/create_surface_error.rs +++ b/tests/tests/create_surface_error.rs @@ -1,22 +1,21 @@ //! Test that `create_surface_*()` accurately reports those errors we can provoke. -/// This test applies to those cfgs that have a `create_surface_from_canvas` method, which +/// This test applies to those cfgs that can create a surface from a canvas, which /// include WebGL and WebGPU, but *not* Emscripten GLES. #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] #[wasm_bindgen_test::wasm_bindgen_test] fn canvas_get_context_returned_null() { - // Not using initialize_test() because that goes straight to creating the canvas for us. + // Not using the normal testing infrastructure because that goes straight to creating the canvas for us. let instance = wgpu_test::initialize_instance(); - // Create canvas and cleanup on drop - let canvas_g = wgpu_test::SurfaceGuard { - canvas: wgpu_test::create_html_canvas(), - }; + // Create canvas + let canvas = wgpu_test::initialize_html_canvas(); + // Using a context id that is not "webgl2" or "webgpu" will render the canvas unusable by wgpu. - canvas_g.canvas.get_context("2d").unwrap(); + canvas.get_context("2d").unwrap(); #[allow(clippy::redundant_clone)] // false positive — can't and shouldn't move out. let error = instance - .create_surface_from_canvas(canvas_g.canvas.clone()) + .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .unwrap_err(); assert!( diff --git a/tests/tests/device.rs b/tests/tests/device.rs index f43791f86e..5ab54927f8 100644 --- a/tests/tests/device.rs +++ b/tests/tests/device.rs @@ -1,42 +1,551 @@ -use wasm_bindgen_test::*; +use wgpu_test::{fail, gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -use wgpu_test::{initialize_test, FailureCase, TestParameters}; +#[gpu_test] +static CROSS_DEVICE_BIND_GROUP_USAGE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::always())) + .run_async(|ctx| async move { + // Create a bind group uisng a layout from another device. This should be a validation + // error but currently crashes. + let (device2, _) = + pollster::block_on(ctx.adapter.request_device(&Default::default(), None)).unwrap(); + { + let bind_group_layout = + device2.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[], + }); + + let _bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[], + }); + } + + ctx.async_poll(wgpu::Maintain::Poll) + .await + .panic_on_timeout(); + }); + +#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] #[test] -#[wasm_bindgen_test] -fn device_initialization() { - initialize_test(TestParameters::default(), |_ctx| { - // intentionally empty - }) +fn device_lifetime_check() { + use pollster::FutureExt as _; + + env_logger::init(); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::all()), + dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(), + gles_minor_version: wgpu::util::gles_minor_version_from_env().unwrap_or_default(), + flags: wgpu::InstanceFlags::debugging().with_env(), + }); + + let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, None) + .block_on() + .expect("failed to create adapter"); + + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor::default(), None) + .block_on() + .expect("failed to create device"); + + instance.poll_all(false); + + let pre_report = instance.generate_report().unwrap().unwrap(); + + drop(queue); + drop(device); + let post_report = instance.generate_report().unwrap().unwrap(); + assert_ne!( + pre_report, post_report, + "Queue and Device has not been dropped as expected" + ); } -#[test] -#[ignore] -fn device_mismatch() { - initialize_test( - // https://github.com/gfx-rs/wgpu/issues/3927 - TestParameters::default().expect_fail(FailureCase::always()), - |ctx| { - // Create a bind group uisng a lyaout from another device. This should be a validation - // error but currently crashes. - let (device2, _) = - pollster::block_on(ctx.adapter.request_device(&Default::default(), None)).unwrap(); - - { - let bind_group_layout = - device2.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[], - }); - - let _bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &bind_group_layout, +#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] +#[gpu_test] +static REQUEST_DEVICE_ERROR_MESSAGE_NATIVE: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|_ctx| request_device_error_message()); + +/// Check that `RequestDeviceError`s produced have some diagnostic information. +/// +/// Note: this is a wasm *and* native test. On wasm it is run directly; on native, indirectly +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] +async fn request_device_error_message() { + // Not using initialize_test() because that doesn't let us catch the error + // nor .await anything + let (_instance, adapter, _surface_guard) = wgpu_test::initialize_adapter(0).await; + + let device_error = adapter + .request_device( + &wgpu::DeviceDescriptor { + // Force a failure by requesting absurd limits. + required_features: wgpu::Features::all(), + required_limits: wgpu::Limits { + max_texture_dimension_1d: u32::MAX, + max_texture_dimension_2d: u32::MAX, + max_texture_dimension_3d: u32::MAX, + max_bind_groups: u32::MAX, + max_push_constant_size: u32::MAX, + ..Default::default() + }, + ..Default::default() + }, + None, + ) + .await + .unwrap_err(); + + let device_error = device_error.to_string(); + cfg_if::cfg_if! { + if #[cfg(all(target_arch = "wasm32", not(feature = "webgl")))] { + // On WebGPU, so the error we get will be from the browser WebGPU API. + // Per the WebGPU specification this should be a `TypeError` when features are not + // available, , + // and the stringification it goes through for Rust should put that in the message. + let expected = "TypeError"; + } else { + // This message appears whenever wgpu-core is used as the implementation. + let expected = "Unsupported features were requested: Features("; + } + } + assert!(device_error.contains(expected), "{device_error}"); +} + +// This is a test of device behavior after device.destroy. Specifically, all operations +// should trigger errors since the device is lost. +// +// On DX12 this test fails with a validation error in the very artifical actions taken +// after lose the device. The error is "ID3D12CommandAllocator::Reset: The command +// allocator cannot be reset because a command list is currently being recorded with the +// allocator." That may indicate that DX12 doesn't like opened command buffers staying +// open even after they return an error. For now, this test is skipped on DX12. +// +// The DX12 issue may be related to https://github.com/gfx-rs/wgpu/issues/3193. +#[gpu_test] +static DEVICE_DESTROY_THEN_MORE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::CLEAR_TEXTURE)) + .run_sync(|ctx| { + // Create some resources on the device that we will attempt to use *after* losing + // the device. + + // Create some 512 x 512 2D textures. + let texture_extent = wgpu::Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }; + let texture_for_view = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let target_view = texture_for_view.create_view(&wgpu::TextureViewDescriptor::default()); + + let texture_for_read = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let texture_for_write = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + // Create some buffers. + let buffer_source = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + let buffer_dest = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let buffer_for_map = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + let buffer_for_unmap = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: true, + }); + + // Create a bind group layout. + let bind_group_layout = + ctx.device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, entries: &[], }); - } - ctx.device.poll(wgpu::Maintain::Poll); - }, - ); -} + // Create a shader module. + let shader_module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")), + }); + + // Create some command encoders. + let mut encoder_for_clear = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_compute_pass = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_render_pass = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_buffer_buffer_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_buffer_texture_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_texture_buffer_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_texture_texture_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + // Destroy the device. This will cause all other requests to return some variation of + // a device invalid error. + ctx.device.destroy(); + + // TODO: verify the following operations will return an invalid device error: + // * Run a compute or render pass + // * Finish a render bundle encoder + // * Create a texture from HAL + // * Create a buffer from HAL + // * Create a sampler + // * Validate a surface configuration + // * Start or stop capture + // * Get or set buffer sub data + + // TODO: figure out how to structure a test around these operations which panic when + // the device is invalid: + // * device.features() + // * device.limits() + // * device.downlevel_properties() + // * device.create_query_set() + + // TODO: change these fail calls to check for the specific errors which indicate that + // the device is not valid. + + // Creating a commmand encoder should fail. + fail(&ctx.device, || { + ctx.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + }); + + // Creating a buffer should fail. + fail(&ctx.device, || { + ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + }); + + // Creating a texture should fail. + fail(&ctx.device, || { + ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + }); + + // Texture clear should fail. + fail(&ctx.device, || { + encoder_for_clear.clear_texture( + &texture_for_write, + &wgpu::ImageSubresourceRange { + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }, + ); + }); + + // Creating a compute pass should fail. + fail(&ctx.device, || { + encoder_for_compute_pass.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + }); + + // Creating a render pass should fail. + fail(&ctx.device, || { + encoder_for_render_pass.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + ops: wgpu::Operations::default(), + resolve_target: None, + view: &target_view, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + }); + + // Copying a buffer to a buffer should fail. + fail(&ctx.device, || { + encoder_for_buffer_buffer_copy.copy_buffer_to_buffer( + &buffer_source, + 0, + &buffer_dest, + 0, + 256, + ); + }); + + // Copying a buffer to a texture should fail. + fail(&ctx.device, || { + encoder_for_buffer_texture_copy.copy_buffer_to_texture( + wgpu::ImageCopyBuffer { + buffer: &buffer_source, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: None, + }, + }, + texture_for_write.as_image_copy(), + texture_extent, + ); + }); + + // Copying a texture to a buffer should fail. + fail(&ctx.device, || { + encoder_for_texture_buffer_copy.copy_texture_to_buffer( + texture_for_read.as_image_copy(), + wgpu::ImageCopyBuffer { + buffer: &buffer_source, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: None, + }, + }, + texture_extent, + ); + }); + + // Copying a texture to a texture should fail. + fail(&ctx.device, || { + encoder_for_texture_texture_copy.copy_texture_to_texture( + texture_for_read.as_image_copy(), + texture_for_write.as_image_copy(), + texture_extent, + ); + }); + + // Creating a bind group layout should fail. + fail(&ctx.device, || { + ctx.device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[], + }); + }); + + // Creating a bind group should fail. + fail(&ctx.device, || { + ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + buffer_source.as_entire_buffer_binding(), + ), + }], + }); + }); + + // Creating a pipeline layout should fail. + fail(&ctx.device, || { + ctx.device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + }); + + // Creating a shader module should fail. + fail(&ctx.device, || { + ctx.device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")), + }); + }); + + // Creating a shader module spirv should fail. + fail(&ctx.device, || unsafe { + ctx.device + .create_shader_module_spirv(&wgpu::ShaderModuleDescriptorSpirV { + label: None, + source: std::borrow::Cow::Borrowed(&[]), + }); + }); + + // Creating a render pipeline should fail. + fail(&ctx.device, || { + ctx.device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + module: &shader_module, + entry_point: "", + buffers: &[], + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + fragment: None, + multiview: None, + }); + }); + + // Creating a compute pipeline should fail. + fail(&ctx.device, || { + ctx.device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: None, + module: &shader_module, + entry_point: "", + }); + }); + + // Buffer map should fail. + fail(&ctx.device, || { + buffer_for_map + .slice(..) + .map_async(wgpu::MapMode::Write, |_| ()); + }); + + // Buffer unmap should fail. + fail(&ctx.device, || { + buffer_for_unmap.unmap(); + }); + }); + +#[gpu_test] +static DEVICE_DESTROY_THEN_LOST: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default()) + .run_async(|ctx| async move { + // This test checks that when device.destroy is called, the provided + // DeviceLostClosure is called with reason DeviceLostReason::Destroyed. + let was_called = std::sync::Arc::::new(false.into()); + + // Set a LoseDeviceCallback on the device. + let was_called_clone = was_called.clone(); + let callback = Box::new(move |reason, _m| { + was_called_clone.store(true, std::sync::atomic::Ordering::SeqCst); + assert!( + matches!(reason, wgt::DeviceLostReason::Destroyed), + "Device lost info reason should match DeviceLostReason::Destroyed." + ); + }); + ctx.device.set_device_lost_callback(callback); + + // Destroy the device. + ctx.device.destroy(); + + // Make sure the device queues are empty, which ensures that the closure + // has been called. + assert!(ctx + .async_poll(wgpu::Maintain::wait()) + .await + .is_queue_empty()); + + assert!( + was_called.load(std::sync::atomic::Ordering::SeqCst), + "Device lost callback should have been called." + ); + }); + +#[gpu_test] +static DEVICE_DROP_THEN_LOST: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::webgl2())) + .run_sync(|ctx| { + // This test checks that when the device is dropped (such as in a GC), + // the provided DeviceLostClosure is called with reason DeviceLostReason::Unknown. + // Fails on webgl because webgl doesn't implement drop. + let was_called = std::sync::Arc::::new(false.into()); + + // Set a LoseDeviceCallback on the device. + let was_called_clone = was_called.clone(); + let callback = Box::new(move |reason, message| { + was_called_clone.store(true, std::sync::atomic::Ordering::SeqCst); + assert!( + matches!(reason, wgt::DeviceLostReason::Unknown), + "Device lost info reason should match DeviceLostReason::Unknown." + ); + assert!( + message == "Device dropped.", + "Device lost info message should be \"Device dropped.\"." + ); + }); + ctx.device.set_device_lost_callback(callback); + + // Drop the device. + drop(ctx.device); + + assert!( + was_called.load(std::sync::atomic::Ordering::SeqCst), + "Device lost callback should have been called." + ); + }); diff --git a/tests/tests/encoder.rs b/tests/tests/encoder.rs index 5914cd22da..3487eb05d1 100644 --- a/tests/tests/encoder.rs +++ b/tests/tests/encoder.rs @@ -1,30 +1,24 @@ -use wasm_bindgen_test::*; -use wgpu::RenderPassDescriptor; -use wgpu_test::{fail, initialize_test, FailureCase, TestParameters}; +use wgpu_test::{fail, gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -#[test] -#[wasm_bindgen_test] -fn drop_encoder() { - initialize_test(TestParameters::default(), |ctx| { - let encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - drop(encoder); - }) -} +#[gpu_test] +static DROP_ENCODER: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + drop(encoder); +}); -#[test] -fn drop_encoder_after_error() { - // This test crashes on DX12 with the exception: - // - // ID3D12CommandAllocator::Reset: The command allocator cannot be reset because a - // command list is currently being recorded with the allocator. [ EXECUTION ERROR - // #543: COMMAND_ALLOCATOR_CANNOT_RESET] - // - // For now, we mark the test as failing on DX12. - let parameters = - TestParameters::default().expect_fail(FailureCase::backend(wgpu::Backends::DX12)); - initialize_test(parameters, |ctx| { +// This test crashes on DX12 with the exception: +// +// ID3D12CommandAllocator::Reset: The command allocator cannot be reset because a +// command list is currently being recorded with the allocator. [ EXECUTION ERROR +// #543: COMMAND_ALLOCATOR_CANNOT_RESET] +// +// For now, we mark the test as failing on DX12. +#[gpu_test] +static DROP_ENCODER_AFTER_ERROR: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::backend(wgpu::Backends::DX12))) + .run_sync(|ctx| { let mut encoder = ctx .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); @@ -45,7 +39,7 @@ fn drop_encoder_after_error() { }); let target_view = target_tex.create_view(&wgpu::TextureViewDescriptor::default()); - let mut renderpass = encoder.begin_render_pass(&RenderPassDescriptor { + let mut renderpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("renderpass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { ops: wgpu::Operations::default(), @@ -67,5 +61,4 @@ fn drop_encoder_after_error() { // a CommandEncoder which errored out when processing a command. // The encoder is still open! drop(encoder); - }) -} + }); diff --git a/tests/tests/external_texture.rs b/tests/tests/external_texture.rs index 2c3a8b987d..f518bf8666 100644 --- a/tests/tests/external_texture.rs +++ b/tests/tests/external_texture.rs @@ -1,138 +1,137 @@ #![cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] use wasm_bindgen::JsCast; -use wasm_bindgen_test::*; use wgpu::ExternalImageSource; -use wgpu_test::{fail_if, initialize_test, TestParameters}; +use wgpu_test::{fail_if, gpu_test, GpuTestConfiguration}; -#[wasm_bindgen_test] -async fn image_bitmap_import() { - let image_encoded = include_bytes!("3x3_colors.png"); +#[gpu_test] +static IMAGE_BITMAP_IMPORT: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let image_encoded = include_bytes!("3x3_colors.png"); - // Create an array-of-arrays for Blob's constructor - let array = js_sys::Array::new(); - array.push(&js_sys::Uint8Array::from(&image_encoded[..])); + // Create an array-of-arrays for Blob's constructor + let array = js_sys::Array::new(); + array.push(&js_sys::Uint8Array::from(&image_encoded[..])); - // We're passing an array of Uint8Arrays - let blob = web_sys::Blob::new_with_u8_array_sequence(&array).unwrap(); + // We're passing an array of Uint8Arrays + let blob = web_sys::Blob::new_with_u8_array_sequence(&array).unwrap(); - // Parse the image from the blob + // Parse the image from the blob - // Because we need to call the function in a way that isn't bound by - // web_sys, we need to manually construct the options struct and call - // the function. - let image_bitmap_function: js_sys::Function = web_sys::window() - .unwrap() - .get("createImageBitmap") - .unwrap() - .dyn_into() - .unwrap(); + // Because we need to call the function in a way that isn't bound by + // web_sys, we need to manually construct the options struct and call + // the function. + let image_bitmap_function: js_sys::Function = web_sys::window() + .unwrap() + .get("createImageBitmap") + .unwrap() + .dyn_into() + .unwrap(); - let options_arg = js_sys::Object::new(); - js_sys::Reflect::set( - &options_arg, - &wasm_bindgen::JsValue::from_str("premultiplyAlpha"), - &wasm_bindgen::JsValue::from_str("none"), - ) - .unwrap(); - let image_bitmap_promise: js_sys::Promise = image_bitmap_function - .call2(&wasm_bindgen::JsValue::UNDEFINED, &blob, &options_arg) - .unwrap() - .dyn_into() + let options_arg = js_sys::Object::new(); + js_sys::Reflect::set( + &options_arg, + &wasm_bindgen::JsValue::from_str("premultiplyAlpha"), + &wasm_bindgen::JsValue::from_str("none"), + ) .unwrap(); - - // Wait for the parsing to be done - let image_bitmap: web_sys::ImageBitmap = - wasm_bindgen_futures::JsFuture::from(image_bitmap_promise) - .await + let image_bitmap_promise: js_sys::Promise = image_bitmap_function + .call2(&wasm_bindgen::JsValue::UNDEFINED, &blob, &options_arg) .unwrap() .dyn_into() .unwrap(); - // Sanity checks - assert_eq!(image_bitmap.width(), 3); - assert_eq!(image_bitmap.height(), 3); + // Wait for the parsing to be done + let image_bitmap: web_sys::ImageBitmap = + wasm_bindgen_futures::JsFuture::from(image_bitmap_promise) + .await + .unwrap() + .dyn_into() + .unwrap(); - // Due to restrictions with premultiplication with ImageBitmaps, we also create an HtmlCanvasElement - // by drawing the image bitmap onto the canvas. - let canvas: web_sys::HtmlCanvasElement = web_sys::window() - .unwrap() - .document() - .unwrap() - .create_element("canvas") - .unwrap() - .dyn_into() - .unwrap(); - canvas.set_width(3); - canvas.set_height(3); + // Sanity checks + assert_eq!(image_bitmap.width(), 3); + assert_eq!(image_bitmap.height(), 3); - let d2_context: web_sys::CanvasRenderingContext2d = canvas - .get_context("2d") - .unwrap() - .unwrap() - .dyn_into() - .unwrap(); - d2_context - .draw_image_with_image_bitmap(&image_bitmap, 0.0, 0.0) - .unwrap(); + // Due to restrictions with premultiplication with ImageBitmaps, we also create an HtmlCanvasElement + // by drawing the image bitmap onto the canvas. + let canvas: web_sys::HtmlCanvasElement = web_sys::window() + .unwrap() + .document() + .unwrap() + .create_element("canvas") + .unwrap() + .dyn_into() + .unwrap(); + canvas.set_width(3); + canvas.set_height(3); - // Decode it cpu side - let raw_image = image::load_from_memory_with_format(image_encoded, image::ImageFormat::Png) - .unwrap() - .into_rgba8(); + let d2_context: web_sys::CanvasRenderingContext2d = canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into() + .unwrap(); + d2_context + .draw_image_with_image_bitmap(&image_bitmap, 0.0, 0.0) + .unwrap(); - // Set of test cases to test with image import - #[derive(Debug, Copy, Clone)] - enum TestCase { - // Import the image as normal - Normal, - // Sets the FlipY flag. Deals with global state on GLES, so run before other tests to ensure it's reset. - // - // Only works on canvases. - FlipY, - // Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset. - // - // Only works on canvases. - Premultiplied, - // Sets the color space to P3. - // - // Only works on canvases. - ColorSpace, - // Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset. - // Set both the input offset and output offset to 1 in x, so the first column is omitted. - TrimLeft, - // Set the size to 2 in x, so the last column is omitted - TrimRight, - // Set only the output offset to 1, so the second column gets the first column's data. - SlideRight, - // Try to copy from out of bounds of the source image - SourceOutOfBounds, - // Try to copy from out of bounds of the destination image - DestOutOfBounds, - // Try to copy more than one slice from the source - MultiSliceCopy, - // Copy into the second slice of a 2D array texture, - SecondSliceCopy, - } - let sources = [ - ExternalImageSource::ImageBitmap(image_bitmap), - ExternalImageSource::HTMLCanvasElement(canvas), - ]; - let cases = [ - TestCase::Normal, - TestCase::FlipY, - TestCase::Premultiplied, - TestCase::ColorSpace, - TestCase::TrimLeft, - TestCase::TrimRight, - TestCase::SlideRight, - TestCase::SourceOutOfBounds, - TestCase::DestOutOfBounds, - TestCase::MultiSliceCopy, - TestCase::SecondSliceCopy, - ]; + // Decode it cpu side + let raw_image = image::load_from_memory_with_format(image_encoded, image::ImageFormat::Png) + .unwrap() + .into_rgba8(); + + // Set of test cases to test with image import + #[derive(Debug, Copy, Clone)] + enum TestCase { + // Import the image as normal + Normal, + // Sets the FlipY flag. Deals with global state on GLES, so run before other tests to ensure it's reset. + // + // Only works on canvases. + FlipY, + // Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset. + // + // Only works on canvases. + Premultiplied, + // Sets the color space to P3. + // + // Only works on canvases. + ColorSpace, + // Sets the premultiplied alpha flag. Deals with global state on GLES, so run before other tests to ensure it's reset. + // Set both the input offset and output offset to 1 in x, so the first column is omitted. + TrimLeft, + // Set the size to 2 in x, so the last column is omitted + TrimRight, + // Set only the output offset to 1, so the second column gets the first column's data. + SlideRight, + // Try to copy from out of bounds of the source image + SourceOutOfBounds, + // Try to copy from out of bounds of the destination image + DestOutOfBounds, + // Try to copy more than one slice from the source + MultiSliceCopy, + // Copy into the second slice of a 2D array texture, + SecondSliceCopy, + } + let sources = [ + ExternalImageSource::ImageBitmap(image_bitmap), + ExternalImageSource::HTMLCanvasElement(canvas), + ]; + let cases = [ + TestCase::Normal, + TestCase::FlipY, + TestCase::Premultiplied, + TestCase::ColorSpace, + TestCase::TrimLeft, + TestCase::TrimRight, + TestCase::SlideRight, + TestCase::SourceOutOfBounds, + TestCase::DestOutOfBounds, + TestCase::MultiSliceCopy, + TestCase::SecondSliceCopy, + ]; - initialize_test(TestParameters::default(), |ctx| { for source in sources { for case in cases { // Copy the data, so we can modify it for tests @@ -324,7 +323,9 @@ async fn image_bitmap_import() { readback_buffer .slice(..) .map_async(wgpu::MapMode::Read, |_| ()); - ctx.device.poll(wgpu::Maintain::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); let buffer = readback_buffer.slice(..).get_mapped_range(); @@ -346,5 +347,4 @@ async fn image_bitmap_import() { } } } - }) -} + }); diff --git a/tests/tests/float32_filterable.rs b/tests/tests/float32_filterable.rs new file mode 100644 index 0000000000..c170deda9b --- /dev/null +++ b/tests/tests/float32_filterable.rs @@ -0,0 +1,75 @@ +//! Tests for FLOAT32_FILTERABLE feature. + +use wgpu_test::{fail, gpu_test, GpuTestConfiguration, TestParameters}; + +fn create_texture_binding(device: &wgpu::Device, format: wgpu::TextureFormat, filterable: bool) { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable }, + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }], + }); + + let _bg = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&view), + }], + }); +} + +#[gpu_test] +static FLOAT32_FILTERABLE_WITHOUT_FEATURE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default()) + .run_sync(|ctx| { + let device = &ctx.device; + // Unorm textures are always filterable + create_texture_binding(device, wgpu::TextureFormat::R8Unorm, true); + create_texture_binding(device, wgpu::TextureFormat::R8Unorm, false); + // As are float16 textures + create_texture_binding(device, wgpu::TextureFormat::R16Float, true); + create_texture_binding(device, wgpu::TextureFormat::R16Float, false); + // Float 32 textures can be used as non-filterable only + create_texture_binding(device, wgpu::TextureFormat::R32Float, false); + // This is supposed to fail, since we have not activated the feature + fail(&ctx.device, || { + create_texture_binding(device, wgpu::TextureFormat::R32Float, true); + }); + }); + +#[gpu_test] +static FLOAT32_FILTERABLE_WITH_FEATURE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::FLOAT32_FILTERABLE)) + .run_sync(|ctx| { + let device = &ctx.device; + // With the feature enabled, it does work! + create_texture_binding(device, wgpu::TextureFormat::R32Float, true); + create_texture_binding(device, wgpu::TextureFormat::Rg32Float, true); + create_texture_binding(device, wgpu::TextureFormat::Rgba32Float, true); + }); diff --git a/tests/tests/instance.rs b/tests/tests/instance.rs index b231e8d879..c74b7aedd2 100644 --- a/tests/tests/instance.rs +++ b/tests/tests/instance.rs @@ -1,36 +1,4 @@ -use wasm_bindgen_test::*; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn initialize() { - let _ = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all), - dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(), - gles_minor_version: wgpu::util::gles_minor_version_from_env().unwrap_or_default(), - }); -} - -fn request_adapter_inner(power: wgt::PowerPreference) { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all), - dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(), - gles_minor_version: wgpu::util::gles_minor_version_from_env().unwrap_or_default(), - }); - - let _adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: power, - force_fallback_adapter: false, - compatible_surface: None, - })) - .unwrap(); -} - -#[test] -fn request_adapter_low_power() { - request_adapter_inner(wgt::PowerPreference::LowPower); -} - -#[test] -fn request_adapter_high_power() { - request_adapter_inner(wgt::PowerPreference::HighPerformance); -} +#[gpu_test] +static INITIALIZE: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|_ctx| {}); diff --git a/tests/tests/life_cycle.rs b/tests/tests/life_cycle.rs new file mode 100644 index 0000000000..e465402431 --- /dev/null +++ b/tests/tests/life_cycle.rs @@ -0,0 +1,111 @@ +use wgpu_test::{fail, gpu_test, GpuTestConfiguration}; + +#[gpu_test] +static BUFFER_DESTROY: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + + buffer.destroy(); + + buffer.destroy(); + + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + + fail(&ctx.device, || { + buffer + .slice(..) + .map_async(wgpu::MapMode::Write, move |_| {}); + }); + + buffer.destroy(); + + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + + buffer.destroy(); + + buffer.destroy(); + + let descriptor = wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }; + + // Scopes to mix up the drop/poll ordering. + { + let buffer = ctx.device.create_buffer(&descriptor); + buffer.destroy(); + let buffer = ctx.device.create_buffer(&descriptor); + buffer.destroy(); + } + let buffer = ctx.device.create_buffer(&descriptor); + buffer.destroy(); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + let buffer = ctx.device.create_buffer(&descriptor); + buffer.destroy(); + { + let buffer = ctx.device.create_buffer(&descriptor); + buffer.destroy(); + let buffer = ctx.device.create_buffer(&descriptor); + buffer.destroy(); + let buffer = ctx.device.create_buffer(&descriptor); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + buffer.destroy(); + } + let buffer = ctx.device.create_buffer(&descriptor); + buffer.destroy(); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + }); + +#[gpu_test] +static TEXTURE_DESTROY: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 128, + height: 128, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, // multisampling is not supported for clear + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Snorm, + usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + + texture.destroy(); + + texture.destroy(); + + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + + texture.destroy(); + + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + + texture.destroy(); + + texture.destroy(); + }); diff --git a/tests/tests/mem_leaks.rs b/tests/tests/mem_leaks.rs new file mode 100644 index 0000000000..91a329d20c --- /dev/null +++ b/tests/tests/mem_leaks.rs @@ -0,0 +1,293 @@ +#[cfg(any( + not(target_arch = "wasm32"), + target_os = "emscripten", + feature = "webgl" +))] +async fn draw_test_with_reports( + ctx: wgpu_test::TestingContext, + expected: &[u32], + function: impl FnOnce(&mut wgpu::RenderPass<'_>), +) { + use std::num::NonZeroU64; + + use wgpu::util::DeviceExt; + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.devices.num_allocated, 1); + assert_eq!(report.queues.num_allocated, 1); + + let shader = ctx + .device + .create_shader_module(wgpu::include_wgsl!("./vertex_indices/draw.vert.wgsl")); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.shader_modules.num_allocated, 1); + + let bgl = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: NonZeroU64::new(4), + }, + visibility: wgpu::ShaderStages::VERTEX, + count: None, + }], + }); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.buffers.num_allocated, 0); + assert_eq!(report.bind_groups.num_allocated, 0); + assert_eq!(report.bind_group_layouts.num_allocated, 1); + + let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 4 * expected.len() as u64, + usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::STORAGE, + mapped_at_creation: false, + }); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.buffers.num_allocated, 1); + + let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.buffers.num_allocated, 1); + assert_eq!(report.bind_groups.num_allocated, 1); + assert_eq!(report.bind_group_layouts.num_allocated, 1); + + let ppl = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bgl], + push_constant_ranges: &[], + }); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.buffers.num_allocated, 1); + assert_eq!(report.pipeline_layouts.num_allocated, 1); + assert_eq!(report.render_pipelines.num_allocated, 0); + assert_eq!(report.compute_pipelines.num_allocated, 0); + + let pipeline = ctx + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&ppl), + vertex: wgpu::VertexState { + buffers: &[], + entry_point: "vs_main_builtin", + module: &shader, + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + fragment: Some(wgpu::FragmentState { + entry_point: "fs_main", + module: &shader, + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + }); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.buffers.num_allocated, 1); + assert_eq!(report.bind_groups.num_allocated, 1); + assert_eq!(report.bind_group_layouts.num_allocated, 1); + assert_eq!(report.shader_modules.num_allocated, 1); + assert_eq!(report.pipeline_layouts.num_allocated, 1); + assert_eq!(report.render_pipelines.num_allocated, 1); + assert_eq!(report.compute_pipelines.num_allocated, 0); + + drop(shader); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.shader_modules.num_allocated, 1); + assert_eq!(report.shader_modules.num_kept_from_user, 0); + assert_eq!(report.textures.num_allocated, 0); + assert_eq!(report.texture_views.num_allocated, 0); + + let texture = ctx.device.create_texture_with_data( + &ctx.queue, + &wgpu::TextureDescriptor { + label: Some("dummy"), + size: wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }, + wgpu::util::TextureDataOrder::LayerMajor, + &[0, 0, 0, 1], + ); + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.buffers.num_allocated, 1); + assert_eq!(report.texture_views.num_allocated, 1); + assert_eq!(report.textures.num_allocated, 1); + + drop(texture); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.buffers.num_allocated, 1); + assert_eq!(report.texture_views.num_allocated, 1); + assert_eq!(report.texture_views.num_kept_from_user, 1); + assert_eq!(report.textures.num_allocated, 1); + assert_eq!(report.textures.num_kept_from_user, 0); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.command_buffers.num_allocated, 1); + assert_eq!(report.buffers.num_allocated, 1); + + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + ops: wgpu::Operations::default(), + resolve_target: None, + view: &texture_view, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&pipeline); + rpass.set_bind_group(0, &bg, &[]); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.buffers.num_allocated, 1); + assert_eq!(report.bind_groups.num_allocated, 1); + assert_eq!(report.bind_group_layouts.num_allocated, 1); + assert_eq!(report.pipeline_layouts.num_allocated, 1); + assert_eq!(report.render_pipelines.num_allocated, 1); + assert_eq!(report.compute_pipelines.num_allocated, 0); + assert_eq!(report.command_buffers.num_allocated, 1); + assert_eq!(report.render_bundles.num_allocated, 0); + assert_eq!(report.texture_views.num_allocated, 1); + assert_eq!(report.textures.num_allocated, 1); + + function(&mut rpass); + + drop(rpass); + drop(pipeline); + drop(texture_view); + drop(ppl); + drop(bgl); + drop(bg); + drop(buffer); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.command_buffers.num_kept_from_user, 1); + assert_eq!(report.render_pipelines.num_kept_from_user, 0); + assert_eq!(report.pipeline_layouts.num_kept_from_user, 0); + assert_eq!(report.bind_group_layouts.num_kept_from_user, 0); + assert_eq!(report.bind_groups.num_kept_from_user, 0); + assert_eq!(report.buffers.num_kept_from_user, 0); + assert_eq!(report.texture_views.num_kept_from_user, 0); + assert_eq!(report.textures.num_kept_from_user, 0); + assert_eq!(report.command_buffers.num_allocated, 1); + assert_eq!(report.render_pipelines.num_allocated, 1); + assert_eq!(report.pipeline_layouts.num_allocated, 1); + assert_eq!(report.bind_group_layouts.num_allocated, 1); + assert_eq!(report.bind_groups.num_allocated, 1); + assert_eq!(report.buffers.num_allocated, 1); + assert_eq!(report.texture_views.num_allocated, 1); + assert_eq!(report.textures.num_allocated, 1); + + let submit_index = ctx.queue.submit(Some(encoder.finish())); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + assert_eq!(report.command_buffers.num_allocated, 0); + + ctx.async_poll(wgpu::Maintain::wait_for(submit_index)) + .await + .panic_on_timeout(); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + + assert_eq!(report.render_pipelines.num_allocated, 0); + assert_eq!(report.bind_groups.num_allocated, 0); + assert_eq!(report.bind_group_layouts.num_allocated, 0); + assert_eq!(report.pipeline_layouts.num_allocated, 0); + assert_eq!(report.texture_views.num_allocated, 0); + assert_eq!(report.textures.num_allocated, 0); + assert_eq!(report.buffers.num_allocated, 0); + + drop(ctx.queue); + drop(ctx.device); + drop(ctx.adapter); + + let global_report = ctx.instance.generate_report().unwrap(); + let report = global_report.hub_report(ctx.adapter_info.backend); + + assert_eq!(report.queues.num_kept_from_user, 0); + assert_eq!(report.textures.num_kept_from_user, 0); + assert_eq!(report.devices.num_kept_from_user, 0); + assert_eq!(report.queues.num_allocated, 0); + assert_eq!(report.buffers.num_allocated, 0); + assert_eq!(report.textures.num_allocated, 0); + assert_eq!(report.texture_views.num_allocated, 0); + assert_eq!(report.devices.num_allocated, 0); +} + +#[cfg(any( + not(target_arch = "wasm32"), + target_os = "emscripten", + feature = "webgl" +))] +#[wgpu_test::gpu_test] +static SIMPLE_DRAW_CHECK_MEM_LEAKS: wgpu_test::GpuTestConfiguration = + wgpu_test::GpuTestConfiguration::new() + .parameters( + wgpu_test::TestParameters::default() + .test_features_limits() + .features(wgpu::Features::VERTEX_WRITABLE_STORAGE), + ) + .run_async(|ctx| { + draw_test_with_reports(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { + cmb.draw(0..6, 0..1); + }) + }); diff --git a/tests/tests/multi-instance.rs b/tests/tests/multi-instance.rs new file mode 100644 index 0000000000..196d791765 --- /dev/null +++ b/tests/tests/multi-instance.rs @@ -0,0 +1,33 @@ +#![cfg(not(target_arch = "wasm32"))] + +async fn get() -> wgpu::Adapter { + let adapter = { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::util::backend_bits_from_env().unwrap_or_default(), + ..Default::default() + }); + instance + .request_adapter(&wgpu::RequestAdapterOptions::default()) + .await + .unwrap() + }; + + log::info!("Selected adapter: {:?}", adapter.get_info()); + + adapter +} + +#[test] +fn multi_instance() { + { + env_logger::init(); + + // Sequential instances. + for _ in 0..3 { + pollster::block_on(get()); + } + + // Concurrent instances + let _instances: Vec<_> = (0..3).map(|_| pollster::block_on(get())).collect(); + } +} diff --git a/tests/tests/nv12_texture/mod.rs b/tests/tests/nv12_texture/mod.rs new file mode 100644 index 0000000000..aba12e02b6 --- /dev/null +++ b/tests/tests/nv12_texture/mod.rs @@ -0,0 +1,227 @@ +//! Tests for nv12 texture creation and sampling. + +use wgpu_test::{fail, gpu_test, GpuTestConfiguration, TestParameters}; + +#[gpu_test] +static NV12_TEXTURE_CREATION_SAMPLING: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .run_sync(|ctx| { + let size = wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }; + let target_format = wgpu::TextureFormat::Bgra8UnormSrgb; + + let shader = ctx + .device + .create_shader_module(wgpu::include_wgsl!("nv12_texture.wgsl")); + let pipeline = ctx + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("nv12 pipeline"), + layout: None, + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(target_format.into())], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + strip_index_format: Some(wgpu::IndexFormat::Uint32), + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + dimension: wgpu::TextureDimension::D2, + size, + format: wgpu::TextureFormat::NV12, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + mip_level_count: 1, + sample_count: 1, + view_formats: &[], + }); + let y_view = tex.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::R8Unorm), + aspect: wgpu::TextureAspect::Plane0, + ..Default::default() + }); + let uv_view = tex.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::Rg8Unorm), + aspect: wgpu::TextureAspect::Plane1, + ..Default::default() + }); + let sampler = ctx.device.create_sampler(&wgpu::SamplerDescriptor { + min_filter: wgpu::FilterMode::Linear, + mag_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &pipeline.get_bind_group_layout(0), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&y_view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(&uv_view), + }, + ], + }); + + let target_tex = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: target_format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let target_view = target_tex.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + ops: wgpu::Operations::default(), + resolve_target: None, + view: &target_view, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_pipeline(&pipeline); + rpass.set_bind_group(0, &bind_group, &[]); + rpass.draw(0..4, 0..1); + drop(rpass); + ctx.queue.submit(Some(encoder.finish())); + }); + +#[gpu_test] +static NV12_TEXTURE_VIEW_PLANE_ON_NON_PLANAR_FORMAT: GpuTestConfiguration = + GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .run_sync(|ctx| { + let size = wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }; + let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + dimension: wgpu::TextureDimension::D2, + size, + format: wgpu::TextureFormat::R8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + mip_level_count: 1, + sample_count: 1, + view_formats: &[], + }); + fail(&ctx.device, || { + let _ = tex.create_view(&wgpu::TextureViewDescriptor { + aspect: wgpu::TextureAspect::Plane0, + ..Default::default() + }); + }); + }); + +#[gpu_test] +static NV12_TEXTURE_VIEW_PLANE_OUT_OF_BOUNDS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .run_sync(|ctx| { + let size = wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }; + let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + dimension: wgpu::TextureDimension::D2, + size, + format: wgpu::TextureFormat::NV12, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + mip_level_count: 1, + sample_count: 1, + view_formats: &[], + }); + fail(&ctx.device, || { + let _ = tex.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::R8Unorm), + aspect: wgpu::TextureAspect::Plane2, + ..Default::default() + }); + }); + }); + +#[gpu_test] +static NV12_TEXTURE_BAD_FORMAT_VIEW_PLANE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .run_sync(|ctx| { + let size = wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }; + let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + dimension: wgpu::TextureDimension::D2, + size, + format: wgpu::TextureFormat::NV12, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + mip_level_count: 1, + sample_count: 1, + view_formats: &[], + }); + fail(&ctx.device, || { + let _ = tex.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::Rg8Unorm), + aspect: wgpu::TextureAspect::Plane0, + ..Default::default() + }); + }); + }); + +#[gpu_test] +static NV12_TEXTURE_BAD_SIZE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::TEXTURE_FORMAT_NV12)) + .run_sync(|ctx| { + let size = wgpu::Extent3d { + width: 255, + height: 255, + depth_or_array_layers: 1, + }; + + fail(&ctx.device, || { + let _ = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + dimension: wgpu::TextureDimension::D2, + size, + format: wgpu::TextureFormat::NV12, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + mip_level_count: 1, + sample_count: 1, + view_formats: &[], + }); + }); + }); diff --git a/tests/tests/nv12_texture/nv12_texture.wgsl b/tests/tests/nv12_texture/nv12_texture.wgsl new file mode 100644 index 0000000000..ea974100b1 --- /dev/null +++ b/tests/tests/nv12_texture/nv12_texture.wgsl @@ -0,0 +1,33 @@ +struct VertexOutput { + @builtin(position) pos: vec4, + @location(0) uv: vec2, +} + +@vertex +fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput { + var output: VertexOutput; + // 0, 0 + // 2, 0 + // 0, 2 + // 2, 2 + let v_data = vec2(f32((vertexIndex << 1u) & 2u), f32(vertexIndex & 2u)); + output.pos = vec4(v_data - 1.0, 0.0, 1.0); + output.uv = v_data / 2.0; + return output; +} + +@group(0) @binding(0) var s: sampler; +@group(0) @binding(1) var tex_y: texture_2d; +@group(0) @binding(2) var tex_uv: texture_2d; + +@fragment +fn fs_main(v_ouput: VertexOutput) -> @location(0) vec4 { + let luminance = textureSample(tex_y, s, v_ouput.uv).r; + let chrominance = textureSample(tex_uv, s, v_ouput.uv).rg; + let rgb = mat3x3( + 1.000000, 1.000000, 1.000000, + 0.000000,-0.187324, 1.855600, + 1.574800,-0.468124, 0.000000, + ) * vec3(luminance, chrominance.r - 0.5, chrominance.g - 0.5); + return vec4(rgb, 1.0); +} diff --git a/tests/tests/occlusion_query/mod.rs b/tests/tests/occlusion_query/mod.rs index eab0828e41..0c3f3072a5 100644 --- a/tests/tests/occlusion_query/mod.rs +++ b/tests/tests/occlusion_query/mod.rs @@ -1,9 +1,10 @@ use std::borrow::Cow; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -#[test] -fn occlusion_query() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static OCCLUSION_QUERY: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::webgl2())) + .run_async(|ctx| async move { // Create depth texture let depth_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: Some("Depth texture"), @@ -69,7 +70,7 @@ fn occlusion_query() { view: &depth_texture_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: true, + store: wgpu::StoreOp::Store, }), stencil_ops: None, }), @@ -116,7 +117,9 @@ fn occlusion_query() { mapping_buffer .slice(..) .map_async(wgpu::MapMode::Read, |_| ()); - ctx.device.poll(wgpu::Maintain::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); let query_buffer_view = mapping_buffer.slice(..).get_mapped_range(); let query_data: &[u64; 3] = bytemuck::from_bytes(&query_buffer_view); @@ -124,5 +127,4 @@ fn occlusion_query() { assert_ne!(query_data[0], 0); assert_ne!(query_data[1], 0); assert_eq!(query_data[2], 0); - }) -} + }); diff --git a/tests/tests/partially_bounded_arrays/mod.rs b/tests/tests/partially_bounded_arrays/mod.rs index 43844e456e..a1350718f5 100644 --- a/tests/tests/partially_bounded_arrays/mod.rs +++ b/tests/tests/partially_bounded_arrays/mod.rs @@ -1,12 +1,10 @@ use std::{borrow::Cow, num::NonZeroU32}; -use wasm_bindgen_test::*; -use wgpu_test::{image::ReadbackBuffers, initialize_test, TestParameters}; +use wgpu_test::{gpu_test, image::ReadbackBuffers, GpuTestConfiguration, TestParameters}; -#[test] -#[wasm_bindgen_test] -fn partially_bounded_array() { - initialize_test( +#[gpu_test] +static PARTIALLY_BOUNDED_ARRAY: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features( wgpu::Features::TEXTURE_BINDING_ARRAY @@ -15,96 +13,91 @@ fn partially_bounded_array() { | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, ) .limits(wgpu::Limits::downlevel_defaults()), - |ctx| { - let device = &ctx.device; + ) + .run_async(|ctx| async move { + let device = &ctx.device; - let texture_extent = wgpu::Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }; - let storage_texture = device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: texture_extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba32Float, - usage: wgpu::TextureUsages::TEXTURE_BINDING - | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::STORAGE_BINDING - | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); + let texture_extent = wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }; + let storage_texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba32Float, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::STORAGE_BINDING + | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); - let texture_view = storage_texture.create_view(&wgpu::TextureViewDescriptor::default()); + let texture_view = storage_texture.create_view(&wgpu::TextureViewDescriptor::default()); - let bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("bind group layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::StorageTexture { - access: wgpu::StorageTextureAccess::WriteOnly, - format: wgpu::TextureFormat::Rgba32Float, - view_dimension: wgpu::TextureViewDimension::D2, - }, + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bind group layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::WriteOnly, + format: wgpu::TextureFormat::Rgba32Float, + view_dimension: wgpu::TextureViewDimension::D2, + }, - count: NonZeroU32::new(4), - }], - }); + count: NonZeroU32::new(4), + }], + }); - let cs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: None, - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), - }); + let cs_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + }); - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("main"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("main"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); - let compute_pipeline = - device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - module: &cs_module, - entry_point: "main", - }); + let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &cs_module, + entry_point: "main", + }); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureViewArray(&[&texture_view]), - }], - layout: &bind_group_layout, - label: Some("bind group"), - }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureViewArray(&[&texture_view]), + }], + layout: &bind_group_layout, + label: Some("bind group"), + }); - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - { - let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: None, - timestamp_writes: None, - }); - cpass.set_pipeline(&compute_pipeline); - cpass.set_bind_group(0, &bind_group, &[]); - cpass.dispatch_workgroups(1, 1, 1); - } + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + cpass.set_pipeline(&compute_pipeline); + cpass.set_bind_group(0, &bind_group, &[]); + cpass.dispatch_workgroups(1, 1, 1); + } - let readback_buffers = ReadbackBuffers::new(&ctx.device, &storage_texture); - readback_buffers.copy_from(&ctx.device, &mut encoder, &storage_texture); + let readback_buffers = ReadbackBuffers::new(&ctx.device, &storage_texture); + readback_buffers.copy_from(&ctx.device, &mut encoder, &storage_texture); - ctx.queue.submit(Some(encoder.finish())); + ctx.queue.submit(Some(encoder.finish())); - assert!( - readback_buffers - .check_buffer_contents(device, bytemuck::bytes_of(&[4.0f32, 3.0, 2.0, 1.0])), - "texture storage values are incorrect!" - ); - }, - ) -} + readback_buffers + .assert_buffer_contents(&ctx, bytemuck::bytes_of(&[4.0f32, 3.0, 2.0, 1.0])) + .await; + }); diff --git a/tests/tests/pipeline.rs b/tests/tests/pipeline.rs new file mode 100644 index 0000000000..2350f10663 --- /dev/null +++ b/tests/tests/pipeline.rs @@ -0,0 +1,35 @@ +use wgpu_test::{fail, gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; + +// Create an invalid shader and a compute pipeline that uses it +// with a default bindgroup layout, and then ask for that layout. +// Validation should fail, but wgpu should not panic. +#[gpu_test] +static PIPELINE_DEFAULT_LAYOUT_BAD_MODULE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + // https://github.com/gfx-rs/wgpu/issues/4167 + .expect_fail(FailureCase::always().panic("Pipeline is invalid")), + ) + .run_sync(|ctx| { + ctx.device.push_error_scope(wgpu::ErrorFilter::Validation); + + fail(&ctx.device, || { + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl("not valid wgsl".into()), + }); + + let pipeline = ctx + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("mandelbrot compute pipeline"), + layout: None, + module: &module, + entry_point: "doesn't exist", + }); + + pipeline.get_bind_group_layout(0); + }); + }); diff --git a/tests/tests/poll.rs b/tests/tests/poll.rs index e27a47a42c..740618f23c 100644 --- a/tests/tests/poll.rs +++ b/tests/tests/poll.rs @@ -1,130 +1,127 @@ use std::num::NonZeroU64; use wgpu::{ - BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, - BindingResource, BindingType, BufferBindingType, BufferDescriptor, BufferUsages, CommandBuffer, - CommandEncoderDescriptor, ComputePassDescriptor, Maintain, ShaderStages, + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBindingType, + BufferDescriptor, BufferUsages, CommandBuffer, CommandEncoderDescriptor, ComputePassDescriptor, + Maintain, ShaderStages, }; -use wasm_bindgen_test::*; -use wgpu_test::{initialize_test, FailureCase, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestingContext}; -fn generate_dummy_work(ctx: &TestingContext) -> CommandBuffer { - let buffer = ctx.device.create_buffer(&BufferDescriptor { - label: None, - size: 16, - usage: BufferUsages::UNIFORM, - mapped_at_creation: false, - }); +struct DummyWorkData { + _buffer: Buffer, + _bgl: BindGroupLayout, + _bg: BindGroup, + cmd_buf: CommandBuffer, +} + +impl DummyWorkData { + fn new(ctx: &TestingContext) -> Self { + let buffer = ctx.device.create_buffer(&BufferDescriptor { + label: None, + size: 16, + usage: BufferUsages::UNIFORM, + mapped_at_creation: false, + }); - let bind_group_layout = ctx - .device - .create_bind_group_layout(&BindGroupLayoutDescriptor { + let bind_group_layout = ctx + .device + .create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(NonZeroU64::new(16).unwrap()), + }, + count: None, + }], + }); + + let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { label: None, - entries: &[BindGroupLayoutEntry { + layout: &bind_group_layout, + entries: &[BindGroupEntry { binding: 0, - visibility: ShaderStages::COMPUTE, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: Some(NonZeroU64::new(16).unwrap()), - }, - count: None, + resource: BindingResource::Buffer(buffer.as_entire_buffer_binding()), }], }); - let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { - label: None, - layout: &bind_group_layout, - entries: &[BindGroupEntry { - binding: 0, - resource: BindingResource::Buffer(buffer.as_entire_buffer_binding()), - }], - }); + let mut cmd_buf = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()); + + let mut cpass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::default()); + cpass.set_bind_group(0, &bind_group, &[]); + drop(cpass); + + Self { + _buffer: buffer, + _bgl: bind_group_layout, + _bg: bind_group, + cmd_buf: cmd_buf.finish(), + } + } +} - let mut cmd_buf = ctx - .device - .create_command_encoder(&CommandEncoderDescriptor::default()); +#[gpu_test] +static WAIT: GpuTestConfiguration = GpuTestConfiguration::new().run_async(|ctx| async move { + let data = DummyWorkData::new(&ctx); - let mut cpass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::default()); - cpass.set_bind_group(0, &bind_group, &[]); - drop(cpass); + ctx.queue.submit(Some(data.cmd_buf)); + ctx.async_poll(Maintain::wait()).await.panic_on_timeout(); +}); - cmd_buf.finish() -} +#[gpu_test] +static DOUBLE_WAIT: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let data = DummyWorkData::new(&ctx); -#[test] -#[wasm_bindgen_test] -fn wait() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf = generate_dummy_work(&ctx); - - ctx.queue.submit(Some(cmd_buf)); - ctx.device.poll(Maintain::Wait); - }, - ) -} + ctx.queue.submit(Some(data.cmd_buf)); + ctx.async_poll(Maintain::wait()).await.panic_on_timeout(); + ctx.async_poll(Maintain::wait()).await.panic_on_timeout(); + }); -#[test] -#[wasm_bindgen_test] -fn double_wait() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf = generate_dummy_work(&ctx); - - ctx.queue.submit(Some(cmd_buf)); - ctx.device.poll(Maintain::Wait); - ctx.device.poll(Maintain::Wait); - }, - ) -} +#[gpu_test] +static WAIT_ON_SUBMISSION: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let data = DummyWorkData::new(&ctx); -#[test] -#[wasm_bindgen_test] -fn wait_on_submission() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf = generate_dummy_work(&ctx); - - let index = ctx.queue.submit(Some(cmd_buf)); - ctx.device.poll(Maintain::WaitForSubmissionIndex(index)); - }, - ) -} + let index = ctx.queue.submit(Some(data.cmd_buf)); + ctx.async_poll(Maintain::wait_for(index)) + .await + .panic_on_timeout(); + }); -#[test] -#[wasm_bindgen_test] -fn double_wait_on_submission() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf = generate_dummy_work(&ctx); - - let index = ctx.queue.submit(Some(cmd_buf)); - ctx.device - .poll(Maintain::WaitForSubmissionIndex(index.clone())); - ctx.device.poll(Maintain::WaitForSubmissionIndex(index)); - }, - ) -} +#[gpu_test] +static DOUBLE_WAIT_ON_SUBMISSION: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let data = DummyWorkData::new(&ctx); + + let index = ctx.queue.submit(Some(data.cmd_buf)); + ctx.async_poll(Maintain::wait_for(index.clone())) + .await + .panic_on_timeout(); + ctx.async_poll(Maintain::wait_for(index)) + .await + .panic_on_timeout(); + }); -#[test] -#[wasm_bindgen_test] -fn wait_out_of_order() { - initialize_test( - TestParameters::default().skip(FailureCase::always()), - |ctx| { - let cmd_buf1 = generate_dummy_work(&ctx); - let cmd_buf2 = generate_dummy_work(&ctx); - - let index1 = ctx.queue.submit(Some(cmd_buf1)); - let index2 = ctx.queue.submit(Some(cmd_buf2)); - ctx.device.poll(Maintain::WaitForSubmissionIndex(index2)); - ctx.device.poll(Maintain::WaitForSubmissionIndex(index1)); - }, - ) -} +#[gpu_test] +static WAIT_OUT_OF_ORDER: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let data1 = DummyWorkData::new(&ctx); + let data2 = DummyWorkData::new(&ctx); + + let index1 = ctx.queue.submit(Some(data1.cmd_buf)); + let index2 = ctx.queue.submit(Some(data2.cmd_buf)); + ctx.async_poll(Maintain::wait_for(index2)) + .await + .panic_on_timeout(); + ctx.async_poll(Maintain::wait_for(index1)) + .await + .panic_on_timeout(); + }); diff --git a/tests/tests/push_constants.rs b/tests/tests/push_constants.rs new file mode 100644 index 0000000000..489d9a10af --- /dev/null +++ b/tests/tests/push_constants.rs @@ -0,0 +1,153 @@ +use std::num::NonZeroU64; + +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +/// We want to test that partial updates to push constants work as expected. +/// +/// As such, we dispatch two compute passes, one which writes the values +/// before a parital update, and one which writes the values after the partial update. +/// +/// If the update code is working correctly, the values not written to by the second update +/// will remain unchanged. +#[gpu_test] +static PARTIAL_UPDATE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features(wgpu::Features::PUSH_CONSTANTS) + .limits(wgpu::Limits { + max_push_constant_size: 32, + ..Default::default() + }), + ) + .run_async(partial_update_test); + +const SHADER: &str = r#" + struct Pc { + offset: u32, + vector: vec4f, + } + + var pc: Pc; + + @group(0) @binding(0) + var output: array; + + @compute @workgroup_size(1) + fn main() { + output[pc.offset] = pc.vector; + } +"#; + +async fn partial_update_test(ctx: TestingContext) { + let sm = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("shader"), + source: wgpu::ShaderSource::Wgsl(SHADER.into()), + }); + + let bgl = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bind_group_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: NonZeroU64::new(16), + }, + count: None, + }], + }); + + let gpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("gpu_buffer"), + size: 32, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + + let cpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("cpu_buffer"), + size: 32, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bind_group"), + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: gpu_buffer.as_entire_binding(), + }], + }); + + let pipeline_layout = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("pipeline_layout"), + bind_group_layouts: &[&bgl], + push_constant_ranges: &[wgpu::PushConstantRange { + stages: wgpu::ShaderStages::COMPUTE, + range: 0..32, + }], + }); + + let pipeline = ctx + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("pipeline"), + layout: Some(&pipeline_layout), + module: &sm, + entry_point: "main", + }); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("encoder"), + }); + + { + let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("compute_pass"), + timestamp_writes: None, + }); + cpass.set_pipeline(&pipeline); + cpass.set_bind_group(0, &bind_group, &[]); + + // -- Dispatch 0 -- + + // Dispatch number + cpass.set_push_constants(0, bytemuck::bytes_of(&[0_u32])); + // Update the whole vector. + cpass.set_push_constants(16, bytemuck::bytes_of(&[1.0_f32, 2.0, 3.0, 4.0])); + cpass.dispatch_workgroups(1, 1, 1); + + // -- Dispatch 1 -- + + // Dispatch number + cpass.set_push_constants(0, bytemuck::bytes_of(&[1_u32])); + // Update just the y component of the vector. + cpass.set_push_constants(20, bytemuck::bytes_of(&[5.0_f32])); + cpass.dispatch_workgroups(1, 1, 1); + } + + encoder.copy_buffer_to_buffer(&gpu_buffer, 0, &cpu_buffer, 0, 32); + ctx.queue.submit([encoder.finish()]); + cpu_buffer.slice(..).map_async(wgpu::MapMode::Read, |_| ()); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + + let data = cpu_buffer.slice(..).get_mapped_range(); + + let floats: &[f32] = bytemuck::cast_slice(&data); + + // first 4 floats the initial value + // second 4 floats the first update + assert_eq!(floats, [1.0, 2.0, 3.0, 4.0, 1.0, 5.0, 3.0, 4.0]); +} diff --git a/tests/tests/query_set.rs b/tests/tests/query_set.rs new file mode 100644 index 0000000000..69d0f868db --- /dev/null +++ b/tests/tests/query_set.rs @@ -0,0 +1,23 @@ +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; + +#[gpu_test] +static DROP_FAILED_TIMESTAMP_QUERY_SET: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default()) + .run_sync(|ctx| { + // Enter an error scope, so the validation catch-all doesn't + // report the error too early. + ctx.device.push_error_scope(wgpu::ErrorFilter::Validation); + + // Creating this query set should fail, since we didn't include + // TIMESTAMP_QUERY in our required features. + let bad_query_set = ctx.device.create_query_set(&wgpu::QuerySetDescriptor { + label: Some("doomed query set"), + ty: wgpu::QueryType::Timestamp, + count: 1, + }); + + // Dropping this should not panic. + drop(bad_query_set); + + assert!(pollster::block_on(ctx.device.pop_error_scope()).is_some()); + }); diff --git a/tests/tests/queue_transfer.rs b/tests/tests/queue_transfer.rs index f17030f372..a1ae41572b 100644 --- a/tests/tests/queue_transfer.rs +++ b/tests/tests/queue_transfer.rs @@ -1,12 +1,10 @@ //! Tests for buffer copy validation. -use wasm_bindgen_test::*; -use wgpu_test::{fail, initialize_test, TestParameters}; +use wgpu_test::{fail, gpu_test, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn queue_write_texture_overflow() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static QUEUE_WRITE_TEXTURE_OVERFLOW: GpuTestConfiguration = + GpuTestConfiguration::new().run_sync(|ctx| { let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { @@ -47,4 +45,3 @@ fn queue_write_texture_overflow() { ); }); }); -} diff --git a/tests/tests/ray_tracing/mesh_gen.rs b/tests/tests/ray_tracing/mesh_gen.rs index 0c7bb61132..8bc4b03b7d 100644 --- a/tests/tests/ray_tracing/mesh_gen.rs +++ b/tests/tests/ray_tracing/mesh_gen.rs @@ -1,5 +1,5 @@ use bytemuck::{Pod, Zeroable}; -use glam::{Affine3A, Mat4, Quat, Vec3}; +use glam::Affine3A; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] diff --git a/tests/tests/ray_tracing/mod.rs b/tests/tests/ray_tracing/mod.rs index e55da63fe8..e3440034df 100644 --- a/tests/tests/ray_tracing/mod.rs +++ b/tests/tests/ray_tracing/mod.rs @@ -1,6 +1,6 @@ use std::{iter, mem}; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; use rt::traits::*; use wgpu::ray_tracing as rt; @@ -20,102 +20,103 @@ fn required_features() -> wgpu::Features { | wgpu::Features::RAY_TRACING_ACCELERATION_STRUCTURE } -#[test] -fn create_tests() { - initialize_test( - TestParameters::default().features(required_features()), - |ctx| { - let max_instances = 1000; - let device = &ctx.device; - - let (vertex_data, index_data) = mesh_gen::create_vertices(); - - let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(&vertex_data), - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, - }); - - let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(&index_data), - usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, - }); - - let blas_geo_size_desc = rt::BlasTriangleGeometrySizeDescriptor { - vertex_format: wgpu::VertexFormat::Float32x4, - vertex_count: vertex_data.len() as u32, - index_format: Some(wgpu::IndexFormat::Uint16), - index_count: Some(index_data.len() as u32), - flags: rt::AccelerationStructureGeometryFlags::OPAQUE, - }; - - let blas = device.create_blas( - &rt::CreateBlasDescriptor { - label: None, - flags: rt::AccelerationStructureFlags::PREFER_FAST_TRACE, - update_mode: rt::AccelerationStructureUpdateMode::Build, - }, - rt::BlasGeometrySizeDescriptors::Triangles { - desc: vec![blas_geo_size_desc.clone()], - }, - ); - - let tlas = device.create_tlas(&rt::CreateTlasDescriptor { - label: None, - flags: rt::AccelerationStructureFlags::PREFER_FAST_TRACE, - update_mode: rt::AccelerationStructureUpdateMode::Build, - max_instances, - }); - - let mut tlas_package = rt::TlasPackage::new(tlas, max_instances); - - for i in 0..10000 { - for j in 0..max_instances { - *tlas_package.get_mut_single(0).unwrap() = Some(rt::TlasInstance::new( - &blas, - AccelerationStructureInstance::affine_to_rows( - &Affine3A::from_rotation_translation( - Quat::from_rotation_y(45.9_f32.to_radians()), - Vec3 { - x: j as f32, - y: i as f32, - z: 0.0, - }, - ), - ), - 0, - 0xff, - )); - } - - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - encoder.build_acceleration_structures( - iter::once(&rt::BlasBuildEntry { - blas: &blas, - geometry: rt::BlasGeometries::TriangleGeometries(vec![ - rt::BlasTriangleGeometry { - size: &blas_geo_size_desc, - vertex_buffer: &vertex_buf, - first_vertex: 0, - vertex_stride: mem::size_of::() as u64, - index_buffer: Some(&index_buf), - index_buffer_offset: Some(0), - transform_buffer: None, - transform_buffer_offset: None, - }, - ]), - }), - // iter::empty(), - iter::once(&tlas_package), - ); - - ctx.queue.submit(Some(encoder.finish())); - } - - ctx.device.poll(wgpu::Maintain::Wait); +fn execute(ctx: TestingContext) { + let max_instances = 1000; + let device = &ctx.device; + + let (vertex_data, index_data) = mesh_gen::create_vertices(); + + let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertex_data), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&index_data), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let blas_geo_size_desc = rt::BlasTriangleGeometrySizeDescriptor { + vertex_format: wgpu::VertexFormat::Float32x4, + vertex_count: vertex_data.len() as u32, + index_format: Some(wgpu::IndexFormat::Uint16), + index_count: Some(index_data.len() as u32), + flags: rt::AccelerationStructureGeometryFlags::OPAQUE, + }; + + let blas = device.create_blas( + &rt::CreateBlasDescriptor { + label: None, + flags: rt::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: rt::AccelerationStructureUpdateMode::Build, + }, + rt::BlasGeometrySizeDescriptors::Triangles { + desc: vec![blas_geo_size_desc.clone()], }, ); + + let tlas = device.create_tlas(&rt::CreateTlasDescriptor { + label: None, + flags: rt::AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: rt::AccelerationStructureUpdateMode::Build, + max_instances, + }); + + let mut tlas_package = rt::TlasPackage::new(tlas, max_instances); + + for i in 0..10000 { + for j in 0..max_instances { + *tlas_package.get_mut_single(0).unwrap() = Some(rt::TlasInstance::new( + &blas, + AccelerationStructureInstance::affine_to_rows( + &Affine3A::from_rotation_translation( + Quat::from_rotation_y(45.9_f32.to_radians()), + Vec3 { + x: j as f32, + y: i as f32, + z: 0.0, + }, + ), + ), + 0, + 0xff, + )); + } + + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + encoder.build_acceleration_structures( + iter::once(&rt::BlasBuildEntry { + blas: &blas, + geometry: rt::BlasGeometries::TriangleGeometries(vec![rt::BlasTriangleGeometry { + size: &blas_geo_size_desc, + vertex_buffer: &vertex_buf, + first_vertex: 0, + vertex_stride: mem::size_of::() as u64, + index_buffer: Some(&index_buf), + index_buffer_offset: Some(0), + transform_buffer: None, + transform_buffer_offset: None, + }]), + }), + // iter::empty(), + iter::once(&tlas_package), + ); + + ctx.queue.submit(Some(encoder.finish())); + } + + ctx.device.poll(wgpu::Maintain::Wait); } + +#[gpu_test] +static RAY_TRACING: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(required_features()), + ) + .run_sync(execute); diff --git a/tests/tests/regression/issue_3349.fs.wgsl b/tests/tests/regression/issue_3349.fs.wgsl new file mode 100644 index 0000000000..d6a5ea5ceb --- /dev/null +++ b/tests/tests/regression/issue_3349.fs.wgsl @@ -0,0 +1,46 @@ +struct ShaderData { + a: f32, + b: f32, + c: f32, + d: f32, +} + +@group(0) @binding(0) +var data1: ShaderData; + +var data2: ShaderData; + +struct FsIn { + @builtin(position) position: vec4f, + @location(0) data1: vec4f, + @location(1) data2: vec4f, +} + +@fragment +fn fs_main(fs_in: FsIn) -> @location(0) vec4f { + let floored = vec2u(floor(fs_in.position.xy)); + // We're outputting a 2x2 image, each pixel coming from a different source + let serial = floored.x + floored.y * 2u; + + switch serial { + // (0, 0) - uniform buffer from the vertex shader + case 0u: { + return fs_in.data1; + } + // (1, 0) - push constant from the vertex shader + case 1u: { + return fs_in.data2; + } + // (0, 1) - uniform buffer from the fragment shader + case 2u: { + return vec4f(data1.a, data1.b, data1.c, data1.d); + } + // (1, 1) - push constant from the fragment shader + case 3u: { + return vec4f(data2.a, data2.b, data2.c, data2.d); + } + default: { + return vec4f(0.0); + } + } +} diff --git a/tests/tests/regression/issue_3349.rs b/tests/tests/regression/issue_3349.rs new file mode 100644 index 0000000000..2d94d56920 --- /dev/null +++ b/tests/tests/regression/issue_3349.rs @@ -0,0 +1,178 @@ +use wgpu::util::DeviceExt; +use wgpu_test::{ + gpu_test, image::ReadbackBuffers, GpuTestConfiguration, TestParameters, TestingContext, +}; + +/// We thought we had an OpenGL bug that, when running without explicit in-shader locations, +/// we will not properly bind uniform buffers to both the vertex and fragment +/// shaders. This turned out to not reproduce at all with this test case. +/// +/// However, it also caught issues with the push constant implementation, +/// making sure that it works correctly with different definitions for the push constant +/// block in vertex and fragment shaders. +/// +/// This test needs to be able to run on GLES 3.0 +/// +/// What this test does is render a 2x2 texture. Each pixel corresponds to a different +/// data source. +/// +/// top left: Vertex Shader / Uniform Buffer +/// top right: Vertex Shader / Push Constant +/// bottom left: Fragment Shader / Uniform Buffer +/// bottom right: Fragment Shader / Push Constant +/// +/// We then validate the data is correct from every position. +#[gpu_test] +static MULTI_STAGE_DATA_BINDING: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features(wgpu::Features::PUSH_CONSTANTS) + .limits(wgpu::Limits { + max_push_constant_size: 16, + ..Default::default() + }), + ) + .run_async(multi_stage_data_binding_test); + +async fn multi_stage_data_binding_test(ctx: TestingContext) { + // We use different shader modules to allow us to use different + // types for the uniform and push constant blocks between stages. + let vs_sm = ctx + .device + .create_shader_module(wgpu::include_wgsl!("issue_3349.vs.wgsl")); + + let fs_sm = ctx + .device + .create_shader_module(wgpu::include_wgsl!("issue_3349.fs.wgsl")); + + // We start with u8s then convert to float, to make sure we don't have + // cross-vendor rounding issues unorm. + let input_as_unorm: [u8; 4] = [25_u8, 50, 75, 100]; + let input = input_as_unorm.map(|v| v as f32 / 255.0); + + let buffer = ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("buffer"), + contents: bytemuck::cast_slice(&input), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let bgl = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bgl"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bg"), + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + let pll = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("pll"), + bind_group_layouts: &[&bgl], + push_constant_ranges: &[wgpu::PushConstantRange { + stages: wgpu::ShaderStages::VERTEX_FRAGMENT, + range: 0..16, + }], + }); + + let pipeline = ctx + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("pipeline"), + layout: Some(&pll), + vertex: wgpu::VertexState { + module: &vs_sm, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &fs_sm, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: Some("texture"), + size: wgpu::Extent3d { + width: 2, + height: 2, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + // Important: NOT srgb. + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("encoder"), + }); + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("rpass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&pipeline); + rpass.set_bind_group(0, &bg, &[]); + rpass.set_push_constants( + wgpu::ShaderStages::VERTEX_FRAGMENT, + 0, + bytemuck::cast_slice(&input), + ); + rpass.draw(0..3, 0..1); + } + + let buffers = ReadbackBuffers::new(&ctx.device, &texture); + buffers.copy_from(&ctx.device, &mut encoder, &texture); + ctx.queue.submit([encoder.finish()]); + + let result = input_as_unorm.repeat(4); + buffers.assert_buffer_contents(&ctx, &result).await; +} diff --git a/tests/tests/regression/issue_3349.vs.wgsl b/tests/tests/regression/issue_3349.vs.wgsl new file mode 100644 index 0000000000..85992a756b --- /dev/null +++ b/tests/tests/regression/issue_3349.vs.wgsl @@ -0,0 +1,22 @@ +@group(0) @binding(0) +var data1: vec4f; + +// D3DCompile requires this to be a struct +struct Pc { + inner: vec4f, +} + +var data2: Pc; + +struct VsOut { + @builtin(position) position: vec4f, + @location(0) data1: vec4f, + @location(1) data2: vec4f, +} + +@vertex +fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VsOut { + let uv = vec2f(f32((vertexIndex << 1u) & 2u), f32(vertexIndex & 2u)); + let position = vec4f(uv * 2.0 - 1.0, 0.0, 1.0); + return VsOut(position, data1, data2.inner); +} diff --git a/tests/tests/regression/issue_3457.rs b/tests/tests/regression/issue_3457.rs index 2dccd3d427..cc811b3e33 100644 --- a/tests/tests/regression/issue_3457.rs +++ b/tests/tests/regression/issue_3457.rs @@ -1,6 +1,5 @@ -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -use wasm_bindgen_test::wasm_bindgen_test; use wgpu::*; /// The core issue here was that we weren't properly disabling vertex attributes on GL @@ -15,10 +14,9 @@ use wgpu::*; /// /// We use non-consecutive vertex attribute locations (0 and 5) in order to also test /// that we unset the correct locations (see PR #3706). -#[wasm_bindgen_test] -#[test] -fn pass_reset_vertex_buffer() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static PASS_RESET_VERTEX_BUFFER: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { let module = ctx .device .create_shader_module(include_wgsl!("issue_3457.wgsl")); @@ -140,7 +138,7 @@ fn pass_reset_vertex_buffer() { resolve_target: None, ops: Operations { load: LoadOp::Clear(Color::BLACK), - store: false, + store: StoreOp::Discard, }, })], depth_stencil_attachment: None, @@ -162,7 +160,7 @@ fn pass_reset_vertex_buffer() { drop(vertex_buffer2); // Make sure the buffers are actually deleted. - ctx.device.poll(Maintain::Wait); + ctx.async_poll(Maintain::wait()).await.panic_on_timeout(); let mut encoder2 = ctx .device @@ -175,7 +173,7 @@ fn pass_reset_vertex_buffer() { resolve_target: None, ops: Operations { load: LoadOp::Clear(Color::BLACK), - store: false, + store: StoreOp::Discard, }, })], depth_stencil_attachment: None, @@ -190,5 +188,4 @@ fn pass_reset_vertex_buffer() { drop(single_rpass); ctx.queue.submit(Some(encoder2.finish())); - }) -} + }); diff --git a/tests/tests/regression/issue_4024.rs b/tests/tests/regression/issue_4024.rs index 959f58ffa5..f4fccfa657 100644 --- a/tests/tests/regression/issue_4024.rs +++ b/tests/tests/regression/issue_4024.rs @@ -1,9 +1,8 @@ use std::sync::Arc; use parking_lot::Mutex; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -use wasm_bindgen_test::wasm_bindgen_test; use wgpu::*; /// The WebGPU specification has very specific requirements about the ordering of map_async @@ -13,10 +12,9 @@ use wgpu::*; /// /// We previously immediately invoked on_submitted_work_done callbacks if there was no active submission /// to add them to. This is incorrect, as we do not immediatley invoke map_async callbacks. -#[wasm_bindgen_test] -#[test] -fn queue_submitted_callback_ordering() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static QUEUE_SUBMITTED_CALLBACK_ORDERING: GpuTestConfiguration = GpuTestConfiguration::new() + .run_async(|ctx| async move { // Create a mappable buffer let buffer = ctx.device.create_buffer(&BufferDescriptor { label: Some("mappable buffer"), @@ -38,7 +36,7 @@ fn queue_submitted_callback_ordering() { // Submit the work. ctx.queue.submit(Some(encoder.finish())); // Ensure the work is finished. - ctx.device.poll(MaintainBase::Wait); + ctx.async_poll(Maintain::wait()).await.panic_on_timeout(); #[derive(Debug)] struct OrderingContext { @@ -76,10 +74,10 @@ fn queue_submitted_callback_ordering() { }); // No GPU work is happening at this point, but we want to process callbacks. - ctx.device.poll(MaintainBase::Poll); + ctx.async_poll(MaintainBase::Poll).await.panic_on_timeout(); // Extract the ordering out of the arc. - let ordering = Arc::try_unwrap(ordering).unwrap().into_inner(); + let ordering = Arc::into_inner(ordering).unwrap().into_inner(); // There were two callbacks invoked assert_eq!(ordering.counter, 2); @@ -87,5 +85,4 @@ fn queue_submitted_callback_ordering() { assert_eq!(ordering.value_read_map_async, Some(0)); // The queue submitted work done callback was invoked second. assert_eq!(ordering.value_read_queue_submitted, Some(1)); - }) -} + }); diff --git a/tests/tests/regression/issue_4122.rs b/tests/tests/regression/issue_4122.rs new file mode 100644 index 0000000000..1dc32f6528 --- /dev/null +++ b/tests/tests/regression/issue_4122.rs @@ -0,0 +1,106 @@ +use std::ops::Range; + +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +async fn fill_test(ctx: &TestingContext, range: Range, size: u64) -> bool { + let gpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("gpu_buffer"), + size, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + + let cpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("cpu_buffer"), + size, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + // Initialize the whole buffer with values. + let buffer_contents = vec![0xFF_u8; size as usize]; + ctx.queue.write_buffer(&gpu_buffer, 0, &buffer_contents); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("encoder"), + }); + + encoder.clear_buffer(&gpu_buffer, range.start, Some(range.end - range.start)); + encoder.copy_buffer_to_buffer(&gpu_buffer, 0, &cpu_buffer, 0, size); + + ctx.queue.submit(Some(encoder.finish())); + cpu_buffer.slice(..).map_async(wgpu::MapMode::Read, |_| ()); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + + let buffer_slice = cpu_buffer.slice(..); + let buffer_data = buffer_slice.get_mapped_range(); + + let first_clear_byte = buffer_data + .iter() + .enumerate() + .find_map(|(index, byte)| (*byte == 0x00).then_some(index)) + .expect("No clear happened at all"); + + let first_dirty_byte = buffer_data + .iter() + .enumerate() + .skip(first_clear_byte) + .find_map(|(index, byte)| (*byte != 0x00).then_some(index)) + .unwrap_or(size as usize); + + let second_clear_byte = buffer_data + .iter() + .enumerate() + .skip(first_dirty_byte) + .find_map(|(index, byte)| (*byte == 0x00).then_some(index)); + + if second_clear_byte.is_some() { + eprintln!("Found multiple cleared ranges instead of a single clear range of {}..{} on a buffer of size {}.", range.start, range.end, size); + return false; + } + + let cleared_range = first_clear_byte as u64..first_dirty_byte as u64; + + if cleared_range != range { + eprintln!( + "Cleared range is {}..{}, but the clear range is {}..{} on a buffer of size {}.", + cleared_range.start, cleared_range.end, range.start, range.end, size + ); + return false; + } + + eprintln!( + "Cleared range is {}..{} on a buffer of size {}.", + cleared_range.start, cleared_range.end, size + ); + + true +} + +/// Nvidia has a bug in vkCmdFillBuffer where the clear range is not properly respected under +/// certain conditions. See https://github.com/gfx-rs/wgpu/issues/4122 for more information. +/// +/// This test will fail on nvidia if the bug is not properly worked around. +#[gpu_test] +static CLEAR_BUFFER_RANGE_RESPECTED: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default()) + .run_async(|ctx| async move { + // This hits most of the cases in nvidia's clear buffer bug + let mut succeeded = true; + for power in 4..14 { + let size = 1 << power; + for start_offset in (0..=36).step_by(4) { + for size_offset in (0..=36).step_by(4) { + let range = start_offset..size + size_offset + start_offset; + let result = fill_test(&ctx, range, 1 << 16).await; + + succeeded &= result; + } + } + } + assert!(succeeded); + }); diff --git a/tests/tests/resource_descriptor_accessor.rs b/tests/tests/resource_descriptor_accessor.rs index 5f70258ac3..ee984da60e 100644 --- a/tests/tests/resource_descriptor_accessor.rs +++ b/tests/tests/resource_descriptor_accessor.rs @@ -1,22 +1,17 @@ -use wasm_bindgen_test::*; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -/// Buffer's size and usage can be read back. -#[test] -#[wasm_bindgen_test] -fn buffer_size_and_usage() { - initialize_test(TestParameters::default(), |ctx| { - let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 1234, - usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); +#[gpu_test] +static BUFFER_SIZE_AND_USAGE: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 1234, + usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); - assert_eq!(buffer.size(), 1234); - assert_eq!( - buffer.usage(), - wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST - ); - }) -} + assert_eq!(buffer.size(), 1234); + assert_eq!( + buffer.usage(), + wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST + ); +}); diff --git a/tests/tests/resource_error.rs b/tests/tests/resource_error.rs index c02033b33c..0a81801a9a 100644 --- a/tests/tests/resource_error.rs +++ b/tests/tests/resource_error.rs @@ -1,57 +1,48 @@ -use wasm_bindgen_test::*; -use wgpu_test::{fail, initialize_test, valid, TestParameters}; +use wgpu_test::{fail, gpu_test, valid, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn bad_buffer() { +#[gpu_test] +static BAD_BUFFER: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { // Create a buffer with bad parameters and call a few methods. // Validation should fail but there should be not panic. - initialize_test(TestParameters::default(), |ctx| { - let buffer = fail(&ctx.device, || { - ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 99999999, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::STORAGE, - mapped_at_creation: false, - }) - }); + let buffer = fail(&ctx.device, || { + ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 99999999, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::STORAGE, + mapped_at_creation: false, + }) + }); - fail(&ctx.device, || { - buffer.slice(..).map_async(wgpu::MapMode::Write, |_| {}) - }); - fail(&ctx.device, || buffer.unmap()); - valid(&ctx.device, || buffer.destroy()); - valid(&ctx.device, || buffer.destroy()); + fail(&ctx.device, || { + buffer.slice(..).map_async(wgpu::MapMode::Write, |_| {}) }); -} + fail(&ctx.device, || buffer.unmap()); + valid(&ctx.device, || buffer.destroy()); + valid(&ctx.device, || buffer.destroy()); +}); -#[test] -#[wasm_bindgen_test] -fn bad_texture() { - // Create a texture with bad parameters and call a few methods. - // Validation should fail but there should be not panic. - initialize_test(TestParameters::default(), |ctx| { - let texture = fail(&ctx.device, || { - ctx.device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: 0, - height: 12345678, - depth_or_array_layers: 9001, - }, - mip_level_count: 2000, - sample_count: 27, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::all(), - view_formats: &[], - }) - }); +#[gpu_test] +static BAD_TEXTURE: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let texture = fail(&ctx.device, || { + ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 0, + height: 12345678, + depth_or_array_layers: 9001, + }, + mip_level_count: 2000, + sample_count: 27, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::all(), + view_formats: &[], + }) + }); - fail(&ctx.device, || { - let _ = texture.create_view(&wgpu::TextureViewDescriptor::default()); - }); - valid(&ctx.device, || texture.destroy()); - valid(&ctx.device, || texture.destroy()); + fail(&ctx.device, || { + let _ = texture.create_view(&wgpu::TextureViewDescriptor::default()); }); -} + valid(&ctx.device, || texture.destroy()); + valid(&ctx.device, || texture.destroy()); +}); diff --git a/tests/tests/root.rs b/tests/tests/root.rs index 1852636010..8f24acb32a 100644 --- a/tests/tests/root.rs +++ b/tests/tests/root.rs @@ -1,10 +1,11 @@ -use wasm_bindgen_test::wasm_bindgen_test_configure; - mod regression { + mod issue_3349; mod issue_3457; mod issue_4024; + mod issue_4122; } +mod bgra8unorm_storage; mod bind_group_layout_dedup; mod buffer; mod buffer_copy; @@ -13,12 +14,18 @@ mod clear_texture; mod create_surface_error; mod device; mod encoder; -mod example_wgsl; mod external_texture; +mod float32_filterable; mod instance; +mod life_cycle; +mod mem_leaks; +mod nv12_texture; mod occlusion_query; mod partially_bounded_arrays; +mod pipeline; mod poll; +mod push_constants; +mod query_set; mod queue_transfer; mod ray_tracing; mod resource_descriptor_accessor; @@ -33,4 +40,4 @@ mod vertex_indices; mod write_texture; mod zero_init_texture_after_discard; -wasm_bindgen_test_configure!(run_in_browser); +wgpu_test::gpu_test_main!(); diff --git a/tests/tests/scissor_tests/mod.rs b/tests/tests/scissor_tests/mod.rs index da050cb61f..11b72ba7a4 100644 --- a/tests/tests/scissor_tests/mod.rs +++ b/tests/tests/scissor_tests/mod.rs @@ -1,4 +1,4 @@ -use wgpu_test::{image, initialize_test, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, image, GpuTestConfiguration, TestingContext}; struct Rect { x: u32, @@ -11,7 +11,11 @@ const TEXTURE_HEIGHT: u32 = 2; const TEXTURE_WIDTH: u32 = 2; const BUFFER_SIZE: usize = (TEXTURE_WIDTH * TEXTURE_HEIGHT * 4) as usize; -fn scissor_test_impl(ctx: &TestingContext, scissor_rect: Rect, expected_data: [u8; BUFFER_SIZE]) { +async fn scissor_test_impl( + ctx: &TestingContext, + scissor_rect: Rect, + expected_data: [u8; BUFFER_SIZE], +) { let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { label: Some("Offscreen texture"), size: wgpu::Extent3d { @@ -75,7 +79,7 @@ fn scissor_test_impl(ctx: &TestingContext, scissor_rect: Rect, expected_data: [u b: 0.0, a: 0.0, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, @@ -94,12 +98,14 @@ fn scissor_test_impl(ctx: &TestingContext, scissor_rect: Rect, expected_data: [u readback_buffer.copy_from(&ctx.device, &mut encoder, &texture); ctx.queue.submit(Some(encoder.finish())); } - assert!(readback_buffer.check_buffer_contents(&ctx.device, &expected_data)); + readback_buffer + .assert_buffer_contents(ctx, &expected_data) + .await; } -#[test] -fn scissor_test_full_rect() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static SCISSOR_TEST_FULL_RECT: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { scissor_test_impl( &ctx, Rect { @@ -109,13 +115,13 @@ fn scissor_test_full_rect() { height: TEXTURE_HEIGHT, }, [255; BUFFER_SIZE], - ); - }) -} + ) + .await + }); -#[test] -fn scissor_test_empty_rect() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static SCISSOR_TEST_EMPTY_RECT: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { scissor_test_impl( &ctx, Rect { @@ -125,13 +131,13 @@ fn scissor_test_empty_rect() { height: 0, }, [0; BUFFER_SIZE], - ); - }) -} + ) + .await; + }); -#[test] -fn scissor_test_empty_rect_with_offset() { - initialize_test(TestParameters::default(), |ctx| { +#[gpu_test] +static SCISSOR_TEST_EMPTY_RECT_WITH_OFFSET: GpuTestConfiguration = GpuTestConfiguration::new() + .run_async(|ctx| async move { scissor_test_impl( &ctx, Rect { @@ -141,16 +147,17 @@ fn scissor_test_empty_rect_with_offset() { height: 0, }, [0; BUFFER_SIZE], - ); - }) -} + ) + .await + }); + +#[gpu_test] +static SCISSOR_TEST_CUSTOM_RECT: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let mut expected_result = [0; BUFFER_SIZE]; + expected_result[((3 * BUFFER_SIZE) / 4)..][..BUFFER_SIZE / 4] + .copy_from_slice(&[255; BUFFER_SIZE / 4]); -#[test] -fn scissor_test_custom_rect() { - let mut expected_result = [0; BUFFER_SIZE]; - expected_result[((3 * BUFFER_SIZE) / 4)..][..BUFFER_SIZE / 4] - .copy_from_slice(&[255; BUFFER_SIZE / 4]); - initialize_test(TestParameters::default(), |ctx| { scissor_test_impl( &ctx, Rect { @@ -160,6 +167,6 @@ fn scissor_test_custom_rect() { height: TEXTURE_HEIGHT / 2, }, expected_result, - ); - }) -} + ) + .await; + }); diff --git a/tests/tests/shader/mod.rs b/tests/tests/shader/mod.rs index 498c16c337..1a981971f7 100644 --- a/tests/tests/shader/mod.rs +++ b/tests/tests/shader/mod.rs @@ -15,9 +15,9 @@ use wgpu::{ use wgpu_test::TestingContext; -mod numeric_builtins; -mod struct_layout; -mod zero_init_workgroup_mem; +pub mod numeric_builtins; +pub mod struct_layout; +pub mod zero_init_workgroup_mem; #[derive(Clone, Copy, PartialEq)] enum InputStorageType { @@ -40,6 +40,8 @@ impl InputStorageType { struct ShaderTest { /// Human readable name name: String, + /// Header text. This is arbitrary code injected at the top of the shader. Replaces {{header}} + header: String, /// This text will be the body of the `Input` struct. Replaces "{{input_members}}" /// in the shader_test shader. custom_struct_members: String, @@ -132,6 +134,7 @@ impl ShaderTest { ) -> Self { Self { name, + header: String::new(), custom_struct_members, body, input_type: String::from("CustomStruct"), @@ -144,6 +147,12 @@ impl ShaderTest { } } + fn header(mut self, header: String) -> Self { + self.header = header; + + self + } + /// Add another set of possible outputs. If any of the given /// output values are seen it's considered a success (i.e. this is OR, not AND). /// @@ -168,7 +177,7 @@ impl ShaderTest { const MAX_BUFFER_SIZE: u64 = 128; /// Runs the given shader tests with the given storage_type for the input_buffer. -fn shader_input_output_test( +async fn shader_input_output_test( ctx: TestingContext, storage_type: InputStorageType, tests: Vec, @@ -272,6 +281,7 @@ fn shader_input_output_test( // This isn't terribly efficient but the string is short and it's a test. // The body and input members are the longest part, so do them last. let mut processed = source + .replace("{{header}}", &test.header) .replace("{{storage_type}}", storage_type.as_str()) .replace("{{input_type}}", &test.input_type) .replace("{{output_type}}", &test.output_type) @@ -345,7 +355,7 @@ fn shader_input_output_test( ctx.queue.submit(Some(encoder.finish())); mapping_buffer.slice(..).map_async(MapMode::Read, |_| ()); - ctx.device.poll(Maintain::Wait); + ctx.async_poll(Maintain::wait()).await.panic_on_timeout(); let mapped = mapping_buffer.slice(..).get_mapped_range(); diff --git a/tests/tests/shader/numeric_builtins.rs b/tests/tests/shader/numeric_builtins.rs index 2f6981ef7b..26c2a89d92 100644 --- a/tests/tests/shader/numeric_builtins.rs +++ b/tests/tests/shader/numeric_builtins.rs @@ -1,8 +1,7 @@ -use wasm_bindgen_test::*; use wgpu::{DownlevelFlags, Limits}; use crate::shader::{shader_input_output_test, InputStorageType, ShaderTest}; -use wgpu_test::{initialize_test, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; fn create_numeric_builtin_test() -> Vec { let mut tests = Vec::new(); @@ -38,19 +37,17 @@ fn create_numeric_builtin_test() -> Vec { tests } -#[test] -#[wasm_bindgen_test] -fn numeric_builtins() { - initialize_test( +#[gpu_test] +static NUMERIC_BUILTINS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) .limits(Limits::downlevel_defaults()), - |ctx| { - shader_input_output_test( - ctx, - InputStorageType::Storage, - create_numeric_builtin_test(), - ); - }, - ); -} + ) + .run_async(|ctx| { + shader_input_output_test( + ctx, + InputStorageType::Storage, + create_numeric_builtin_test(), + ) + }); diff --git a/tests/tests/shader/shader_test.wgsl b/tests/tests/shader/shader_test.wgsl index efe8692bd5..91c8636574 100644 --- a/tests/tests/shader/shader_test.wgsl +++ b/tests/tests/shader/shader_test.wgsl @@ -1,3 +1,5 @@ +{{header}} + struct CustomStruct { {{input_members}} } diff --git a/tests/tests/shader/struct_layout.rs b/tests/tests/shader/struct_layout.rs index 7da8cfeef8..f5acde971c 100644 --- a/tests/tests/shader/struct_layout.rs +++ b/tests/tests/shader/struct_layout.rs @@ -1,10 +1,9 @@ use std::fmt::Write; -use wasm_bindgen_test::*; use wgpu::{Backends, DownlevelFlags, Features, Limits}; use crate::shader::{shader_input_output_test, InputStorageType, ShaderTest, MAX_BUFFER_SIZE}; -use wgpu_test::{initialize_test, FailureCase, TestParameters}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; fn create_struct_layout_tests(storage_type: InputStorageType) -> Vec { let input_values: Vec<_> = (0..(MAX_BUFFER_SIZE as u32 / 4)).collect(); @@ -100,7 +99,7 @@ fn create_struct_layout_tests(storage_type: InputStorageType) -> Vec } } - // https://github.com/gfx-rs/naga/issues/1785 + // https://github.com/gfx-rs/wgpu/issues/4371 let failures = if storage_type == InputStorageType::Uniform && rows == 2 { Backends::GL } else { @@ -172,63 +171,104 @@ fn create_struct_layout_tests(storage_type: InputStorageType) -> Vec } } + // Nested struct and array test. + // + // This tries to exploit all the weird edge cases of the struct layout algorithm. + { + let header = + String::from("struct Inner { scalar: f32, member: array, 2>, scalar2: f32 }"); + let members = String::from("inner: Inner, scalar3: f32, vector: vec3, scalar4: f32"); + let direct = String::from( + "\ + output[0] = bitcast(input.inner.scalar); + output[1] = bitcast(input.inner.member[0].x); + output[2] = bitcast(input.inner.member[0].y); + output[3] = bitcast(input.inner.member[0].z); + output[4] = bitcast(input.inner.member[1].x); + output[5] = bitcast(input.inner.member[1].y); + output[6] = bitcast(input.inner.member[1].z); + output[7] = bitcast(input.inner.scalar2); + output[8] = bitcast(input.scalar3); + output[9] = bitcast(input.vector.x); + output[10] = bitcast(input.vector.y); + output[11] = bitcast(input.vector.z); + output[12] = bitcast(input.scalar4); + ", + ); + + tests.push( + ShaderTest::new( + String::from("nested struct and array"), + members, + direct, + &input_values, + &[ + 0, // inner.scalar + 4, 5, 6, // inner.member[0] + 8, 9, 10, // inner.member[1] + 12, // scalar2 + 16, // scalar3 + 20, 21, 22, // vector + 23, // scalar4 + ], + ) + .header(header), + ); + } + tests } -#[test] -#[wasm_bindgen_test] -fn uniform_input() { - initialize_test( +#[gpu_test] +static UNIFORM_INPUT: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) - // Validation errors thrown by the SPIR-V validator https://github.com/gfx-rs/naga/issues/2034 - .expect_fail(FailureCase::backend(wgpu::Backends::VULKAN)) + // Validation errors thrown by the SPIR-V validator https://github.com/gfx-rs/wgpu/issues/4371 + .expect_fail( + FailureCase::backend(wgpu::Backends::VULKAN) + .validation_error("a matrix with stride 8 not satisfying alignment to 16"), + ) .limits(Limits::downlevel_defaults()), - |ctx| { - shader_input_output_test( - ctx, - InputStorageType::Uniform, - create_struct_layout_tests(InputStorageType::Uniform), - ); - }, - ); -} + ) + .run_async(|ctx| { + shader_input_output_test( + ctx, + InputStorageType::Uniform, + create_struct_layout_tests(InputStorageType::Uniform), + ) + }); -#[test] -#[wasm_bindgen_test] -fn storage_input() { - initialize_test( +#[gpu_test] +static STORAGE_INPUT: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) .limits(Limits::downlevel_defaults()), - |ctx| { - shader_input_output_test( - ctx, - InputStorageType::Storage, - create_struct_layout_tests(InputStorageType::Storage), - ); - }, - ); -} + ) + .run_async(|ctx| { + shader_input_output_test( + ctx, + InputStorageType::Storage, + create_struct_layout_tests(InputStorageType::Storage), + ) + }); -#[test] -#[wasm_bindgen_test] -fn push_constant_input() { - initialize_test( +#[gpu_test] +static PUSH_CONSTANT_INPUT: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .features(Features::PUSH_CONSTANTS) .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) .limits(Limits { max_push_constant_size: MAX_BUFFER_SIZE as u32, ..Limits::downlevel_defaults() - }) - .expect_fail(FailureCase::backend(Backends::GL)), - |ctx| { - shader_input_output_test( - ctx, - InputStorageType::PushConstant, - create_struct_layout_tests(InputStorageType::PushConstant), - ); - }, - ); -} + }), + ) + .run_async(|ctx| { + shader_input_output_test( + ctx, + InputStorageType::PushConstant, + create_struct_layout_tests(InputStorageType::PushConstant), + ) + }); diff --git a/tests/tests/shader/zero_init_workgroup_mem.rs b/tests/tests/shader/zero_init_workgroup_mem.rs index cbd1b3e561..6774f1aac1 100644 --- a/tests/tests/shader/zero_init_workgroup_mem.rs +++ b/tests/tests/shader/zero_init_workgroup_mem.rs @@ -8,170 +8,162 @@ use wgpu::{ ShaderStages, }; -use wgpu_test::{initialize_test, FailureCase, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters}; -#[test] -fn zero_init_workgroup_mem() { - initialize_test( +#[gpu_test] +static ZERO_INIT_WORKGROUP_MEMORY: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags(DownlevelFlags::COMPUTE_SHADERS) .limits(Limits::downlevel_defaults()) - // remove both of these once we get to https://github.com/gfx-rs/wgpu/issues/3193 or - // https://github.com/gfx-rs/wgpu/issues/3160 + // remove once we get to https://github.com/gfx-rs/wgpu/issues/3193 .skip(FailureCase { backends: Some(Backends::DX12), vendor: Some(5140), adapter: Some("Microsoft Basic Render Driver"), ..FailureCase::default() - }) - .skip(FailureCase::backend_adapter( - Backends::VULKAN, - "swiftshader", - )), - zero_init_workgroup_mem_impl, - ); -} - -const DISPATCH_SIZE: (u32, u32, u32) = (64, 64, 64); -const TOTAL_WORK_GROUPS: u32 = DISPATCH_SIZE.0 * DISPATCH_SIZE.1 * DISPATCH_SIZE.2; - -/// nr of bytes we use in the shader -const SHADER_WORKGROUP_MEMORY: u32 = 512 * 4 + 4; -// assume we have this much workgroup memory (2GB) -const MAX_DEVICE_WORKGROUP_MEMORY: u32 = i32::MAX as u32; -const NR_OF_DISPATCHES: u32 = - MAX_DEVICE_WORKGROUP_MEMORY / (SHADER_WORKGROUP_MEMORY * TOTAL_WORK_GROUPS) + 1; // TODO: use div_ceil once stabilized + }), + ) + .run_async(|ctx| async move { + let bgl = ctx + .device + .create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: true, + min_binding_size: None, + }, + count: None, + }], + }); + + let output_buffer = ctx.device.create_buffer(&BufferDescriptor { + label: Some("output buffer"), + size: BUFFER_SIZE, + usage: BufferUsages::COPY_DST | BufferUsages::COPY_SRC | BufferUsages::STORAGE, + mapped_at_creation: false, + }); -const OUTPUT_ARRAY_SIZE: u32 = TOTAL_WORK_GROUPS * NR_OF_DISPATCHES; -const BUFFER_SIZE: u64 = OUTPUT_ARRAY_SIZE as u64 * 4; -const BUFFER_BINDING_SIZE: u32 = TOTAL_WORK_GROUPS * 4; + let mapping_buffer = ctx.device.create_buffer(&BufferDescriptor { + label: Some("mapping buffer"), + size: BUFFER_SIZE, + usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ, + mapped_at_creation: false, + }); -fn zero_init_workgroup_mem_impl(ctx: TestingContext) { - let bgl = ctx - .device - .create_bind_group_layout(&BindGroupLayoutDescriptor { + let bg = ctx.device.create_bind_group(&BindGroupDescriptor { label: None, - entries: &[BindGroupLayoutEntry { + layout: &bgl, + entries: &[BindGroupEntry { binding: 0, - visibility: ShaderStages::COMPUTE, - ty: BindingType::Buffer { - ty: BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: true, - min_binding_size: None, - }, - count: None, + resource: BindingResource::Buffer(BufferBinding { + buffer: &output_buffer, + offset: 0, + size: Some(NonZeroU64::new(BUFFER_BINDING_SIZE as u64).unwrap()), + }), }], }); - let output_buffer = ctx.device.create_buffer(&BufferDescriptor { - label: Some("output buffer"), - size: BUFFER_SIZE, - usage: BufferUsages::COPY_DST | BufferUsages::COPY_SRC | BufferUsages::STORAGE, - mapped_at_creation: false, - }); + let pll = ctx + .device + .create_pipeline_layout(&PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bgl], + push_constant_ranges: &[], + }); - let mapping_buffer = ctx.device.create_buffer(&BufferDescriptor { - label: Some("mapping buffer"), - size: BUFFER_SIZE, - usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ, - mapped_at_creation: false, - }); + let sm = ctx + .device + .create_shader_module(include_wgsl!("zero_init_workgroup_mem.wgsl")); - let bg = ctx.device.create_bind_group(&BindGroupDescriptor { - label: None, - layout: &bgl, - entries: &[BindGroupEntry { - binding: 0, - resource: BindingResource::Buffer(BufferBinding { - buffer: &output_buffer, - offset: 0, - size: Some(NonZeroU64::new(BUFFER_BINDING_SIZE as u64).unwrap()), - }), - }], - }); + let pipeline_read = ctx + .device + .create_compute_pipeline(&ComputePipelineDescriptor { + label: Some("pipeline read"), + layout: Some(&pll), + module: &sm, + entry_point: "read", + }); - let pll = ctx - .device - .create_pipeline_layout(&PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[&bgl], - push_constant_ranges: &[], - }); + let pipeline_write = ctx + .device + .create_compute_pipeline(&ComputePipelineDescriptor { + label: Some("pipeline write"), + layout: None, + module: &sm, + entry_point: "write", + }); - let sm = ctx - .device - .create_shader_module(include_wgsl!("zero_init_workgroup_mem.wgsl")); - - let pipeline_read = ctx - .device - .create_compute_pipeline(&ComputePipelineDescriptor { - label: Some("pipeline read"), - layout: Some(&pll), - module: &sm, - entry_point: "read", - }); + // -- Initializing data -- - let pipeline_write = ctx - .device - .create_compute_pipeline(&ComputePipelineDescriptor { - label: Some("pipeline write"), - layout: None, - module: &sm, - entry_point: "write", - }); + let output_pre_init_data = vec![1; OUTPUT_ARRAY_SIZE as usize]; + ctx.queue.write_buffer( + &output_buffer, + 0, + bytemuck::cast_slice(&output_pre_init_data), + ); - // -- Initializing data -- + // -- Run test -- - let output_pre_init_data = vec![1; OUTPUT_ARRAY_SIZE as usize]; - ctx.queue.write_buffer( - &output_buffer, - 0, - bytemuck::cast_slice(&output_pre_init_data), - ); + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor::default()); - // -- Run test -- + let mut cpass = encoder.begin_compute_pass(&ComputePassDescriptor::default()); - let mut encoder = ctx - .device - .create_command_encoder(&CommandEncoderDescriptor::default()); + cpass.set_pipeline(&pipeline_write); + for _ in 0..NR_OF_DISPATCHES { + cpass.dispatch_workgroups(DISPATCH_SIZE.0, DISPATCH_SIZE.1, DISPATCH_SIZE.2); + } - let mut cpass = encoder.begin_compute_pass(&ComputePassDescriptor::default()); + cpass.set_pipeline(&pipeline_read); + for i in 0..NR_OF_DISPATCHES { + cpass.set_bind_group(0, &bg, &[i * BUFFER_BINDING_SIZE]); + cpass.dispatch_workgroups(DISPATCH_SIZE.0, DISPATCH_SIZE.1, DISPATCH_SIZE.2); + } + drop(cpass); - cpass.set_pipeline(&pipeline_write); - for _ in 0..NR_OF_DISPATCHES { - cpass.dispatch_workgroups(DISPATCH_SIZE.0, DISPATCH_SIZE.1, DISPATCH_SIZE.2); - } + // -- Pulldown data -- - cpass.set_pipeline(&pipeline_read); - for i in 0..NR_OF_DISPATCHES { - cpass.set_bind_group(0, &bg, &[i * BUFFER_BINDING_SIZE]); - cpass.dispatch_workgroups(DISPATCH_SIZE.0, DISPATCH_SIZE.1, DISPATCH_SIZE.2); - } - drop(cpass); + encoder.copy_buffer_to_buffer(&output_buffer, 0, &mapping_buffer, 0, BUFFER_SIZE); - // -- Pulldown data -- + ctx.queue.submit(Some(encoder.finish())); - encoder.copy_buffer_to_buffer(&output_buffer, 0, &mapping_buffer, 0, BUFFER_SIZE); + mapping_buffer.slice(..).map_async(MapMode::Read, |_| ()); + ctx.async_poll(Maintain::wait()).await.panic_on_timeout(); - ctx.queue.submit(Some(encoder.finish())); + let mapped = mapping_buffer.slice(..).get_mapped_range(); - mapping_buffer.slice(..).map_async(MapMode::Read, |_| ()); - ctx.device.poll(Maintain::Wait); + let typed: &[u32] = bytemuck::cast_slice(&mapped); - let mapped = mapping_buffer.slice(..).get_mapped_range(); + // -- Check results -- - let typed: &[u32] = bytemuck::cast_slice(&mapped); + let num_disptaches_failed = typed.iter().filter(|&&res| res != 0).count(); + let ratio = (num_disptaches_failed as f32 / OUTPUT_ARRAY_SIZE as f32) * 100.; - // -- Check results -- + assert!( + num_disptaches_failed == 0, + "Zero-initialization of workgroup memory failed ({ratio:.0}% of disptaches failed)." + ); - let num_disptaches_failed = typed.iter().filter(|&&res| res != 0).count(); - let ratio = (num_disptaches_failed as f32 / OUTPUT_ARRAY_SIZE as f32) * 100.; + drop(mapped); + mapping_buffer.unmap(); + }); + +const DISPATCH_SIZE: (u32, u32, u32) = (64, 64, 64); +const TOTAL_WORK_GROUPS: u32 = DISPATCH_SIZE.0 * DISPATCH_SIZE.1 * DISPATCH_SIZE.2; - assert!( - num_disptaches_failed == 0, - "Zero-initialization of workgroup memory failed ({ratio:.0}% of disptaches failed)." - ); +/// nr of bytes we use in the shader +const SHADER_WORKGROUP_MEMORY: u32 = 512 * 4 + 4; +// assume we have this much workgroup memory (2GB) +const MAX_DEVICE_WORKGROUP_MEMORY: u32 = i32::MAX as u32; +const NR_OF_DISPATCHES: u32 = + MAX_DEVICE_WORKGROUP_MEMORY / (SHADER_WORKGROUP_MEMORY * TOTAL_WORK_GROUPS) + 1; // TODO: use div_ceil once stabilized - drop(mapped); - mapping_buffer.unmap(); -} +const OUTPUT_ARRAY_SIZE: u32 = TOTAL_WORK_GROUPS * NR_OF_DISPATCHES; +const BUFFER_SIZE: u64 = OUTPUT_ARRAY_SIZE as u64 * 4; +const BUFFER_BINDING_SIZE: u32 = TOTAL_WORK_GROUPS * 4; diff --git a/tests/tests/shader_primitive_index/mod.rs b/tests/tests/shader_primitive_index/mod.rs index a05d1cd5f0..096df9c0f7 100644 --- a/tests/tests/shader_primitive_index/mod.rs +++ b/tests/tests/shader_primitive_index/mod.rs @@ -1,7 +1,5 @@ -use wasm_bindgen_test::*; - use wgpu::util::DeviceExt; -use wgpu_test::{image, initialize_test, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; // // These tests render two triangles to a 2x2 render target. The first triangle @@ -37,57 +35,56 @@ use wgpu_test::{image, initialize_test, TestParameters, TestingContext}; // draw_indexed() draws the triangles in the opposite order, using index // buffer [3, 4, 5, 0, 1, 2]. This also swaps the resulting pixel colors. // -#[test] -#[wasm_bindgen_test] -fn draw() { - // - // +-----+-----+ - // |white|blue | - // +-----+-----+ - // | red |white| - // +-----+-----+ - // - let expected = [ - 255, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, - ]; - initialize_test( + +#[gpu_test] +static DRAW: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .test_features_limits() .features(wgpu::Features::SHADER_PRIMITIVE_INDEX), - |ctx| { - pulling_common(ctx, &expected, |rpass| { - rpass.draw(0..6, 0..1); - }) - }, - ); -} + ) + .run_async(|ctx| async move { + // + // +-----+-----+ + // |white|blue | + // +-----+-----+ + // | red |white| + // +-----+-----+ + // + let expected = [ + 255, 255, 255, 255, 0, 0, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, + ]; + pulling_common(ctx, &expected, |rpass| { + rpass.draw(0..6, 0..1); + }) + .await; + }); -#[test] -#[wasm_bindgen_test] -fn draw_indexed() { - // - // +-----+-----+ - // |white| red | - // +-----+-----+ - // |blue |white| - // +-----+-----+ - // - let expected = [ - 255, 255, 255, 255, 255, 0, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, - ]; - initialize_test( +#[gpu_test] +static DRAW_INDEXED: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .test_features_limits() .features(wgpu::Features::SHADER_PRIMITIVE_INDEX), - |ctx| { - pulling_common(ctx, &expected, |rpass| { - rpass.draw_indexed(0..6, 0, 0..1); - }) - }, - ); -} + ) + .run_async(|ctx| async move { + // + // +-----+-----+ + // |white| red | + // +-----+-----+ + // |blue |white| + // +-----+-----+ + // + let expected = [ + 255, 255, 255, 255, 255, 0, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, + ]; + pulling_common(ctx, &expected, |rpass| { + rpass.draw_indexed(0..6, 0, 0..1); + }) + .await; + }); -fn pulling_common( +async fn pulling_common( ctx: TestingContext, expected: &[u8], draw_command: impl FnOnce(&mut wgpu::RenderPass<'_>), @@ -169,7 +166,7 @@ fn pulling_common( }); let color_view = color_texture.create_view(&wgpu::TextureViewDescriptor::default()); - let readback_buffer = image::ReadbackBuffers::new(&ctx.device, &color_texture); + let readback_buffer = wgpu_test::image::ReadbackBuffers::new(&ctx.device, &color_texture); let mut encoder = ctx .device @@ -180,7 +177,7 @@ fn pulling_common( color_attachments: &[Some(wgpu::RenderPassColorAttachment { ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), - store: true, + store: wgpu::StoreOp::Store, }, resolve_target: None, view: &color_view, @@ -197,5 +194,5 @@ fn pulling_common( } readback_buffer.copy_from(&ctx.device, &mut encoder, &color_texture); ctx.queue.submit(Some(encoder.finish())); - assert!(readback_buffer.check_buffer_contents(&ctx.device, expected)); + readback_buffer.assert_buffer_contents(&ctx, expected).await; } diff --git a/tests/tests/shader_view_format/mod.rs b/tests/tests/shader_view_format/mod.rs index 46741b4ea8..842388763b 100644 --- a/tests/tests/shader_view_format/mod.rs +++ b/tests/tests/shader_view_format/mod.rs @@ -1,18 +1,14 @@ use wgpu::{util::DeviceExt, DownlevelFlags, Limits, TextureFormat}; -use wgpu_test::{ - image::calc_difference, initialize_test, FailureCase, TestParameters, TestingContext, -}; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; -#[test] -fn reinterpret_srgb_ness() { - let parameters = TestParameters::default() - .downlevel_flags(DownlevelFlags::VIEW_FORMATS) - .limits(Limits::downlevel_defaults()) - .skip(FailureCase { - backends: Some(wgpu::Backends::GL), - ..FailureCase::default() - }); - initialize_test(parameters, |ctx| { +#[gpu_test] +static REINTERPRET_SRGB: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .downlevel_flags(DownlevelFlags::VIEW_FORMATS) + .limits(Limits::downlevel_defaults()), + ) + .run_async(|ctx| async move { let unorm_data: [[u8; 4]; 4] = [ [180, 0, 0, 255], [0, 84, 0, 127], @@ -45,7 +41,8 @@ fn reinterpret_srgb_ness() { TextureFormat::Rgba8UnormSrgb, &unorm_data, &srgb_data, - ); + ) + .await; // Reinterpret Rgba8UnormSrgb back to Rgba8Unorm reinterpret( @@ -56,11 +53,11 @@ fn reinterpret_srgb_ness() { TextureFormat::Rgba8Unorm, &srgb_data, &unorm_data, - ); + ) + .await; }); -} -fn reinterpret( +async fn reinterpret( ctx: &TestingContext, shader: &wgpu::ShaderModule, size: wgpu::Extent3d, @@ -81,6 +78,7 @@ fn reinterpret( sample_count: 1, view_formats: &[reinterpret_to], }, + wgpu::util::TextureDataOrder::LayerMajor, bytemuck::cast_slice(src_data), ); let tv = tex.create_view(&wgpu::TextureViewDescriptor { @@ -182,7 +180,9 @@ fn reinterpret( let slice = read_buffer.slice(..); slice.map_async(wgpu::MapMode::Read, |_| ()); - ctx.device.poll(wgpu::Maintain::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); let data: Vec = slice.get_mapped_range().to_vec(); let tolerance_data: [[u8; 4]; 4] = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [1, 1, 1, 0]]; @@ -193,10 +193,10 @@ fn reinterpret( let expect = expect_data[(h * size.width + w) as usize]; let tolerance = tolerance_data[(h * size.width + w) as usize]; let index = (w * 4 + offset) as usize; - if calc_difference(expect[0], data[index]) > tolerance[0] - || calc_difference(expect[1], data[index + 1]) > tolerance[1] - || calc_difference(expect[2], data[index + 2]) > tolerance[2] - || calc_difference(expect[3], data[index + 3]) > tolerance[3] + if expect[0].abs_diff(data[index]) > tolerance[0] + || expect[1].abs_diff(data[index + 1]) > tolerance[1] + || expect[2].abs_diff(data[index + 2]) > tolerance[2] + || expect[3].abs_diff(data[index + 3]) > tolerance[3] { panic!( "Reinterpret {:?} as {:?} mismatch! expect {:?} get [{}, {}, {}, {}]", diff --git a/tests/tests/texture_bounds.rs b/tests/tests/texture_bounds.rs index da6cc6b528..48b933109d 100644 --- a/tests/tests/texture_bounds.rs +++ b/tests/tests/texture_bounds.rs @@ -1,33 +1,27 @@ //! Tests for texture copy bounds checks. -use wasm_bindgen_test::*; -use wgpu_test::{fail_if, initialize_test, TestParameters}; +use wgpu_test::{fail_if, gpu_test, GpuTestConfiguration}; -#[test] -#[wasm_bindgen_test] -fn bad_copy_origin() { - fn try_origin(origin: wgpu::Origin3d, size: wgpu::Extent3d, should_panic: bool) { - let parameters = TestParameters::default(); +#[gpu_test] +static BAD_COPY_ORIGIN_TEST: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let try_origin = |origin, size, should_panic| { + let texture = ctx.device.create_texture(&TEXTURE_DESCRIPTOR); + let data = vec![255; BUFFER_SIZE as usize]; - initialize_test(parameters, |ctx| { - let texture = ctx.device.create_texture(&TEXTURE_DESCRIPTOR); - let data = vec![255; BUFFER_SIZE as usize]; - - fail_if(&ctx.device, should_panic, || { - ctx.queue.write_texture( - wgpu::ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin, - aspect: wgpu::TextureAspect::All, - }, - &data, - BUFFER_COPY_LAYOUT, - size, - ) - }); + fail_if(&ctx.device, should_panic, || { + ctx.queue.write_texture( + wgpu::ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin, + aspect: wgpu::TextureAspect::All, + }, + &data, + BUFFER_COPY_LAYOUT, + size, + ) }); - } + }; try_origin(wgpu::Origin3d { x: 0, y: 0, z: 0 }, TEXTURE_SIZE, false); try_origin(wgpu::Origin3d { x: 1, y: 0, z: 0 }, TEXTURE_SIZE, true); @@ -86,7 +80,7 @@ fn bad_copy_origin() { }, true, ); -} +}); const TEXTURE_SIZE: wgpu::Extent3d = wgpu::Extent3d { width: 64, diff --git a/tests/tests/transfer.rs b/tests/tests/transfer.rs index 8263b217be..f8bd43530b 100644 --- a/tests/tests/transfer.rs +++ b/tests/tests/transfer.rs @@ -1,69 +1,65 @@ -use wgpu_test::{fail, initialize_test, TestParameters}; +use wgpu_test::{fail, gpu_test, GpuTestConfiguration}; -#[test] -fn copy_overflow_z() { - // A simple crash test exercising validation that used to happen a bit too - // late, letting an integer overflow slip through. - initialize_test(TestParameters::default(), |ctx| { - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); +#[gpu_test] +static COPY_OVERFLOW_Z: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - let t1 = ctx.device.create_texture(&wgpu::TextureDescriptor { - label: None, - dimension: wgpu::TextureDimension::D2, - size: wgpu::Extent3d { - width: 256, - height: 256, - depth_or_array_layers: 1, - }, - format: wgpu::TextureFormat::Rgba8Uint, - usage: wgpu::TextureUsages::COPY_DST, - mip_level_count: 1, - sample_count: 1, - view_formats: &[], - }); - let t2 = ctx.device.create_texture(&wgpu::TextureDescriptor { - label: None, - dimension: wgpu::TextureDimension::D2, - size: wgpu::Extent3d { - width: 256, - height: 256, - depth_or_array_layers: 1, - }, - format: wgpu::TextureFormat::Rgba8Uint, - usage: wgpu::TextureUsages::COPY_DST, - mip_level_count: 1, - sample_count: 1, - view_formats: &[], - }); + let t1 = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + dimension: wgpu::TextureDimension::D2, + size: wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Rgba8Uint, + usage: wgpu::TextureUsages::COPY_DST, + mip_level_count: 1, + sample_count: 1, + view_formats: &[], + }); + let t2 = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + dimension: wgpu::TextureDimension::D2, + size: wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Rgba8Uint, + usage: wgpu::TextureUsages::COPY_DST, + mip_level_count: 1, + sample_count: 1, + view_formats: &[], + }); - fail(&ctx.device, || { - // Validation should catch the silly selected z layer range without panicking. - encoder.copy_texture_to_texture( - wgpu::ImageCopyTexture { - texture: &t1, - mip_level: 1, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - wgpu::ImageCopyTexture { - texture: &t2, - mip_level: 1, - origin: wgpu::Origin3d { - x: 0, - y: 0, - z: 3824276442, - }, - aspect: wgpu::TextureAspect::All, - }, - wgpu::Extent3d { - width: 100, - height: 3, - depth_or_array_layers: 613286111, + fail(&ctx.device, || { + // Validation should catch the silly selected z layer range without panicking. + encoder.copy_texture_to_texture( + wgpu::ImageCopyTexture { + texture: &t1, + mip_level: 1, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyTexture { + texture: &t2, + mip_level: 1, + origin: wgpu::Origin3d { + x: 0, + y: 0, + z: 3824276442, }, - ); - ctx.queue.submit(Some(encoder.finish())); - }); - }) -} + aspect: wgpu::TextureAspect::All, + }, + wgpu::Extent3d { + width: 100, + height: 3, + depth_or_array_layers: 613286111, + }, + ); + ctx.queue.submit(Some(encoder.finish())); + }); +}); diff --git a/tests/tests/vertex_indices/draw.vert.wgsl b/tests/tests/vertex_indices/draw.vert.wgsl index 6fc8e3746f..a5f89f787a 100644 --- a/tests/tests/vertex_indices/draw.vert.wgsl +++ b/tests/tests/vertex_indices/draw.vert.wgsl @@ -2,7 +2,16 @@ var indices: array; // this is used as both input and output for convenience @vertex -fn vs_main(@builtin(instance_index) instance: u32, @builtin(vertex_index) index: u32) -> @builtin(position) vec4 { +fn vs_main_builtin(@builtin(instance_index) instance: u32, @builtin(vertex_index) index: u32) -> @builtin(position) vec4 { + return vs_inner(instance, index); +} + +@vertex +fn vs_main_buffers(@location(0) instance: u32, @location(1) index: u32) -> @builtin(position) vec4 { + return vs_inner(instance, index); +} + +fn vs_inner(instance: u32, index: u32) -> vec4 { let idx = instance * 3u + index; indices[idx] = idx; return vec4(0.0, 0.0, 0.0, 1.0); diff --git a/tests/tests/vertex_indices/mod.rs b/tests/tests/vertex_indices/mod.rs index edd4f7b057..156df2d06f 100644 --- a/tests/tests/vertex_indices/mod.rs +++ b/tests/tests/vertex_indices/mod.rs @@ -1,15 +1,237 @@ -use std::num::NonZeroU64; +//! Tests that vertex buffers, vertex indices, and instance indices are properly handled. +//! +//! We need tests for these as the backends use various schemes to work around the lack +//! of support for things like `gl_BaseInstance` in shaders. -use wasm_bindgen_test::*; -use wgpu::util::DeviceExt; +use std::{num::NonZeroU64, ops::Range}; -use wgpu_test::{initialize_test, FailureCase, TestParameters, TestingContext}; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; + +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +/// Generic struct representing a draw call +struct Draw { + vertex: Range, + instance: Range, + /// If present, is an indexed call + base_vertex: Option, +} + +impl Draw { + /// Directly execute the draw call + fn execute(&self, rpass: &mut wgpu::RenderPass<'_>) { + if let Some(base_vertex) = self.base_vertex { + rpass.draw_indexed(self.vertex.clone(), base_vertex, self.instance.clone()); + } else { + rpass.draw(self.vertex.clone(), self.instance.clone()); + } + } + + /// Add the draw call to the given indirect buffer + fn add_to_buffer(&self, bytes: &mut Vec, features: wgpu::Features) { + // The behavior of non-zero first_instance in indirect draw calls in currently undefined if INDIRECT_FIRST_INSTANCE is not supported. + let supports_first_instance = features.contains(wgpu::Features::INDIRECT_FIRST_INSTANCE); + let first_instance = if supports_first_instance { + self.instance.start + } else { + 0 + }; + + if let Some(base_vertex) = self.base_vertex { + bytes.extend_from_slice( + wgpu::util::DrawIndexedIndirectArgs { + index_count: self.vertex.end - self.vertex.start, + instance_count: self.instance.end - self.instance.start, + base_vertex, + first_index: self.vertex.start, + first_instance, + } + .as_bytes(), + ) + } else { + bytes.extend_from_slice( + wgpu::util::DrawIndirectArgs { + vertex_count: self.vertex.end - self.vertex.start, + instance_count: self.instance.end - self.instance.start, + first_vertex: self.vertex.start, + first_instance, + } + .as_bytes(), + ) + } + } + + /// Execute the draw call from the given indirect buffer + fn execute_indirect<'rpass>( + &self, + rpass: &mut wgpu::RenderPass<'rpass>, + indirect: &'rpass wgpu::Buffer, + offset: &mut u64, + ) { + if self.base_vertex.is_some() { + rpass.draw_indexed_indirect(indirect, *offset); + *offset += 20; + } else { + rpass.draw_indirect(indirect, *offset); + *offset += 16; + } + } +} + +#[derive(Debug, Copy, Clone)] +enum TestCase { + /// A single draw call with 6 vertices + Draw, + /// Two draw calls of 0..3 and 3..6 verts + DrawNonZeroFirstVertex, + /// A single draw call with 6 vertices and a vertex offset of 3 + DrawBaseVertex, + /// A single draw call with 3 vertices and 2 instances + DrawInstanced, + /// Two draw calls with 3 vertices and 0..1 and 1..2 instances. + DrawNonZeroFirstInstance, +} + +impl TestCase { + const ARRAY: [Self; 5] = [ + Self::Draw, + Self::DrawNonZeroFirstVertex, + Self::DrawBaseVertex, + Self::DrawInstanced, + Self::DrawNonZeroFirstInstance, + ]; + + // Get the draw calls for this test case + fn draws(&self) -> &'static [Draw] { + match self { + TestCase::Draw => &[Draw { + vertex: 0..6, + instance: 0..1, + base_vertex: None, + }], + TestCase::DrawNonZeroFirstVertex => &[ + Draw { + vertex: 0..3, + instance: 0..1, + base_vertex: None, + }, + Draw { + vertex: 3..6, + instance: 0..1, + base_vertex: None, + }, + ], + TestCase::DrawBaseVertex => &[Draw { + vertex: 0..6, + instance: 0..1, + base_vertex: Some(3), + }], + TestCase::DrawInstanced => &[Draw { + vertex: 0..3, + instance: 0..2, + base_vertex: None, + }], + TestCase::DrawNonZeroFirstInstance => &[ + Draw { + vertex: 0..3, + instance: 0..1, + base_vertex: None, + }, + Draw { + vertex: 0..3, + instance: 1..2, + base_vertex: None, + }, + ], + } + } +} + +#[derive(Debug, Copy, Clone)] +enum IdSource { + /// Use buffers to load the vertex and instance index + Buffers, + /// Use builtins to load the vertex and instance index + Builtins, +} + +impl IdSource { + const ARRAY: [Self; 2] = [Self::Buffers, Self::Builtins]; +} + +#[derive(Debug, Copy, Clone)] +enum DrawCallKind { + Direct, + Indirect, +} + +impl DrawCallKind { + const ARRAY: [Self; 2] = [Self::Direct, Self::Indirect]; +} + +struct Test { + case: TestCase, + id_source: IdSource, + draw_call_kind: DrawCallKind, +} + +impl Test { + /// Get the expected result from this test, taking into account + /// the various features and capabilities that may be missing. + fn expectation(&self, ctx: &TestingContext) -> &'static [u32] { + let is_indirect = matches!(self.draw_call_kind, DrawCallKind::Indirect); + + // Both of these failure modes require indirect rendering + + // If this is false, the first instance will be ignored. + let non_zero_first_instance_supported = ctx + .adapter + .features() + .contains(wgpu::Features::INDIRECT_FIRST_INSTANCE) + || !is_indirect; + + // If this is false, it won't be ignored, but it won't show up in the shader + // + // If the IdSource is buffers, this doesn't apply + let first_vert_instance_supported = ctx.adapter_downlevel_capabilities.flags.contains( + wgpu::DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW, + ) || matches!(self.id_source, IdSource::Buffers) + || !is_indirect; + + match self.case { + TestCase::DrawBaseVertex => { + if !first_vert_instance_supported { + return &[0, 1, 2, 3, 4, 5]; + } + + &[0, 0, 0, 3, 4, 5, 6, 7, 8] + } + TestCase::Draw | TestCase::DrawInstanced => &[0, 1, 2, 3, 4, 5], + TestCase::DrawNonZeroFirstVertex => { + if !first_vert_instance_supported { + return &[0, 1, 2, 0, 0, 0]; + } + + &[0, 1, 2, 3, 4, 5] + } + TestCase::DrawNonZeroFirstInstance => { + if !first_vert_instance_supported || !non_zero_first_instance_supported { + return &[0, 1, 2, 0, 0, 0]; + } + + &[0, 1, 2, 3, 4, 5] + } + } + } +} + +async fn vertex_index_common(ctx: TestingContext) { + let identity_buffer = ctx.device.create_buffer_init(&BufferInitDescriptor { + label: Some("identity buffer"), + contents: bytemuck::cast_slice(&[0u32, 1, 2, 3, 4, 5, 6, 7, 8]), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::INDEX, + }); -fn pulling_common( - ctx: TestingContext, - expected: &[u32], - function: impl FnOnce(&mut wgpu::RenderPass<'_>), -) { let shader = ctx .device .create_shader_module(wgpu::include_wgsl!("draw.vert.wgsl")); @@ -30,24 +252,6 @@ fn pulling_common( }], }); - let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: 4 * expected.len() as u64, - usage: wgpu::BufferUsages::COPY_SRC - | wgpu::BufferUsages::STORAGE - | wgpu::BufferUsages::MAP_READ, - mapped_at_creation: false, - }); - - let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &bgl, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }); - let ppl = ctx .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { @@ -56,30 +260,43 @@ fn pulling_common( push_constant_ranges: &[], }); - let pipeline = ctx - .device - .create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: None, - layout: Some(&ppl), - vertex: wgpu::VertexState { - buffers: &[], - entry_point: "vs_main", - module: &shader, - }, - primitive: wgpu::PrimitiveState::default(), - depth_stencil: None, - multisample: wgpu::MultisampleState::default(), - fragment: Some(wgpu::FragmentState { - entry_point: "fs_main", - module: &shader, - targets: &[Some(wgpu::ColorTargetState { - format: wgpu::TextureFormat::Rgba8Unorm, - blend: None, - write_mask: wgpu::ColorWrites::ALL, - })], - }), - multiview: None, - }); + let mut pipeline_desc = wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&ppl), + vertex: wgpu::VertexState { + buffers: &[], + entry_point: "vs_main_builtin", + module: &shader, + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + fragment: Some(wgpu::FragmentState { + entry_point: "fs_main", + module: &shader, + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + }; + let builtin_pipeline = ctx.device.create_render_pipeline(&pipeline_desc); + pipeline_desc.vertex.entry_point = "vs_main_buffers"; + pipeline_desc.vertex.buffers = &[ + wgpu::VertexBufferLayout { + array_stride: 4, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array![0 => Uint32], + }, + wgpu::VertexBufferLayout { + array_stride: 4, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array![1 => Uint32], + }, + ]; + let buffer_pipeline = ctx.device.create_render_pipeline(&pipeline_desc); let dummy = ctx .device @@ -99,89 +316,155 @@ fn pulling_common( usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, view_formats: &[], }, + wgpu::util::TextureDataOrder::LayerMajor, &[0, 0, 0, 1], ) .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = ctx - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + let mut tests = Vec::with_capacity(5 * 2 * 2); + for case in TestCase::ARRAY { + for id_source in IdSource::ARRAY { + for draw_call_kind in DrawCallKind::ARRAY { + tests.push(Test { + case, + id_source, + draw_call_kind, + }) + } + } + } - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - ops: wgpu::Operations::default(), - resolve_target: None, - view: &dummy, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); + let features = ctx.adapter.features(); - rpass.set_pipeline(&pipeline); - rpass.set_bind_group(0, &bg, &[]); - function(&mut rpass); + let mut failed = false; + for test in tests { + let pipeline = match test.id_source { + IdSource::Buffers => &buffer_pipeline, + IdSource::Builtins => &builtin_pipeline, + }; - drop(rpass); + let expected = test.expectation(&ctx); - ctx.queue.submit(Some(encoder.finish())); - let slice = buffer.slice(..); - slice.map_async(wgpu::MapMode::Read, |_| ()); - ctx.device.poll(wgpu::Maintain::Wait); - let data: Vec = bytemuck::cast_slice(&slice.get_mapped_range()).to_vec(); + let buffer_size = 4 * expected.len() as u64; + let cpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: buffer_size, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); - assert_eq!(data, expected); -} + let gpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: buffer_size, + usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::STORAGE, + mapped_at_creation: false, + }); -#[test] -#[wasm_bindgen_test] -fn draw() { - initialize_test(TestParameters::default().test_features_limits(), |ctx| { - pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { - cmb.draw(0..6, 0..1); - }) - }) -} + let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: gpu_buffer.as_entire_binding(), + }], + }); -#[test] -#[wasm_bindgen_test] -fn draw_vertex_offset() { - initialize_test( - TestParameters::default() - .test_features_limits() - .expect_fail(FailureCase::backend(wgpu::Backends::DX11)), - |ctx| { - pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { - cmb.draw(0..3, 0..1); - cmb.draw(3..6, 0..1); - }) - }, - ) -} + let mut encoder1 = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let indirect_buffer; + let mut rpass = encoder1.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + ops: wgpu::Operations::default(), + resolve_target: None, + view: &dummy, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); -#[test] -#[wasm_bindgen_test] -fn draw_instanced() { - initialize_test(TestParameters::default().test_features_limits(), |ctx| { - pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { - cmb.draw(0..3, 0..2); - }) - }) + rpass.set_vertex_buffer(0, identity_buffer.slice(..)); + rpass.set_vertex_buffer(1, identity_buffer.slice(..)); + rpass.set_index_buffer(identity_buffer.slice(..), wgpu::IndexFormat::Uint32); + rpass.set_pipeline(pipeline); + rpass.set_bind_group(0, &bg, &[]); + + let draws = test.case.draws(); + + match test.draw_call_kind { + DrawCallKind::Direct => { + for draw in draws { + draw.execute(&mut rpass); + } + } + DrawCallKind::Indirect => { + let mut indirect_bytes = Vec::new(); + for draw in draws { + draw.add_to_buffer(&mut indirect_bytes, features); + } + indirect_buffer = ctx.device.create_buffer_init(&BufferInitDescriptor { + label: Some("indirect"), + contents: &indirect_bytes, + usage: wgpu::BufferUsages::INDIRECT, + }); + let mut offset = 0; + for draw in draws { + draw.execute_indirect(&mut rpass, &indirect_buffer, &mut offset); + } + } + } + + drop(rpass); + + let mut encoder2 = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + encoder2.copy_buffer_to_buffer(&gpu_buffer, 0, &cpu_buffer, 0, buffer_size); + + // See https://github.com/gfx-rs/wgpu/issues/4732 for why this is split between two submissions + // with a hard wait in between. + ctx.queue.submit([encoder1.finish()]); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + ctx.queue.submit([encoder2.finish()]); + let slice = cpu_buffer.slice(..); + slice.map_async(wgpu::MapMode::Read, |_| ()); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); + let data: Vec = bytemuck::cast_slice(&slice.get_mapped_range()).to_vec(); + + if data != expected { + eprintln!( + "Failed: Got: {:?} Expected: {:?} - Case {:?} getting indices from {:?} using {:?} draw calls", + data, + expected, + test.case, + test.id_source, + test.draw_call_kind + ); + failed = true; + } else { + eprintln!( + "Passed: Case {:?} getting indices from {:?} using {:?} draw calls", + test.case, test.id_source, test.draw_call_kind + ); + } + } + + assert!(!failed); } -#[test] -#[wasm_bindgen_test] -fn draw_instanced_offset() { - initialize_test( +#[gpu_test] +static VERTEX_INDICES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .test_features_limits() - .expect_fail(FailureCase::backend(wgpu::Backends::DX11)), - |ctx| { - pulling_common(ctx, &[0, 1, 2, 3, 4, 5], |cmb| { - cmb.draw(0..3, 0..1); - cmb.draw(0..3, 1..2); - }) - }, + .features(wgpu::Features::VERTEX_WRITABLE_STORAGE), ) -} + .run_async(vertex_index_common); diff --git a/tests/tests/write_texture.rs b/tests/tests/write_texture.rs index 8b33cae7f5..f8d99d6d14 100644 --- a/tests/tests/write_texture.rs +++ b/tests/tests/write_texture.rs @@ -1,16 +1,12 @@ //! Tests for texture copy -use wgpu_test::{initialize_test, FailureCase, TestParameters}; +use wgpu_test::{gpu_test, GpuTestConfiguration}; -use wasm_bindgen_test::*; +#[gpu_test] +static WRITE_TEXTURE_SUBSET_2D: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let size = 256; -#[test] -#[wasm_bindgen_test] -fn write_texture_subset_2d() { - let size = 256; - let parameters = - TestParameters::default().expect_fail(FailureCase::backend(wgpu::Backends::DX12)); - initialize_test(parameters, |ctx| { let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, dimension: wgpu::TextureDimension::D2, @@ -88,7 +84,9 @@ fn write_texture_subset_2d() { let slice = read_buffer.slice(..); slice.map_async(wgpu::MapMode::Read, |_| ()); - ctx.device.poll(wgpu::Maintain::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); let data: Vec = slice.get_mapped_range().to_vec(); for byte in &data[..(size as usize * 2)] { @@ -98,15 +96,12 @@ fn write_texture_subset_2d() { assert_eq!(*byte, 0); } }); -} - -#[test] -#[wasm_bindgen_test] -fn write_texture_subset_3d() { - let size = 256; - let depth = 4; - let parameters = TestParameters::default(); - initialize_test(parameters, |ctx| { + +#[gpu_test] +static WRITE_TEXTURE_SUBSET_3D: GpuTestConfiguration = + GpuTestConfiguration::new().run_async(|ctx| async move { + let size = 256; + let depth = 4; let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { label: None, dimension: wgpu::TextureDimension::D3, @@ -184,7 +179,9 @@ fn write_texture_subset_3d() { let slice = read_buffer.slice(..); slice.map_async(wgpu::MapMode::Read, |_| ()); - ctx.device.poll(wgpu::Maintain::Wait); + ctx.async_poll(wgpu::Maintain::wait()) + .await + .panic_on_timeout(); let data: Vec = slice.get_mapped_range().to_vec(); for byte in &data[..((size * size) as usize * 2)] { @@ -194,4 +191,3 @@ fn write_texture_subset_3d() { assert_eq!(*byte, 0); } }); -} diff --git a/tests/tests/zero_init_texture_after_discard.rs b/tests/tests/zero_init_texture_after_discard.rs index 2b757e069a..d947006d1d 100644 --- a/tests/tests/zero_init_texture_after_discard.rs +++ b/tests/tests/zero_init_texture_after_discard.rs @@ -1,58 +1,81 @@ -use wasm_bindgen_test::*; use wgpu::*; use wgpu_test::{ - image::ReadbackBuffers, initialize_test, FailureCase, TestParameters, TestingContext, + gpu_test, image::ReadbackBuffers, FailureCase, GpuTestConfiguration, TestParameters, + TestingContext, }; // Checks if discarding a color target resets its init state, causing a zero read of this texture when copied in after submit of the encoder. -#[test] -#[wasm_bindgen_test] -fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_after_submit() { - initialize_test( - TestParameters::default().skip(FailureCase::webgl2()), - |mut ctx| { - let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); - case.create_command_encoder(); - case.discard(); - case.submit_command_encoder(); +#[gpu_test] +static DISCARDING_COLOR_TARGET_RESETS_TEXTURE_INIT_STATE_CHECK_VISIBLE_ON_COPY_AFTER_SUBMIT: + GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::webgl2())) + .run_async(|mut ctx| async move { + let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); + case.create_command_encoder(); + case.discard(); + case.submit_command_encoder(); - case.create_command_encoder(); - case.copy_texture_to_buffer(); - case.submit_command_encoder(); + case.create_command_encoder(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); - case.assert_buffers_are_zero(); - }, - ); -} + case.assert_buffers_are_zero().await; + }); -// Checks if discarding a color target resets its init state, causing a zero read of this texture when copied in the same encoder to a buffer. -#[test] -#[wasm_bindgen_test] -fn discarding_color_target_resets_texture_init_state_check_visible_on_copy_in_same_encoder() { - initialize_test( - TestParameters::default().skip(FailureCase::webgl2()), - |mut ctx| { - let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); - case.create_command_encoder(); - case.discard(); - case.copy_texture_to_buffer(); - case.submit_command_encoder(); +#[gpu_test] +static DISCARDING_COLOR_TARGET_RESETS_TEXTURE_INIT_STATE_CHECK_VISIBLE_ON_COPY_IN_SAME_ENCODER: + GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().expect_fail(FailureCase::webgl2())) + .run_async(|mut ctx| async move { + let mut case = TestCase::new(&mut ctx, TextureFormat::Rgba8UnormSrgb); + case.create_command_encoder(); + case.discard(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); - case.assert_buffers_are_zero(); - }, - ); -} + case.assert_buffers_are_zero().await; + }); -#[test] -#[wasm_bindgen_test] -fn discarding_depth_target_resets_texture_init_state_check_visible_on_copy_in_same_encoder() { - initialize_test( +#[gpu_test] +static DISCARDING_DEPTH_TARGET_RESETS_TEXTURE_INIT_STATE_CHECK_VISIBLE_ON_COPY_IN_SAME_ENCODER: + GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( TestParameters::default() .downlevel_flags( DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES | DownlevelFlags::COMPUTE_SHADERS, ) .limits(Limits::downlevel_defaults()), - |mut ctx| { + ) + .run_async(|mut ctx| async move { + for format in [ + TextureFormat::Stencil8, + TextureFormat::Depth16Unorm, + TextureFormat::Depth24Plus, + TextureFormat::Depth24PlusStencil8, + TextureFormat::Depth32Float, + ] { + let mut case = TestCase::new(&mut ctx, format); + case.create_command_encoder(); + case.discard(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); + + case.assert_buffers_are_zero().await; + } + }); + +#[gpu_test] +static DISCARDING_EITHER_DEPTH_OR_STENCIL_ASPECT_TEST: GpuTestConfiguration = + GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .downlevel_flags( + DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES + | DownlevelFlags::COMPUTE_SHADERS, + ) + .limits(Limits::downlevel_defaults()), + ) + .run_async(|mut ctx| async move { for format in [ TextureFormat::Stencil8, TextureFormat::Depth16Unorm, @@ -62,43 +85,20 @@ fn discarding_depth_target_resets_texture_init_state_check_visible_on_copy_in_sa ] { let mut case = TestCase::new(&mut ctx, format); case.create_command_encoder(); - case.discard(); - case.copy_texture_to_buffer(); + case.discard_depth(); case.submit_command_encoder(); - case.assert_buffers_are_zero(); - } - }, - ); -} - -#[test] -#[wasm_bindgen_test] -fn discarding_either_depth_or_stencil_aspect() { - initialize_test( - TestParameters::default() - .downlevel_flags( - DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES | DownlevelFlags::COMPUTE_SHADERS, - ) - .limits(Limits::downlevel_defaults()), - |mut ctx| { - let mut case = TestCase::new(&mut ctx, TextureFormat::Depth24PlusStencil8); - case.create_command_encoder(); - case.discard_depth(); - case.submit_command_encoder(); - - case.create_command_encoder(); - case.discard_stencil(); - case.submit_command_encoder(); + case.create_command_encoder(); + case.discard_stencil(); + case.submit_command_encoder(); - case.create_command_encoder(); - case.copy_texture_to_buffer(); - case.submit_command_encoder(); + case.create_command_encoder(); + case.copy_texture_to_buffer(); + case.submit_command_encoder(); - case.assert_buffers_are_zero(); - }, - ); -} + case.assert_buffers_are_zero().await; + } + }); struct TestCase<'ctx> { ctx: &'ctx mut TestingContext, @@ -155,11 +155,11 @@ impl<'ctx> TestCase<'ctx> { view: &texture.create_view(&TextureViewDescriptor::default()), depth_ops: format.has_depth_aspect().then_some(Operations { load: LoadOp::Clear(1.0), - store: true, + store: StoreOp::Store, }), stencil_ops: format.has_stencil_aspect().then_some(Operations { load: LoadOp::Clear(0xFFFFFFFF), - store: true, + store: StoreOp::Store, }), }), timestamp_writes: None, @@ -167,7 +167,7 @@ impl<'ctx> TestCase<'ctx> { }); ctx.queue.submit([encoder.finish()]); } else { - let block_size = format.block_size(None).unwrap(); + let block_size = format.block_copy_size(None).unwrap(); let bytes_per_row = texture.width() * block_size; // Size for tests is chosen so that we don't need to care about buffer alignments. @@ -230,7 +230,7 @@ impl<'ctx> TestCase<'ctx> { resolve_target: None, ops: Operations { load: LoadOp::Load, - store: false, // discard! + store: StoreOp::Discard, }, }, )], @@ -239,11 +239,11 @@ impl<'ctx> TestCase<'ctx> { view: &self.texture.create_view(&TextureViewDescriptor::default()), depth_ops: self.format.has_depth_aspect().then_some(Operations { load: LoadOp::Load, - store: false, // discard! + store: StoreOp::Discard, }), stencil_ops: self.format.has_stencil_aspect().then_some(Operations { load: LoadOp::Load, - store: false, // discard! + store: StoreOp::Discard, }), }, ), @@ -264,11 +264,11 @@ impl<'ctx> TestCase<'ctx> { view: &self.texture.create_view(&TextureViewDescriptor::default()), depth_ops: Some(Operations { load: LoadOp::Load, - store: false, // discard! + store: StoreOp::Discard, }), stencil_ops: self.format.has_stencil_aspect().then_some(Operations { load: LoadOp::Clear(0), - store: true, + store: StoreOp::Store, }), }, ), @@ -289,11 +289,11 @@ impl<'ctx> TestCase<'ctx> { view: &self.texture.create_view(&TextureViewDescriptor::default()), depth_ops: self.format.has_depth_aspect().then_some(Operations { load: LoadOp::Clear(0.0), - store: true, + store: StoreOp::Store, }), stencil_ops: Some(Operations { load: LoadOp::Load, - store: false, // discard! + store: StoreOp::Discard, }), }, ), @@ -310,9 +310,9 @@ impl<'ctx> TestCase<'ctx> { ); } - pub fn assert_buffers_are_zero(&mut self) { + pub async fn assert_buffers_are_zero(&mut self) { assert!( - self.readback_buffers.are_zero(&self.ctx.device), + self.readback_buffers.are_zero(self.ctx).await, "texture was not fully cleared" ); } diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index 5cebd9fdca..eed57cc406 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "wgpu-core" -version = "0.17.0" -authors = ["wgpu developers"] +version = "0.18.0" +authors = ["gfx-rs developers"] edition = "2021" description = "WebGPU core logic on wgpu-hal" homepage = "https://wgpu.rs/" @@ -9,6 +9,12 @@ repository = "https://github.com/gfx-rs/wgpu" keywords = ["graphics"] license = "MIT OR Apache-2.0" +# Override the workspace's `rust-version` key. Firefox uses `cargo vendor` to +# copy the crates it actually uses out of the workspace, so it's meaningful for +# them to have less restrictive MSRVs individually than the workspace as a +# whole, if their code permits. See `../README.md` for details. +rust-version = "1.70" + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] @@ -24,68 +30,104 @@ targets = [ [features] default = ["link"] -# Backends, passed through to wgpu-hal -metal = ["hal/metal"] -vulkan = ["hal/vulkan"] -gles = ["hal/gles"] -dx11 = ["hal/dx11"] -dx12 = ["hal/dx12"] +## Log all API entry points at info instead of trace level. +api_log_info = [] + +## Log resource lifecycle management at info instead of trace level. +resource_log_info = [] -# Use static linking for libraries. Disale to manually link. Enabled by default. +## Use static linking for libraries. Disale to manually link. Enabled by default. link = ["hal/link"] -# Support the Renderdoc graphics debugger: -# https://renderdoc.org/ +## Support the Renderdoc graphics debugger: +## renderdoc = ["hal/renderdoc"] -# Apply run-time checks, even in release builds. These are in addition -# to the validation carried out at public APIs in all builds. +## Apply run-time checks, even in release builds. These are in addition +## to the validation carried out at public APIs in all builds. strict_asserts = ["wgt/strict_asserts"] -angle = ["hal/gles"] -# Enable API tracing + +## Enable API tracing. trace = ["ron", "serde", "wgt/trace", "arrayvec/serde", "naga/serialize"] -# Enable API replaying + +## Enable API replaying replay = ["serde", "wgt/replay", "arrayvec/serde", "naga/deserialize"] -# Enable serializable compute/render passes, and bundle encoders. + +## Enable serializable compute/render passes, and bundle encoders. serial-pass = ["serde", "wgt/serde", "arrayvec/serde"] -id32 = [] -# Enable `ShaderModuleSource::Wgsl` + +## Enable `ShaderModuleSource::Wgsl` wgsl = ["naga/wgsl-in"] -# Implement `Send` and `Sync` on Wasm. -fragile-send-sync-non-atomic-wasm = ["hal/fragile-send-sync-non-atomic-wasm", "wgt/fragile-send-sync-non-atomic-wasm"] + +## Implement `Send` and `Sync` on Wasm, but only if atomics are not enabled. +## +## WebGL/WebGPU objects can not be shared between threads. +## However, it can be useful to artificially mark them as `Send` and `Sync` +## anyways to make it easier to write cross-platform code. +## This is technically *very* unsafe in a multithreaded environment, +## but on a wasm binary compiled without atomics we know we are definitely +## not in a multithreaded environment. +fragile-send-sync-non-atomic-wasm = [ + "hal/fragile-send-sync-non-atomic-wasm", + "wgt/fragile-send-sync-non-atomic-wasm", +] + +#! ### Backends, passed through to wgpu-hal +# -------------------------------------------------------------------- + +## Enable the `metal` backend. +metal = ["hal/metal"] + +## Enable the `vulkan` backend. +vulkan = ["hal/vulkan"] + +## Enable the `GLES` backend. +## +## This is used for all of GLES, OpenGL, and WebGL. +gles = ["hal/gles"] + +## Enable the `dx12` backend. +dx12 = ["hal/dx12"] [dependencies] arrayvec = "0.7" -bitflags = "2" bit-vec = "0.6" +bitflags = "2" codespan-reporting = "0.11" -rustc-hash = "1.1" +indexmap = "2" log = "0.4" +once_cell = "1" # parking_lot 0.12 switches from `winapi` to `windows`; permit either parking_lot = ">=0.11,<0.13" profiling = { version = "1", default-features = false } -raw-window-handle = { version = "0.5", optional = true } +raw-window-handle = { version = "0.6", optional = true } ron = { version = "0.8", optional = true } +rustc-hash = "1.1" serde = { version = "1", features = ["serde_derive"], optional = true } smallvec = "1" thiserror = "1" [dependencies.naga] -git = "https://github.com/gfx-rs/naga" -rev = "7a19f3af909202c7eafd36633b5584bfbb353ecb" -version = "0.13.0" -features = ["clone", "span", "validate"] +path = "../naga" +version = "0.14.0" +features = ["clone"] [dependencies.wgt] package = "wgpu-types" path = "../wgpu-types" -version = "0.17" +version = "0.18.0" [dependencies.hal] package = "wgpu-hal" path = "../wgpu-hal" -version = "0.17" +version = "0.18.0" default_features = false [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies] -web-sys = { version = "0.3.64", features = ["HtmlCanvasElement", "OffscreenCanvas"] } +web-sys = { version = "0.3.66", features = [ + "HtmlCanvasElement", + "OffscreenCanvas", +] } + +[build-dependencies] +cfg_aliases.workspace = true diff --git a/wgpu-core/build.rs b/wgpu-core/build.rs new file mode 100644 index 0000000000..2f715fdb2a --- /dev/null +++ b/wgpu-core/build.rs @@ -0,0 +1,13 @@ +fn main() { + cfg_aliases::cfg_aliases! { + send_sync: { any( + not(target_arch = "wasm32"), + all(feature = "fragile-send-sync-non-atomic-wasm", not(target_feature = "atomics")) + ) }, + webgl: { all(target_arch = "wasm32", not(target_os = "emscripten"), gles) }, + dx12: { all(target_os = "windows", feature = "dx12") }, + gles: { all(feature = "gles") }, + metal: { all(any(target_os = "ios", target_os = "macos"), feature = "metal") }, + vulkan: { all(not(target_arch = "wasm32"), feature = "vulkan") } + } +} diff --git a/wgpu-core/src/any_surface.rs b/wgpu-core/src/any_surface.rs new file mode 100644 index 0000000000..48d4b1d45a --- /dev/null +++ b/wgpu-core/src/any_surface.rs @@ -0,0 +1,96 @@ +use wgt::Backend; + +/// The `AnySurface` type: a `Arc` of a `HalSurface` for any backend `A`. +use crate::hal_api::HalApi; +use crate::instance::HalSurface; + +use std::any::Any; +use std::fmt; +use std::sync::Arc; + +/// A `Arc` of a `HalSurface`, for any backend `A`. +/// +/// Any `AnySurface` is just like an `Arc>`, except that the +/// `A` type parameter is erased. To access the `Surface`, you must +/// downcast to a particular backend with the \[`downcast_ref`\] or +/// \[`take`\] methods. +pub struct AnySurface(Arc); + +impl AnySurface { + /// Return an `AnySurface` that holds an owning `Arc` to `HalSurface`. + pub fn new(surface: HalSurface) -> AnySurface { + AnySurface(Arc::new(surface)) + } + + pub fn backend(&self) -> Backend { + #[cfg(vulkan)] + if self.downcast_ref::().is_some() { + return Backend::Vulkan; + } + #[cfg(metal)] + if self.downcast_ref::().is_some() { + return Backend::Metal; + } + #[cfg(dx12)] + if self.downcast_ref::().is_some() { + return Backend::Dx12; + } + #[cfg(gles)] + if self.downcast_ref::().is_some() { + return Backend::Gl; + } + Backend::Empty + } + + /// If `self` is an `Arc>`, return a reference to the + /// HalSurface. + pub fn downcast_ref(&self) -> Option<&HalSurface> { + self.0.downcast_ref::>() + } + + /// If `self` is an `Arc>`, returns that. + pub fn take(self) -> Option>> { + // `Arc::downcast` returns `Arc`, but requires that `T` be `Sync` and + // `Send`, and this is not the case for `HalSurface` in wasm builds. + // + // But as far as I can see, `Arc::downcast` has no particular reason to + // require that `T` be `Sync` and `Send`; the steps used here are sound. + if (self.0).is::>() { + // Turn the `Arc`, which is a pointer to an `ArcInner` struct, into + // a pointer to the `ArcInner`'s `data` field. Carry along the + // vtable from the original `Arc`. + let raw_erased: *const (dyn Any + 'static) = Arc::into_raw(self.0); + // Remove the vtable, and supply the concrete type of the `data`. + let raw_typed: *const HalSurface = raw_erased.cast::>(); + // Convert the pointer to the `data` field back into a pointer to + // the `ArcInner`, and restore reference-counting behavior. + let arc_typed: Arc> = unsafe { + // Safety: + // - We checked that the `dyn Any` was indeed a `HalSurface` above. + // - We're calling `Arc::from_raw` on the same pointer returned + // by `Arc::into_raw`, except that we stripped off the vtable + // pointer. + // - The pointer must still be live, because we've borrowed `self`, + // which holds another reference to it. + // - The format of a `ArcInner` must be the same as + // that of an `ArcInner>`, or else `AnyHalSurface::new` + // wouldn't be possible. + Arc::from_raw(raw_typed) + }; + Some(arc_typed) + } else { + None + } + } +} + +impl fmt::Debug for AnySurface { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("AnySurface") + } +} + +#[cfg(send_sync)] +unsafe impl Send for AnySurface {} +#[cfg(send_sync)] +unsafe impl Sync for AnySurface {} diff --git a/wgpu-core/src/binding_model.rs b/wgpu-core/src/binding_model.rs index 99190c5241..744a53d8a7 100644 --- a/wgpu-core/src/binding_model.rs +++ b/wgpu-core/src/binding_model.rs @@ -1,15 +1,22 @@ +#[cfg(feature = "trace")] +use crate::device::trace; use crate::{ - device::{DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT}, + device::{ + bgl, Device, DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT, + }, error::{ErrorFormatter, PrettyError}, hal_api::HalApi, id::{ - BindGroupLayoutId, BufferId, DeviceId, SamplerId, TextureId, TextureViewId, TlasId, Valid, + BindGroupId, BindGroupLayoutId, BufferId, PipelineLayoutId, SamplerId, TextureId, + TextureViewId, TlasId, }, init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction}, - resource::Resource, + resource::{Resource, ResourceInfo, ResourceType}, + resource_log, + snatch::SnatchGuard, track::{BindGroupStates, UsageConflict}, validation::{MissingBufferUsageError, MissingTextureUsageError}, - FastHashMap, Label, LifeGuard, MultiRefCount, Stored, + Label, }; use arrayvec::ArrayVec; @@ -19,7 +26,7 @@ use serde::Deserialize; #[cfg(feature = "trace")] use serde::Serialize; -use std::{borrow::Cow, ops::Range}; +use std::{borrow::Cow, ops::Range, sync::Arc}; use thiserror::Error; @@ -442,74 +449,66 @@ pub struct BindGroupLayoutDescriptor<'a> { pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>, } -pub(crate) type BindEntryMap = FastHashMap; - pub type BindGroupLayouts = crate::storage::Storage, BindGroupLayoutId>; /// Bind group layout. -/// -/// The lifetime of BGLs is a bit special. They are only referenced on CPU -/// without considering GPU operations. And on CPU they get manual -/// inc-refs and dec-refs. In particular, the following objects depend on them: -/// - produced bind groups -/// - produced pipeline layouts -/// - pipelines with implicit layouts #[derive(Debug)] -pub struct BindGroupLayout { - pub(crate) raw: A::BindGroupLayout, - pub(crate) device_id: Stored, - pub(crate) multi_ref_count: MultiRefCount, - pub(crate) entries: BindEntryMap, - // When a layout created and there already exists a compatible layout the new layout - // keeps a reference to the older compatible one. In some places we substitute the - // bind group layout id with its compatible sibling. - // Since this substitution can come at a cost, it is skipped when wgpu-core generates - // its own resource IDs. - pub(crate) compatible_layout: Option>, +pub struct BindGroupLayout { + pub(crate) raw: Option, + pub(crate) device: Arc>, + pub(crate) entries: bgl::EntryMap, + /// It is very important that we know if the bind group comes from the BGL pool. + /// + /// If it does, then we need to remove it from the pool when we drop it. + /// + /// We cannot unconditionally remove from the pool, as BGLs that don't come from the pool + /// (derived BGLs) must not be removed. + pub(crate) origin: bgl::Origin, #[allow(unused)] - pub(crate) dynamic_count: usize, - pub(crate) count_validator: BindingTypeMaxCountValidator, - #[cfg(debug_assertions)] + pub(crate) binding_count_validator: BindingTypeMaxCountValidator, + pub(crate) info: ResourceInfo, pub(crate) label: String, } -impl Resource for BindGroupLayout { - const TYPE: &'static str = "BindGroupLayout"; +impl Drop for BindGroupLayout { + fn drop(&mut self) { + if matches!(self.origin, bgl::Origin::Pool) { + self.device.bgl_pool.remove(&self.entries); + } + if let Some(raw) = self.raw.take() { + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyBindGroupLayout(self.info.id())); + } - fn life_guard(&self) -> &LifeGuard { - unreachable!() + resource_log!("Destroy raw BindGroupLayout {:?}", self.info.label()); + unsafe { + use hal::Device; + self.device.raw().destroy_bind_group_layout(raw); + } + } } +} + +impl Resource for BindGroupLayout { + const TYPE: ResourceType = "BindGroupLayout"; - fn label(&self) -> &str { - #[cfg(debug_assertions)] - return &self.label; - #[cfg(not(debug_assertions))] - return ""; + fn as_info(&self) -> &ResourceInfo { + &self.info } -} -// If a bindgroup needs to be substitued with its compatible equivalent, return the latter. -pub(crate) fn try_get_bind_group_layout( - layouts: &BindGroupLayouts, - id: BindGroupLayoutId, -) -> Option<&BindGroupLayout> { - let layout = layouts.get(id).ok()?; - if let Some(compat) = layout.compatible_layout { - return Some(&layouts[compat]); + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } - Some(layout) + fn label(&self) -> String { + self.label.clone() + } } - -pub(crate) fn get_bind_group_layout( - layouts: &BindGroupLayouts, - id: Valid, -) -> (Valid, &BindGroupLayout) { - let layout = &layouts[id]; - layout - .compatible_layout - .map(|compat| (compat, &layouts[compat])) - .unwrap_or((id, layout)) +impl BindGroupLayout { + pub(crate) fn raw(&self) -> &A::BindGroupLayout { + self.raw.as_ref().unwrap() + } } #[derive(Clone, Debug, Error)] @@ -609,15 +608,44 @@ pub struct PipelineLayoutDescriptor<'a> { } #[derive(Debug)] -pub struct PipelineLayout { - pub(crate) raw: A::PipelineLayout, - pub(crate) device_id: Stored, - pub(crate) life_guard: LifeGuard, - pub(crate) bind_group_layout_ids: ArrayVec, { hal::MAX_BIND_GROUPS }>, +pub struct PipelineLayout { + pub(crate) raw: Option, + pub(crate) device: Arc>, + pub(crate) info: ResourceInfo, + pub(crate) bind_group_layouts: ArrayVec>, { hal::MAX_BIND_GROUPS }>, pub(crate) push_constant_ranges: ArrayVec, } -impl PipelineLayout { +impl Drop for PipelineLayout { + fn drop(&mut self) { + if let Some(raw) = self.raw.take() { + resource_log!("Destroy raw PipelineLayout {:?}", self.info.label()); + + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyPipelineLayout(self.info.id())); + } + + unsafe { + use hal::Device; + self.device.raw().destroy_pipeline_layout(raw); + } + } + } +} + +impl PipelineLayout { + pub(crate) fn raw(&self) -> &A::PipelineLayout { + self.raw.as_ref().unwrap() + } + + pub(crate) fn get_binding_maps(&self) -> ArrayVec<&bgl::EntryMap, { hal::MAX_BIND_GROUPS }> { + self.bind_group_layouts + .iter() + .map(|bgl| &bgl.entries) + .collect() + } + /// Validate push constants match up with expected ranges. pub(crate) fn validate_push_constant_ranges( &self, @@ -697,11 +725,15 @@ impl PipelineLayout { } } -impl Resource for PipelineLayout { - const TYPE: &'static str = "PipelineLayout"; +impl Resource for PipelineLayout { + const TYPE: ResourceType = "PipelineLayout"; - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } @@ -805,21 +837,51 @@ pub(crate) fn buffer_binding_type_alignment( } } +#[derive(Debug)] pub struct BindGroup { - pub(crate) raw: A::BindGroup, - pub(crate) device_id: Stored, - pub(crate) layout_id: Valid, - pub(crate) life_guard: LifeGuard, + pub(crate) raw: Option, + pub(crate) device: Arc>, + pub(crate) layout: Arc>, + pub(crate) info: ResourceInfo, pub(crate) used: BindGroupStates, - pub(crate) used_buffer_ranges: Vec, - pub(crate) used_texture_ranges: Vec, + pub(crate) used_buffer_ranges: Vec>, + pub(crate) used_texture_ranges: Vec>, pub(crate) dynamic_binding_info: Vec, /// Actual binding sizes for buffers that don't have `min_binding_size` /// specified in BGL. Listed in the order of iteration of `BGL.entries`. pub(crate) late_buffer_binding_sizes: Vec, } +impl Drop for BindGroup { + fn drop(&mut self) { + if let Some(raw) = self.raw.take() { + resource_log!("Destroy raw BindGroup {:?}", self.info.label()); + + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyBindGroup(self.info.id())); + } + + unsafe { + use hal::Device; + self.device.raw().destroy_bind_group(raw); + } + } + } +} + impl BindGroup { + pub(crate) fn raw(&self, guard: &SnatchGuard) -> Option<&A::BindGroup> { + // Clippy insist on writing it this way. The idea is to return None + // if any of the raw buffer is not valid anymore. + for buffer in &self.used_buffer_ranges { + let _ = buffer.buffer.raw(guard)?; + } + for texture in &self.used_texture_ranges { + let _ = texture.texture.raw(guard)?; + } + self.raw.as_ref() + } pub(crate) fn validate_dynamic_bindings( &self, bind_group_index: u32, @@ -869,11 +931,15 @@ impl BindGroup { } } -impl Resource for BindGroup { - const TYPE: &'static str = "BindGroup"; +impl Resource for BindGroup { + const TYPE: ResourceType = "BindGroup"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } diff --git a/wgpu-core/src/command/bind.rs b/wgpu-core/src/command/bind.rs index 05f90c6bc9..7b2ac54552 100644 --- a/wgpu-core/src/command/bind.rs +++ b/wgpu-core/src/command/bind.rs @@ -1,13 +1,12 @@ +use std::sync::Arc; + use crate::{ - binding_model::{ - BindGroup, BindGroupLayouts, LateMinBufferBindingSizeMismatch, PipelineLayout, - }, + binding_model::{BindGroup, LateMinBufferBindingSizeMismatch, PipelineLayout}, device::SHADER_STAGE_COUNT, hal_api::HalApi, - id::{BindGroupId, PipelineLayoutId, Valid}, + id::BindGroupId, pipeline::LateSizedBufferGroup, - storage::Storage, - Stored, + resource::Resource, }; use arrayvec::ArrayVec; @@ -15,73 +14,172 @@ use arrayvec::ArrayVec; type BindGroupMask = u8; mod compat { - use crate::{ - binding_model::BindGroupLayouts, - id::{BindGroupLayoutId, Valid}, - }; - use std::ops::Range; + use arrayvec::ArrayVec; - #[derive(Debug, Default)] - struct Entry { - assigned: Option>, - expected: Option>, + use crate::{binding_model::BindGroupLayout, device::bgl, hal_api::HalApi, resource::Resource}; + use std::{ops::Range, sync::Arc}; + + #[derive(Debug, Clone)] + struct Entry { + assigned: Option>>, + expected: Option>>, } - impl Entry { + impl Entry { + fn empty() -> Self { + Self { + assigned: None, + expected: None, + } + } fn is_active(&self) -> bool { self.assigned.is_some() && self.expected.is_some() } - fn is_valid(&self, bind_group_layouts: &BindGroupLayouts) -> bool { - if self.expected.is_none() || self.expected == self.assigned { + fn is_valid(&self) -> bool { + if self.expected.is_none() { return true; } + if let Some(expected_bgl) = self.expected.as_ref() { + if let Some(assigned_bgl) = self.assigned.as_ref() { + if expected_bgl.is_equal(assigned_bgl) { + return true; + } + } + } + false + } - if let Some(id) = self.assigned { - return bind_group_layouts[id].compatible_layout == self.expected; + fn is_incompatible(&self) -> bool { + self.expected.is_none() || !self.is_valid() + } + + // Describe how bind group layouts are incompatible, for validation + // error message. + fn bgl_diff(&self) -> Vec { + let mut diff = Vec::new(); + + if let Some(expected_bgl) = self.expected.as_ref() { + let expected_bgl_type = match expected_bgl.origin { + bgl::Origin::Derived => "implicit", + bgl::Origin::Pool => "explicit", + }; + let expected_label = expected_bgl.label(); + diff.push(format!( + "Should be compatible an with an {expected_bgl_type} bind group layout {}", + if expected_label.is_empty() { + "without label".to_string() + } else { + format!("with label = `{}`", expected_label) + } + )); + if let Some(assigned_bgl) = self.assigned.as_ref() { + let assigned_bgl_type = match assigned_bgl.origin { + bgl::Origin::Derived => "implicit", + bgl::Origin::Pool => "explicit", + }; + let assigned_label = assigned_bgl.label(); + diff.push(format!( + "Assigned {assigned_bgl_type} bind group layout {}", + if assigned_label.is_empty() { + "without label".to_string() + } else { + format!("with label = `{}`", assigned_label) + } + )); + for (id, e_entry) in expected_bgl.entries.iter() { + if let Some(a_entry) = assigned_bgl.entries.get(*id) { + if a_entry.binding != e_entry.binding { + diff.push(format!( + "Entry {id} binding expected {}, got {}", + e_entry.binding, a_entry.binding + )); + } + if a_entry.count != e_entry.count { + diff.push(format!( + "Entry {id} count expected {:?}, got {:?}", + e_entry.count, a_entry.count + )); + } + if a_entry.ty != e_entry.ty { + diff.push(format!( + "Entry {id} type expected {:?}, got {:?}", + e_entry.ty, a_entry.ty + )); + } + if a_entry.visibility != e_entry.visibility { + diff.push(format!( + "Entry {id} visibility expected {:?}, got {:?}", + e_entry.visibility, a_entry.visibility + )); + } + } else { + diff.push(format!( + "Entry {id} not found in assigned bind group layout" + )) + } + } + + assigned_bgl.entries.iter().for_each(|(id, _e_entry)| { + if !expected_bgl.entries.contains_key(*id) { + diff.push(format!( + "Entry {id} not found in expected bind group layout" + )) + } + }); + + if expected_bgl.origin != assigned_bgl.origin { + diff.push(format!("Expected {expected_bgl_type} bind group layout, got {assigned_bgl_type}")) + } + } else { + diff.push("Assigned bind group layout not found (internal error)".to_owned()); + } + } else { + diff.push("Expected bind group layout not found (internal error)".to_owned()); } - false + diff } } - #[derive(Debug)] - pub(crate) struct BoundBindGroupLayouts { - entries: [Entry; hal::MAX_BIND_GROUPS], + #[derive(Debug, Default)] + pub(crate) struct BoundBindGroupLayouts { + entries: ArrayVec, { hal::MAX_BIND_GROUPS }>, } - impl BoundBindGroupLayouts { + impl BoundBindGroupLayouts { pub fn new() -> Self { Self { - entries: Default::default(), + entries: (0..hal::MAX_BIND_GROUPS).map(|_| Entry::empty()).collect(), } } - fn make_range(&self, start_index: usize) -> Range { // find first incompatible entry let end = self .entries .iter() - .position(|e| e.expected.is_none() || e.assigned != e.expected) + .position(|e| e.is_incompatible()) .unwrap_or(self.entries.len()); start_index..end.max(start_index) } pub fn update_expectations( &mut self, - expectations: &[Valid], + expectations: &[Arc>], ) -> Range { let start_index = self .entries .iter() .zip(expectations) - .position(|(e, &expect)| e.expected != Some(expect)) + .position(|(e, expect)| { + e.expected.is_none() || !e.expected.as_ref().unwrap().is_equal(expect) + }) .unwrap_or(expectations.len()); - for (e, &expect) in self.entries[start_index..] + for (e, expect) in self.entries[start_index..] .iter_mut() .zip(expectations[start_index..].iter()) { - e.expected = Some(expect); + e.expected = Some(expect.clone()); } for e in self.entries[expectations.len()..].iter_mut() { e.expected = None; @@ -89,7 +187,7 @@ mod compat { self.make_range(start_index) } - pub fn assign(&mut self, index: usize, value: Valid) -> Range { + pub fn assign(&mut self, index: usize, value: Arc>) -> Range { self.entries[index].assigned = Some(value); self.make_range(index) } @@ -101,52 +199,24 @@ mod compat { .filter_map(|(i, e)| if e.is_active() { Some(i) } else { None }) } - pub fn invalid_mask( - &self, - bind_group_layouts: &BindGroupLayouts, - ) -> super::BindGroupMask { + pub fn invalid_mask(&self) -> super::BindGroupMask { self.entries.iter().enumerate().fold(0, |mask, (i, entry)| { - if entry.is_valid(bind_group_layouts) { + if entry.is_valid() { mask } else { mask | 1u8 << i } }) } - } - #[test] - fn test_compatibility() { - fn id(val: u32) -> Valid { - BindGroupLayoutId::dummy(val) + pub fn bgl_diff(&self) -> Vec { + for e in &self.entries { + if !e.is_valid() { + return e.bgl_diff(); + } + } + vec![String::from("No differences detected? (internal error)")] } - - let mut man = BoundBindGroupLayouts::new(); - man.entries[0] = Entry { - expected: Some(id(3)), - assigned: Some(id(2)), - }; - man.entries[1] = Entry { - expected: Some(id(1)), - assigned: Some(id(1)), - }; - man.entries[2] = Entry { - expected: Some(id(4)), - assigned: Some(id(5)), - }; - // check that we rebind [1] after [0] became compatible - assert_eq!(man.assign(0, id(3)), 0..2); - // check that nothing is rebound - assert_eq!(man.update_expectations(&[id(3), id(2)]), 1..1); - // check that [1] and [2] are rebound on expectations change - assert_eq!(man.update_expectations(&[id(3), id(1), id(5)]), 1..3); - // reset the first two bindings - assert_eq!(man.update_expectations(&[id(4), id(6), id(5)]), 0..0); - // check that nothing is rebound, even if there is a match, - // since earlier binding is incompatible. - assert_eq!(man.assign(1, id(6)), 1..1); - // finally, bind everything - assert_eq!(man.assign(0, id(4)), 0..3); } } @@ -156,9 +226,9 @@ struct LateBufferBinding { bound_size: wgt::BufferAddress, } -#[derive(Debug, Default)] -pub(super) struct EntryPayload { - pub(super) group_id: Option>, +#[derive(Debug)] +pub(super) struct EntryPayload { + pub(super) group: Option>>, pub(super) dynamic_offsets: Vec, late_buffer_bindings: Vec, /// Since `LateBufferBinding` may contain information about the bindings @@ -166,49 +236,57 @@ pub(super) struct EntryPayload { pub(super) late_bindings_effective_count: usize, } -impl EntryPayload { +impl Default for EntryPayload { + fn default() -> Self { + Self { + group: None, + dynamic_offsets: Default::default(), + late_buffer_bindings: Default::default(), + late_bindings_effective_count: Default::default(), + } + } +} + +impl EntryPayload { fn reset(&mut self) { - self.group_id = None; + self.group = None; self.dynamic_offsets.clear(); self.late_buffer_bindings.clear(); self.late_bindings_effective_count = 0; } } -#[derive(Debug)] -pub(super) struct Binder { - pub(super) pipeline_layout_id: Option>, //TODO: strongly `Stored` - manager: compat::BoundBindGroupLayouts, - payloads: [EntryPayload; hal::MAX_BIND_GROUPS], +#[derive(Debug, Default)] +pub(super) struct Binder { + pub(super) pipeline_layout: Option>>, + manager: compat::BoundBindGroupLayouts, + payloads: [EntryPayload; hal::MAX_BIND_GROUPS], } -impl Binder { +impl Binder { pub(super) fn new() -> Self { Self { - pipeline_layout_id: None, + pipeline_layout: None, manager: compat::BoundBindGroupLayouts::new(), payloads: Default::default(), } } - pub(super) fn reset(&mut self) { - self.pipeline_layout_id = None; + self.pipeline_layout = None; self.manager = compat::BoundBindGroupLayouts::new(); for payload in self.payloads.iter_mut() { payload.reset(); } } - pub(super) fn change_pipeline_layout<'a, A: HalApi>( + pub(super) fn change_pipeline_layout<'a>( &'a mut self, - guard: &Storage, PipelineLayoutId>, - new_id: Valid, + new: &Arc>, late_sized_buffer_groups: &[LateSizedBufferGroup], - ) -> (usize, &'a [EntryPayload]) { - let old_id_opt = self.pipeline_layout_id.replace(new_id); - let new = &guard[new_id]; + ) -> (usize, &'a [EntryPayload]) { + let old_id_opt = self.pipeline_layout.replace(new.clone()); - let mut bind_range = self.manager.update_expectations(&new.bind_group_layout_ids); + let mut bind_range = self.manager.update_expectations(&new.bind_group_layouts); // Update the buffer binding sizes that are required by shaders. for (payload, late_group) in self.payloads.iter_mut().zip(late_sized_buffer_groups) { @@ -232,8 +310,7 @@ impl Binder { } } - if let Some(old_id) = old_id_opt { - let old = &guard[old_id]; + if let Some(old) = old_id_opt { // root constants are the base compatibility property if old.push_constant_ranges != new.push_constant_ranges { bind_range.start = 0; @@ -243,21 +320,18 @@ impl Binder { (bind_range.start, &self.payloads[bind_range]) } - pub(super) fn assign_group<'a, A: HalApi>( + pub(super) fn assign_group<'a>( &'a mut self, index: usize, - bind_group_id: Valid, - bind_group: &BindGroup, + bind_group: &Arc>, offsets: &[wgt::DynamicOffset], - ) -> &'a [EntryPayload] { + ) -> &'a [EntryPayload] { + let bind_group_id = bind_group.as_info().id(); log::trace!("\tBinding [{}] = group {:?}", index, bind_group_id); - debug_assert_eq!(A::VARIANT, bind_group_id.0.backend()); + debug_assert_eq!(A::VARIANT, bind_group_id.backend()); let payload = &mut self.payloads[index]; - payload.group_id = Some(Stored { - value: bind_group_id, - ref_count: bind_group.life_guard.add_ref(), - }); + payload.group = Some(bind_group.clone()); payload.dynamic_offsets.clear(); payload.dynamic_offsets.extend_from_slice(offsets); @@ -281,22 +355,23 @@ impl Binder { } } - let bind_range = self.manager.assign(index, bind_group.layout_id); + let bind_range = self.manager.assign(index, bind_group.layout.clone()); &self.payloads[bind_range] } - pub(super) fn list_active(&self) -> impl Iterator> + '_ { + pub(super) fn list_active(&self) -> impl Iterator + '_ { let payloads = &self.payloads; self.manager .list_active() - .map(move |index| payloads[index].group_id.as_ref().unwrap().value) + .map(move |index| payloads[index].group.as_ref().unwrap().as_info().id()) } - pub(super) fn invalid_mask( - &self, - bind_group_layouts: &BindGroupLayouts, - ) -> BindGroupMask { - self.manager.invalid_mask(bind_group_layouts) + pub(super) fn invalid_mask(&self) -> BindGroupMask { + self.manager.invalid_mask() + } + + pub(super) fn bgl_diff(&self) -> Vec { + self.manager.bgl_diff() } /// Scan active buffer bindings corresponding to layouts without `min_binding_size` specified. diff --git a/wgpu-core/src/command/bundle.rs b/wgpu-core/src/command/bundle.rs index 0a4660a798..40e652fef4 100644 --- a/wgpu-core/src/command/bundle.rs +++ b/wgpu-core/src/command/bundle.rs @@ -78,8 +78,10 @@ index format changes. #![allow(clippy::reversed_empty_ranges)] +#[cfg(feature = "trace")] +use crate::device::trace; use crate::{ - binding_model::{self, buffer_binding_type_alignment}, + binding_model::{buffer_binding_type_alignment, BindGroup, BindGroupLayout, PipelineLayout}, command::{ BasePass, BindGroupStateChange, ColorAttachmentError, DrawError, MapPassErr, PassErrorScope, RenderCommand, RenderCommandError, StateChange, @@ -91,19 +93,19 @@ use crate::{ }, error::{ErrorFormatter, PrettyError}, hal_api::HalApi, - hub::{Hub, Token}, - id, - identity::GlobalIdentityHandlerFactory, + hub::Hub, + id::{self, RenderBundleId}, init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction}, - pipeline::{self, PipelineFlags}, - resource::{self, Resource}, - storage::Storage, + pipeline::{self, PipelineFlags, RenderPipeline}, + resource::{Resource, ResourceInfo, ResourceType}, + resource_log, track::RenderBundleScope, validation::check_buffer_usage, - Label, LabelHelpers, LifeGuard, Stored, + Label, LabelHelpers, }; use arrayvec::ArrayVec; -use std::{borrow::Cow, mem, num::NonZeroU32, ops::Range}; + +use std::{borrow::Cow, mem, num::NonZeroU32, ops::Range, sync::Arc}; use thiserror::Error; use hal::CommandEncoder as _; @@ -251,19 +253,17 @@ impl RenderBundleEncoder { /// and accumulate buffer and texture initialization actions. /// /// [`ExecuteBundle`]: RenderCommand::ExecuteBundle - pub(crate) fn finish( + pub(crate) fn finish( self, desc: &RenderBundleDescriptor, - device: &Device, - hub: &Hub, - token: &mut Token>, + device: &Arc>, + hub: &Hub, ) -> Result, RenderBundleError> { - let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(token); - let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token); - let (pipeline_guard, mut token) = hub.render_pipelines.read(&mut token); - let (query_set_guard, mut token) = hub.query_sets.read(&mut token); - let (buffer_guard, mut token) = hub.buffers.read(&mut token); - let (texture_guard, _) = hub.textures.read(&mut token); + let bind_group_guard = hub.bind_groups.read(); + let pipeline_guard = hub.render_pipelines.read(); + let query_set_guard = hub.query_sets.read(); + let buffer_guard = hub.buffers.read(); + let texture_guard = hub.textures.read(); let mut state = State { trackers: RenderBundleScope::new( @@ -295,13 +295,14 @@ impl RenderBundleEncoder { } => { let scope = PassErrorScope::SetBindGroup(bind_group_id); - let bind_group: &binding_model::BindGroup = state + let bind_group = state .trackers .bind_groups + .write() .add_single(&*bind_group_guard, bind_group_id) .ok_or(RenderCommandError::InvalidBindGroup(bind_group_id)) .map_pass_err(scope)?; - self.check_valid_to_use(bind_group.device_id.value) + self.check_valid_to_use(bind_group.device.info.id()) .map_pass_err(scope)?; let max_bind_groups = device.limits.max_bind_groups; @@ -314,7 +315,7 @@ impl RenderBundleEncoder { } // Identify the next `num_dynamic_offsets` entries from `base.dynamic_offsets`. - let num_dynamic_offsets = num_dynamic_offsets as usize; + let num_dynamic_offsets = num_dynamic_offsets; let offsets_range = next_dynamic_offset..next_dynamic_offset + num_dynamic_offsets; next_dynamic_offset = offsets_range.end; @@ -347,11 +348,11 @@ impl RenderBundleEncoder { buffer_memory_init_actions.extend_from_slice(&bind_group.used_buffer_ranges); texture_memory_init_actions.extend_from_slice(&bind_group.used_texture_ranges); - state.set_bind_group(index, bind_group_id, bind_group.layout_id, offsets_range); + state.set_bind_group(index, bind_group_guard.get(bind_group_id).as_ref().unwrap(), &bind_group.layout, offsets_range); unsafe { state .trackers - .merge_bind_group(&*texture_guard, &bind_group.used) + .merge_bind_group(&bind_group.used) .map_pass_err(scope)? }; //Note: stateless trackers are not merged: the lifetime reference @@ -360,13 +361,14 @@ impl RenderBundleEncoder { RenderCommand::SetPipeline(pipeline_id) => { let scope = PassErrorScope::SetPipelineRender(pipeline_id); - let pipeline: &pipeline::RenderPipeline = state + let pipeline = state .trackers .render_pipelines + .write() .add_single(&*pipeline_guard, pipeline_id) .ok_or(RenderCommandError::InvalidPipeline(pipeline_id)) .map_pass_err(scope)?; - self.check_valid_to_use(pipeline.device_id.value) + self.check_valid_to_use(pipeline.device.info.id()) .map_pass_err(scope)?; self.context @@ -383,8 +385,7 @@ impl RenderBundleEncoder { .map_pass_err(scope); } - let layout = &pipeline_layout_guard[pipeline.layout_id.value]; - let pipeline_state = PipelineState::new(pipeline_id, pipeline, layout); + let pipeline_state = PipelineState::new(pipeline); commands.push(command); @@ -393,7 +394,7 @@ impl RenderBundleEncoder { commands.extend(iter) } - state.invalidate_bind_groups(&pipeline_state, layout); + state.invalidate_bind_groups(&pipeline_state, &pipeline.layout); state.pipeline = Some(pipeline_state); } RenderCommand::SetIndexBuffer { @@ -403,12 +404,13 @@ impl RenderBundleEncoder { size, } => { let scope = PassErrorScope::SetIndexBuffer(buffer_id); - let buffer: &resource::Buffer = state + let buffer = state .trackers .buffers + .write() .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDEX) .map_pass_err(scope)?; - self.check_valid_to_use(buffer.device_id.value) + self.check_valid_to_use(buffer.device.info.id()) .map_pass_err(scope)?; check_buffer_usage(buffer.usage, wgt::BufferUsages::INDEX) .map_pass_err(scope)?; @@ -417,8 +419,8 @@ impl RenderBundleEncoder { Some(s) => offset + s.get(), None => buffer.size, }; - buffer_memory_init_actions.extend(buffer.initialization_status.create_action( - buffer_id, + buffer_memory_init_actions.extend(buffer.initialization_status.read().create_action( + buffer, offset..end, MemoryInitKind::NeedsInitializedMemory, )); @@ -431,12 +433,23 @@ impl RenderBundleEncoder { size, } => { let scope = PassErrorScope::SetVertexBuffer(buffer_id); - let buffer: &resource::Buffer = state + + let max_vertex_buffers = device.limits.max_vertex_buffers; + if slot >= max_vertex_buffers { + return Err(RenderCommandError::VertexBufferIndexOutOfRange { + index: slot, + max: max_vertex_buffers, + }) + .map_pass_err(scope); + } + + let buffer = state .trackers .buffers + .write() .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::VERTEX) .map_pass_err(scope)?; - self.check_valid_to_use(buffer.device_id.value) + self.check_valid_to_use(buffer.device.info.id()) .map_pass_err(scope)?; check_buffer_usage(buffer.usage, wgt::BufferUsages::VERTEX) .map_pass_err(scope)?; @@ -445,8 +458,8 @@ impl RenderBundleEncoder { Some(s) => offset + s.get(), None => buffer.size, }; - buffer_memory_init_actions.extend(buffer.initialization_status.create_action( - buffer_id, + buffer_memory_init_actions.extend(buffer.initialization_status.read().create_action( + buffer, offset..end, MemoryInitKind::NeedsInitializedMemory, )); @@ -461,10 +474,9 @@ impl RenderBundleEncoder { let scope = PassErrorScope::SetPushConstant; let end_offset = offset + size_bytes; - let pipeline = state.pipeline(scope)?; - let pipeline_layout = &pipeline_layout_guard[pipeline.layout_id]; + let pipeline_state = state.pipeline(scope)?; - pipeline_layout + pipeline_state.pipeline.layout .validate_push_constant_ranges(stages, offset, end_offset) .map_pass_err(scope)?; @@ -484,7 +496,7 @@ impl RenderBundleEncoder { let pipeline = state.pipeline(scope)?; let used_bind_groups = pipeline.used_bind_groups; let vertex_limits = state.vertex_limits(pipeline); - let last_vertex = first_vertex + vertex_count; + let last_vertex = first_vertex as u64 + vertex_count as u64; if last_vertex > vertex_limits.vertex_limit { return Err(DrawError::VertexBeyondLimit { last_vertex, @@ -493,7 +505,7 @@ impl RenderBundleEncoder { }) .map_pass_err(scope); } - let last_instance = first_instance + instance_count; + let last_instance = first_instance as u64 + instance_count as u64; if last_instance > vertex_limits.instance_limit { return Err(DrawError::InstanceBeyondLimit { last_instance, @@ -527,7 +539,7 @@ impl RenderBundleEncoder { //TODO: validate that base_vertex + max_index() is within the provided range let vertex_limits = state.vertex_limits(pipeline); let index_limit = index.limit(); - let last_index = first_index + index_count; + let last_index = first_index as u64 + index_count as u64; if last_index > index_limit { return Err(DrawError::IndexBeyondLimit { last_index, @@ -535,7 +547,7 @@ impl RenderBundleEncoder { }) .map_pass_err(scope); } - let last_instance = first_instance + instance_count; + let last_instance = first_instance as u64 + instance_count as u64; if last_instance > vertex_limits.instance_limit { return Err(DrawError::InstanceBeyondLimit { last_instance, @@ -567,18 +579,19 @@ impl RenderBundleEncoder { let pipeline = state.pipeline(scope)?; let used_bind_groups = pipeline.used_bind_groups; - let buffer: &resource::Buffer = state + let buffer = state .trackers .buffers + .write() .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) .map_pass_err(scope)?; - self.check_valid_to_use(buffer.device_id.value) + self.check_valid_to_use(buffer.device.info.id()) .map_pass_err(scope)?; check_buffer_usage(buffer.usage, wgt::BufferUsages::INDIRECT) .map_pass_err(scope)?; - buffer_memory_init_actions.extend(buffer.initialization_status.create_action( - buffer_id, + buffer_memory_init_actions.extend(buffer.initialization_status.read().create_action( + buffer, offset..(offset + mem::size_of::() as u64), MemoryInitKind::NeedsInitializedMemory, )); @@ -605,18 +618,19 @@ impl RenderBundleEncoder { let pipeline = state.pipeline(scope)?; let used_bind_groups = pipeline.used_bind_groups; - let buffer: &resource::Buffer = state + let buffer = state .trackers .buffers + .write() .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) .map_pass_err(scope)?; - self.check_valid_to_use(buffer.device_id.value) + self.check_valid_to_use(buffer.device.info.id()) .map_pass_err(scope)?; check_buffer_usage(buffer.usage, wgt::BufferUsages::INDIRECT) .map_pass_err(scope)?; - buffer_memory_init_actions.extend(buffer.initialization_status.create_action( - buffer_id, + buffer_memory_init_actions.extend(buffer.initialization_status.read().create_action( + buffer, offset..(offset + mem::size_of::() as u64), MemoryInitKind::NeedsInitializedMemory, )); @@ -659,23 +673,20 @@ impl RenderBundleEncoder { }, is_depth_read_only: self.is_depth_read_only, is_stencil_read_only: self.is_stencil_read_only, - device_id: Stored { - value: id::Valid(self.parent_id), - ref_count: device.life_guard.add_ref(), - }, + device: device.clone(), used: state.trackers, buffer_memory_init_actions, texture_memory_init_actions, context: self.context, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), + info: ResourceInfo::new(desc.label.borrow_or_default()), + discard_hal_labels: device + .instance_flags + .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS), }) } - fn check_valid_to_use( - &self, - device_id: id::Valid, - ) -> Result<(), RenderBundleErrorInner> { - if device_id.0 != self.parent_id { + fn check_valid_to_use(&self, device_id: id::DeviceId) -> Result<(), RenderBundleErrorInner> { + if device_id != self.parent_id { return Err(RenderBundleErrorInner::NotValidToUse); } @@ -714,6 +725,8 @@ pub enum CreateRenderBundleError { pub enum ExecutionError { #[error("Buffer {0:?} is destroyed")] DestroyedBuffer(id::BufferId), + #[error("BindGroup {0:?} is invalid")] + InvalidBindGroup(id::BindGroupId), #[error("Using {0} in a render bundle is not implemented")] Unimplemented(&'static str), } @@ -724,6 +737,9 @@ impl PrettyError for ExecutionError { Self::DestroyedBuffer(id) => { fmt.buffer_label(&id); } + Self::InvalidBindGroup(id) => { + fmt.bind_group_label(&id); + } Self::Unimplemented(_reason) => {} }; } @@ -734,35 +750,36 @@ pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor>; //Note: here, `RenderBundle` is just wrapping a raw stream of render commands. // The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle, // or Metal indirect command buffer. +#[derive(Debug)] pub struct RenderBundle { // Normalized command stream. It can be executed verbatim, // without re-binding anything on the pipeline change. base: BasePass, pub(super) is_depth_read_only: bool, pub(super) is_stencil_read_only: bool, - pub(crate) device_id: Stored, + pub(crate) device: Arc>, pub(crate) used: RenderBundleScope, - pub(super) buffer_memory_init_actions: Vec, - pub(super) texture_memory_init_actions: Vec, + pub(super) buffer_memory_init_actions: Vec>, + pub(super) texture_memory_init_actions: Vec>, pub(super) context: RenderPassContext, - pub(crate) life_guard: LifeGuard, + pub(crate) info: ResourceInfo, + discard_hal_labels: bool, } -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] +impl Drop for RenderBundle { + fn drop(&mut self) { + resource_log!("Destroy raw RenderBundle {:?}", self.info.label()); + + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyRenderBundle(self.info.id())); + } + } +} + +#[cfg(send_sync)] unsafe impl Send for RenderBundle {} -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] +#[cfg(send_sync)] unsafe impl Sync for RenderBundle {} impl RenderBundle { @@ -775,23 +792,18 @@ impl RenderBundle { /// Note that the function isn't expected to fail, generally. /// All the validation has already been done by this point. /// The only failure condition is if some of the used buffers are destroyed. - pub(super) unsafe fn execute( - &self, - raw: &mut A::CommandEncoder, - pipeline_layout_guard: &Storage< - crate::binding_model::PipelineLayout, - id::PipelineLayoutId, - >, - bind_group_guard: &Storage, id::BindGroupId>, - pipeline_guard: &Storage, id::RenderPipelineId>, - buffer_guard: &Storage, id::BufferId>, - ) -> Result<(), ExecutionError> { + pub(super) unsafe fn execute(&self, raw: &mut A::CommandEncoder) -> Result<(), ExecutionError> { + let trackers = &self.used; let mut offsets = self.base.dynamic_offsets.as_slice(); - let mut pipeline_layout_id = None::>; - if let Some(ref label) = self.base.label { - unsafe { raw.begin_debug_marker(label) }; + let mut pipeline_layout = None::>>; + if !self.discard_hal_labels { + if let Some(ref label) = self.base.label { + unsafe { raw.begin_debug_marker(label) }; + } } + let snatch_guard = self.device.snatchable_lock.read(); + for command in self.base.commands.iter() { match *command { RenderCommand::SetBindGroup { @@ -799,22 +811,27 @@ impl RenderBundle { num_dynamic_offsets, bind_group_id, } => { - let bind_group = bind_group_guard.get(bind_group_id).unwrap(); + let bind_groups = trackers.bind_groups.read(); + let bind_group = bind_groups.get(bind_group_id).unwrap(); + let raw_bg = bind_group + .raw(&snatch_guard) + .ok_or(ExecutionError::InvalidBindGroup(bind_group_id))?; unsafe { raw.set_bind_group( - &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw, + pipeline_layout.as_ref().unwrap().raw(), index, - &bind_group.raw, - &offsets[..num_dynamic_offsets as usize], + raw_bg, + &offsets[..num_dynamic_offsets], ) }; - offsets = &offsets[num_dynamic_offsets as usize..]; + offsets = &offsets[num_dynamic_offsets..]; } RenderCommand::SetPipeline(pipeline_id) => { - let pipeline = pipeline_guard.get(pipeline_id).unwrap(); - unsafe { raw.set_render_pipeline(&pipeline.raw) }; + let render_pipelines = trackers.render_pipelines.read(); + let pipeline = render_pipelines.get(pipeline_id).unwrap(); + unsafe { raw.set_render_pipeline(pipeline.raw()) }; - pipeline_layout_id = Some(pipeline.layout_id.value); + pipeline_layout = Some(pipeline.layout.clone()); } RenderCommand::SetIndexBuffer { buffer_id, @@ -822,11 +839,11 @@ impl RenderBundle { offset, size, } => { - let buffer = buffer_guard + let buffers = trackers.buffers.read(); + let buffer: &A::Buffer = buffers .get(buffer_id) - .unwrap() - .raw - .as_ref() + .ok_or(ExecutionError::DestroyedBuffer(buffer_id))? + .raw(&snatch_guard) .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?; let bb = hal::BufferBinding { buffer, @@ -841,11 +858,11 @@ impl RenderBundle { offset, size, } => { - let buffer = buffer_guard + let buffers = trackers.buffers.read(); + let buffer = buffers .get(buffer_id) - .unwrap() - .raw - .as_ref() + .ok_or(ExecutionError::DestroyedBuffer(buffer_id))? + .raw(&snatch_guard) .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?; let bb = hal::BufferBinding { buffer, @@ -860,8 +877,7 @@ impl RenderBundle { size_bytes, values_offset, } => { - let pipeline_layout_id = pipeline_layout_id.unwrap(); - let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id]; + let pipeline_layout = pipeline_layout.as_ref().unwrap(); if let Some(values_offset) = values_offset { let values_end_offset = @@ -870,7 +886,12 @@ impl RenderBundle { [(values_offset as usize)..values_end_offset]; unsafe { - raw.set_push_constants(&pipeline_layout.raw, stages, offset, data_slice) + raw.set_push_constants( + pipeline_layout.raw(), + stages, + offset, + data_slice, + ) } } else { super::push_constant_clear( @@ -879,7 +900,7 @@ impl RenderBundle { |clear_offset, clear_data| { unsafe { raw.set_push_constants( - &pipeline_layout.raw, + pipeline_layout.raw(), stages, clear_offset, clear_data, @@ -920,11 +941,11 @@ impl RenderBundle { count: None, indexed: false, } => { - let buffer = buffer_guard + let buffers = trackers.buffers.read(); + let buffer = buffers .get(buffer_id) - .unwrap() - .raw - .as_ref() + .ok_or(ExecutionError::DestroyedBuffer(buffer_id))? + .raw(&snatch_guard) .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?; unsafe { raw.draw_indirect(buffer, offset, 1) }; } @@ -934,11 +955,11 @@ impl RenderBundle { count: None, indexed: true, } => { - let buffer = buffer_guard + let buffers = trackers.buffers.read(); + let buffer = buffers .get(buffer_id) - .unwrap() - .raw - .as_ref() + .ok_or(ExecutionError::DestroyedBuffer(buffer_id))? + .raw(&snatch_guard) .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?; unsafe { raw.draw_indexed_indirect(buffer, offset, 1) }; } @@ -966,19 +987,25 @@ impl RenderBundle { } } - if let Some(_) = self.base.label { - unsafe { raw.end_debug_marker() }; + if !self.discard_hal_labels { + if let Some(_) = self.base.label { + unsafe { raw.end_debug_marker() }; + } } Ok(()) } } -impl Resource for RenderBundle { - const TYPE: &'static str = "RenderBundle"; +impl Resource for RenderBundle { + const TYPE: ResourceType = "RenderBundle"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } @@ -999,12 +1026,13 @@ impl IndexState { /// Return the number of entries in the current index buffer. /// /// Panic if no index buffer has been set. - fn limit(&self) -> u32 { + fn limit(&self) -> u64 { let bytes_per_index = match self.format { wgt::IndexFormat::Uint16 => 2, wgt::IndexFormat::Uint32 => 4, }; - ((self.range.end - self.range.start) / bytes_per_index) as u32 + + (self.range.end - self.range.start) / bytes_per_index } /// Generate a `SetIndexBuffer` command to prepare for an indexed draw @@ -1069,12 +1097,12 @@ impl VertexState { /// A bind group that has been set at a particular index during render bundle encoding. #[derive(Debug)] -struct BindState { +struct BindState { /// The id of the bind group set at this index. - bind_group_id: id::BindGroupId, + bind_group: Arc>, /// The layout of `group`. - layout_id: id::Valid, + layout: Arc>, /// The range of dynamic offsets for this bind group, in the original /// command stream's `BassPass::dynamic_offsets` array. @@ -1088,22 +1116,19 @@ struct BindState { #[derive(Debug)] struct VertexLimitState { /// Length of the shortest vertex rate vertex buffer - vertex_limit: u32, + vertex_limit: u64, /// Buffer slot which the shortest vertex rate vertex buffer is bound to vertex_limit_slot: u32, /// Length of the shortest instance rate vertex buffer - instance_limit: u32, + instance_limit: u64, /// Buffer slot which the shortest instance rate vertex buffer is bound to instance_limit_slot: u32, } /// The bundle's current pipeline, and some cached information needed for validation. -struct PipelineState { - /// The pipeline's id. - id: id::RenderPipelineId, - - /// The id of the pipeline's layout. - layout_id: id::Valid, +struct PipelineState { + /// The pipeline + pipeline: Arc>, /// How this pipeline's vertex shader traverses each vertex buffer, indexed /// by vertex buffer slot number. @@ -1117,18 +1142,18 @@ struct PipelineState { used_bind_groups: usize, } -impl PipelineState { - fn new( - pipeline_id: id::RenderPipelineId, - pipeline: &pipeline::RenderPipeline, - layout: &binding_model::PipelineLayout, - ) -> Self { +impl PipelineState { + fn new(pipeline: &Arc>) -> Self { Self { - id: pipeline_id, - layout_id: pipeline.layout_id.value, + pipeline: pipeline.clone(), steps: pipeline.vertex_steps.to_vec(), - push_constant_ranges: layout.push_constant_ranges.iter().cloned().collect(), - used_bind_groups: layout.bind_group_layout_ids.len(), + push_constant_ranges: pipeline + .layout + .push_constant_ranges + .iter() + .cloned() + .collect(), + used_bind_groups: pipeline.layout.bind_group_layouts.len(), } } @@ -1170,10 +1195,10 @@ struct State { trackers: RenderBundleScope, /// The currently set pipeline, if any. - pipeline: Option, + pipeline: Option>, /// The bind group set at each index, if any. - bind: ArrayVec, { hal::MAX_BIND_GROUPS }>, + bind: ArrayVec>, { hal::MAX_BIND_GROUPS }>, /// The state of each vertex buffer slot. vertex: ArrayVec, { hal::MAX_VERTEX_BUFFERS }>, @@ -1192,16 +1217,16 @@ struct State { } impl State { - fn vertex_limits(&self, pipeline: &PipelineState) -> VertexLimitState { + fn vertex_limits(&self, pipeline: &PipelineState) -> VertexLimitState { let mut vert_state = VertexLimitState { - vertex_limit: u32::MAX, + vertex_limit: u32::MAX as u64, vertex_limit_slot: 0, - instance_limit: u32::MAX, + instance_limit: u32::MAX as u64, instance_limit_slot: 0, }; for (idx, (vbs, step)) in self.vertex.iter().zip(&pipeline.steps).enumerate() { if let Some(ref vbs) = *vbs { - let limit = ((vbs.range.end - vbs.range.start) / step.stride) as u32; + let limit = (vbs.range.end - vbs.range.start) / step.stride; match step.mode { wgt::VertexStepMode::Vertex => { if limit < vert_state.vertex_limit { @@ -1223,11 +1248,11 @@ impl State { /// Return the id of the current pipeline, if any. fn pipeline_id(&self) -> Option { - self.pipeline.as_ref().map(|p| p.id) + self.pipeline.as_ref().map(|p| p.pipeline.as_info().id()) } /// Return the current pipeline state. Return an error if none is set. - fn pipeline(&self, scope: PassErrorScope) -> Result<&PipelineState, RenderBundleError> { + fn pipeline(&self, scope: PassErrorScope) -> Result<&PipelineState, RenderBundleError> { self.pipeline .as_ref() .ok_or(DrawError::MissingPipeline) @@ -1244,8 +1269,8 @@ impl State { fn set_bind_group( &mut self, slot: u32, - bind_group_id: id::BindGroupId, - layout_id: id::Valid, + bind_group: &Arc>, + layout: &Arc>, dynamic_offsets: Range, ) { // If this call wouldn't actually change this index's state, we can @@ -1253,7 +1278,7 @@ impl State { // be different.) if dynamic_offsets.is_empty() { if let Some(ref contents) = self.bind[slot as usize] { - if contents.bind_group_id == bind_group_id { + if contents.bind_group.is_equal(bind_group) { return; } } @@ -1261,8 +1286,8 @@ impl State { // Record the index's new state. self.bind[slot as usize] = Some(BindState { - bind_group_id, - layout_id, + bind_group: bind_group.clone(), + layout: layout.clone(), dynamic_offsets, is_dirty: true, }); @@ -1285,18 +1310,14 @@ impl State { /// /// - Changing the push constant ranges at all requires re-establishing /// all bind groups. - fn invalidate_bind_groups( - &mut self, - new: &PipelineState, - layout: &binding_model::PipelineLayout, - ) { + fn invalidate_bind_groups(&mut self, new: &PipelineState, layout: &PipelineLayout) { match self.pipeline { None => { // Establishing entirely new pipeline state. self.invalidate_bind_group_from(0); } Some(ref old) => { - if old.id == new.id { + if old.pipeline.is_equal(&new.pipeline) { // Everything is derived from the pipeline, so if the id has // not changed, there's no need to consider anything else. return; @@ -1306,14 +1327,12 @@ impl State { if old.push_constant_ranges != new.push_constant_ranges { self.invalidate_bind_group_from(0); } else { - let first_changed = self - .bind - .iter() - .zip(&layout.bind_group_layout_ids) - .position(|(entry, &layout_id)| match *entry { - Some(ref contents) => contents.layout_id != layout_id, + let first_changed = self.bind.iter().zip(&layout.bind_group_layouts).position( + |(entry, layout)| match *entry { + Some(ref contents) => !contents.layout.is_equal(layout), None => false, - }); + }, + ); if let Some(slot) = first_changed { self.invalidate_bind_group_from(slot); } @@ -1387,8 +1406,8 @@ impl State { let offsets = &contents.dynamic_offsets; return Some(RenderCommand::SetBindGroup { index: i.try_into().unwrap(), - bind_group_id: contents.bind_group_id, - num_dynamic_offsets: (offsets.end - offsets.start) as u8, + bind_group_id: contents.bind_group.as_info().id(), + num_dynamic_offsets: offsets.end - offsets.start, }); } } @@ -1491,7 +1510,7 @@ pub mod bundle_ffi { bundle.base.commands.push(RenderCommand::SetBindGroup { index, - num_dynamic_offsets: offset_length.try_into().unwrap(), + num_dynamic_offsets: offset_length, bind_group_id, }); } diff --git a/wgpu-core/src/command/clear.rs b/wgpu-core/src/command/clear.rs index ceceb2ba58..1a4b4cdeb1 100644 --- a/wgpu-core/src/command/clear.rs +++ b/wgpu-core/src/command/clear.rs @@ -1,26 +1,24 @@ -use std::ops::Range; +use std::{ops::Range, sync::Arc}; #[cfg(feature = "trace")] use crate::device::trace::Command as TraceCommand; use crate::{ + api_log, command::CommandBuffer, + device::DeviceError, get_lowest_common_denom, global::Global, hal_api::HalApi, - hub::Token, - id::{BufferId, CommandEncoderId, DeviceId, TextureId, Valid}, + id::{BufferId, CommandEncoderId, DeviceId, TextureId}, identity::GlobalIdentityHandlerFactory, init_tracker::{MemoryInitKind, TextureInitRange}, - resource::{Texture, TextureClearMode}, - storage, + resource::{Resource, Texture, TextureClearMode}, track::{TextureSelector, TextureTracker}, }; use hal::CommandEncoder as _; use thiserror::Error; -use wgt::{ - math::align_to, BufferAddress, BufferSize, BufferUsages, ImageSubresourceRange, TextureAspect, -}; +use wgt::{math::align_to, BufferAddress, BufferUsages, ImageSubresourceRange, TextureAspect}; /// Error encountered while attempting a clear. #[derive(Clone, Debug, Error)] @@ -39,7 +37,7 @@ pub enum ClearError { #[error("Texture {0:?} can not be cleared")] NoValidTextureClearMode(TextureId), #[error("Buffer clear size {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")] - UnalignedFillSize(BufferSize), + UnalignedFillSize(BufferAddress), #[error("Buffer offset {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")] UnalignedBufferOffset(BufferAddress), #[error("Clear of {start_offset}..{end_offset} would end up overrunning the bounds of the buffer of size {buffer_size}")] @@ -69,6 +67,8 @@ whereas subesource range specified start {subresource_base_array_layer} and coun subresource_base_array_layer: u32, subresource_array_layer_count: Option, }, + #[error(transparent)] + Device(#[from] DeviceError), } impl Global { @@ -77,30 +77,38 @@ impl Global { command_encoder_id: CommandEncoderId, dst: BufferId, offset: BufferAddress, - size: Option, + size: Option, ) -> Result<(), ClearError> { - profiling::scope!("CommandEncoder::fill_buffer"); + profiling::scope!("CommandEncoder::clear_buffer"); + api_log!("CommandEncoder::clear_buffer {dst:?}"); let hub = A::hub(self); - let mut token = Token::root(); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id) + + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id) .map_err(|_| ClearError::InvalidCommandEncoder(command_encoder_id))?; - let (buffer_guard, _) = hub.buffers.read(&mut token); + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(TraceCommand::ClearBuffer { dst, offset, size }); } - let (dst_buffer, dst_pending) = cmd_buf - .trackers - .buffers - .set_single(&*buffer_guard, dst, hal::BufferUses::COPY_DST) - .ok_or(ClearError::InvalidBuffer(dst))?; + let (dst_buffer, dst_pending) = { + let buffer_guard = hub.buffers.read(); + let dst_buffer = buffer_guard + .get(dst) + .map_err(|_| ClearError::InvalidBuffer(dst))?; + cmd_buf_data + .trackers + .buffers + .set_single(dst_buffer, hal::BufferUses::COPY_DST) + .ok_or(ClearError::InvalidBuffer(dst))? + }; + let snatch_guard = dst_buffer.device.snatchable_lock.read(); let dst_raw = dst_buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(ClearError::InvalidBuffer(dst))?; if !dst_buffer.usage.contains(BufferUsages::COPY_DST) { return Err(ClearError::MissingCopyDstUsageFlag(Some(dst), None)); @@ -111,10 +119,10 @@ impl Global { return Err(ClearError::UnalignedBufferOffset(offset)); } if let Some(size) = size { - if size.get() % wgt::COPY_BUFFER_ALIGNMENT != 0 { + if size % wgt::COPY_BUFFER_ALIGNMENT != 0 { return Err(ClearError::UnalignedFillSize(size)); } - let destination_end_offset = offset + size.get(); + let destination_end_offset = offset + size; if destination_end_offset > dst_buffer.size { return Err(ClearError::BufferOverrun { start_offset: offset, @@ -125,7 +133,7 @@ impl Global { } let end = match size { - Some(size) => offset + size.get(), + Some(size) => offset + size, None => dst_buffer.size, }; if offset == end { @@ -134,16 +142,17 @@ impl Global { } // Mark dest as initialized. - cmd_buf - .buffer_memory_init_actions - .extend(dst_buffer.initialization_status.create_action( - dst, + cmd_buf_data.buffer_memory_init_actions.extend( + dst_buffer.initialization_status.read().create_action( + &dst_buffer, offset..end, MemoryInitKind::ImplicitlyInitialized, - )); + ), + ); + // actual hal barrier & operation - let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer)); - let cmd_buf_raw = cmd_buf.encoder.open(); + let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard)); + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_buffers(dst_barrier.into_iter()); cmd_buf_raw.clear_buffer(dst_raw, offset..end); @@ -158,18 +167,17 @@ impl Global { subresource_range: &ImageSubresourceRange, ) -> Result<(), ClearError> { profiling::scope!("CommandEncoder::clear_texture"); + api_log!("CommandEncoder::clear_texture {dst:?}"); let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.write(&mut token); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id) + + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id) .map_err(|_| ClearError::InvalidCommandEncoder(command_encoder_id))?; - let (_, mut token) = hub.buffers.read(&mut token); // skip token - let (texture_guard, _) = hub.textures.read(&mut token); + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(TraceCommand::ClearTexture { dst, subresource_range: *subresource_range, @@ -180,7 +188,8 @@ impl Global { return Err(ClearError::MissingClearTextureFeature); } - let dst_texture = texture_guard + let dst_texture = hub + .textures .get(dst) .map_err(|_| ClearError::InvalidTexture(dst))?; @@ -218,48 +227,52 @@ impl Global { }); } - let device = &device_guard[cmd_buf.device_id.value]; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(ClearError::InvalidDevice(cmd_buf.device.as_info().id())); + } + let (encoder, tracker) = cmd_buf_data.open_encoder_and_tracker()?; clear_texture( - &*texture_guard, - Valid(dst), + &dst_texture, TextureInitRange { mip_range: subresource_mip_range, layer_range: subresource_layer_range, }, - cmd_buf.encoder.open(), - &mut cmd_buf.trackers.textures, + encoder, + &mut tracker.textures, &device.alignments, - &device.zero_buffer, + device.zero_buffer.as_ref().unwrap(), ) } } pub(crate) fn clear_texture( - storage: &storage::Storage, TextureId>, - dst_texture_id: Valid, + dst_texture: &Arc>, range: TextureInitRange, encoder: &mut A::CommandEncoder, texture_tracker: &mut TextureTracker, alignments: &hal::Alignments, zero_buffer: &A::Buffer, ) -> Result<(), ClearError> { - let dst_texture = &storage[dst_texture_id]; - + let snatch_guard = dst_texture.device.snatchable_lock.read(); let dst_raw = dst_texture - .inner - .as_raw() - .ok_or(ClearError::InvalidTexture(dst_texture_id.0))?; + .raw(&snatch_guard) + .ok_or_else(|| ClearError::InvalidTexture(dst_texture.as_info().id()))?; // Issue the right barrier. - let clear_usage = match dst_texture.clear_mode { + let clear_usage = match *dst_texture.clear_mode.read() { TextureClearMode::BufferCopy => hal::TextureUses::COPY_DST, TextureClearMode::RenderPass { is_color: false, .. } => hal::TextureUses::DEPTH_STENCIL_WRITE, - TextureClearMode::RenderPass { is_color: true, .. } => hal::TextureUses::COLOR_TARGET, + TextureClearMode::Surface { .. } | TextureClearMode::RenderPass { is_color: true, .. } => { + hal::TextureUses::COLOR_TARGET + } TextureClearMode::None => { - return Err(ClearError::NoValidTextureClearMode(dst_texture_id.0)); + return Err(ClearError::NoValidTextureClearMode( + dst_texture.as_info().id(), + )); } }; @@ -282,15 +295,15 @@ pub(crate) fn clear_texture( // clear_texture api in order to remove this check and call the cheaper // change_replace_tracked whenever possible. let dst_barrier = texture_tracker - .set_single(dst_texture, dst_texture_id.0, selector, clear_usage) + .set_single(dst_texture, selector, clear_usage) .unwrap() - .map(|pending| pending.into_hal(dst_texture)); + .map(|pending| pending.into_hal(dst_raw)); unsafe { encoder.transition_textures(dst_barrier.into_iter()); } // Record actual clearing - match dst_texture.clear_mode { + match *dst_texture.clear_mode.read() { TextureClearMode::BufferCopy => clear_texture_via_buffer_copies::( &dst_texture.desc, alignments, @@ -299,17 +312,22 @@ pub(crate) fn clear_texture( encoder, dst_raw, ), + TextureClearMode::Surface { .. } => { + clear_texture_via_render_passes(dst_texture, range, true, encoder)? + } TextureClearMode::RenderPass { is_color, .. } => { clear_texture_via_render_passes(dst_texture, range, is_color, encoder)? } TextureClearMode::None => { - return Err(ClearError::NoValidTextureClearMode(dst_texture_id.0)); + return Err(ClearError::NoValidTextureClearMode( + dst_texture.as_info().id(), + )); } } Ok(()) } -fn clear_texture_via_buffer_copies( +fn clear_texture_via_buffer_copies( texture_desc: &wgt::TextureDescriptor<(), Vec>, alignments: &hal::Alignments, zero_buffer: &A::Buffer, // Buffer of size device::ZERO_BUFFER_SIZE @@ -317,16 +335,18 @@ fn clear_texture_via_buffer_copies( encoder: &mut A::CommandEncoder, dst_raw: &A::Texture, ) { - assert_eq!( - hal::FormatAspects::from(texture_desc.format), - hal::FormatAspects::COLOR - ); + assert!(!texture_desc.format.is_depth_stencil_format()); + + if texture_desc.format == wgt::TextureFormat::NV12 { + // TODO: Currently COPY_DST for NV12 textures is unsupported. + return; + } // Gather list of zero_buffer copies and issue a single command then to perform them let mut zero_buffer_copy_regions = Vec::new(); let buffer_copy_pitch = alignments.buffer_copy_pitch.get() as u32; let (block_width, block_height) = texture_desc.format.block_dimensions(); - let block_size = texture_desc.format.block_size(None).unwrap(); + let block_size = texture_desc.format.block_copy_size(None).unwrap(); let bytes_per_row_alignment = get_lowest_common_denom(buffer_copy_pitch, block_size); @@ -401,7 +421,7 @@ fn clear_texture_via_buffer_copies( } } -fn clear_texture_via_render_passes( +fn clear_texture_via_render_passes( dst_texture: &Texture, range: TextureInitRange, is_color: bool, @@ -414,6 +434,7 @@ fn clear_texture_via_render_passes( height: dst_texture.desc.size.height, depth_or_array_layers: 1, // Only one layer is cleared at a time. }; + let clear_mode = &dst_texture.clear_mode.read(); for mip_level in range.mip_range { let extent = extent_base.mip_level_size(mip_level, dst_texture.desc.dimension); @@ -422,7 +443,12 @@ fn clear_texture_via_render_passes( let (color_attachments, depth_stencil_attachment) = if is_color { color_attachments_tmp = [Some(hal::ColorAttachment { target: hal::Attachment { - view: dst_texture.get_clear_view(mip_level, depth_or_layer), + view: Texture::get_clear_view( + clear_mode, + &dst_texture.desc, + mip_level, + depth_or_layer, + ), usage: hal::TextureUses::COLOR_TARGET, }, resolve_target: None, @@ -435,7 +461,12 @@ fn clear_texture_via_render_passes( &[][..], Some(hal::DepthStencilAttachment { target: hal::Attachment { - view: dst_texture.get_clear_view(mip_level, depth_or_layer), + view: Texture::get_clear_view( + clear_mode, + &dst_texture.desc, + mip_level, + depth_or_layer, + ), usage: hal::TextureUses::DEPTH_STENCIL_WRITE, }, depth_ops: hal::AttachmentOps::STORE, diff --git a/wgpu-core/src/command/compute.rs b/wgpu-core/src/command/compute.rs index e20477362b..3b60cf6440 100644 --- a/wgpu-core/src/command/compute.rs +++ b/wgpu-core/src/command/compute.rs @@ -1,7 +1,9 @@ +use crate::device::DeviceError; +use crate::resource::Resource; +use crate::snatch::SnatchGuard; use crate::{ binding_model::{ - BindError, BindGroup, BindGroupLayouts, LateMinBufferBindingSizeMismatch, - PushConstantUploadError, + BindError, BindGroup, LateMinBufferBindingSizeMismatch, PushConstantUploadError, }, command::{ bind::Binder, @@ -14,12 +16,12 @@ use crate::{ error::{ErrorFormatter, PrettyError}, global::Global, hal_api::HalApi, - hub::Token, - id, + hal_label, id, + id::DeviceId, identity::GlobalIdentityHandlerFactory, init_tracker::MemoryInitKind, pipeline, - resource::{self, Buffer, Texture}, + resource::{self}, storage::Storage, track::{Tracker, UsageConflict, UsageScope}, validation::{check_buffer_usage, MissingBufferUsageError}, @@ -49,7 +51,7 @@ use std::{fmt, mem, str}; pub enum ComputeCommand { SetBindGroup { index: u32, - num_dynamic_offsets: u8, + num_dynamic_offsets: usize, bind_group_id: id::BindGroupId, }, SetPipeline(id::ComputePipelineId), @@ -172,12 +174,8 @@ pub struct ComputePassDescriptor<'a> { pub enum DispatchError { #[error("Compute pipeline must be set")] MissingPipeline, - #[error("The pipeline layout, associated with the current compute pipeline, contains a bind group layout at index {index} which is incompatible with the bind group layout associated with the bind group at {index}")] - IncompatibleBindGroup { - index: u32, - //expected: BindGroupLayoutId, - //provided: Option<(BindGroupLayoutId, BindGroupId)>, - }, + #[error("Incompatible bind group at index {index} in the current compute pipeline")] + IncompatibleBindGroup { index: u32, diff: Vec }, #[error( "Each current dispatch group size dimension ({current:?}) must be less or equal to {limit}" )] @@ -189,10 +187,14 @@ pub enum DispatchError { /// Error encountered when performing a compute pass. #[derive(Clone, Debug, Error)] pub enum ComputePassErrorInner { + #[error(transparent)] + Device(#[from] DeviceError), #[error(transparent)] Encoder(#[from] CommandEncoderError), - #[error("Bind group {0:?} is invalid")] - InvalidBindGroup(id::BindGroupId), + #[error("Bind group at index {0:?} is invalid")] + InvalidBindGroup(usize), + #[error("Device {0:?} is invalid")] + InvalidDevice(DeviceId), #[error("Bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")] BindGroupIndexOutOfRange { index: u32, max: u32 }, #[error("Compute pipeline {0:?} is invalid")] @@ -233,15 +235,17 @@ impl PrettyError for ComputePassErrorInner { fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { fmt.error(self); match *self { - Self::InvalidBindGroup(id) => { - fmt.bind_group_label(&id); - } Self::InvalidPipeline(id) => { fmt.compute_pipeline_label(&id); } Self::InvalidIndirectBuffer(id) => { fmt.buffer_label(&id); } + Self::Dispatch(DispatchError::IncompatibleBindGroup { ref diff, .. }) => { + for d in diff { + fmt.note(&d); + } + } _ => {} }; } @@ -277,19 +281,22 @@ where } struct State { - binder: Binder, + binder: Binder, pipeline: Option, scope: UsageScope, debug_scope_depth: u32, } impl State { - fn is_ready(&self, bind_group_layouts: &BindGroupLayouts) -> Result<(), DispatchError> { - let bind_mask = self.binder.invalid_mask(bind_group_layouts); + fn is_ready(&self) -> Result<(), DispatchError> { + let bind_mask = self.binder.invalid_mask(); if bind_mask != 0 { //let (expected, provided) = self.binder.entries[index as usize].info(); + let index = bind_mask.trailing_zeros(); + return Err(DispatchError::IncompatibleBindGroup { - index: bind_mask.trailing_zeros(), + index, + diff: self.binder.bgl_diff(), }); } if self.pipeline.is_none() { @@ -307,15 +314,11 @@ impl State { raw_encoder: &mut A::CommandEncoder, base_trackers: &mut Tracker, bind_group_guard: &Storage, id::BindGroupId>, - buffer_guard: &Storage, id::BufferId>, - texture_guard: &Storage, id::TextureId>, - indirect_buffer: Option>, + indirect_buffer: Option, + snatch_guard: &SnatchGuard, ) -> Result<(), UsageConflict> { for id in self.binder.list_active() { - unsafe { - self.scope - .merge_bind_group(texture_guard, &bind_group_guard[id].used)? - }; + unsafe { self.scope.merge_bind_group(&bind_group_guard[id].used)? }; // Note: stateless trackers are not merged: the lifetime reference // is held to the bind group itself. } @@ -323,7 +326,6 @@ impl State { for id in self.binder.list_active() { unsafe { base_trackers.set_and_remove_from_usage_scope_sparse( - texture_guard, &mut self.scope, &bind_group_guard[id].used, ) @@ -339,7 +341,7 @@ impl State { log::trace!("Encoding dispatch barriers"); - CommandBuffer::drain_barriers(raw_encoder, base_trackers, buffer_guard, texture_guard); + CommandBuffer::drain_barriers(raw_encoder, base_trackers, snatch_guard); Ok(()) } } @@ -367,47 +369,50 @@ impl Global { timestamp_writes: Option<&ComputePassTimestampWrites>, ) -> Result<(), ComputePassError> { profiling::scope!("CommandEncoder::run_compute_pass"); - let init_scope = PassErrorScope::Pass(encoder_id); + let pass_scope = PassErrorScope::Pass(encoder_id); let hub = A::hub(self); - let mut token = Token::root(); - - let (device_guard, mut token) = hub.devices.read(&mut token); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - // Spell out the type, to placate rust-analyzer. - // https://github.com/rust-lang/rust-analyzer/issues/12247 - let cmd_buf: &mut CommandBuffer = - CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, encoder_id) - .map_pass_err(init_scope)?; - - // We automatically keep extending command buffers over time, and because - // we want to insert a command buffer _before_ what we're about to record, - // we need to make sure to close the previous one. - cmd_buf.encoder.close(); - // We will reset this to `Recording` if we succeed, acts as a fail-safe. - cmd_buf.status = CommandEncoderStatus::Error; - let raw = cmd_buf.encoder.open(); + let cmd_buf = CommandBuffer::get_encoder(hub, encoder_id).map_pass_err(pass_scope)?; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(ComputePassErrorInner::InvalidDevice( + cmd_buf.device.as_info().id(), + )) + .map_pass_err(pass_scope); + } - let device = &device_guard[cmd_buf.device_id.value]; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(crate::device::trace::Command::RunComputePass { base: BasePass::from_ref(base), timestamp_writes: timestamp_writes.cloned(), }); } - let (_, mut token) = hub.render_bundles.read(&mut token); - let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(&mut token); - let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token); - let (pipeline_guard, mut token) = hub.compute_pipelines.read(&mut token); - let (query_set_guard, mut token) = hub.query_sets.read(&mut token); - let (bind_group_layout_guard, mut token) = hub.bind_group_layouts.read(&mut token); - let (buffer_guard, mut token) = hub.buffers.read(&mut token); - let (texture_guard, mut token) = hub.textures.read(&mut token); - let (tlas_guard, _) = hub.tlas_s.read(&mut token); + let encoder = &mut cmd_buf_data.encoder; + let status = &mut cmd_buf_data.status; + let tracker = &mut cmd_buf_data.trackers; + let buffer_memory_init_actions = &mut cmd_buf_data.buffer_memory_init_actions; + let texture_memory_actions = &mut cmd_buf_data.texture_memory_actions; + + // We automatically keep extending command buffers over time, and because + // we want to insert a command buffer _before_ what we're about to record, + // we need to make sure to close the previous one. + encoder.close().map_pass_err(pass_scope)?; + // will be reset to true if recording is done without errors + *status = CommandEncoderStatus::Error; + let raw = encoder.open().map_pass_err(pass_scope)?; + + let bind_group_guard = hub.bind_groups.read(); + let pipeline_guard = hub.compute_pipelines.read(); + let query_set_guard = hub.query_sets.read(); + let buffer_guard = hub.buffers.read(); + let texture_guard = hub.textures.read(); + let tlas_guard = hub.tlas_s.read(); let mut state = State { binder: Binder::new(), @@ -421,12 +426,11 @@ impl Global { let mut active_query = None; let timestamp_writes = if let Some(tw) = timestamp_writes { - let query_set: &resource::QuerySet = cmd_buf - .trackers + let query_set: &resource::QuerySet = tracker .query_sets .add_single(&*query_set_guard, tw.query_set) .ok_or(ComputePassErrorInner::InvalidQuerySet(tw.query_set)) - .map_pass_err(init_scope)?; + .map_pass_err(pass_scope)?; // Unlike in render passes we can't delay resetting the query sets since // there is no auxillary pass. @@ -443,12 +447,12 @@ impl Global { // But no point in erroring over that nuance here! if let Some(range) = range { unsafe { - raw.reset_queries(&query_set.raw, range); + raw.reset_queries(query_set.raw.as_ref().unwrap(), range); } } Some(hal::ComputePassTimestampWrites { - query_set: &query_set.raw, + query_set: query_set.raw.as_ref().unwrap(), beginning_of_pass_write_index: tw.beginning_of_pass_write_index, end_of_pass_write_index: tw.end_of_pass_write_index, }) @@ -456,7 +460,9 @@ impl Global { None }; - cmd_buf.trackers.set_size( + let snatch_guard = device.snatchable_lock.read(); + + tracker.set_size( Some(&*buffer_guard), Some(&*texture_guard), None, @@ -470,8 +476,12 @@ impl Global { Some(&*tlas_guard), ); + let discard_hal_labels = self + .instance + .flags + .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS); let hal_desc = hal::ComputePassDescriptor { - label: base.label, + label: hal_label(base.label, self.instance.flags), timestamp_writes, }; @@ -505,67 +515,70 @@ impl Global { temp_offsets.clear(); temp_offsets.extend_from_slice( - &base.dynamic_offsets[dynamic_offset_count - ..dynamic_offset_count + (num_dynamic_offsets as usize)], + &base.dynamic_offsets + [dynamic_offset_count..dynamic_offset_count + num_dynamic_offsets], ); - dynamic_offset_count += num_dynamic_offsets as usize; + dynamic_offset_count += num_dynamic_offsets; - let bind_group: &BindGroup = cmd_buf - .trackers + let bind_group = tracker .bind_groups .add_single(&*bind_group_guard, bind_group_id) - .ok_or(ComputePassErrorInner::InvalidBindGroup(bind_group_id)) + .ok_or(ComputePassErrorInner::InvalidBindGroup(index as usize)) .map_pass_err(scope)?; bind_group .validate_dynamic_bindings(index, &temp_offsets, &cmd_buf.limits) .map_pass_err(scope)?; - cmd_buf.buffer_memory_init_actions.extend( - bind_group.used_buffer_ranges.iter().filter_map( - |action| match buffer_guard.get(action.id) { - Ok(buffer) => buffer.initialization_status.check_action(action), - Err(_) => None, - }, - ), + buffer_memory_init_actions.extend( + bind_group.used_buffer_ranges.iter().filter_map(|action| { + action + .buffer + .initialization_status + .read() + .check_action(action) + }), ); for action in bind_group.used_texture_ranges.iter() { - pending_discard_init_fixups.extend( - cmd_buf - .texture_memory_actions - .register_init_action(action, &texture_guard), - ); + pending_discard_init_fixups + .extend(texture_memory_actions.register_init_action(action)); } - cmd_buf.tlas_actions.extend( - bind_group.used.acceleration_structures.used().map(|id| { - cmd_buf.trackers.tlas_s.add_single(&tlas_guard, id.0); + let used_resource = bind_group + .used + .acceleration_structures + .used_resources() + .map(|tlas| { + tracker.tlas_s.add_single(&tlas_guard, tlas.as_info().id()); crate::ray_tracing::TlasAction { - id: id.0, + id: tlas.as_info().id(), kind: crate::ray_tracing::TlasActionKind::Use, } - }), - ); + }); - let pipeline_layout_id = state.binder.pipeline_layout_id; - let entries = state.binder.assign_group( - index as usize, - id::Valid(bind_group_id), - bind_group, - &temp_offsets, - ); - if !entries.is_empty() { - let pipeline_layout = - &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw; + cmd_buf_data.tlas_actions.extend(used_resource); + + let pipeline_layout = state.binder.pipeline_layout.clone(); + let entries = + state + .binder + .assign_group(index as usize, bind_group, &temp_offsets); + if !entries.is_empty() && pipeline_layout.is_some() { + let pipeline_layout = pipeline_layout.as_ref().unwrap().raw(); for (i, e) in entries.iter().enumerate() { - let raw_bg = &bind_group_guard[e.group_id.as_ref().unwrap().value].raw; - unsafe { - raw.set_bind_group( - pipeline_layout, - index + i as u32, - raw_bg, - &e.dynamic_offsets, - ); + if let Some(group) = e.group.as_ref() { + let raw_bg = group + .raw(&snatch_guard) + .ok_or(ComputePassErrorInner::InvalidBindGroup(i)) + .map_pass_err(scope)?; + unsafe { + raw.set_bind_group( + pipeline_layout, + index + i as u32, + raw_bg, + &e.dynamic_offsets, + ); + } } } } @@ -575,44 +588,51 @@ impl Global { state.pipeline = Some(pipeline_id); - let pipeline: &pipeline::ComputePipeline = cmd_buf - .trackers + let pipeline: &pipeline::ComputePipeline = tracker .compute_pipelines .add_single(&*pipeline_guard, pipeline_id) .ok_or(ComputePassErrorInner::InvalidPipeline(pipeline_id)) .map_pass_err(scope)?; unsafe { - raw.set_compute_pipeline(&pipeline.raw); + raw.set_compute_pipeline(pipeline.raw()); } // Rebind resources - if state.binder.pipeline_layout_id != Some(pipeline.layout_id.value) { - let pipeline_layout = &pipeline_layout_guard[pipeline.layout_id.value]; - + if state.binder.pipeline_layout.is_none() + || !state + .binder + .pipeline_layout + .as_ref() + .unwrap() + .is_equal(&pipeline.layout) + { let (start_index, entries) = state.binder.change_pipeline_layout( - &*pipeline_layout_guard, - pipeline.layout_id.value, + &pipeline.layout, &pipeline.late_sized_buffer_groups, ); if !entries.is_empty() { for (i, e) in entries.iter().enumerate() { - let raw_bg = - &bind_group_guard[e.group_id.as_ref().unwrap().value].raw; - unsafe { - raw.set_bind_group( - &pipeline_layout.raw, - start_index as u32 + i as u32, - raw_bg, - &e.dynamic_offsets, - ); + if let Some(group) = e.group.as_ref() { + let raw_bg = group + .raw(&snatch_guard) + .ok_or(ComputePassErrorInner::InvalidBindGroup(i)) + .map_pass_err(scope)?; + unsafe { + raw.set_bind_group( + pipeline.layout.raw(), + start_index as u32 + i as u32, + raw_bg, + &e.dynamic_offsets, + ); + } } } } // Clear push constant ranges let non_overlapping = super::bind::compute_nonoverlapping_ranges( - &pipeline_layout.push_constant_ranges, + &pipeline.layout.push_constant_ranges, ); for range in non_overlapping { let offset = range.range.start; @@ -622,7 +642,7 @@ impl Global { size_bytes, |clear_offset, clear_data| unsafe { raw.set_push_constants( - &pipeline_layout.raw, + pipeline.layout.raw(), wgt::ShaderStages::COMPUTE, clear_offset, clear_data, @@ -645,15 +665,15 @@ impl Global { let data_slice = &base.push_constant_data[(values_offset as usize)..values_end_offset]; - let pipeline_layout_id = state + let pipeline_layout = state .binder - .pipeline_layout_id + .pipeline_layout + .as_ref() //TODO: don't error here, lazily update the push constants .ok_or(ComputePassErrorInner::Dispatch( DispatchError::MissingPipeline, )) .map_pass_err(scope)?; - let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id]; pipeline_layout .validate_push_constant_ranges( @@ -665,7 +685,7 @@ impl Global { unsafe { raw.set_push_constants( - &pipeline_layout.raw, + pipeline_layout.raw(), wgt::ShaderStages::COMPUTE, offset, data_slice, @@ -677,18 +697,15 @@ impl Global { indirect: false, pipeline: state.pipeline, }; + state.is_ready().map_pass_err(scope)?; - state - .is_ready(&*bind_group_layout_guard) - .map_pass_err(scope)?; state .flush_states( raw, &mut intermediate_trackers, &*bind_group_guard, - &*buffer_guard, - &*texture_guard, None, + &snatch_guard, ) .map_pass_err(scope)?; @@ -717,15 +734,13 @@ impl Global { pipeline: state.pipeline, }; - state - .is_ready(&*bind_group_layout_guard) - .map_pass_err(scope)?; + state.is_ready().map_pass_err(scope)?; device .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION) .map_pass_err(scope)?; - let indirect_buffer: &Buffer = state + let indirect_buffer = state .scope .buffers .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) @@ -745,15 +760,15 @@ impl Global { let buf_raw = indirect_buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(ComputePassErrorInner::InvalidIndirectBuffer(buffer_id)) .map_pass_err(scope)?; let stride = 3 * 4; // 3 integers, x/y/z group size - cmd_buf.buffer_memory_init_actions.extend( - indirect_buffer.initialization_status.create_action( - buffer_id, + buffer_memory_init_actions.extend( + indirect_buffer.initialization_status.read().create_action( + indirect_buffer, offset..(offset + stride), MemoryInitKind::NeedsInitializedMemory, ), @@ -764,9 +779,8 @@ impl Global { raw, &mut intermediate_trackers, &*bind_group_guard, - &*buffer_guard, - &*texture_guard, - Some(id::Valid(buffer_id)), + Some(buffer_id), + &snatch_guard, ) .map_pass_err(scope)?; unsafe { @@ -775,13 +789,15 @@ impl Global { } ComputeCommand::PushDebugGroup { color: _, len } => { state.debug_scope_depth += 1; - let label = - str::from_utf8(&base.string_data[string_offset..string_offset + len]) - .unwrap(); - string_offset += len; - unsafe { - raw.begin_debug_marker(label); + if !discard_hal_labels { + let label = + str::from_utf8(&base.string_data[string_offset..string_offset + len]) + .unwrap(); + unsafe { + raw.begin_debug_marker(label); + } } + string_offset += len; } ComputeCommand::PopDebugGroup => { let scope = PassErrorScope::PopDebugGroup; @@ -791,16 +807,20 @@ impl Global { .map_pass_err(scope); } state.debug_scope_depth -= 1; - unsafe { - raw.end_debug_marker(); + if !discard_hal_labels { + unsafe { + raw.end_debug_marker(); + } } } ComputeCommand::InsertDebugMarker { color: _, len } => { - let label = - str::from_utf8(&base.string_data[string_offset..string_offset + len]) - .unwrap(); + if !discard_hal_labels { + let label = + str::from_utf8(&base.string_data[string_offset..string_offset + len]) + .unwrap(); + unsafe { raw.insert_debug_marker(label) } + } string_offset += len; - unsafe { raw.insert_debug_marker(label) } } ComputeCommand::WriteTimestamp { query_set_id, @@ -812,8 +832,7 @@ impl Global { .require_features(wgt::Features::TIMESTAMP_QUERY_INSIDE_PASSES) .map_pass_err(scope)?; - let query_set: &resource::QuerySet = cmd_buf - .trackers + let query_set: &resource::QuerySet = tracker .query_sets .add_single(&*query_set_guard, query_set_id) .ok_or(ComputePassErrorInner::InvalidQuerySet(query_set_id)) @@ -829,8 +848,7 @@ impl Global { } => { let scope = PassErrorScope::BeginPipelineStatisticsQuery; - let query_set: &resource::QuerySet = cmd_buf - .trackers + let query_set: &resource::QuerySet = tracker .query_sets .add_single(&*query_set_guard, query_set_id) .ok_or(ComputePassErrorInner::InvalidQuerySet(query_set_id)) @@ -858,33 +876,32 @@ impl Global { unsafe { raw.end_compute_pass(); } + // We've successfully recorded the compute pass, bring the // command buffer out of the error state. - cmd_buf.status = CommandEncoderStatus::Recording; + *status = CommandEncoderStatus::Recording; // Stop the current command buffer. - cmd_buf.encoder.close(); + encoder.close().map_pass_err(pass_scope)?; // Create a new command buffer, which we will insert _before_ the body of the compute pass. // // Use that buffer to insert barriers and clear discarded images. - let transit = cmd_buf.encoder.open(); + let transit = encoder.open().map_pass_err(pass_scope)?; fixup_discarded_surfaces( pending_discard_init_fixups.into_iter(), transit, - &texture_guard, - &mut cmd_buf.trackers.textures, + &mut tracker.textures, device, ); CommandBuffer::insert_barriers_from_tracker( transit, - &mut cmd_buf.trackers, + tracker, &intermediate_trackers, - &*buffer_guard, - &*texture_guard, + &snatch_guard, ); // Close the command buffer, and swap it with the previous. - cmd_buf.encoder.close_and_swap(); + encoder.close_and_swap().map_pass_err(pass_scope)?; Ok(()) } @@ -924,7 +941,7 @@ pub mod compute_ffi { pass.base.commands.push(ComputeCommand::SetBindGroup { index, - num_dynamic_offsets: offset_length.try_into().unwrap(), + num_dynamic_offsets: offset_length, bind_group_id, }); } diff --git a/wgpu-core/src/command/draw.rs b/wgpu-core/src/command/draw.rs index 50ca9516b4..6dc8b4fbb9 100644 --- a/wgpu-core/src/command/draw.rs +++ b/wgpu-core/src/command/draw.rs @@ -25,26 +25,22 @@ pub enum DrawError { MissingVertexBuffer { index: u32 }, #[error("Index buffer must be set")] MissingIndexBuffer, - #[error("The pipeline layout, associated with the current render pipeline, contains a bind group layout at index {index} which is incompatible with the bind group layout associated with the bind group at {index}")] - IncompatibleBindGroup { - index: u32, - //expected: BindGroupLayoutId, - //provided: Option<(BindGroupLayoutId, BindGroupId)>, - }, + #[error("Incompatible bind group at index {index} in the current render pipeline")] + IncompatibleBindGroup { index: u32, diff: Vec }, #[error("Vertex {last_vertex} extends beyond limit {vertex_limit} imposed by the buffer in slot {slot}. Did you bind the correct `Vertex` step-rate vertex buffer?")] VertexBeyondLimit { - last_vertex: u32, - vertex_limit: u32, + last_vertex: u64, + vertex_limit: u64, slot: u32, }, #[error("Instance {last_instance} extends beyond limit {instance_limit} imposed by the buffer in slot {slot}. Did you bind the correct `Instance` step-rate vertex buffer?")] InstanceBeyondLimit { - last_instance: u32, - instance_limit: u32, + last_instance: u64, + instance_limit: u64, slot: u32, }, #[error("Index {last_index} extends beyond limit {index_limit}. Did you bind the correct index buffer?")] - IndexBeyondLimit { last_index: u32, index_limit: u32 }, + IndexBeyondLimit { last_index: u64, index_limit: u64 }, #[error( "Pipeline index format ({pipeline:?}) and buffer index format ({buffer:?}) do not match" )] @@ -67,6 +63,8 @@ pub enum RenderCommandError { InvalidRenderBundle(id::RenderBundleId), #[error("Bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")] BindGroupIndexOutOfRange { index: u32, max: u32 }, + #[error("Vertex buffer index {index} is greater than the device's requested `max_vertex_buffers` limit {max}")] + VertexBufferIndexOutOfRange { index: u32, max: u32 }, #[error("Dynamic buffer offset {0} does not respect device's requested `{1}` limit {2}")] UnalignedBufferOffset(u64, &'static str, u32), #[error("Number of buffer offsets ({actual}) does not match the number of dynamic bindings ({expected})")] @@ -149,7 +147,7 @@ pub struct Rect { pub enum RenderCommand { SetBindGroup { index: u32, - num_dynamic_offsets: u8, + num_dynamic_offsets: usize, bind_group_id: id::BindGroupId, }, SetPipeline(id::RenderPipelineId), diff --git a/wgpu-core/src/command/memory_init.rs b/wgpu-core/src/command/memory_init.rs index e8d5f73139..3bfc71f4f7 100644 --- a/wgpu-core/src/command/memory_init.rs +++ b/wgpu-core/src/command/memory_init.rs @@ -1,48 +1,56 @@ -use std::{collections::hash_map::Entry, ops::Range, vec::Drain}; +use std::{collections::hash_map::Entry, ops::Range, sync::Arc, vec::Drain}; use hal::CommandEncoder; use crate::{ device::Device, hal_api::HalApi, - id::{self, TextureId}, init_tracker::*, - resource::{Buffer, Texture}, - storage::Storage, + resource::{Resource, Texture}, track::{TextureTracker, Tracker}, FastHashMap, }; -use super::{clear::clear_texture, BakedCommands, DestroyedBufferError, DestroyedTextureError}; +use super::{ + clear::clear_texture, BakedCommands, ClearError, DestroyedBufferError, DestroyedTextureError, +}; /// Surface that was discarded by `StoreOp::Discard` of a preceding renderpass. /// Any read access to this surface needs to be preceded by a texture initialization. #[derive(Clone)] -pub(crate) struct TextureSurfaceDiscard { - pub texture: TextureId, +pub(crate) struct TextureSurfaceDiscard { + pub texture: Arc>, pub mip_level: u32, pub layer: u32, } -pub(crate) type SurfacesInDiscardState = Vec; +pub(crate) type SurfacesInDiscardState = Vec>; -#[derive(Default)] -pub(crate) struct CommandBufferTextureMemoryActions { +pub(crate) struct CommandBufferTextureMemoryActions { /// The tracker actions that we need to be executed before the command /// buffer is executed. - init_actions: Vec, + init_actions: Vec>, /// All the discards that haven't been followed by init again within the /// command buffer i.e. everything in this list resets the texture init /// state *after* the command buffer execution - discards: Vec, + discards: Vec>, +} + +impl Default for CommandBufferTextureMemoryActions { + fn default() -> Self { + Self { + init_actions: Default::default(), + discards: Default::default(), + } + } } -impl CommandBufferTextureMemoryActions { - pub(crate) fn drain_init_actions(&mut self) -> Drain { +impl CommandBufferTextureMemoryActions { + pub(crate) fn drain_init_actions(&mut self) -> Drain> { self.init_actions.drain(..) } - pub(crate) fn discard(&mut self, discard: TextureSurfaceDiscard) { + pub(crate) fn discard(&mut self, discard: TextureSurfaceDiscard) { self.discards.push(discard); } @@ -50,11 +58,10 @@ impl CommandBufferTextureMemoryActions { // Returns previously discarded surface that need to be initialized *immediately* now. // Only returns a non-empty list if action is MemoryInitKind::NeedsInitializedMemory. #[must_use] - pub(crate) fn register_init_action( + pub(crate) fn register_init_action( &mut self, - action: &TextureInitTrackerAction, - texture_guard: &Storage, TextureId>, - ) -> SurfacesInDiscardState { + action: &TextureInitTrackerAction, + ) -> SurfacesInDiscardState { let mut immediately_necessary_clears = SurfacesInDiscardState::new(); // Note that within a command buffer we may stack arbitrary memory init @@ -64,18 +71,20 @@ impl CommandBufferTextureMemoryActions { // We don't need to add MemoryInitKind::NeedsInitializedMemory to // init_actions if a surface is part of the discard list. But that would // mean splitting up the action which is more than we'd win here. - self.init_actions - .extend(match texture_guard.get(action.id) { - Ok(texture) => texture.initialization_status.check_action(action), - Err(_) => return immediately_necessary_clears, // texture no longer exists - }); + self.init_actions.extend( + action + .texture + .initialization_status + .read() + .check_action(action), + ); // We expect very few discarded surfaces at any point in time which is // why a simple linear search is likely best. (i.e. most of the time // self.discards is empty!) let init_actions = &mut self.init_actions; self.discards.retain(|discarded_surface| { - if discarded_surface.texture == action.id + if discarded_surface.texture.as_info().id() == action.texture.as_info().id() && action.range.layer_range.contains(&discarded_surface.layer) && action .range @@ -89,7 +98,7 @@ impl CommandBufferTextureMemoryActions { // because it might have been uninitialized prior to // discarding init_actions.push(TextureInitTrackerAction { - id: discarded_surface.texture, + texture: discarded_surface.texture.clone(), range: TextureInitRange { mip_range: discarded_surface.mip_level ..(discarded_surface.mip_level + 1), @@ -109,20 +118,16 @@ impl CommandBufferTextureMemoryActions { // Shortcut for register_init_action when it is known that the action is an // implicit init, not requiring any immediate resource init. - pub(crate) fn register_implicit_init( + pub(crate) fn register_implicit_init( &mut self, - id: id::Valid, + texture: &Arc>, range: TextureInitRange, - texture_guard: &Storage, TextureId>, ) { - let must_be_empty = self.register_init_action( - &TextureInitTrackerAction { - id: id.0, - range, - kind: MemoryInitKind::ImplicitlyInitialized, - }, - texture_guard, - ); + let must_be_empty = self.register_init_action(&TextureInitTrackerAction { + texture: texture.clone(), + range, + kind: MemoryInitKind::ImplicitlyInitialized, + }); assert!(must_be_empty.is_empty()); } } @@ -133,18 +138,16 @@ impl CommandBufferTextureMemoryActions { // Takes care of barriers as well! pub(crate) fn fixup_discarded_surfaces< A: HalApi, - InitIter: Iterator, + InitIter: Iterator>, >( inits: InitIter, encoder: &mut A::CommandEncoder, - texture_guard: &Storage, TextureId>, texture_tracker: &mut TextureTracker, device: &Device, ) { for init in inits { clear_texture( - texture_guard, - id::Valid(init.texture), + &init.texture, TextureInitRange { mip_range: init.mip_level..(init.mip_level + 1), layer_range: init.layer..(init.layer + 1), @@ -152,7 +155,7 @@ pub(crate) fn fixup_discarded_surfaces< encoder, texture_tracker, &device.alignments, - &device.zero_buffer, + device.zero_buffer.as_ref().unwrap(), ) .unwrap(); } @@ -164,16 +167,13 @@ impl BakedCommands { pub(crate) fn initialize_buffer_memory( &mut self, device_tracker: &mut Tracker, - buffer_guard: &mut Storage, id::BufferId>, ) -> Result<(), DestroyedBufferError> { // Gather init ranges for each buffer so we can collapse them. // It is not possible to do this at an earlier point since previously // executed command buffer change the resource init state. let mut uninitialized_ranges_per_buffer = FastHashMap::default(); for buffer_use in self.buffer_memory_init_actions.drain(..) { - let buffer = buffer_guard - .get_mut(buffer_use.id) - .map_err(|_| DestroyedBufferError(buffer_use.id))?; + let mut initialization_status = buffer_use.buffer.initialization_status.write(); // align the end to 4 let end_remainder = buffer_use.range.end % wgt::COPY_BUFFER_ALIGNMENT; @@ -182,28 +182,27 @@ impl BakedCommands { } else { buffer_use.range.end + wgt::COPY_BUFFER_ALIGNMENT - end_remainder }; - let uninitialized_ranges = buffer - .initialization_status - .drain(buffer_use.range.start..end); + let uninitialized_ranges = initialization_status.drain(buffer_use.range.start..end); match buffer_use.kind { MemoryInitKind::ImplicitlyInitialized => {} MemoryInitKind::NeedsInitializedMemory => { - match uninitialized_ranges_per_buffer.entry(buffer_use.id) { + match uninitialized_ranges_per_buffer.entry(buffer_use.buffer.as_info().id()) { Entry::Vacant(e) => { - e.insert( + e.insert(( + buffer_use.buffer.clone(), uninitialized_ranges.collect::>>(), - ); + )); } Entry::Occupied(mut e) => { - e.get_mut().extend(uninitialized_ranges); + e.get_mut().1.extend(uninitialized_ranges); } } } } } - for (buffer_id, mut ranges) in uninitialized_ranges_per_buffer { + for (buffer_id, (buffer, mut ranges)) in uninitialized_ranges_per_buffer { // Collapse touching ranges. ranges.sort_by_key(|r| r.start); for i in (1..ranges.len()).rev() { @@ -222,19 +221,20 @@ impl BakedCommands { // must already know about it. let transition = device_tracker .buffers - .set_single(buffer_guard, buffer_id, hal::BufferUses::COPY_DST) + .set_single(&buffer, hal::BufferUses::COPY_DST) .unwrap() .1; - let buffer = buffer_guard - .get_mut(buffer_id) - .map_err(|_| DestroyedBufferError(buffer_id))?; - let raw_buf = buffer.raw.as_ref().ok_or(DestroyedBufferError(buffer_id))?; + let snatch_guard = buffer.device.snatchable_lock.read(); + let raw_buf = buffer + .raw + .get(&snatch_guard) + .ok_or(DestroyedBufferError(buffer_id))?; unsafe { self.encoder.transition_buffers( transition - .map(|pending| pending.into_hal(buffer)) + .map(|pending| pending.into_hal(&buffer, &snatch_guard)) .into_iter(), ); } @@ -270,18 +270,13 @@ impl BakedCommands { pub(crate) fn initialize_texture_memory( &mut self, device_tracker: &mut Tracker, - texture_guard: &mut Storage, TextureId>, device: &Device, ) -> Result<(), DestroyedTextureError> { let mut ranges: Vec = Vec::new(); for texture_use in self.texture_memory_actions.drain_init_actions() { - let texture = texture_guard - .get_mut(texture_use.id) - .map_err(|_| DestroyedTextureError(texture_use.id))?; - + let mut initialization_status = texture_use.texture.initialization_status.write(); let use_range = texture_use.range; - let affected_mip_trackers = texture - .initialization_status + let affected_mip_trackers = initialization_status .mips .iter_mut() .enumerate() @@ -308,16 +303,26 @@ impl BakedCommands { // TODO: Could we attempt some range collapsing here? for range in ranges.drain(..) { - clear_texture( - texture_guard, - id::Valid(texture_use.id), + let clear_result = clear_texture( + &texture_use.texture, range, &mut self.encoder, &mut device_tracker.textures, &device.alignments, - &device.zero_buffer, - ) - .unwrap(); + device.zero_buffer.as_ref().unwrap(), + ); + + // A Texture can be destroyed between the command recording + // and now, this is out of our control so we have to handle + // it gracefully. + if let Err(ClearError::InvalidTexture(id)) = clear_result { + return Err(DestroyedTextureError(id)); + } + + // Other errors are unexpected. + if let Err(error) = clear_result { + panic!("{error}"); + } } } @@ -325,11 +330,10 @@ impl BakedCommands { // cmdbuf start, we discard init states for textures it left discarded // after its execution. for surface_discard in self.texture_memory_actions.discards.iter() { - let texture = texture_guard - .get_mut(surface_discard.texture) - .map_err(|_| DestroyedTextureError(surface_discard.texture))?; - texture + surface_discard + .texture .initialization_status + .write() .discard(surface_discard.mip_level, surface_discard.layer); } diff --git a/wgpu-core/src/command/mod.rs b/wgpu-core/src/command/mod.rs index 96003c13c6..0ae2361db6 100644 --- a/wgpu-core/src/command/mod.rs +++ b/wgpu-core/src/command/mod.rs @@ -10,6 +10,7 @@ mod render; mod transfer; use std::slice; +use std::sync::Arc; pub(crate) use self::clear::clear_texture; pub use self::{ @@ -18,22 +19,23 @@ pub use self::{ use self::memory_init::CommandBufferTextureMemoryActions; +use crate::device::{Device, DeviceError}; use crate::error::{ErrorFormatter, PrettyError}; +use crate::hub::Hub; +use crate::id::CommandBufferId; +use crate::snatch::SnatchGuard; + use crate::init_tracker::BufferInitTrackerAction; use crate::ray_tracing::{BlasAction, TlasAction}; +use crate::resource::{Resource, ResourceInfo, ResourceType}; use crate::track::{Tracker, UsageScope}; use crate::{ - global::Global, - hal_api::HalApi, - hub::Token, - id, - identity::GlobalIdentityHandlerFactory, - resource::{Buffer, Texture}, - storage::Storage, - Label, Stored, + api_log, global::Global, hal_api::HalApi, id, identity::GlobalIdentityHandlerFactory, + resource_log, Label, }; use hal::CommandEncoder as _; +use parking_lot::Mutex; use thiserror::Error; #[cfg(feature = "trace")] @@ -42,13 +44,13 @@ use crate::device::trace::Command as TraceCommand; const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64]; #[derive(Debug)] -enum CommandEncoderStatus { +pub(crate) enum CommandEncoderStatus { Recording, Finished, Error, } -struct CommandEncoder { +pub(crate) struct CommandEncoder { raw: A::CommandEncoder, list: Vec, is_open: bool, @@ -56,22 +58,26 @@ struct CommandEncoder { } //TODO: handle errors better -impl CommandEncoder { +impl CommandEncoder { /// Closes the live encoder - fn close_and_swap(&mut self) { + fn close_and_swap(&mut self) -> Result<(), DeviceError> { if self.is_open { self.is_open = false; - let new = unsafe { self.raw.end_encoding().unwrap() }; + let new = unsafe { self.raw.end_encoding()? }; self.list.insert(self.list.len() - 1, new); } + + Ok(()) } - fn close(&mut self) { + fn close(&mut self) -> Result<(), DeviceError> { if self.is_open { self.is_open = false; - let cmd_buf = unsafe { self.raw.end_encoding().unwrap() }; + let cmd_buf = unsafe { self.raw.end_encoding()? }; self.list.push(cmd_buf); } + + Ok(()) } fn discard(&mut self) { @@ -81,18 +87,21 @@ impl CommandEncoder { } } - fn open(&mut self) -> &mut A::CommandEncoder { + fn open(&mut self) -> Result<&mut A::CommandEncoder, DeviceError> { if !self.is_open { self.is_open = true; let label = self.label.as_deref(); - unsafe { self.raw.begin_encoding(label).unwrap() }; + unsafe { self.raw.begin_encoding(label)? }; } - &mut self.raw + + Ok(&mut self.raw) } - fn open_pass(&mut self, label: Option<&str>) { + fn open_pass(&mut self, label: Option<&str>) -> Result<(), DeviceError> { self.is_open = true; - unsafe { self.raw.begin_encoding(label).unwrap() }; + unsafe { self.raw.begin_encoding(label)? }; + + Ok(()) } } @@ -100,8 +109,8 @@ pub struct BakedCommands { pub(crate) encoder: A::CommandEncoder, pub(crate) list: Vec, pub(crate) trackers: Tracker, - buffer_memory_init_actions: Vec, - texture_memory_actions: CommandBufferTextureMemoryActions, + buffer_memory_init_actions: Vec>, + texture_memory_actions: CommandBufferTextureMemoryActions, blas_actions: Vec, tlas_actions: Vec, } @@ -109,55 +118,94 @@ pub struct BakedCommands { pub(crate) struct DestroyedBufferError(pub id::BufferId); pub(crate) struct DestroyedTextureError(pub id::TextureId); -pub struct CommandBuffer { +pub struct CommandBufferMutable { encoder: CommandEncoder, status: CommandEncoderStatus, - pub(crate) device_id: Stored, pub(crate) trackers: Tracker, - buffer_memory_init_actions: Vec, - texture_memory_actions: CommandBufferTextureMemoryActions, + buffer_memory_init_actions: Vec>, + texture_memory_actions: CommandBufferTextureMemoryActions, pub(crate) pending_query_resets: QueryResetMap, blas_actions: Vec, tlas_actions: Vec, - limits: wgt::Limits, - support_clear_texture: bool, #[cfg(feature = "trace")] pub(crate) commands: Option>, } +impl CommandBufferMutable { + pub(crate) fn open_encoder_and_tracker( + &mut self, + ) -> Result<(&mut A::CommandEncoder, &mut Tracker), DeviceError> { + let encoder = self.encoder.open()?; + let tracker = &mut self.trackers; + + Ok((encoder, tracker)) + } +} + +pub struct CommandBuffer { + pub(crate) device: Arc>, + limits: wgt::Limits, + support_clear_texture: bool, + pub(crate) info: ResourceInfo, + pub(crate) data: Mutex>>, +} + +impl Drop for CommandBuffer { + fn drop(&mut self) { + if self.data.lock().is_none() { + return; + } + resource_log!("resource::CommandBuffer::drop {:?}", self.info.label()); + let mut baked = self.extract_baked_commands(); + unsafe { + baked.encoder.reset_all(baked.list.into_iter()); + } + unsafe { + use hal::Device; + self.device.raw().destroy_command_encoder(baked.encoder); + } + } +} + impl CommandBuffer { pub(crate) fn new( encoder: A::CommandEncoder, - device_id: Stored, - limits: wgt::Limits, - _downlevel: wgt::DownlevelCapabilities, - features: wgt::Features, + device: &Arc>, #[cfg(feature = "trace")] enable_tracing: bool, - label: &Label, + label: Option, ) -> Self { CommandBuffer { - encoder: CommandEncoder { - raw: encoder, - is_open: false, - list: Vec::new(), - label: crate::LabelHelpers::borrow_option(label).map(|s| s.to_string()), - }, - status: CommandEncoderStatus::Recording, - device_id, - trackers: Tracker::new(), - buffer_memory_init_actions: Default::default(), - texture_memory_actions: Default::default(), - pending_query_resets: QueryResetMap::new(), - blas_actions: Default::default(), - tlas_actions: Default::default(), - limits, - support_clear_texture: features.contains(wgt::Features::CLEAR_TEXTURE), - #[cfg(feature = "trace")] - commands: if enable_tracing { - Some(Vec::new()) - } else { - None - }, + device: device.clone(), + limits: device.limits.clone(), + support_clear_texture: device.features.contains(wgt::Features::CLEAR_TEXTURE), + info: ResourceInfo::new( + label + .as_ref() + .unwrap_or(&String::from("")) + .as_str(), + ), + //Todo come back + data: Mutex::new(Some(CommandBufferMutable { + encoder: CommandEncoder { + raw: encoder, + is_open: false, + list: Vec::new(), + label, + }, + status: CommandEncoderStatus::Recording, + trackers: Tracker::new(), + buffer_memory_init_actions: Default::default(), + texture_memory_actions: Default::default(), + pending_query_resets: QueryResetMap::new(), + blas_actions: Default::default(), + tlas_actions: Default::default(), + #[cfg(feature = "trace")] + commands: if enable_tracing { + Some(Vec::new()) + } else { + None + }, + })), } } @@ -165,50 +213,43 @@ impl CommandBuffer { raw: &mut A::CommandEncoder, base: &mut Tracker, head: &Tracker, - buffer_guard: &Storage, id::BufferId>, - texture_guard: &Storage, id::TextureId>, + snatch_guard: &SnatchGuard, ) { profiling::scope!("insert_barriers"); base.buffers.set_from_tracker(&head.buffers); - base.textures - .set_from_tracker(texture_guard, &head.textures); + base.textures.set_from_tracker(&head.textures); - Self::drain_barriers(raw, base, buffer_guard, texture_guard); + Self::drain_barriers(raw, base, snatch_guard); } pub(crate) fn insert_barriers_from_scope( raw: &mut A::CommandEncoder, base: &mut Tracker, head: &UsageScope, - buffer_guard: &Storage, id::BufferId>, - texture_guard: &Storage, id::TextureId>, + snatch_guard: &SnatchGuard, ) { profiling::scope!("insert_barriers"); base.buffers.set_from_usage_scope(&head.buffers); - base.textures - .set_from_usage_scope(texture_guard, &head.textures); + base.textures.set_from_usage_scope(&head.textures); - Self::drain_barriers(raw, base, buffer_guard, texture_guard); + Self::drain_barriers(raw, base, snatch_guard); } pub(crate) fn drain_barriers( raw: &mut A::CommandEncoder, base: &mut Tracker, - buffer_guard: &Storage, id::BufferId>, - texture_guard: &Storage, id::TextureId>, + snatch_guard: &SnatchGuard, ) { profiling::scope!("drain_barriers"); - let buffer_barriers = base.buffers.drain().map(|pending| { - let buf = unsafe { &buffer_guard.get_unchecked(pending.id) }; - pending.into_hal(buf) - }); - let texture_barriers = base.textures.drain().map(|pending| { - let tex = unsafe { texture_guard.get_unchecked(pending.id) }; - pending.into_hal(tex) - }); + let buffer_barriers = base.buffers.drain_transitions(snatch_guard); + let (transitions, textures) = base.textures.drain_transitions(snatch_guard); + let texture_barriers = transitions + .into_iter() + .enumerate() + .map(|(i, p)| p.into_hal(textures[i].unwrap().raw().unwrap())); unsafe { raw.transition_buffers(buffer_barriers); @@ -218,13 +259,14 @@ impl CommandBuffer { } impl CommandBuffer { - fn get_encoder_mut( - storage: &mut Storage, + fn get_encoder( + hub: &Hub, id: id::CommandEncoderId, - ) -> Result<&mut Self, CommandEncoderError> { - match storage.get_mut(id) { - Ok(cmd_buf) => match cmd_buf.status { - CommandEncoderStatus::Recording => Ok(cmd_buf), + ) -> Result, CommandEncoderError> { + let storage = hub.command_buffers.read(); + match storage.get(id) { + Ok(cmd_buf) => match cmd_buf.data.lock().as_ref().unwrap().status { + CommandEncoderStatus::Recording => Ok(cmd_buf.clone()), CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording), CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid), }, @@ -233,34 +275,55 @@ impl CommandBuffer { } pub fn is_finished(&self) -> bool { - match self.status { + match self.data.lock().as_ref().unwrap().status { CommandEncoderStatus::Finished => true, _ => false, } } - pub(crate) fn into_baked(self) -> BakedCommands { + pub(crate) fn extract_baked_commands(&mut self) -> BakedCommands { + log::trace!( + "Extracting BakedCommands from CommandBuffer {:?}", + self.info.label() + ); + let data = self.data.lock().take().unwrap(); BakedCommands { - encoder: self.encoder.raw, - list: self.encoder.list, - trackers: self.trackers, - buffer_memory_init_actions: self.buffer_memory_init_actions, - texture_memory_actions: self.texture_memory_actions, - blas_actions: self.blas_actions, - tlas_actions: self.tlas_actions, + encoder: data.encoder.raw, + list: data.encoder.list, + trackers: data.trackers, + buffer_memory_init_actions: data.buffer_memory_init_actions, + texture_memory_actions: data.texture_memory_actions, + blas_actions: data.blas_actions, + tlas_actions: data.tlas_actions, + } + } + + pub(crate) fn from_arc_into_baked(self: Arc) -> BakedCommands { + if let Some(mut command_buffer) = Arc::into_inner(self) { + command_buffer.extract_baked_commands() + } else { + panic!("CommandBuffer cannot be destroyed because is still in use"); } } } -impl crate::resource::Resource for CommandBuffer { - const TYPE: &'static str = "CommandBuffer"; +impl Resource for CommandBuffer { + const TYPE: ResourceType = "CommandBuffer"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } - fn life_guard(&self) -> &crate::LifeGuard { - unreachable!() + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } - fn label(&self) -> &str { - self.encoder.label.as_ref().map_or("", |s| s.as_str()) + fn label(&self) -> String { + let str = match self.data.lock().as_ref().unwrap().encoder.label.as_ref() { + Some(label) => label.clone(), + _ => String::new(), + }; + str } } @@ -359,6 +422,8 @@ pub enum CommandEncoderError { Invalid, #[error("Command encoder must be active")] NotRecording, + #[error(transparent)] + Device(#[from] DeviceError), } impl Global { @@ -366,29 +431,34 @@ impl Global { &self, encoder_id: id::CommandEncoderId, _desc: &wgt::CommandBufferDescriptor, } -impl QueryResetMap { +impl QueryResetMap { pub fn new() -> Self { Self { map: FastHashMap::default(), @@ -69,7 +69,7 @@ impl QueryResetMap { // We've hit the end of a run, dispatch a reset (Some(start), false) => { run_start = None; - unsafe { raw_encoder.reset_queries(&query_set.raw, start..idx as u32) }; + unsafe { raw_encoder.reset_queries(query_set.raw(), start..idx as u32) }; } // We're starting a run (None, true) => { @@ -105,6 +105,8 @@ impl From for SimplifiedQueryType { #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum QueryError { + #[error(transparent)] + Device(#[from] DeviceError), #[error(transparent)] Encoder(#[from] CommandEncoderError), #[error("Error encountered while trying to use queries")] @@ -211,7 +213,7 @@ impl QuerySet { }); } - Ok(&self.raw) + Ok(self.raw()) } pub(super) fn validate_and_write_timestamp( @@ -232,7 +234,7 @@ impl QuerySet { unsafe { // If we don't have a reset state tracker which can defer resets, we must reset now. if needs_reset { - raw_encoder.reset_queries(&self.raw, query_index..(query_index + 1)); + raw_encoder.reset_queries(self.raw(), query_index..(query_index + 1)); } raw_encoder.write_timestamp(query_set, query_index); } @@ -266,7 +268,8 @@ impl QuerySet { unsafe { // If we don't have a reset state tracker which can defer resets, we must reset now. if needs_reset { - raw_encoder.reset_queries(&self.raw, query_index..(query_index + 1)); + raw_encoder + .reset_queries(self.raw.as_ref().unwrap(), query_index..(query_index + 1)); } raw_encoder.begin_query(query_set, query_index); } @@ -300,7 +303,7 @@ impl QuerySet { unsafe { // If we don't have a reset state tracker which can defer resets, we must reset now. if needs_reset { - raw_encoder.reset_queries(&self.raw, query_index..(query_index + 1)); + raw_encoder.reset_queries(self.raw(), query_index..(query_index + 1)); } raw_encoder.begin_query(query_set, query_index); } @@ -318,7 +321,7 @@ pub(super) fn end_occlusion_query( // We can unwrap here as the validity was validated when the active query was set let query_set = storage.get(query_set_id).unwrap(); - unsafe { raw_encoder.end_query(&query_set.raw, query_index) }; + unsafe { raw_encoder.end_query(query_set.raw.as_ref().unwrap(), query_index) }; Ok(()) } else { @@ -335,7 +338,7 @@ pub(super) fn end_pipeline_statistics_query( // We can unwrap here as the validity was validated when the active query was set let query_set = storage.get(query_set_id).unwrap(); - unsafe { raw_encoder.end_query(&query_set.raw, query_index) }; + unsafe { raw_encoder.end_query(query_set.raw(), query_index) }; Ok(()) } else { @@ -351,24 +354,26 @@ impl Global { query_index: u32, ) -> Result<(), QueryError> { let hub = A::hub(self); - let mut token = Token::root(); - - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let (query_set_guard, _) = hub.query_sets.read(&mut token); - let cmd_buf = CommandBuffer::get_encoder_mut(&mut cmd_buf_guard, command_encoder_id)?; - let raw_encoder = cmd_buf.encoder.open(); + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(TraceCommand::WriteTimestamp { query_set_id, query_index, }); } - let query_set = cmd_buf - .trackers + let encoder = &mut cmd_buf_data.encoder; + let tracker = &mut cmd_buf_data.trackers; + + let raw_encoder = encoder.open()?; + + let query_set_guard = hub.query_sets.read(); + let query_set = tracker .query_sets .add_single(&*query_set_guard, query_set_id) .ok_or(QueryError::InvalidQuerySet(query_set_id))?; @@ -388,17 +393,13 @@ impl Global { destination_offset: BufferAddress, ) -> Result<(), QueryError> { let hub = A::hub(self); - let mut token = Token::root(); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let (query_set_guard, mut token) = hub.query_sets.read(&mut token); - let (buffer_guard, _) = hub.buffers.read(&mut token); - - let cmd_buf = CommandBuffer::get_encoder_mut(&mut cmd_buf_guard, command_encoder_id)?; - let raw_encoder = cmd_buf.encoder.open(); + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(TraceCommand::ResolveQuerySet { query_set_id, start_query, @@ -408,22 +409,34 @@ impl Global { }); } + let encoder = &mut cmd_buf_data.encoder; + let tracker = &mut cmd_buf_data.trackers; + let buffer_memory_init_actions = &mut cmd_buf_data.buffer_memory_init_actions; + let raw_encoder = encoder.open()?; + if destination_offset % wgt::QUERY_RESOLVE_BUFFER_ALIGNMENT != 0 { return Err(QueryError::Resolve(ResolveError::BufferOffsetAlignment)); } - - let query_set = cmd_buf - .trackers + let query_set_guard = hub.query_sets.read(); + let query_set = tracker .query_sets .add_single(&*query_set_guard, query_set_id) .ok_or(QueryError::InvalidQuerySet(query_set_id))?; - let (dst_buffer, dst_pending) = cmd_buf - .trackers - .buffers - .set_single(&*buffer_guard, destination, hal::BufferUses::COPY_DST) - .ok_or(QueryError::InvalidBuffer(destination))?; - let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer)); + let (dst_buffer, dst_pending) = { + let buffer_guard = hub.buffers.read(); + let dst_buffer = buffer_guard + .get(destination) + .map_err(|_| QueryError::InvalidBuffer(destination))?; + tracker + .buffers + .set_single(dst_buffer, hal::BufferUses::COPY_DST) + .ok_or(QueryError::InvalidBuffer(destination))? + }; + + let snatch_guard = dst_buffer.device.snatchable_lock.read(); + + let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard)); if !dst_buffer.usage.contains(wgt::BufferUsages::QUERY_RESOLVE) { return Err(ResolveError::MissingBufferUsage.into()); @@ -463,20 +476,22 @@ impl Global { } // TODO(https://github.com/gfx-rs/wgpu/issues/3993): Need to track initialization state. - cmd_buf - .buffer_memory_init_actions - .extend(dst_buffer.initialization_status.create_action( - destination, - buffer_start_offset..buffer_end_offset, - MemoryInitKind::ImplicitlyInitialized, - )); + buffer_memory_init_actions.extend(dst_buffer.initialization_status.read().create_action( + &dst_buffer, + buffer_start_offset..buffer_end_offset, + MemoryInitKind::ImplicitlyInitialized, + )); + + let raw_dst_buffer = dst_buffer + .raw(&snatch_guard) + .ok_or(QueryError::InvalidBuffer(destination))?; unsafe { raw_encoder.transition_buffers(dst_barrier.into_iter()); raw_encoder.copy_query_results( - &query_set.raw, + query_set.raw(), start_query..end_query, - dst_buffer.raw.as_ref().unwrap(), + raw_dst_buffer, destination_offset, wgt::BufferSize::new_unchecked(stride as u64), ); diff --git a/wgpu-core/src/command/ray_tracing.rs b/wgpu-core/src/command/ray_tracing.rs index 1098138e15..c161686597 100644 --- a/wgpu-core/src/command/ray_tracing.rs +++ b/wgpu-core/src/command/ray_tracing.rs @@ -3,7 +3,6 @@ use crate::{ device::queue::TempResource, global::Global, hal_api::HalApi, - hub::Token, id::{BlasId, CommandEncoderId, TlasId}, identity::GlobalIdentityHandlerFactory, init_tracker::MemoryInitKind, @@ -17,9 +16,15 @@ use crate::{ FastHashSet, }; -use hal::{CommandEncoder, Device}; use wgt::{math::align_to, BufferUsages}; +use crate::ray_tracing::BlasTriangleGeometry; +use crate::resource::{Buffer, Resource, ResourceInfo, StagingBuffer}; +use crate::track::PendingTransition; +use hal::{BufferUses, CommandEncoder, Device}; +use parking_lot::{Mutex, RwLockReadGuard}; +use std::ops::Deref; +use std::sync::Arc; use std::{cmp::max, iter, num::NonZeroU64, ops::Range, ptr}; use super::BakedCommands; @@ -37,16 +42,14 @@ impl Global { profiling::scope!("CommandEncoder::build_acceleration_structures_unsafe_tlas"); let hub = A::hub(self); - let mut token = Token::root(); - let (mut device_guard, mut token) = hub.devices.write(&mut token); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id)?; - let (buffer_guard, mut token) = hub.buffers.read(&mut token); - let (blas_guard, mut token) = hub.blas_s.read(&mut token); - let (tlas_guard, _) = hub.tlas_s.read(&mut token); + let device_guard = hub.devices.write(); + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let buffer_guard = hub.buffers.read(); + let blas_guard = hub.blas_s.read(); + let tlas_guard = hub.tlas_s.read(); - let device = &mut device_guard[cmd_buf.device_id.value]; + let device = &mut device_guard.get(cmd_buf.device.as_info().id()).unwrap(); let build_command_index = NonZeroU64::new( device @@ -86,9 +89,8 @@ impl Global { #[cfg(feature = "trace")] let trace_tlas: Vec = tlas_iter.collect(); - #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf.data.lock().as_mut().unwrap().commands { list.push( crate::device::trace::Command::BuildAccelerationStructuresUnsafeTlas { blas: trace_blas.clone(), @@ -103,20 +105,18 @@ impl Global { #[cfg(feature = "trace")] let blas_iter = trace_blas.iter().map(|x| { let geometries = match &x.geometries { - &crate::ray_tracing::TraceBlasGeometries::TriangleGeometries( - ref triangle_geometries, + crate::ray_tracing::TraceBlasGeometries::TriangleGeometries( + triangle_geometries, ) => { - let iter = triangle_geometries.iter().map(|tg| { - crate::ray_tracing::BlasTriangleGeometry { - size: &tg.size, - vertex_buffer: tg.vertex_buffer, - index_buffer: tg.index_buffer, - transform_buffer: tg.transform_buffer, - first_vertex: tg.first_vertex, - vertex_stride: tg.vertex_stride, - index_buffer_offset: tg.index_buffer_offset, - transform_buffer_offset: tg.transform_buffer_offset, - } + let iter = triangle_geometries.iter().map(|tg| BlasTriangleGeometry { + size: &tg.size, + vertex_buffer: tg.vertex_buffer, + index_buffer: tg.index_buffer, + transform_buffer: tg.transform_buffer, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, }); BlasGeometries::TriangleGeometries(Box::new(iter)) } @@ -131,12 +131,21 @@ impl Global { let tlas_iter = trace_tlas.iter(); let mut input_barriers = Vec::>::new(); + let mut buf_storage = Vec::<( + Arc>, + Option>, + Option<(Arc>, Option>)>, + Option<(Arc>, Option>)>, + BlasTriangleGeometry, + Option>>, + )>::new(); let mut scratch_buffer_blas_size = 0; let mut blas_storage = Vec::<(&Blas, hal::AccelerationStructureEntries, u64)>::new(); - + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); for entry in blas_iter { - let blas = cmd_buf + let blas = cmd_buf_data .trackers .blas_s .add_single(&blas_guard, entry.blas_id) @@ -146,18 +155,16 @@ impl Global { return Err(BuildAccelerationStructureError::InvalidBlas(entry.blas_id)); } - cmd_buf.blas_actions.push(BlasAction { + cmd_buf_data.blas_actions.push(BlasAction { id: entry.blas_id, kind: crate::ray_tracing::BlasActionKind::Build(build_command_index), }); match entry.geometries { BlasGeometries::TriangleGeometries(triangle_geometries) => { - let mut triangle_entries = Vec::>::new(); - for (i, mesh) in triangle_geometries.enumerate() { let size_desc = match &blas.sizes { - &wgt::BlasGeometrySizeDescriptors::Triangles { ref desc } => desc, + wgt::BlasGeometrySizeDescriptors::Triangles { desc } => desc, }; if i >= size_desc.len() { return Err( @@ -191,61 +198,24 @@ impl Global { entry.blas_id, )); } - - let vertex_buffer = { - let (vertex_buffer, vertex_pending) = cmd_buf - .trackers - .buffers - .set_single( - &*buffer_guard, - mesh.vertex_buffer, - hal::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, - ) - .ok_or(BuildAccelerationStructureError::InvalidBuffer( - mesh.vertex_buffer, - ))?; - let vertex_raw = vertex_buffer.raw.as_ref().ok_or( - BuildAccelerationStructureError::InvalidBuffer(mesh.vertex_buffer), - )?; - if !vertex_buffer.usage.contains(BufferUsages::BLAS_INPUT) { - return Err( - BuildAccelerationStructureError::MissingBlasInputUsageFlag( - mesh.vertex_buffer, - ), - ); - } - if let Some(barrier) = - vertex_pending.map(|pending| pending.into_hal(vertex_buffer)) - { - input_barriers.push(barrier); - } - if vertex_buffer.size - < (mesh.size.vertex_count + mesh.first_vertex) as u64 - * mesh.vertex_stride - { - return Err( - BuildAccelerationStructureError::InsufficientBufferSize( - mesh.vertex_buffer, - vertex_buffer.size, - (mesh.size.vertex_count + mesh.first_vertex) as u64 - * mesh.vertex_stride, - ), - ); - } - let vertex_buffer_offset = - mesh.first_vertex as u64 * mesh.vertex_stride; - cmd_buf.buffer_memory_init_actions.extend( - vertex_buffer.initialization_status.create_action( - mesh.vertex_buffer, - vertex_buffer_offset - ..(vertex_buffer_offset - + mesh.size.vertex_count as u64 * mesh.vertex_stride), - MemoryInitKind::NeedsInitializedMemory, - ), - ); - vertex_raw - }; - let index_buffer = if let Some(index_id) = mesh.index_buffer { + let (vertex_buffer, vertex_pending) = cmd_buf_data + .trackers + .buffers + .set_single( + match buffer_guard.get(mesh.vertex_buffer) { + Ok(buffer) => buffer, + Err(_) => { + return Err(BuildAccelerationStructureError::InvalidBuffer( + mesh.vertex_buffer, + )) + } + }, + BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + ) + .ok_or(BuildAccelerationStructureError::InvalidBuffer( + mesh.vertex_buffer, + ))?; + let index_data = if let Some(index_id) = mesh.index_buffer { if mesh.index_buffer_offset.is_none() || mesh.size.index_count.is_none() || mesh.size.index_count.is_none() @@ -256,78 +226,28 @@ impl Global { ), ); } - let (index_buffer, index_pending) = cmd_buf + let data = cmd_buf_data .trackers .buffers .set_single( - &*buffer_guard, - index_id, + match buffer_guard.get(index_id) { + Ok(buffer) => buffer, + Err(_) => { + return Err( + BuildAccelerationStructureError::InvalidBuffer( + index_id, + ), + ) + } + }, hal::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, ) .ok_or(BuildAccelerationStructureError::InvalidBuffer(index_id))?; - let index_raw = index_buffer - .raw - .as_ref() - .ok_or(BuildAccelerationStructureError::InvalidBuffer(index_id))?; - if !index_buffer.usage.contains(BufferUsages::BLAS_INPUT) { - return Err( - BuildAccelerationStructureError::MissingBlasInputUsageFlag( - index_id, - ), - ); - } - if let Some(barrier) = - index_pending.map(|pending| pending.into_hal(index_buffer)) - { - input_barriers.push(barrier); - } - let index_stride = match mesh.size.index_format.unwrap() { - wgt::IndexFormat::Uint16 => 2, - wgt::IndexFormat::Uint32 => 4, - }; - if mesh.index_buffer_offset.unwrap() % index_stride != 0 { - return Err( - BuildAccelerationStructureError::UnalignedIndexBufferOffset( - index_id, - ), - ); - } - let index_buffer_size = - mesh.size.index_count.unwrap() as u64 * index_stride; - - if mesh.size.index_count.unwrap() % 3 != 0 { - return Err(BuildAccelerationStructureError::InvalidIndexCount( - index_id, - mesh.size.index_count.unwrap(), - )); - } - if index_buffer.size - < mesh.size.index_count.unwrap() as u64 * index_stride - + mesh.index_buffer_offset.unwrap() - { - return Err( - BuildAccelerationStructureError::InsufficientBufferSize( - index_id, - index_buffer.size, - mesh.size.index_count.unwrap() as u64 * index_stride - + mesh.index_buffer_offset.unwrap(), - ), - ); - } - - cmd_buf.buffer_memory_init_actions.extend( - index_buffer.initialization_status.create_action( - index_id, - mesh.index_buffer_offset.unwrap() - ..(mesh.index_buffer_offset.unwrap() + index_buffer_size), - MemoryInitKind::NeedsInitializedMemory, - ), - ); - Some(index_raw) + Some(data) } else { None }; - let transform_buffer = if let Some(transform_id) = mesh.transform_buffer { + let transform_data = if let Some(transform_id) = mesh.transform_buffer { if mesh.transform_buffer_offset.is_none() { return Err( BuildAccelerationStructureError::MissingAssociatedData( @@ -335,122 +255,276 @@ impl Global { ), ); } - let (transform_buffer, transform_pending) = cmd_buf + let data = cmd_buf_data .trackers .buffers .set_single( - &*buffer_guard, - transform_id, - hal::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + match buffer_guard.get(transform_id) { + Ok(buffer) => buffer, + Err(_) => { + return Err( + BuildAccelerationStructureError::InvalidBuffer( + transform_id, + ), + ) + } + }, + BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, ) .ok_or(BuildAccelerationStructureError::InvalidBuffer( transform_id, ))?; - let transform_raw = transform_buffer.raw.as_ref().ok_or( - BuildAccelerationStructureError::InvalidBuffer(transform_id), - )?; - if !transform_buffer.usage.contains(BufferUsages::BLAS_INPUT) { - return Err( - BuildAccelerationStructureError::MissingBlasInputUsageFlag( - transform_id, - ), - ); - } - if let Some(barrier) = - transform_pending.map(|pending| pending.into_hal(transform_buffer)) - { - input_barriers.push(barrier); - } - if mesh.transform_buffer_offset.unwrap() - % wgt::TRANSFORM_BUFFER_ALIGNMENT - != 0 - { - return Err( - BuildAccelerationStructureError::UnalignedTransformBufferOffset( - transform_id, - ), - ); - } - if transform_buffer.size < 48 + mesh.transform_buffer_offset.unwrap() { - return Err( - BuildAccelerationStructureError::InsufficientBufferSize( - transform_id, - transform_buffer.size, - 48 + mesh.transform_buffer_offset.unwrap(), - ), - ); - } - cmd_buf.buffer_memory_init_actions.extend( - transform_buffer.initialization_status.create_action( - transform_id, - mesh.transform_buffer_offset.unwrap() - ..(mesh.index_buffer_offset.unwrap() + 48), - MemoryInitKind::NeedsInitializedMemory, - ), - ); - Some(transform_raw) + Some(data) } else { None }; - let triangles = hal::AccelerationStructureTriangles { - vertex_buffer: Some(vertex_buffer), - vertex_format: mesh.size.vertex_format, - first_vertex: mesh.first_vertex, - vertex_count: mesh.size.vertex_count, - vertex_stride: mesh.vertex_stride, - indices: index_buffer.map(|index_buffer| { - hal::AccelerationStructureTriangleIndices { - format: mesh.size.index_format.unwrap(), - buffer: Some(index_buffer), - offset: mesh.index_buffer_offset.unwrap() as u32, - count: mesh.size.index_count.unwrap(), - } - }), - transform: transform_buffer.map(|transform_buffer| { - hal::AccelerationStructureTriangleTransform { - buffer: transform_buffer, - offset: mesh.transform_buffer_offset.unwrap() as u32, - } - }), - flags: mesh.size.flags, - }; - - triangle_entries.push(triangles); + buf_storage.push(( + vertex_buffer, + vertex_pending, + index_data, + transform_data, + mesh, + None, + )) + } + if let Some(last) = buf_storage.last_mut() { + last.5 = Some(blas.clone()); } + } + } + } - let scratch_buffer_offset = scratch_buffer_blas_size; - scratch_buffer_blas_size += align_to( - blas.size_info.build_scratch_size as u32, - SCRATCH_BUFFER_ALIGNMENT, - ) as u64; - - blas_storage.push(( - blas, - hal::AccelerationStructureEntries::Triangles(triangle_entries), - scratch_buffer_offset, - )) + let mut triangle_entries = Vec::>::new(); + let snatch_guard = device.snatchable_lock.read(); + for buf in &mut buf_storage { + let mesh = &buf.4; + let vertex_buffer = { + let vertex_buffer = buf.0.as_ref(); + let vertex_raw = vertex_buffer.raw.get(&snatch_guard).ok_or( + BuildAccelerationStructureError::InvalidBuffer(mesh.vertex_buffer), + )?; + if !vertex_buffer.usage.contains(BufferUsages::BLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingBlasInputUsageFlag( + mesh.vertex_buffer, + )); + } + if let Some(barrier) = buf + .1 + .take() + .map(|pending| pending.into_hal(vertex_buffer, &snatch_guard)) + { + input_barriers.push(barrier); + } + if vertex_buffer.size + < (mesh.size.vertex_count + mesh.first_vertex) as u64 * mesh.vertex_stride + { + return Err(BuildAccelerationStructureError::InsufficientBufferSize( + mesh.vertex_buffer, + vertex_buffer.size, + (mesh.size.vertex_count + mesh.first_vertex) as u64 * mesh.vertex_stride, + )); + } + let vertex_buffer_offset = mesh.first_vertex as u64 * mesh.vertex_stride; + cmd_buf_data.buffer_memory_init_actions.extend( + vertex_buffer.initialization_status.read().create_action( + buffer_guard.get(mesh.vertex_buffer).unwrap(), + vertex_buffer_offset + ..(vertex_buffer_offset + + mesh.size.vertex_count as u64 * mesh.vertex_stride), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + vertex_raw + }; + let index_buffer = if let Some((ref mut index_buffer, ref mut index_pending)) = buf.2 { + let index_id = mesh.index_buffer.as_ref().unwrap(); + let index_raw = index_buffer + .raw + .get(&snatch_guard) + .ok_or(BuildAccelerationStructureError::InvalidBuffer(*index_id))?; + if !index_buffer.usage.contains(BufferUsages::BLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingBlasInputUsageFlag( + *index_id, + )); + } + if let Some(barrier) = index_pending + .take() + .map(|pending| pending.into_hal(index_buffer, &snatch_guard)) + { + input_barriers.push(barrier); + } + let index_stride = match mesh.size.index_format.unwrap() { + wgt::IndexFormat::Uint16 => 2, + wgt::IndexFormat::Uint32 => 4, + }; + if mesh.index_buffer_offset.unwrap() % index_stride != 0 { + return Err(BuildAccelerationStructureError::UnalignedIndexBufferOffset( + *index_id, + )); } + let index_buffer_size = mesh.size.index_count.unwrap() as u64 * index_stride; + + if mesh.size.index_count.unwrap() % 3 != 0 { + return Err(BuildAccelerationStructureError::InvalidIndexCount( + *index_id, + mesh.size.index_count.unwrap(), + )); + } + if index_buffer.size + < mesh.size.index_count.unwrap() as u64 * index_stride + + mesh.index_buffer_offset.unwrap() + { + return Err(BuildAccelerationStructureError::InsufficientBufferSize( + *index_id, + index_buffer.size, + mesh.size.index_count.unwrap() as u64 * index_stride + + mesh.index_buffer_offset.unwrap(), + )); + } + + cmd_buf_data.buffer_memory_init_actions.extend( + index_buffer.initialization_status.read().create_action( + match buffer_guard.get(*index_id) { + Ok(buffer) => buffer, + Err(_) => { + return Err(BuildAccelerationStructureError::InvalidBuffer( + *index_id, + )) + } + }, + mesh.index_buffer_offset.unwrap() + ..(mesh.index_buffer_offset.unwrap() + index_buffer_size), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + Some(index_raw) + } else { + None + }; + let transform_buffer = + if let Some((ref mut transform_buffer, ref mut transform_pending)) = buf.3 { + let transform_id = mesh.transform_buffer.as_ref().unwrap(); + if mesh.transform_buffer_offset.is_none() { + return Err(BuildAccelerationStructureError::MissingAssociatedData( + *transform_id, + )); + } + let transform_raw = transform_buffer.raw.get(&snatch_guard).ok_or( + BuildAccelerationStructureError::InvalidBuffer(*transform_id), + )?; + if !transform_buffer.usage.contains(BufferUsages::BLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingBlasInputUsageFlag( + *transform_id, + )); + } + if let Some(barrier) = transform_pending + .take() + .map(|pending| pending.into_hal(transform_buffer, &snatch_guard)) + { + input_barriers.push(barrier); + } + if mesh.transform_buffer_offset.unwrap() % wgt::TRANSFORM_BUFFER_ALIGNMENT != 0 + { + return Err( + BuildAccelerationStructureError::UnalignedTransformBufferOffset( + *transform_id, + ), + ); + } + if transform_buffer.size < 48 + mesh.transform_buffer_offset.unwrap() { + return Err(BuildAccelerationStructureError::InsufficientBufferSize( + *transform_id, + transform_buffer.size, + 48 + mesh.transform_buffer_offset.unwrap(), + )); + } + cmd_buf_data.buffer_memory_init_actions.extend( + transform_buffer.initialization_status.read().create_action( + buffer_guard.get(*transform_id).unwrap(), + mesh.transform_buffer_offset.unwrap() + ..(mesh.index_buffer_offset.unwrap() + 48), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + Some(transform_raw) + } else { + None + }; + + let triangles = hal::AccelerationStructureTriangles { + vertex_buffer: Some(vertex_buffer), + vertex_format: mesh.size.vertex_format, + first_vertex: mesh.first_vertex, + vertex_count: mesh.size.vertex_count, + vertex_stride: mesh.vertex_stride, + indices: index_buffer.map(|index_buffer| { + hal::AccelerationStructureTriangleIndices:: { + format: mesh.size.index_format.unwrap(), + buffer: Some(index_buffer), + offset: mesh.index_buffer_offset.unwrap() as u32, + count: mesh.size.index_count.unwrap(), + } + }), + transform: transform_buffer.map(|transform_buffer| { + hal::AccelerationStructureTriangleTransform { + buffer: transform_buffer, + offset: mesh.transform_buffer_offset.unwrap() as u32, + } + }), + flags: mesh.size.flags, + }; + triangle_entries.push(triangles); + if let Some(blas) = buf.5.as_ref() { + let scratch_buffer_offset = scratch_buffer_blas_size; + scratch_buffer_blas_size += align_to( + blas.size_info.build_scratch_size as u32, + SCRATCH_BUFFER_ALIGNMENT, + ) as u64; + + blas_storage.push(( + blas, + hal::AccelerationStructureEntries::Triangles(triangle_entries), + scratch_buffer_offset, + )); + triangle_entries = Vec::new(); } } let mut scratch_buffer_tlas_size = 0; let mut tlas_storage = Vec::<(&Tlas, hal::AccelerationStructureEntries, u64)>::new(); + let mut tlas_buf_storage = Vec::<( + Arc>, + Option>, + TlasBuildEntry, + )>::new(); for entry in tlas_iter { + let data = cmd_buf_data + .trackers + .buffers + .set_single( + match buffer_guard.get(entry.instance_buffer_id) { + Ok(buffer) => buffer, + Err(_) => { + return Err(BuildAccelerationStructureError::InvalidBuffer( + entry.instance_buffer_id, + )); + } + }, + BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + ) + .ok_or(BuildAccelerationStructureError::InvalidBuffer( + entry.instance_buffer_id, + ))?; + tlas_buf_storage.push((data.0, data.1, entry.clone())); + } + + for tlas_buf in &mut tlas_buf_storage { + let entry = &tlas_buf.2; let instance_buffer = { - let (instance_buffer, instance_pending) = cmd_buf - .trackers - .buffers - .set_single( - &*buffer_guard, - entry.instance_buffer_id, - hal::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, - ) - .ok_or(BuildAccelerationStructureError::InvalidBuffer( - entry.instance_buffer_id, - ))?; - let instance_raw = instance_buffer.raw.as_ref().ok_or( + let (instance_buffer, instance_pending) = (&mut tlas_buf.0, &mut tlas_buf.1); + let instance_raw = instance_buffer.raw.get(&snatch_guard).ok_or( BuildAccelerationStructureError::InvalidBuffer(entry.instance_buffer_id), )?; if !instance_buffer.usage.contains(BufferUsages::TLAS_INPUT) { @@ -458,15 +532,16 @@ impl Global { entry.instance_buffer_id, )); } - if let Some(barrier) = - instance_pending.map(|pending| pending.into_hal(instance_buffer)) + if let Some(barrier) = instance_pending + .take() + .map(|pending| pending.into_hal(instance_buffer, &snatch_guard)) { input_barriers.push(barrier); } instance_raw }; - let tlas = cmd_buf + let tlas = cmd_buf_data .trackers .tlas_s .add_single(&tlas_guard, entry.tlas_id) @@ -476,7 +551,7 @@ impl Global { return Err(BuildAccelerationStructureError::InvalidTlas(entry.tlas_id)); } - cmd_buf.tlas_actions.push(TlasAction { + cmd_buf_data.tlas_actions.push(TlasAction { id: entry.tlas_id, kind: crate::ray_tracing::TlasActionKind::Build { build_index: build_command_index, @@ -507,7 +582,7 @@ impl Global { let scratch_buffer = unsafe { device - .raw + .raw() .create_buffer(&hal::BufferDescriptor { label: Some("(wgpu) scratch buffer"), size: max(scratch_buffer_blas_size, scratch_buffer_tlas_size), @@ -562,7 +637,7 @@ impl Global { let blas_present = !blas_storage.is_empty(); let tlas_present = !tlas_storage.is_empty(); - let cmd_buf_raw = cmd_buf.encoder.open(); + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_buffers(input_barriers.into_iter()); @@ -609,11 +684,28 @@ impl Global { ); } } - + let scratch_mapping = unsafe { + device + .raw() + .map_buffer( + &scratch_buffer, + 0..max(scratch_buffer_blas_size, scratch_buffer_tlas_size), + ) + .map_err(crate::device::DeviceError::from)? + }; device .pending_writes + .lock() + .as_mut() + .unwrap() .temp_resources - .push(TempResource::Buffer(scratch_buffer)); + .push(TempResource::StagingBuffer(Arc::new(StagingBuffer { + raw: Mutex::new(Some(scratch_buffer)), + device: device.clone(), + size: max(scratch_buffer_blas_size, scratch_buffer_tlas_size), + info: ResourceInfo::new("Raytracing scratch buffer"), + is_coherent: scratch_mapping.is_coherent, + }))); Ok(()) } @@ -627,16 +719,13 @@ impl Global { profiling::scope!("CommandEncoder::build_acceleration_structures"); let hub = A::hub(self); - let mut token = Token::root(); - let (mut device_guard, mut token) = hub.devices.write(&mut token); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id)?; - let (buffer_guard, mut token) = hub.buffers.read(&mut token); - let (blas_guard, mut token) = hub.blas_s.read(&mut token); - let (tlas_guard, _) = hub.tlas_s.read(&mut token); + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let buffer_guard = hub.buffers.read(); + let blas_guard = hub.blas_s.read(); + let tlas_guard = hub.tlas_s.read(); - let device = &mut device_guard[cmd_buf.device_id.value]; + let device = &cmd_buf.device; let build_command_index = NonZeroU64::new( device @@ -697,7 +786,7 @@ impl Global { .collect(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf.data.lock().as_mut().unwrap().commands { list.push(crate::device::trace::Command::BuildAccelerationStructures { blas: trace_blas.clone(), tlas: trace_tlas.clone(), @@ -707,20 +796,18 @@ impl Global { #[cfg(feature = "trace")] let blas_iter = trace_blas.iter().map(|x| { let geometries = match &x.geometries { - &crate::ray_tracing::TraceBlasGeometries::TriangleGeometries( - ref triangle_geometries, + crate::ray_tracing::TraceBlasGeometries::TriangleGeometries( + triangle_geometries, ) => { - let iter = triangle_geometries.iter().map(|tg| { - crate::ray_tracing::BlasTriangleGeometry { - size: &tg.size, - vertex_buffer: tg.vertex_buffer, - index_buffer: tg.index_buffer, - transform_buffer: tg.transform_buffer, - first_vertex: tg.first_vertex, - vertex_stride: tg.vertex_stride, - index_buffer_offset: tg.index_buffer_offset, - transform_buffer_offset: tg.transform_buffer_offset, - } + let iter = triangle_geometries.iter().map(|tg| BlasTriangleGeometry { + size: &tg.size, + vertex_buffer: tg.vertex_buffer, + index_buffer: tg.index_buffer, + transform_buffer: tg.transform_buffer, + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, }); BlasGeometries::TriangleGeometries(Box::new(iter)) } @@ -751,12 +838,21 @@ impl Global { }); let mut input_barriers = Vec::>::new(); + let mut buf_storage = Vec::<( + Arc>, + Option>, + Option<(Arc>, Option>)>, + Option<(Arc>, Option>)>, + BlasTriangleGeometry, + Option>>, + )>::new(); let mut scratch_buffer_blas_size = 0; let mut blas_storage = Vec::<(&Blas, hal::AccelerationStructureEntries, u64)>::new(); - + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); for entry in blas_iter { - let blas = cmd_buf + let blas = cmd_buf_data .trackers .blas_s .add_single(&blas_guard, entry.blas_id) @@ -766,18 +862,16 @@ impl Global { return Err(BuildAccelerationStructureError::InvalidBlas(entry.blas_id)); } - cmd_buf.blas_actions.push(BlasAction { + cmd_buf_data.blas_actions.push(BlasAction { id: entry.blas_id, kind: crate::ray_tracing::BlasActionKind::Build(build_command_index), }); match entry.geometries { BlasGeometries::TriangleGeometries(triangle_geometries) => { - let mut triangle_entries = Vec::>::new(); - for (i, mesh) in triangle_geometries.enumerate() { let size_desc = match &blas.sizes { - &wgt::BlasGeometrySizeDescriptors::Triangles { ref desc } => desc, + wgt::BlasGeometrySizeDescriptors::Triangles { desc } => desc, }; if i >= size_desc.len() { return Err( @@ -811,61 +905,24 @@ impl Global { entry.blas_id, )); } - - let vertex_buffer = { - let (vertex_buffer, vertex_pending) = cmd_buf - .trackers - .buffers - .set_single( - &*buffer_guard, - mesh.vertex_buffer, - hal::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, - ) - .ok_or(BuildAccelerationStructureError::InvalidBuffer( - mesh.vertex_buffer, - ))?; - let vertex_raw = vertex_buffer.raw.as_ref().ok_or( - BuildAccelerationStructureError::InvalidBuffer(mesh.vertex_buffer), - )?; - if !vertex_buffer.usage.contains(BufferUsages::BLAS_INPUT) { - return Err( - BuildAccelerationStructureError::MissingBlasInputUsageFlag( - mesh.vertex_buffer, - ), - ); - } - if let Some(barrier) = - vertex_pending.map(|pending| pending.into_hal(vertex_buffer)) - { - input_barriers.push(barrier); - } - if vertex_buffer.size - < (mesh.size.vertex_count + mesh.first_vertex) as u64 - * mesh.vertex_stride - { - return Err( - BuildAccelerationStructureError::InsufficientBufferSize( - mesh.vertex_buffer, - vertex_buffer.size, - (mesh.size.vertex_count + mesh.first_vertex) as u64 - * mesh.vertex_stride, - ), - ); - } - let vertex_buffer_offset = - mesh.first_vertex as u64 * mesh.vertex_stride; - cmd_buf.buffer_memory_init_actions.extend( - vertex_buffer.initialization_status.create_action( - mesh.vertex_buffer, - vertex_buffer_offset - ..(vertex_buffer_offset - + mesh.size.vertex_count as u64 * mesh.vertex_stride), - MemoryInitKind::NeedsInitializedMemory, - ), - ); - vertex_raw - }; - let index_buffer = if let Some(index_id) = mesh.index_buffer { + let (vertex_buffer, vertex_pending) = cmd_buf_data + .trackers + .buffers + .set_single( + match buffer_guard.get(mesh.vertex_buffer) { + Ok(buffer) => buffer, + Err(_) => { + return Err(BuildAccelerationStructureError::InvalidBuffer( + mesh.vertex_buffer, + )) + } + }, + BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + ) + .ok_or(BuildAccelerationStructureError::InvalidBuffer( + mesh.vertex_buffer, + ))?; + let index_data = if let Some(index_id) = mesh.index_buffer { if mesh.index_buffer_offset.is_none() || mesh.size.index_count.is_none() || mesh.size.index_count.is_none() @@ -876,78 +933,28 @@ impl Global { ), ); } - let (index_buffer, index_pending) = cmd_buf + let data = cmd_buf_data .trackers .buffers .set_single( - &*buffer_guard, - index_id, + match buffer_guard.get(index_id) { + Ok(buffer) => buffer, + Err(_) => { + return Err( + BuildAccelerationStructureError::InvalidBuffer( + index_id, + ), + ) + } + }, hal::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, ) .ok_or(BuildAccelerationStructureError::InvalidBuffer(index_id))?; - let index_raw = index_buffer - .raw - .as_ref() - .ok_or(BuildAccelerationStructureError::InvalidBuffer(index_id))?; - if !index_buffer.usage.contains(BufferUsages::BLAS_INPUT) { - return Err( - BuildAccelerationStructureError::MissingBlasInputUsageFlag( - index_id, - ), - ); - } - if let Some(barrier) = - index_pending.map(|pending| pending.into_hal(index_buffer)) - { - input_barriers.push(barrier); - } - let index_stride = match mesh.size.index_format.unwrap() { - wgt::IndexFormat::Uint16 => 2, - wgt::IndexFormat::Uint32 => 4, - }; - if mesh.index_buffer_offset.unwrap() % index_stride != 0 { - return Err( - BuildAccelerationStructureError::UnalignedIndexBufferOffset( - index_id, - ), - ); - } - let index_buffer_size = - mesh.size.index_count.unwrap() as u64 * index_stride; - - if mesh.size.index_count.unwrap() % 3 != 0 { - return Err(BuildAccelerationStructureError::InvalidIndexCount( - index_id, - mesh.size.index_count.unwrap(), - )); - } - if index_buffer.size - < mesh.size.index_count.unwrap() as u64 * index_stride - + mesh.index_buffer_offset.unwrap() - { - return Err( - BuildAccelerationStructureError::InsufficientBufferSize( - index_id, - index_buffer.size, - mesh.size.index_count.unwrap() as u64 * index_stride - + mesh.index_buffer_offset.unwrap(), - ), - ); - } - - cmd_buf.buffer_memory_init_actions.extend( - index_buffer.initialization_status.create_action( - index_id, - mesh.index_buffer_offset.unwrap() - ..(mesh.index_buffer_offset.unwrap() + index_buffer_size), - MemoryInitKind::NeedsInitializedMemory, - ), - ); - Some(index_raw) + Some(data) } else { None }; - let transform_buffer = if let Some(transform_id) = mesh.transform_buffer { + let transform_data = if let Some(transform_id) = mesh.transform_buffer { if mesh.transform_buffer_offset.is_none() { return Err( BuildAccelerationStructureError::MissingAssociatedData( @@ -955,105 +962,260 @@ impl Global { ), ); } - let (transform_buffer, transform_pending) = cmd_buf + let data = cmd_buf_data .trackers .buffers .set_single( - &*buffer_guard, - transform_id, - hal::BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, + match buffer_guard.get(transform_id) { + Ok(buffer) => buffer, + Err(_) => { + return Err( + BuildAccelerationStructureError::InvalidBuffer( + transform_id, + ), + ) + } + }, + BufferUses::BOTTOM_LEVEL_ACCELERATION_STRUCTURE_INPUT, ) .ok_or(BuildAccelerationStructureError::InvalidBuffer( transform_id, ))?; - let transform_raw = transform_buffer.raw.as_ref().ok_or( - BuildAccelerationStructureError::InvalidBuffer(transform_id), - )?; - if !transform_buffer.usage.contains(BufferUsages::BLAS_INPUT) { - return Err( - BuildAccelerationStructureError::MissingBlasInputUsageFlag( - transform_id, - ), - ); - } - if let Some(barrier) = - transform_pending.map(|pending| pending.into_hal(transform_buffer)) - { - input_barriers.push(barrier); - } - if mesh.transform_buffer_offset.unwrap() - % wgt::TRANSFORM_BUFFER_ALIGNMENT - != 0 - { - return Err( - BuildAccelerationStructureError::UnalignedTransformBufferOffset( - transform_id, - ), - ); - } - if transform_buffer.size < 48 + mesh.transform_buffer_offset.unwrap() { - return Err( - BuildAccelerationStructureError::InsufficientBufferSize( - transform_id, - transform_buffer.size, - 48 + mesh.transform_buffer_offset.unwrap(), - ), - ); - } - cmd_buf.buffer_memory_init_actions.extend( - transform_buffer.initialization_status.create_action( - transform_id, - mesh.transform_buffer_offset.unwrap() - ..(mesh.index_buffer_offset.unwrap() + 48), - MemoryInitKind::NeedsInitializedMemory, - ), - ); - Some(transform_raw) + Some(data) } else { None }; - let triangles = hal::AccelerationStructureTriangles { - vertex_buffer: Some(vertex_buffer), - vertex_format: mesh.size.vertex_format, - first_vertex: mesh.first_vertex, - vertex_count: mesh.size.vertex_count, - vertex_stride: mesh.vertex_stride, - indices: index_buffer.map(|index_buffer| { - hal::AccelerationStructureTriangleIndices { - format: mesh.size.index_format.unwrap(), - buffer: Some(index_buffer), - offset: mesh.index_buffer_offset.unwrap() as u32, - count: mesh.size.index_count.unwrap(), - } - }), - transform: transform_buffer.map(|transform_buffer| { - hal::AccelerationStructureTriangleTransform { - buffer: transform_buffer, - offset: mesh.transform_buffer_offset.unwrap() as u32, - } - }), - flags: mesh.size.flags, - }; + buf_storage.push(( + vertex_buffer, + vertex_pending, + index_data, + transform_data, + mesh, + None, + )) + } - triangle_entries.push(triangles); + if let Some(last) = buf_storage.last_mut() { + last.5 = Some(blas.clone()); } + } + } + } + + let mut triangle_entries = Vec::>::new(); + let snatch_guard = device.snatchable_lock.read(); + for buf in &mut buf_storage { + let mesh = &buf.4; + let vertex_buffer = { + let vertex_buffer = buf.0.as_ref(); + let vertex_raw = vertex_buffer.raw.get(&snatch_guard).ok_or( + BuildAccelerationStructureError::InvalidBuffer(mesh.vertex_buffer), + )?; + if !vertex_buffer.usage.contains(BufferUsages::BLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingBlasInputUsageFlag( + mesh.vertex_buffer, + )); + } + if let Some(barrier) = buf + .1 + .take() + .map(|pending| pending.into_hal(vertex_buffer, &snatch_guard)) + { + input_barriers.push(barrier); + } + if vertex_buffer.size + < (mesh.size.vertex_count + mesh.first_vertex) as u64 * mesh.vertex_stride + { + return Err(BuildAccelerationStructureError::InsufficientBufferSize( + mesh.vertex_buffer, + vertex_buffer.size, + (mesh.size.vertex_count + mesh.first_vertex) as u64 * mesh.vertex_stride, + )); + } + let vertex_buffer_offset = mesh.first_vertex as u64 * mesh.vertex_stride; + cmd_buf_data.buffer_memory_init_actions.extend( + vertex_buffer.initialization_status.read().create_action( + buffer_guard.get(mesh.vertex_buffer).unwrap(), + vertex_buffer_offset + ..(vertex_buffer_offset + + mesh.size.vertex_count as u64 * mesh.vertex_stride), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + vertex_raw + }; + let index_buffer = if let Some((ref mut index_buffer, ref mut index_pending)) = buf.2 { + let index_id = mesh.index_buffer.as_ref().unwrap(); + let index_raw = index_buffer + .raw + .get(&snatch_guard) + .ok_or(BuildAccelerationStructureError::InvalidBuffer(*index_id))?; + if !index_buffer.usage.contains(BufferUsages::BLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingBlasInputUsageFlag( + *index_id, + )); + } + if let Some(barrier) = index_pending + .take() + .map(|pending| pending.into_hal(index_buffer, &snatch_guard)) + { + input_barriers.push(barrier); + } + let index_stride = match mesh.size.index_format.unwrap() { + wgt::IndexFormat::Uint16 => 2, + wgt::IndexFormat::Uint32 => 4, + }; + if mesh.index_buffer_offset.unwrap() % index_stride != 0 { + return Err(BuildAccelerationStructureError::UnalignedIndexBufferOffset( + *index_id, + )); + } + let index_buffer_size = mesh.size.index_count.unwrap() as u64 * index_stride; - let scratch_buffer_offset = scratch_buffer_blas_size; - scratch_buffer_blas_size += align_to( - blas.size_info.build_scratch_size as u32, - SCRATCH_BUFFER_ALIGNMENT, - ) as u64; - - blas_storage.push(( - blas, - hal::AccelerationStructureEntries::Triangles(triangle_entries), - scratch_buffer_offset, - )) + if mesh.size.index_count.unwrap() % 3 != 0 { + return Err(BuildAccelerationStructureError::InvalidIndexCount( + *index_id, + mesh.size.index_count.unwrap(), + )); + } + if index_buffer.size + < mesh.size.index_count.unwrap() as u64 * index_stride + + mesh.index_buffer_offset.unwrap() + { + return Err(BuildAccelerationStructureError::InsufficientBufferSize( + *index_id, + index_buffer.size, + mesh.size.index_count.unwrap() as u64 * index_stride + + mesh.index_buffer_offset.unwrap(), + )); } + + cmd_buf_data.buffer_memory_init_actions.extend( + index_buffer.initialization_status.read().create_action( + match buffer_guard.get(*index_id) { + Ok(buffer) => buffer, + Err(_) => { + return Err(BuildAccelerationStructureError::InvalidBuffer( + *index_id, + )) + } + }, + mesh.index_buffer_offset.unwrap() + ..(mesh.index_buffer_offset.unwrap() + index_buffer_size), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + Some(index_raw) + } else { + None + }; + let transform_buffer = + if let Some((ref mut transform_buffer, ref mut transform_pending)) = buf.3 { + let transform_id = mesh.transform_buffer.as_ref().unwrap(); + if mesh.transform_buffer_offset.is_none() { + return Err(BuildAccelerationStructureError::MissingAssociatedData( + *transform_id, + )); + } + let transform_raw = transform_buffer.raw.get(&snatch_guard).ok_or( + BuildAccelerationStructureError::InvalidBuffer(*transform_id), + )?; + if !transform_buffer.usage.contains(BufferUsages::BLAS_INPUT) { + return Err(BuildAccelerationStructureError::MissingBlasInputUsageFlag( + *transform_id, + )); + } + if let Some(barrier) = transform_pending + .take() + .map(|pending| pending.into_hal(transform_buffer, &snatch_guard)) + { + input_barriers.push(barrier); + } + if mesh.transform_buffer_offset.unwrap() % wgt::TRANSFORM_BUFFER_ALIGNMENT != 0 + { + return Err( + BuildAccelerationStructureError::UnalignedTransformBufferOffset( + *transform_id, + ), + ); + } + if transform_buffer.size < 48 + mesh.transform_buffer_offset.unwrap() { + return Err(BuildAccelerationStructureError::InsufficientBufferSize( + *transform_id, + transform_buffer.size, + 48 + mesh.transform_buffer_offset.unwrap(), + )); + } + cmd_buf_data.buffer_memory_init_actions.extend( + transform_buffer.initialization_status.read().create_action( + buffer_guard.get(*transform_id).unwrap(), + mesh.transform_buffer_offset.unwrap() + ..(mesh.index_buffer_offset.unwrap() + 48), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + Some(transform_raw) + } else { + None + }; + + let triangles = hal::AccelerationStructureTriangles { + vertex_buffer: Some(vertex_buffer), + vertex_format: mesh.size.vertex_format, + first_vertex: mesh.first_vertex, + vertex_count: mesh.size.vertex_count, + vertex_stride: mesh.vertex_stride, + indices: index_buffer.map(|index_buffer| { + hal::AccelerationStructureTriangleIndices:: { + format: mesh.size.index_format.unwrap(), + buffer: Some(index_buffer), + offset: mesh.index_buffer_offset.unwrap() as u32, + count: mesh.size.index_count.unwrap(), + } + }), + transform: transform_buffer.map(|transform_buffer| { + hal::AccelerationStructureTriangleTransform { + buffer: transform_buffer, + offset: mesh.transform_buffer_offset.unwrap() as u32, + } + }), + flags: mesh.size.flags, + }; + triangle_entries.push(triangles); + if let Some(blas) = buf.5.as_ref() { + let scratch_buffer_offset = scratch_buffer_blas_size; + scratch_buffer_blas_size += align_to( + blas.size_info.build_scratch_size as u32, + SCRATCH_BUFFER_ALIGNMENT, + ) as u64; + + blas_storage.push(( + blas, + hal::AccelerationStructureEntries::Triangles(triangle_entries), + scratch_buffer_offset, + )); + triangle_entries = Vec::new(); } } + let mut tlas_lock_store = Vec::<( + RwLockReadGuard>, + Option, + Arc>, + )>::new(); + + for package in tlas_iter { + let tlas = cmd_buf_data + .trackers + .tlas_s + .add_single(&tlas_guard, package.tlas_id) + .ok_or(BuildAccelerationStructureError::InvalidTlas( + package.tlas_id, + ))?; + tlas_lock_store.push((tlas.instance_buffer.read(), Some(package), tlas.clone())) + } + let mut scratch_buffer_tlas_size = 0; let mut tlas_storage = Vec::<( &Tlas, @@ -1063,15 +1225,13 @@ impl Global { )>::new(); let mut instance_buffer_staging_source = Vec::::new(); - for entry in tlas_iter { - let tlas = cmd_buf - .trackers - .tlas_s - .add_single(&tlas_guard, entry.tlas_id) - .ok_or(BuildAccelerationStructureError::InvalidTlas(entry.tlas_id))?; - + for entry in &mut tlas_lock_store { + let package = entry.1.take().unwrap(); + let tlas = &entry.2; if tlas.raw.is_none() { - return Err(BuildAccelerationStructureError::InvalidTlas(entry.tlas_id)); + return Err(BuildAccelerationStructureError::InvalidTlas( + package.tlas_id, + )); } let scratch_buffer_offset = scratch_buffer_tlas_size; @@ -1085,13 +1245,13 @@ impl Global { let mut dependencies = Vec::new(); let mut instance_count = 0; - for instance in entry.instances.flatten() { + for instance in package.instances.flatten() { if instance.custom_index >= (1u32 << 24u32) { return Err(BuildAccelerationStructureError::TlasInvalidCustomIndex( - entry.tlas_id, + package.tlas_id, )); } - let blas = cmd_buf + let blas = cmd_buf_data .trackers .blas_s .add_single(&blas_guard, instance.blas_id) @@ -1106,14 +1266,14 @@ impl Global { dependencies.push(instance.blas_id); - cmd_buf.blas_actions.push(BlasAction { + cmd_buf_data.blas_actions.push(BlasAction { id: instance.blas_id, kind: crate::ray_tracing::BlasActionKind::Use, }); } - cmd_buf.tlas_actions.push(TlasAction { - id: entry.tlas_id, + cmd_buf_data.tlas_actions.push(TlasAction { + id: package.tlas_id, kind: crate::ray_tracing::TlasActionKind::Build { build_index: build_command_index, dependencies, @@ -1122,7 +1282,7 @@ impl Global { if instance_count > tlas.max_instance_count { return Err(BuildAccelerationStructureError::TlasInstanceCountExceeded( - entry.tlas_id, + package.tlas_id, instance_count, tlas.max_instance_count, )); @@ -1131,7 +1291,7 @@ impl Global { tlas_storage.push(( tlas, hal::AccelerationStructureEntries::Instances(hal::AccelerationStructureInstances { - buffer: Some(tlas.instance_buffer.as_ref().unwrap()), + buffer: Some(entry.0.as_ref().unwrap()), offset: 0, count: instance_count, }), @@ -1146,20 +1306,19 @@ impl Global { let scratch_buffer = unsafe { device - .raw + .raw() .create_buffer(&hal::BufferDescriptor { label: Some("(wgpu) scratch buffer"), size: max(scratch_buffer_blas_size, scratch_buffer_tlas_size), - usage: hal::BufferUses::ACCELERATION_STRUCTURE_SCRATCH, + usage: hal::BufferUses::ACCELERATION_STRUCTURE_SCRATCH | BufferUses::MAP_WRITE, memory_flags: hal::MemoryFlags::empty(), }) .map_err(crate::device::DeviceError::from)? }; - let staging_buffer = if !instance_buffer_staging_source.is_empty() { unsafe { let staging_buffer = device - .raw + .raw() .create_buffer(&hal::BufferDescriptor { label: Some("(wgpu) instance staging buffer"), size: instance_buffer_staging_source.len() as u64, @@ -1167,27 +1326,33 @@ impl Global { memory_flags: hal::MemoryFlags::empty(), }) .map_err(crate::device::DeviceError::from)?; - let mapping = device - .raw + .raw() .map_buffer( &staging_buffer, 0..instance_buffer_staging_source.len() as u64, ) .map_err(crate::device::DeviceError::from)?; - ptr::copy_nonoverlapping( instance_buffer_staging_source.as_ptr(), mapping.ptr.as_ptr(), instance_buffer_staging_source.len(), ); device - .raw + .raw() .unmap_buffer(&staging_buffer) .map_err(crate::device::DeviceError::from)?; assert!(mapping.is_coherent); - - Some(staging_buffer) + let buf = StagingBuffer { + raw: Mutex::new(Some(staging_buffer)), + device: device.clone(), + size: instance_buffer_staging_source.len() as u64, + info: ResourceInfo::new("Raytracing staging buffer"), + is_coherent: mapping.is_coherent, + }; + let staging_fid = hub.staging_buffers.request(); + let stage_buf = staging_fid.init(buf); + Some(stage_buf) } } else { None @@ -1230,29 +1395,32 @@ impl Global { let scratch_buffer_barrier = hal::BufferBarrier:: { buffer: &scratch_buffer, - usage: hal::BufferUses::ACCELERATION_STRUCTURE_SCRATCH - ..hal::BufferUses::ACCELERATION_STRUCTURE_SCRATCH, + usage: BufferUses::ACCELERATION_STRUCTURE_SCRATCH + ..BufferUses::ACCELERATION_STRUCTURE_SCRATCH, }; - let instance_buffer_barriers = tlas_storage.iter().filter_map( - |&(tlas, ref _entries, ref _scratch_buffer_offset, ref range)| { - let size = (range.end - range.start) as u64; - if size == 0 { - None - } else { - Some(hal::BufferBarrier:: { - buffer: tlas.instance_buffer.as_ref().unwrap(), - usage: hal::BufferUses::COPY_DST - ..hal::BufferUses::TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT, - }) - } - }, - ); + let mut lock_vec = Vec::::Buffer>>>>::new(); + + for tlas in &tlas_storage { + let size = (tlas.3.end - tlas.3.start) as u64; + lock_vec.push(if size == 0 { + None + } else { + Some(tlas.0.instance_buffer.read()) + }) + } + + let instance_buffer_barriers = lock_vec.iter().filter_map(|lock| { + lock.as_ref().map(|lock| hal::BufferBarrier:: { + buffer: lock.as_ref().unwrap(), + usage: BufferUses::COPY_DST..BufferUses::TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT, + }) + }); let blas_present = !blas_storage.is_empty(); let tlas_present = !tlas_storage.is_empty(); - let cmd_buf_raw = cmd_buf.encoder.open(); + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_buffers(input_barriers.into_iter()); @@ -1298,7 +1466,7 @@ impl Global { unsafe { if let Some(ref staging_buffer) = staging_buffer { cmd_buf_raw.transition_buffers(iter::once(hal::BufferBarrier:: { - buffer: staging_buffer, + buffer: staging_buffer.raw.lock().as_ref().unwrap(), usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, })); } @@ -1316,8 +1484,14 @@ impl Global { size: NonZeroU64::new(size).unwrap(), }; cmd_buf_raw.copy_buffer_to_buffer( - staging_buffer.as_ref().unwrap(), - tlas.instance_buffer.as_ref().unwrap(), + staging_buffer + .as_ref() + .unwrap() + .raw + .lock() + .as_ref() + .unwrap(), + tlas.instance_buffer.read().as_ref().unwrap(), iter::once(temp), ); } @@ -1340,15 +1514,40 @@ impl Global { if let Some(staging_buffer) = staging_buffer { device .pending_writes + .lock() + .as_mut() + .unwrap() .temp_resources - .push(TempResource::Buffer(staging_buffer)); + .push(TempResource::StagingBuffer(staging_buffer)); } } + let scratch_mapping = unsafe { + device + .raw() + .map_buffer( + &scratch_buffer, + 0..max(scratch_buffer_blas_size, scratch_buffer_tlas_size), + ) + .map_err(crate::device::DeviceError::from)? + }; + + let buf = StagingBuffer { + raw: Mutex::new(Some(scratch_buffer)), + device: device.clone(), + size: max(scratch_buffer_blas_size, scratch_buffer_tlas_size), + info: ResourceInfo::new("Ratracing scratch buffer"), + is_coherent: scratch_mapping.is_coherent, + }; + let staging_fid = hub.staging_buffers.request(); + let stage_buf = staging_fid.init(buf); device .pending_writes + .lock() + .as_mut() + .unwrap() .temp_resources - .push(TempResource::Buffer(scratch_buffer)); + .push(TempResource::StagingBuffer(stage_buf)); Ok(()) } @@ -1367,16 +1566,16 @@ impl BakedCommands { crate::ray_tracing::BlasActionKind::Build(id) => { built.insert(action.id); let blas = blas_guard - .get_mut(action.id) + .get(action.id) .map_err(|_| ValidateBlasActionsError::InvalidBlas(action.id))?; - blas.built_index = Some(id); + *blas.built_index.write() = Some(id); } crate::ray_tracing::BlasActionKind::Use => { if !built.contains(&action.id) { let blas = blas_guard .get(action.id) .map_err(|_| ValidateBlasActionsError::InvalidBlas(action.id))?; - if blas.built_index == None { + if (*blas.built_index.read()).is_none() { return Err(ValidateBlasActionsError::UsedUnbuilt(action.id)); } } @@ -1400,29 +1599,29 @@ impl BakedCommands { dependencies, } => { let tlas = tlas_guard - .get_mut(action.id) + .get(action.id) .map_err(|_| ValidateTlasActionsError::InvalidTlas(action.id))?; - tlas.built_index = Some(build_index); - tlas.dependencies = dependencies; + *tlas.built_index.write() = Some(build_index); + *tlas.dependencies.write() = dependencies; } crate::ray_tracing::TlasActionKind::Use => { let tlas = tlas_guard .get(action.id) .map_err(|_| ValidateTlasActionsError::InvalidTlas(action.id))?; - let tlas_build_index = tlas.built_index; - let dependencies = &tlas.dependencies; + let tlas_build_index = tlas.built_index.read(); + let dependencies = tlas.dependencies.read(); - if tlas_build_index == None { + if (*tlas_build_index).is_none() { return Err(ValidateTlasActionsError::UsedUnbuilt(action.id)); } - for dependency in dependencies { + for dependency in dependencies.deref() { let blas = blas_guard.get(*dependency).map_err(|_| { ValidateTlasActionsError::InvalidBlas(*dependency, action.id) })?; - let blas_build_index = blas.built_index; - if blas_build_index == None { + let blas_build_index = *blas.built_index.read(); + if blas_build_index.is_none() { return Err(ValidateTlasActionsError::UsedUnbuilt(action.id)); } if blas_build_index.unwrap() > tlas_build_index.unwrap() { diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 233fff9ee9..17b921cab3 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -1,5 +1,7 @@ +use crate::resource::Resource; use crate::{ - binding_model::{BindError, BindGroupLayouts}, + api_log, + binding_model::BindError, command::{ self, bind::Binder, @@ -10,24 +12,23 @@ use crate::{ RenderCommand, RenderCommandError, StateChange, }, device::{ - AttachmentData, Device, MissingDownlevelFlags, MissingFeatures, + AttachmentData, Device, DeviceError, MissingDownlevelFlags, MissingFeatures, RenderPassCompatibilityCheckType, RenderPassCompatibilityError, RenderPassContext, }, error::{ErrorFormatter, PrettyError}, global::Global, hal_api::HalApi, - hub::Token, - id, + hal_label, id, identity::GlobalIdentityHandlerFactory, init_tracker::{MemoryInitKind, TextureInitRange, TextureInitTrackerAction}, pipeline::{self, PipelineFlags}, resource::{Buffer, QuerySet, Texture, TextureView, TextureViewNotRenderableReason}, storage::Storage, - track::{TextureSelector, UsageConflict, UsageScope}, + track::{TextureSelector, Tracker, UsageConflict, UsageScope}, validation::{ check_buffer_usage, check_texture_usage, MissingBufferUsageError, MissingTextureUsageError, }, - Label, Stored, + Label, }; use arrayvec::ArrayVec; @@ -43,9 +44,13 @@ use serde::Deserialize; #[cfg(any(feature = "serial-pass", feature = "trace"))] use serde::Serialize; +use std::sync::Arc; use std::{borrow::Cow, fmt, iter, marker::PhantomData, mem, num::NonZeroU32, ops::Range, str}; -use super::{memory_init::TextureSurfaceDiscard, CommandBufferTextureMemoryActions}; +use super::{ + memory_init::TextureSurfaceDiscard, CommandBufferTextureMemoryActions, CommandEncoder, + QueryResetMap, +}; /// Operation to perform to the output attachment at the start of a renderpass. #[repr(C)] @@ -313,10 +318,10 @@ impl OptionalState { #[derive(Debug, Default)] struct IndexState { - bound_buffer_view: Option<(id::Valid, Range)>, + bound_buffer_view: Option<(id::BufferId, Range)>, format: Option, pipeline_format: Option, - limit: u32, + limit: u64, } impl IndexState { @@ -330,7 +335,8 @@ impl IndexState { IndexFormat::Uint16 => 1, IndexFormat::Uint32 => 2, }; - ((range.end - range.start) >> shift) as u32 + + (range.end - range.start) >> shift } None => 0, } @@ -364,11 +370,11 @@ impl VertexBufferState { struct VertexState { inputs: ArrayVec, /// Length of the shortest vertex rate vertex buffer - vertex_limit: u32, + vertex_limit: u64, /// Buffer slot which the shortest vertex rate vertex buffer is bound to vertex_limit_slot: u32, /// Length of the shortest instance rate vertex buffer - instance_limit: u32, + instance_limit: u64, /// Buffer slot which the shortest instance rate vertex buffer is bound to instance_limit_slot: u32, /// Total amount of buffers required by the pipeline. @@ -377,13 +383,16 @@ struct VertexState { impl VertexState { fn update_limits(&mut self) { - self.vertex_limit = u32::MAX; - self.instance_limit = u32::MAX; + // Ensure that the limits are always smaller than u32::MAX so that + // interger overlows can be prevented via saturating additions. + let max = u32::MAX as u64; + self.vertex_limit = max; + self.instance_limit = max; for (idx, vbs) in self.inputs.iter().enumerate() { if vbs.step.stride == 0 || !vbs.bound { continue; } - let limit = (vbs.total_size / vbs.step.stride) as u32; + let limit = vbs.total_size / vbs.step.stride; match vbs.step.mode { VertexStepMode::Vertex => { if limit < self.vertex_limit { @@ -409,9 +418,9 @@ impl VertexState { } #[derive(Debug)] -struct State { +struct State { pipeline_flags: PipelineFlags, - binder: Binder, + binder: Binder, blend_constant: OptionalState, stencil_reference: u32, pipeline: Option, @@ -420,12 +429,8 @@ struct State { debug_scope_depth: u32, } -impl State { - fn is_ready( - &self, - indexed: bool, - bind_group_layouts: &BindGroupLayouts, - ) -> Result<(), DrawError> { +impl State { + fn is_ready(&self, indexed: bool) -> Result<(), DrawError> { // Determine how many vertex buffers have already been bound let vertex_buffer_count = self.vertex.inputs.iter().take_while(|v| v.bound).count() as u32; // Compare with the needed quantity @@ -435,11 +440,12 @@ impl State { }); } - let bind_mask = self.binder.invalid_mask(bind_group_layouts); + let bind_mask = self.binder.invalid_mask(); if bind_mask != 0 { //let (expected, provided) = self.binder.entries[index as usize].info(); return Err(DrawError::IncompatibleBindGroup { index: bind_mask.trailing_zeros(), + diff: self.binder.bgl_diff(), }); } if self.pipeline.is_none() { @@ -519,6 +525,8 @@ pub enum ColorAttachmentError { /// Error encountered when performing a render pass. #[derive(Clone, Debug, Error)] pub enum RenderPassErrorInner { + #[error(transparent)] + Device(DeviceError), #[error(transparent)] ColorAttachment(#[from] ColorAttachmentError), #[error(transparent)] @@ -570,8 +578,10 @@ pub enum RenderPassErrorInner { }, #[error("Surface texture is dropped before the render pass is finished")] SurfaceTextureDropped, - #[error("Not enough memory left")] + #[error("Not enough memory left for render pass")] OutOfMemory, + #[error("The bind group at index {0:?} is invalid")] + InvalidBindGroup(usize), #[error("Unable to clear non-present/read-only depth")] InvalidDepthOps, #[error("Unable to clear non-present/read-only stencil")] @@ -640,6 +650,11 @@ impl PrettyError for RenderPassErrorInner { if let Self::InvalidAttachment(id) = *self { fmt.texture_view_label_with_key(&id, "attachment"); }; + if let Self::Draw(DrawError::IncompatibleBindGroup { diff, .. }) = self { + for d in diff { + fmt.note(&d); + } + }; } } @@ -655,6 +670,12 @@ impl From for RenderPassErrorInner { } } +impl From for RenderPassErrorInner { + fn from(error: DeviceError) -> Self { + Self::Device(error) + } +} + /// Error encountered when performing a render pass. #[derive(Clone, Debug, Error)] #[error("{scope}")] @@ -684,16 +705,16 @@ where } } -struct RenderAttachment<'a> { - texture_id: &'a Stored, +struct RenderAttachment<'a, A: HalApi> { + texture: Arc>, selector: &'a TextureSelector, usage: hal::TextureUses, } -impl TextureView { - fn to_render_attachment(&self, usage: hal::TextureUses) -> RenderAttachment { +impl TextureView { + fn to_render_attachment(&self, usage: hal::TextureUses) -> RenderAttachment { RenderAttachment { - texture_id: &self.parent_id, + texture: self.parent.read().as_ref().unwrap().clone(), selector: &self.selector, usage, } @@ -707,13 +728,13 @@ struct RenderPassInfo<'a, A: HalApi> { context: RenderPassContext, usage_scope: UsageScope, /// All render attachments, including depth/stencil - render_attachments: AttachmentDataVec>, + render_attachments: AttachmentDataVec>, is_depth_read_only: bool, is_stencil_read_only: bool, extent: wgt::Extent3d, _phantom: PhantomData, - pending_discard_init_fixups: SurfacesInDiscardState, + pending_discard_init_fixups: SurfacesInDiscardState, divergent_discarded_depth_stencil_aspect: Option<(wgt::TextureAspect, &'a TextureView)>, multiview: Option, } @@ -721,27 +742,24 @@ struct RenderPassInfo<'a, A: HalApi> { impl<'a, A: HalApi> RenderPassInfo<'a, A> { fn add_pass_texture_init_actions( channel: &PassChannel, - texture_memory_actions: &mut CommandBufferTextureMemoryActions, + texture_memory_actions: &mut CommandBufferTextureMemoryActions, view: &TextureView, - texture_guard: &Storage, id::TextureId>, - pending_discard_init_fixups: &mut SurfacesInDiscardState, + pending_discard_init_fixups: &mut SurfacesInDiscardState, ) { if channel.load_op == LoadOp::Load { pending_discard_init_fixups.extend(texture_memory_actions.register_init_action( &TextureInitTrackerAction { - id: view.parent_id.value.0, + texture: view.parent.read().as_ref().unwrap().clone(), range: TextureInitRange::from(view.selector.clone()), // Note that this is needed even if the target is discarded, kind: MemoryInitKind::NeedsInitializedMemory, }, - texture_guard, )); } else if channel.store_op == StoreOp::Store { // Clear + Store texture_memory_actions.register_implicit_init( - view.parent_id.value, + view.parent.read().as_ref().unwrap(), TextureInitRange::from(view.selector.clone()), - texture_guard, ); } if channel.store_op == StoreOp::Discard { @@ -749,7 +767,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { // discard right away be alright since the texture can't be used // during the pass anyways texture_memory_actions.discard(TextureSurfaceDiscard { - texture: view.parent_id.value.0, + texture: view.parent.read().as_ref().unwrap().clone(), mip_level: view.selector.mips.start, layer: view.selector.layers.start, }); @@ -763,7 +781,10 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>, timestamp_writes: Option<&RenderPassTimestampWrites>, occlusion_query_set: Option, - cmd_buf: &mut CommandBuffer, + encoder: &mut CommandEncoder, + trackers: &mut Tracker, + texture_memory_actions: &mut CommandBufferTextureMemoryActions, + pending_query_resets: &mut QueryResetMap, view_guard: &'a Storage, id::TextureViewId>, buffer_guard: &'a Storage, id::BufferId>, texture_guard: &'a Storage, id::TextureId>, @@ -777,7 +798,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { let mut is_depth_read_only = false; let mut is_stencil_read_only = false; - let mut render_attachments = AttachmentDataVec::::new(); + let mut render_attachments = AttachmentDataVec::>::new(); let mut discarded_surfaces = AttachmentDataVec::new(); let mut pending_discard_init_fixups = SurfacesInDiscardState::new(); let mut divergent_discarded_depth_stencil_aspect = None; @@ -857,8 +878,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { let mut depth_stencil = None; if let Some(at) = depth_stencil_attachment { - let view: &TextureView = cmd_buf - .trackers + let view: &TextureView = trackers .views .add_single(view_guard, at.view) .ok_or(RenderPassErrorInner::InvalidAttachment(at.view))?; @@ -878,17 +898,15 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { { Self::add_pass_texture_init_actions( &at.depth, - &mut cmd_buf.texture_memory_actions, + texture_memory_actions, view, - texture_guard, &mut pending_discard_init_fixups, ); } else if !ds_aspects.contains(hal::FormatAspects::DEPTH) { Self::add_pass_texture_init_actions( &at.stencil, - &mut cmd_buf.texture_memory_actions, + texture_memory_actions, view, - texture_guard, &mut pending_discard_init_fixups, ); } else { @@ -917,14 +935,11 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { at.depth.load_op == LoadOp::Load || at.stencil.load_op == LoadOp::Load; if need_init_beforehand { pending_discard_init_fixups.extend( - cmd_buf.texture_memory_actions.register_init_action( - &TextureInitTrackerAction { - id: view.parent_id.value.0, - range: TextureInitRange::from(view.selector.clone()), - kind: MemoryInitKind::NeedsInitializedMemory, - }, - texture_guard, - ), + texture_memory_actions.register_init_action(&TextureInitTrackerAction { + texture: view.parent.read().as_ref().unwrap().clone(), + range: TextureInitRange::from(view.selector.clone()), + kind: MemoryInitKind::NeedsInitializedMemory, + }), ); } @@ -938,10 +953,9 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { // (possible optimization: Delay and potentially drop this zeroing) if at.depth.store_op != at.stencil.store_op { if !need_init_beforehand { - cmd_buf.texture_memory_actions.register_implicit_init( - view.parent_id.value, + texture_memory_actions.register_implicit_init( + view.parent.read().as_ref().unwrap(), TextureInitRange::from(view.selector.clone()), - texture_guard, ); } divergent_discarded_depth_stencil_aspect = Some(( @@ -955,7 +969,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { } else if at.depth.store_op == StoreOp::Discard { // Both are discarded using the regular path. discarded_surfaces.push(TextureSurfaceDiscard { - texture: view.parent_id.value.0, + texture: view.parent.read().as_ref().unwrap().clone(), mip_level: view.selector.mips.start, layer: view.selector.layers.start, }); @@ -979,7 +993,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { depth_stencil = Some(hal::DepthStencilAttachment { target: hal::Attachment { - view: &view.raw, + view: view.raw(), usage, }, depth_ops: at.depth.hal_ops(), @@ -995,8 +1009,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { colors.push(None); continue; }; - let color_view: &TextureView = cmd_buf - .trackers + let color_view: &TextureView = trackers .views .add_single(view_guard, at.view) .ok_or(RenderPassErrorInner::InvalidAttachment(at.view))?; @@ -1021,9 +1034,8 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { Self::add_pass_texture_init_actions( &at.channel, - &mut cmd_buf.texture_memory_actions, + texture_memory_actions, color_view, - texture_guard, &mut pending_discard_init_fixups, ); render_attachments @@ -1031,8 +1043,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { let mut hal_resolve_target = None; if let Some(resolve_target) = at.resolve_target { - let resolve_view: &TextureView = cmd_buf - .trackers + let resolve_view: &TextureView = trackers .views .add_single(view_guard, resolve_target) .ok_or(RenderPassErrorInner::InvalidAttachment(resolve_target))?; @@ -1083,23 +1094,22 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { }); } - cmd_buf.texture_memory_actions.register_implicit_init( - resolve_view.parent_id.value, + texture_memory_actions.register_implicit_init( + resolve_view.parent.read().as_ref().unwrap(), TextureInitRange::from(resolve_view.selector.clone()), - texture_guard, ); render_attachments .push(resolve_view.to_render_attachment(hal::TextureUses::COLOR_TARGET)); hal_resolve_target = Some(hal::Attachment { - view: &resolve_view.raw, + view: resolve_view.raw(), usage: hal::TextureUses::COLOR_TARGET, }); } colors.push(Some(hal::ColorAttachment { target: hal::Attachment { - view: &color_view.raw, + view: color_view.raw(), usage: hal::TextureUses::COLOR_TARGET, }, resolve_target: hal_resolve_target, @@ -1136,25 +1146,20 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { }; let timestamp_writes = if let Some(tw) = timestamp_writes { - let query_set = cmd_buf - .trackers + let query_set = trackers .query_sets .add_single(query_set_guard, tw.query_set) .ok_or(RenderPassErrorInner::InvalidQuerySet(tw.query_set))?; if let Some(index) = tw.beginning_of_pass_write_index { - cmd_buf - .pending_query_resets - .use_query_set(tw.query_set, query_set, index); + pending_query_resets.use_query_set(tw.query_set, query_set, index); } if let Some(index) = tw.end_of_pass_write_index { - cmd_buf - .pending_query_resets - .use_query_set(tw.query_set, query_set, index); + pending_query_resets.use_query_set(tw.query_set, query_set, index); } Some(hal::RenderPassTimestampWrites { - query_set: &query_set.raw, + query_set: query_set.raw.as_ref().unwrap(), beginning_of_pass_write_index: tw.beginning_of_pass_write_index, end_of_pass_write_index: tw.end_of_pass_write_index, }) @@ -1163,19 +1168,18 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { }; let occlusion_query_set = if let Some(occlusion_query_set) = occlusion_query_set { - let query_set = cmd_buf - .trackers + let query_set = trackers .query_sets .add_single(query_set_guard, occlusion_query_set) .ok_or(RenderPassErrorInner::InvalidQuerySet(occlusion_query_set))?; - Some(&query_set.raw) + Some(query_set.raw.as_ref().unwrap()) } else { None }; let hal_desc = hal::RenderPassDescriptor { - label, + label: hal_label(label, device.instance_flags), extent, sample_count, color_attachments: &colors, @@ -1185,7 +1189,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { occlusion_query_set, }; unsafe { - cmd_buf.encoder.raw.begin_render_pass(&hal_desc); + encoder.raw.begin_render_pass(&hal_desc); }; Ok(Self { @@ -1205,31 +1209,21 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { fn finish( mut self, raw: &mut A::CommandEncoder, - texture_guard: &Storage, id::TextureId>, - ) -> Result<(UsageScope, SurfacesInDiscardState), RenderPassErrorInner> { + ) -> Result<(UsageScope, SurfacesInDiscardState), RenderPassErrorInner> { profiling::scope!("RenderPassInfo::finish"); unsafe { raw.end_render_pass(); } for ra in self.render_attachments { - if !texture_guard.contains(ra.texture_id.value.0) { - return Err(RenderPassErrorInner::SurfaceTextureDropped); - } - let texture = &texture_guard[ra.texture_id.value]; + let texture = &ra.texture; check_texture_usage(texture.desc.usage, TextureUsages::RENDER_ATTACHMENT)?; // the tracker set of the pass is always in "extend" mode unsafe { self.usage_scope .textures - .merge_single( - texture_guard, - ra.texture_id.value, - Some(ra.selector.clone()), - &ra.texture_id.ref_count, - ra.usage, - ) + .merge_single(texture, Some(ra.selector.clone()), ra.usage) .map_err(UsageConflict::from)? }; } @@ -1262,7 +1256,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { color_attachments: &[], depth_stencil_attachment: Some(hal::DepthStencilAttachment { target: hal::Attachment { - view: &view.raw, + view: view.raw(), usage: hal::TextureUses::DEPTH_STENCIL_WRITE, }, depth_ops, @@ -1311,31 +1305,31 @@ impl Global { timestamp_writes: Option<&RenderPassTimestampWrites>, occlusion_query_set_id: Option, ) -> Result<(), RenderPassError> { - profiling::scope!("CommandEncoder::run_render_pass"); - let init_scope = PassErrorScope::Pass(encoder_id); + profiling::scope!( + "CommandEncoder::run_render_pass {}", + base.label.unwrap_or("") + ); - let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); + let discard_hal_labels = self + .instance + .flags + .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS); + let label = hal_label(base.label, self.instance.flags); - let (scope, pending_discard_init_fixups) = { - let (mut cmb_guard, mut token) = hub.command_buffers.write(&mut token); + let pass_scope = PassErrorScope::Pass(encoder_id); - // Spell out the type, to placate rust-analyzer. - // https://github.com/rust-lang/rust-analyzer/issues/12247 - let cmd_buf: &mut CommandBuffer = - CommandBuffer::get_encoder_mut(&mut *cmb_guard, encoder_id) - .map_pass_err(init_scope)?; + let hub = A::hub(self); - // We automatically keep extending command buffers over time, and because - // we want to insert a command buffer _before_ what we're about to record, - // we need to make sure to close the previous one. - cmd_buf.encoder.close(); - // We will reset this to `Recording` if we succeed, acts as a fail-safe. - cmd_buf.status = CommandEncoderStatus::Error; + let cmd_buf = CommandBuffer::get_encoder(hub, encoder_id).map_pass_err(pass_scope)?; + let device = &cmd_buf.device; + let snatch_guard = device.snatchable_lock.read(); + + let (scope, pending_discard_init_fixups) = { + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(crate::device::trace::Command::RunRenderPass { base: BasePass::from_ref(base), target_colors: color_attachments.to_vec(), @@ -1345,19 +1339,33 @@ impl Global { }); } - let device = &device_guard[cmd_buf.device_id.value]; - cmd_buf.encoder.open_pass(base.label); + if !device.is_valid() { + return Err(DeviceError::Lost).map_pass_err(pass_scope); + } + + let encoder = &mut cmd_buf_data.encoder; + let status = &mut cmd_buf_data.status; + let tracker = &mut cmd_buf_data.trackers; + let buffer_memory_init_actions = &mut cmd_buf_data.buffer_memory_init_actions; + let texture_memory_actions = &mut cmd_buf_data.texture_memory_actions; + let pending_query_resets = &mut cmd_buf_data.pending_query_resets; - let (bundle_guard, mut token) = hub.render_bundles.read(&mut token); - let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(&mut token); - let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token); - let (render_pipeline_guard, mut token) = hub.render_pipelines.read(&mut token); - let (query_set_guard, mut token) = hub.query_sets.read(&mut token); - let (bind_group_layout_guard, mut token) = hub.bind_group_layouts.read(&mut token); - let (buffer_guard, mut token) = hub.buffers.read(&mut token); - let (texture_guard, mut token) = hub.textures.read(&mut token); - let (view_guard, mut token) = hub.texture_views.read(&mut token); - let (tlas_guard, _) = hub.tlas_s.read(&mut token); + // We automatically keep extending command buffers over time, and because + // we want to insert a command buffer _before_ what we're about to record, + // we need to make sure to close the previous one. + encoder.close().map_pass_err(pass_scope)?; + // We will reset this to `Recording` if we succeed, acts as a fail-safe. + *status = CommandEncoderStatus::Error; + encoder.open_pass(label).map_pass_err(pass_scope)?; + + let bundle_guard = hub.render_bundles.read(); + let bind_group_guard = hub.bind_groups.read(); + let render_pipeline_guard = hub.render_pipelines.read(); + let query_set_guard = hub.query_sets.read(); + let buffer_guard = hub.buffers.read(); + let texture_guard = hub.textures.read(); + let view_guard = hub.texture_views.read(); + let tlas_guard = hub.tlas_s.read(); log::trace!( "Encoding render pass begin in command buffer {:?}", @@ -1366,20 +1374,23 @@ impl Global { let mut info = RenderPassInfo::start( device, - base.label, + label, color_attachments, depth_stencil_attachment, timestamp_writes, occlusion_query_set_id, - cmd_buf, + encoder, + tracker, + texture_memory_actions, + pending_query_resets, &*view_guard, &*buffer_guard, &*texture_guard, &*query_set_guard, ) - .map_pass_err(init_scope)?; + .map_pass_err(pass_scope)?; - cmd_buf.trackers.set_size( + tracker.set_size( Some(&*buffer_guard), Some(&*texture_guard), Some(&*view_guard), @@ -1393,7 +1404,7 @@ impl Global { Some(&*tlas_guard), ); - let raw = &mut cmd_buf.encoder.raw; + let raw = &mut encoder.raw; let mut state = State { pipeline_flags: PipelineFlags::empty(), @@ -1417,6 +1428,8 @@ impl Global { num_dynamic_offsets, bind_group_id, } => { + api_log!("RenderPass::set_bind_group {index} {bind_group_id:?}"); + let scope = PassErrorScope::SetBindGroup(bind_group_id); let max_bind_groups = device.limits.max_bind_groups; if index >= max_bind_groups { @@ -1429,17 +1442,21 @@ impl Global { temp_offsets.clear(); temp_offsets.extend_from_slice( - &base.dynamic_offsets[dynamic_offset_count - ..dynamic_offset_count + (num_dynamic_offsets as usize)], + &base.dynamic_offsets + [dynamic_offset_count..dynamic_offset_count + num_dynamic_offsets], ); - dynamic_offset_count += num_dynamic_offsets as usize; + dynamic_offset_count += num_dynamic_offsets; - let bind_group: &crate::binding_model::BindGroup = cmd_buf - .trackers + let bind_group = tracker .bind_groups .add_single(&*bind_group_guard, bind_group_id) .ok_or(RenderCommandError::InvalidBindGroup(bind_group_id)) .map_pass_err(scope)?; + + if bind_group.device.as_info().id() != device.as_info().id() { + return Err(DeviceError::WrongDevice).map_pass_err(scope); + } + bind_group .validate_dynamic_bindings(index, &temp_offsets, &cmd_buf.limits) .map_pass_err(scope)?; @@ -1447,74 +1464,82 @@ impl Global { // merge the resource tracker in unsafe { info.usage_scope - .merge_bind_group(&*texture_guard, &bind_group.used) + .merge_bind_group(&bind_group.used) .map_pass_err(scope)?; } //Note: stateless trackers are not merged: the lifetime reference // is held to the bind group itself. - cmd_buf.buffer_memory_init_actions.extend( + buffer_memory_init_actions.extend( bind_group.used_buffer_ranges.iter().filter_map(|action| { - match buffer_guard.get(action.id) { - Ok(buffer) => buffer.initialization_status.check_action(action), - Err(_) => None, - } + action + .buffer + .initialization_status + .read() + .check_action(action) }), ); for action in bind_group.used_texture_ranges.iter() { - info.pending_discard_init_fixups.extend( - cmd_buf - .texture_memory_actions - .register_init_action(action, &texture_guard), - ); + info.pending_discard_init_fixups + .extend(texture_memory_actions.register_init_action(action)); } - cmd_buf.tlas_actions.extend( - bind_group.used.acceleration_structures.used().map(|id| { - cmd_buf.trackers.tlas_s.add_single(&tlas_guard, id.0); + let mapped_used_resources = bind_group + .used + .acceleration_structures + .used_resources() + .map(|blas| { + tracker.tlas_s.add_single(&tlas_guard, blas.as_info().id()); + crate::ray_tracing::TlasAction { - id: id.0, + id: blas.as_info().id(), kind: crate::ray_tracing::TlasActionKind::Use, } - }), - ); + }); - let pipeline_layout_id = state.binder.pipeline_layout_id; - let entries = state.binder.assign_group( - index as usize, - id::Valid(bind_group_id), - bind_group, - &temp_offsets, - ); - if !entries.is_empty() { - let pipeline_layout = - &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw; + cmd_buf_data.tlas_actions.extend(mapped_used_resources); + + let pipeline_layout = state.binder.pipeline_layout.clone(); + let entries = + state + .binder + .assign_group(index as usize, bind_group, &temp_offsets); + if !entries.is_empty() && pipeline_layout.is_some() { + let pipeline_layout = pipeline_layout.as_ref().unwrap().raw(); for (i, e) in entries.iter().enumerate() { - let raw_bg = - &bind_group_guard[e.group_id.as_ref().unwrap().value].raw; - - unsafe { - raw.set_bind_group( - pipeline_layout, - index + i as u32, - raw_bg, - &e.dynamic_offsets, - ); + if let Some(group) = e.group.as_ref() { + let raw_bg = group + .raw(&snatch_guard) + .ok_or(RenderPassErrorInner::InvalidBindGroup(i)) + .map_pass_err(scope)?; + unsafe { + raw.set_bind_group( + pipeline_layout, + index + i as u32, + raw_bg, + &e.dynamic_offsets, + ); + } } } } } RenderCommand::SetPipeline(pipeline_id) => { + api_log!("RenderPass::set_pipeline {pipeline_id:?}"); + let scope = PassErrorScope::SetPipelineRender(pipeline_id); state.pipeline = Some(pipeline_id); - let pipeline: &pipeline::RenderPipeline = cmd_buf - .trackers + let pipeline: &pipeline::RenderPipeline = tracker .render_pipelines .add_single(&*render_pipeline_guard, pipeline_id) .ok_or(RenderCommandError::InvalidPipeline(pipeline_id)) .map_pass_err(scope)?; + if pipeline.device.as_info().id() != device.as_info().id() { + return Err(DeviceError::WrongDevice).map_pass_err(scope); + } + info.context .check_compatible( &pipeline.pass_context, @@ -1539,7 +1564,7 @@ impl Global { .require(pipeline.flags.contains(PipelineFlags::BLEND_CONSTANT)); unsafe { - raw.set_render_pipeline(&pipeline.raw); + raw.set_render_pipeline(pipeline.raw()); } if pipeline.flags.contains(PipelineFlags::STENCIL_REFERENCE) { @@ -1549,33 +1574,40 @@ impl Global { } // Rebind resource - if state.binder.pipeline_layout_id != Some(pipeline.layout_id.value) { - let pipeline_layout = &pipeline_layout_guard[pipeline.layout_id.value]; - + if state.binder.pipeline_layout.is_none() + || !state + .binder + .pipeline_layout + .as_ref() + .unwrap() + .is_equal(&pipeline.layout) + { let (start_index, entries) = state.binder.change_pipeline_layout( - &*pipeline_layout_guard, - pipeline.layout_id.value, + &pipeline.layout, &pipeline.late_sized_buffer_groups, ); if !entries.is_empty() { for (i, e) in entries.iter().enumerate() { - let raw_bg = - &bind_group_guard[e.group_id.as_ref().unwrap().value].raw; - - unsafe { - raw.set_bind_group( - &pipeline_layout.raw, - start_index as u32 + i as u32, - raw_bg, - &e.dynamic_offsets, - ); + if let Some(group) = e.group.as_ref() { + let raw_bg = group + .raw(&snatch_guard) + .ok_or(RenderPassErrorInner::InvalidBindGroup(i)) + .map_pass_err(scope)?; + unsafe { + raw.set_bind_group( + pipeline.layout.raw(), + start_index as u32 + i as u32, + raw_bg, + &e.dynamic_offsets, + ); + } } } } // Clear push constant ranges let non_overlapping = super::bind::compute_nonoverlapping_ranges( - &pipeline_layout.push_constant_ranges, + &pipeline.layout.push_constant_ranges, ); for range in non_overlapping { let offset = range.range.start; @@ -1585,7 +1617,7 @@ impl Global { size_bytes, |clear_offset, clear_data| unsafe { raw.set_push_constants( - &pipeline_layout.raw, + pipeline.layout.raw(), range.stages, clear_offset, clear_data, @@ -1624,17 +1656,24 @@ impl Global { offset, size, } => { + api_log!("RenderPass::set_index_buffer {buffer_id:?}"); + let scope = PassErrorScope::SetIndexBuffer(buffer_id); - let buffer: &Buffer = info + let buffer = info .usage_scope .buffers .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDEX) .map_pass_err(scope)?; + + if buffer.device.as_info().id() != device.as_info().id() { + return Err(DeviceError::WrongDevice).map_pass_err(scope); + } + check_buffer_usage(buffer.usage, BufferUsages::INDEX) .map_pass_err(scope)?; let buf_raw = buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(RenderCommandError::DestroyedBuffer(buffer_id)) .map_pass_err(scope)?; @@ -1642,14 +1681,14 @@ impl Global { Some(s) => offset + s.get(), None => buffer.size, }; - state.index.bound_buffer_view = Some((id::Valid(buffer_id), offset..end)); + state.index.bound_buffer_view = Some((buffer_id, offset..end)); state.index.format = Some(index_format); state.index.update_limit(); - cmd_buf.buffer_memory_init_actions.extend( - buffer.initialization_status.create_action( - buffer_id, + buffer_memory_init_actions.extend( + buffer.initialization_status.read().create_action( + buffer, offset..end, MemoryInitKind::NeedsInitializedMemory, ), @@ -1670,17 +1709,33 @@ impl Global { offset, size, } => { + api_log!("RenderPass::set_vertex_buffer {slot} {buffer_id:?}"); + let scope = PassErrorScope::SetVertexBuffer(buffer_id); - let buffer: &Buffer = info + let buffer = info .usage_scope .buffers .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::VERTEX) .map_pass_err(scope)?; + + if buffer.device.as_info().id() != device.as_info().id() { + return Err(DeviceError::WrongDevice).map_pass_err(scope); + } + + let max_vertex_buffers = device.limits.max_vertex_buffers; + if slot >= max_vertex_buffers { + return Err(RenderCommandError::VertexBufferIndexOutOfRange { + index: slot, + max: max_vertex_buffers, + }) + .map_pass_err(scope); + } + check_buffer_usage(buffer.usage, BufferUsages::VERTEX) .map_pass_err(scope)?; let buf_raw = buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(RenderCommandError::DestroyedBuffer(buffer_id)) .map_pass_err(scope)?; @@ -1698,9 +1753,9 @@ impl Global { }; vertex_state.bound = true; - cmd_buf.buffer_memory_init_actions.extend( - buffer.initialization_status.create_action( - buffer_id, + buffer_memory_init_actions.extend( + buffer.initialization_status.read().create_action( + buffer, offset..(offset + vertex_state.total_size), MemoryInitKind::NeedsInitializedMemory, ), @@ -1717,6 +1772,8 @@ impl Global { state.vertex.update_limits(); } RenderCommand::SetBlendConstant(ref color) => { + api_log!("RenderPass::set_blend_constant"); + state.blend_constant = OptionalState::Set; let array = [ color.r as f32, @@ -1729,6 +1786,8 @@ impl Global { } } RenderCommand::SetStencilReference(value) => { + api_log!("RenderPass::set_stencil_reference {value}"); + state.stencil_reference = value; if state .pipeline_flags @@ -1744,6 +1803,8 @@ impl Global { depth_min, depth_max, } => { + api_log!("RenderPass::set_viewport {rect:?}"); + let scope = PassErrorScope::SetViewport; if rect.x < 0.0 || rect.y < 0.0 @@ -1780,6 +1841,8 @@ impl Global { size_bytes, values_offset, } => { + api_log!("RenderPass::set_push_constants"); + let scope = PassErrorScope::SetPushConstant; let values_offset = values_offset .ok_or(RenderPassErrorInner::InvalidValuesOffset) @@ -1791,12 +1854,12 @@ impl Global { let data_slice = &base.push_constant_data[(values_offset as usize)..values_end_offset]; - let pipeline_layout_id = state + let pipeline_layout = state .binder - .pipeline_layout_id + .pipeline_layout + .as_ref() .ok_or(DrawError::MissingPipeline) .map_pass_err(scope)?; - let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id]; pipeline_layout .validate_push_constant_ranges(stages, offset, end_offset_bytes) @@ -1804,10 +1867,17 @@ impl Global { .map_pass_err(scope)?; unsafe { - raw.set_push_constants(&pipeline_layout.raw, stages, offset, data_slice) + raw.set_push_constants( + pipeline_layout.raw(), + stages, + offset, + data_slice, + ) } } RenderCommand::SetScissor(ref rect) => { + api_log!("RenderPass::set_scissor_rect {rect:?}"); + let scope = PassErrorScope::SetScissorRect; if rect.x + rect.w > info.extent.width || rect.y + rect.h > info.extent.height @@ -1831,17 +1901,19 @@ impl Global { first_vertex, first_instance, } => { + api_log!( + "RenderPass::draw {vertex_count} {instance_count} {first_vertex} {first_instance}" + ); + let indexed = false; let scope = PassErrorScope::Draw { indexed, indirect: false, pipeline: state.pipeline, }; - state - .is_ready::(indexed, &bind_group_layout_guard) - .map_pass_err(scope)?; + state.is_ready(indexed).map_pass_err(scope)?; - let last_vertex = first_vertex + vertex_count; + let last_vertex = first_vertex as u64 + vertex_count as u64; let vertex_limit = state.vertex.vertex_limit; if last_vertex > vertex_limit { return Err(DrawError::VertexBeyondLimit { @@ -1851,7 +1923,7 @@ impl Global { }) .map_pass_err(scope); } - let last_instance = first_instance + instance_count; + let last_instance = first_instance as u64 + instance_count as u64; let instance_limit = state.vertex.instance_limit; if last_instance > instance_limit { return Err(DrawError::InstanceBeyondLimit { @@ -1873,19 +1945,17 @@ impl Global { base_vertex, first_instance, } => { + api_log!("RenderPass::draw_indexed {index_count} {instance_count} {first_index} {base_vertex} {first_instance}"); + let indexed = true; let scope = PassErrorScope::Draw { indexed, indirect: false, pipeline: state.pipeline, }; - state - .is_ready::(indexed, &*bind_group_layout_guard) - .map_pass_err(scope)?; + state.is_ready(indexed).map_pass_err(scope)?; - //TODO: validate that base_vertex + max_index() is - // within the provided range - let last_index = first_index + index_count; + let last_index = first_index as u64 + index_count as u64; let index_limit = state.index.limit; if last_index > index_limit { return Err(DrawError::IndexBeyondLimit { @@ -1894,7 +1964,7 @@ impl Global { }) .map_pass_err(scope); } - let last_instance = first_instance + instance_count; + let last_instance = first_instance as u64 + instance_count as u64; let instance_limit = state.vertex.instance_limit; if last_instance > instance_limit { return Err(DrawError::InstanceBeyondLimit { @@ -1921,14 +1991,14 @@ impl Global { count, indexed, } => { + api_log!("RenderPass::draw_indirect (indexed:{indexed}) {buffer_id:?} {offset} {count:?}"); + let scope = PassErrorScope::Draw { indexed, indirect: true, pipeline: state.pipeline, }; - state - .is_ready::(indexed, &*bind_group_layout_guard) - .map_pass_err(scope)?; + state.is_ready(indexed).map_pass_err(scope)?; let stride = match indexed { false => mem::size_of::(), @@ -1944,7 +2014,7 @@ impl Global { .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION) .map_pass_err(scope)?; - let indirect_buffer: &Buffer = info + let indirect_buffer = info .usage_scope .buffers .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) @@ -1953,7 +2023,7 @@ impl Global { .map_pass_err(scope)?; let indirect_raw = indirect_buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(RenderCommandError::DestroyedBuffer(buffer_id)) .map_pass_err(scope)?; @@ -1970,9 +2040,9 @@ impl Global { .map_pass_err(scope); } - cmd_buf.buffer_memory_init_actions.extend( - indirect_buffer.initialization_status.create_action( - buffer_id, + buffer_memory_init_actions.extend( + indirect_buffer.initialization_status.read().create_action( + indirect_buffer, offset..end_offset, MemoryInitKind::NeedsInitializedMemory, ), @@ -1995,14 +2065,14 @@ impl Global { max_count, indexed, } => { + api_log!("RenderPass::multi_draw_indirect_count (indexed:{indexed}) {buffer_id:?} {offset} {count_buffer_id:?} {count_buffer_offset:?} {max_count:?}"); + let scope = PassErrorScope::Draw { indexed, indirect: true, pipeline: state.pipeline, }; - state - .is_ready::(indexed, &*bind_group_layout_guard) - .map_pass_err(scope)?; + state.is_ready(indexed).map_pass_err(scope)?; let stride = match indexed { false => mem::size_of::(), @@ -2016,7 +2086,7 @@ impl Global { .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION) .map_pass_err(scope)?; - let indirect_buffer: &Buffer = info + let indirect_buffer = info .usage_scope .buffers .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) @@ -2025,11 +2095,11 @@ impl Global { .map_pass_err(scope)?; let indirect_raw = indirect_buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(RenderCommandError::DestroyedBuffer(buffer_id)) .map_pass_err(scope)?; - let count_buffer: &Buffer = info + let count_buffer = info .usage_scope .buffers .merge_single( @@ -2042,7 +2112,7 @@ impl Global { .map_pass_err(scope)?; let count_raw = count_buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(RenderCommandError::DestroyedBuffer(count_buffer_id)) .map_pass_err(scope)?; @@ -2056,9 +2126,9 @@ impl Global { }) .map_pass_err(scope); } - cmd_buf.buffer_memory_init_actions.extend( - indirect_buffer.initialization_status.create_action( - buffer_id, + buffer_memory_init_actions.extend( + indirect_buffer.initialization_status.read().create_action( + indirect_buffer, offset..end_offset, MemoryInitKind::NeedsInitializedMemory, ), @@ -2074,9 +2144,9 @@ impl Global { }) .map_pass_err(scope); } - cmd_buf.buffer_memory_init_actions.extend( - count_buffer.initialization_status.create_action( - count_buffer_id, + buffer_memory_init_actions.extend( + count_buffer.initialization_status.read().create_action( + count_buffer, count_buffer_offset..end_count_offset, MemoryInitKind::NeedsInitializedMemory, ), @@ -2105,46 +2175,59 @@ impl Global { } RenderCommand::PushDebugGroup { color: _, len } => { state.debug_scope_depth += 1; - let label = - str::from_utf8(&base.string_data[string_offset..string_offset + len]) - .unwrap(); - string_offset += len; - unsafe { - raw.begin_debug_marker(label); + if !discard_hal_labels { + let label = str::from_utf8( + &base.string_data[string_offset..string_offset + len], + ) + .unwrap(); + + api_log!("RenderPass::push_debug_group {label:?}"); + unsafe { + raw.begin_debug_marker(label); + } } + string_offset += len; } RenderCommand::PopDebugGroup => { + api_log!("RenderPass::pop_debug_group"); + let scope = PassErrorScope::PopDebugGroup; if state.debug_scope_depth == 0 { return Err(RenderPassErrorInner::InvalidPopDebugGroup) .map_pass_err(scope); } state.debug_scope_depth -= 1; - unsafe { - raw.end_debug_marker(); + if !discard_hal_labels { + unsafe { + raw.end_debug_marker(); + } } } RenderCommand::InsertDebugMarker { color: _, len } => { - let label = - str::from_utf8(&base.string_data[string_offset..string_offset + len]) - .unwrap(); - string_offset += len; - unsafe { - raw.insert_debug_marker(label); + if !discard_hal_labels { + let label = str::from_utf8( + &base.string_data[string_offset..string_offset + len], + ) + .unwrap(); + api_log!("RenderPass::insert_debug_marker {label:?}"); + unsafe { + raw.insert_debug_marker(label); + } } + string_offset += len; } RenderCommand::WriteTimestamp { query_set_id, query_index, } => { + api_log!("RenderPass::write_timestamps {query_set_id:?} {query_index}"); let scope = PassErrorScope::WriteTimestamp; device .require_features(wgt::Features::TIMESTAMP_QUERY_INSIDE_PASSES) .map_pass_err(scope)?; - let query_set = cmd_buf - .trackers + let query_set = tracker .query_sets .add_single(&*query_set_guard, query_set_id) .ok_or(RenderCommandError::InvalidQuerySet(query_set_id)) @@ -2155,19 +2238,19 @@ impl Global { raw, query_set_id, query_index, - Some(&mut cmd_buf.pending_query_resets), + Some(&mut cmd_buf_data.pending_query_resets), ) .map_pass_err(scope)?; } RenderCommand::BeginOcclusionQuery { query_index } => { + api_log!("RenderPass::begin_occlusion_query {query_index}"); let scope = PassErrorScope::BeginOcclusionQuery; let query_set_id = occlusion_query_set_id .ok_or(RenderPassErrorInner::MissingOcclusionQuerySet) .map_pass_err(scope)?; - let query_set = cmd_buf - .trackers + let query_set = tracker .query_sets .add_single(&*query_set_guard, query_set_id) .ok_or(RenderCommandError::InvalidQuerySet(query_set_id)) @@ -2178,12 +2261,13 @@ impl Global { raw, query_set_id, query_index, - Some(&mut cmd_buf.pending_query_resets), + Some(&mut cmd_buf_data.pending_query_resets), &mut active_query, ) .map_pass_err(scope)?; } RenderCommand::EndOcclusionQuery => { + api_log!("RenderPass::end_occlusion_query"); let scope = PassErrorScope::EndOcclusionQuery; end_occlusion_query(raw, &*query_set_guard, &mut active_query) @@ -2193,10 +2277,10 @@ impl Global { query_set_id, query_index, } => { + api_log!("RenderPass::begin_pipeline_statistics_query {query_set_id:?} {query_index}"); let scope = PassErrorScope::BeginPipelineStatisticsQuery; - let query_set = cmd_buf - .trackers + let query_set = tracker .query_sets .add_single(&*query_set_guard, query_set_id) .ok_or(RenderCommandError::InvalidQuerySet(query_set_id)) @@ -2207,26 +2291,31 @@ impl Global { raw, query_set_id, query_index, - Some(&mut cmd_buf.pending_query_resets), + Some(&mut cmd_buf_data.pending_query_resets), &mut active_query, ) .map_pass_err(scope)?; } RenderCommand::EndPipelineStatisticsQuery => { + api_log!("RenderPass::end_pipeline_statistics_query"); let scope = PassErrorScope::EndPipelineStatisticsQuery; end_pipeline_statistics_query(raw, &*query_set_guard, &mut active_query) .map_pass_err(scope)?; } RenderCommand::ExecuteBundle(bundle_id) => { + api_log!("RenderPass::execute_bundle {bundle_id:?}"); let scope = PassErrorScope::ExecuteBundle; - let bundle: &command::RenderBundle = cmd_buf - .trackers + let bundle: &command::RenderBundle = tracker .bundles .add_single(&*bundle_guard, bundle_id) .ok_or(RenderCommandError::InvalidRenderBundle(bundle_id)) .map_pass_err(scope)?; + if bundle.device.as_info().id() != device.as_info().id() { + return Err(DeviceError::WrongDevice).map_pass_err(scope); + } + info.context .check_compatible( &bundle.context, @@ -2249,48 +2338,42 @@ impl Global { .map_pass_err(scope); } - cmd_buf.buffer_memory_init_actions.extend( + buffer_memory_init_actions.extend( bundle .buffer_memory_init_actions .iter() - .filter_map(|action| match buffer_guard.get(action.id) { - Ok(buffer) => buffer.initialization_status.check_action(action), - Err(_) => None, + .filter_map(|action| { + action + .buffer + .initialization_status + .read() + .check_action(action) }), ); for action in bundle.texture_memory_init_actions.iter() { - info.pending_discard_init_fixups.extend( - cmd_buf - .texture_memory_actions - .register_init_action(action, &texture_guard), - ); + info.pending_discard_init_fixups + .extend(texture_memory_actions.register_init_action(action)); } - unsafe { - bundle.execute( - raw, - &*pipeline_layout_guard, - &*bind_group_guard, - &*render_pipeline_guard, - &*buffer_guard, - ) - } - .map_err(|e| match e { - ExecutionError::DestroyedBuffer(id) => { - RenderCommandError::DestroyedBuffer(id) - } - ExecutionError::Unimplemented(what) => { - RenderCommandError::Unimplemented(what) - } - }) - .map_pass_err(scope)?; + unsafe { bundle.execute(raw) } + .map_err(|e| match e { + ExecutionError::DestroyedBuffer(id) => { + RenderCommandError::DestroyedBuffer(id) + } + ExecutionError::InvalidBindGroup(id) => { + RenderCommandError::InvalidBindGroup(id) + } + ExecutionError::Unimplemented(what) => { + RenderCommandError::Unimplemented(what) + } + }) + .map_pass_err(scope)?; unsafe { info.usage_scope - .merge_render_bundle(&*texture_guard, &bundle.used) + .merge_render_bundle(&bundle.used) .map_pass_err(scope)?; - cmd_buf - .trackers + tracker .add_from_render_bundle(&bundle.used) .map_pass_err(scope)?; }; @@ -2301,50 +2384,52 @@ impl Global { log::trace!("Merging renderpass into cmd_buf {:?}", encoder_id); let (trackers, pending_discard_init_fixups) = - info.finish(raw, &*texture_guard).map_pass_err(init_scope)?; + info.finish(raw).map_pass_err(pass_scope)?; - cmd_buf.encoder.close(); + encoder.close().map_pass_err(pass_scope)?; (trackers, pending_discard_init_fixups) }; - let (mut cmb_guard, mut token) = hub.command_buffers.write(&mut token); - let (query_set_guard, mut token) = hub.query_sets.read(&mut token); - let (buffer_guard, mut token) = hub.buffers.read(&mut token); - let (texture_guard, _) = hub.textures.read(&mut token); + let cmd_buf = hub.command_buffers.get(encoder_id).unwrap(); + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); + + let query_set_guard = hub.query_sets.read(); + + let encoder = &mut cmd_buf_data.encoder; + let status = &mut cmd_buf_data.status; + let tracker = &mut cmd_buf_data.trackers; - let cmd_buf = cmb_guard.get_mut(encoder_id).unwrap(); { - let transit = cmd_buf.encoder.open(); + let transit = encoder.open().map_pass_err(pass_scope)?; fixup_discarded_surfaces( pending_discard_init_fixups.into_iter(), transit, - &texture_guard, - &mut cmd_buf.trackers.textures, - &device_guard[cmd_buf.device_id.value], + &mut tracker.textures, + &cmd_buf.device, ); - cmd_buf + cmd_buf_data .pending_query_resets .reset_queries( transit, &query_set_guard, - cmd_buf.device_id.value.0.backend(), + cmd_buf.device.info.id().backend(), ) .map_err(RenderCommandError::InvalidQuerySet) .map_pass_err(PassErrorScope::QueryReset)?; super::CommandBuffer::insert_barriers_from_scope( transit, - &mut cmd_buf.trackers, + tracker, &scope, - &*buffer_guard, - &*texture_guard, + &snatch_guard, ); } - cmd_buf.status = CommandEncoderStatus::Recording; - cmd_buf.encoder.close_and_swap(); + *status = CommandEncoderStatus::Recording; + encoder.close_and_swap().map_pass_err(pass_scope)?; Ok(()) } @@ -2387,7 +2472,7 @@ pub mod render_ffi { pass.base.commands.push(RenderCommand::SetBindGroup { index, - num_dynamic_offsets: offset_length.try_into().unwrap(), + num_dynamic_offsets: offset_length, bind_group_id, }); } diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index 58f88a70e1..6bde17c646 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -1,22 +1,21 @@ #[cfg(feature = "trace")] use crate::device::trace::Command as TraceCommand; use crate::{ + api_log, command::{clear_texture, CommandBuffer, CommandEncoderError}, conv, - device::{Device, MissingDownlevelFlags}, + device::{Device, DeviceError, MissingDownlevelFlags}, error::{ErrorFormatter, PrettyError}, global::Global, hal_api::HalApi, - hub::Token, - id::{BufferId, CommandEncoderId, TextureId, Valid}, + id::{BufferId, CommandEncoderId, DeviceId, TextureId}, identity::GlobalIdentityHandlerFactory, init_tracker::{ has_copy_partial_init_tracker_coverage, MemoryInitKind, TextureInitRange, TextureInitTrackerAction, }, - resource::{Texture, TextureErrorDimension}, - storage::Storage, - track::TextureSelector, + resource::{Resource, Texture, TextureErrorDimension}, + track::{TextureSelector, Tracker}, }; use arrayvec::ArrayVec; @@ -24,7 +23,9 @@ use hal::CommandEncoder as _; use thiserror::Error; use wgt::{BufferAddress, BufferUsages, Extent3d, TextureUsages}; -use std::iter; +use std::{iter, sync::Arc}; + +use super::{memory_init::CommandBufferTextureMemoryActions, ClearError, CommandEncoder}; pub type ImageCopyBuffer = wgt::ImageCopyBuffer; pub type ImageCopyTexture = wgt::ImageCopyTexture; @@ -40,6 +41,8 @@ pub enum CopySide { #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum TransferError { + #[error("Device {0:?} is invalid")] + InvalidDevice(DeviceId), #[error("Buffer {0:?} is invalid or destroyed")] InvalidBuffer(BufferId), #[error("Texture {0:?} is invalid or destroyed")] @@ -132,7 +135,7 @@ pub enum TransferError { dst_format: wgt::TextureFormat, }, #[error(transparent)] - MemoryInitFailure(#[from] super::ClearError), + MemoryInitFailure(#[from] ClearError), #[error("Cannot encode this copy because of a missing downelevel flag")] MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error("Source texture sample count must be 1, got {sample_count}")] @@ -183,7 +186,13 @@ pub enum CopyError { Transfer(#[from] TransferError), } -pub(crate) fn extract_texture_selector( +impl From for CopyError { + fn from(err: DeviceError) -> Self { + CopyError::Encoder(CommandEncoderError::Device(err)) + } +} + +pub(crate) fn extract_texture_selector( copy_texture: &ImageCopyTexture, copy_size: &Extent3d, texture: &Texture, @@ -252,7 +261,7 @@ pub(crate) fn validate_linear_texture_data( let offset = layout.offset; - let block_size = format.block_size(Some(aspect)).unwrap() as BufferAddress; + let block_size = format.block_copy_size(Some(aspect)).unwrap() as BufferAddress; let (block_width, block_height) = format.block_dimensions(); let block_width = block_width as BufferAddress; let block_height = block_height as BufferAddress; @@ -437,14 +446,16 @@ pub(crate) fn validate_texture_copy_range( fn handle_texture_init( init_kind: MemoryInitKind, - cmd_buf: &mut CommandBuffer, + encoder: &mut CommandEncoder, + trackers: &mut Tracker, + texture_memory_actions: &mut CommandBufferTextureMemoryActions, device: &Device, copy_texture: &ImageCopyTexture, copy_size: &Extent3d, - texture_guard: &Storage, TextureId>, -) { + texture: &Arc>, +) -> Result<(), ClearError> { let init_action = TextureInitTrackerAction { - id: copy_texture.texture, + texture: texture.clone(), range: TextureInitRange { mip_range: copy_texture.mip_level..copy_texture.mip_level + 1, layer_range: copy_texture.origin.z @@ -454,29 +465,27 @@ fn handle_texture_init( }; // Register the init action. - let immediate_inits = cmd_buf - .texture_memory_actions - .register_init_action(&{ init_action }, texture_guard); + let immediate_inits = texture_memory_actions.register_init_action(&{ init_action }); // In rare cases we may need to insert an init operation immediately onto the command buffer. if !immediate_inits.is_empty() { - let cmd_buf_raw = cmd_buf.encoder.open(); + let cmd_buf_raw = encoder.open()?; for init in immediate_inits { clear_texture( - texture_guard, - Valid(init.texture), + &init.texture, TextureInitRange { mip_range: init.mip_level..(init.mip_level + 1), layer_range: init.layer..(init.layer + 1), }, cmd_buf_raw, - &mut cmd_buf.trackers.textures, + &mut trackers.textures, &device.alignments, - &device.zero_buffer, - ) - .unwrap(); + device.zero_buffer.as_ref().unwrap(), + )?; } } + + Ok(()) } /// Prepare a transfer's source texture. @@ -484,24 +493,24 @@ fn handle_texture_init( /// Ensure the source texture of a transfer is in the right initialization /// state, and record the state for after the transfer operation. fn handle_src_texture_init( - cmd_buf: &mut CommandBuffer, + encoder: &mut CommandEncoder, + trackers: &mut Tracker, + texture_memory_actions: &mut CommandBufferTextureMemoryActions, device: &Device, source: &ImageCopyTexture, copy_size: &Extent3d, - texture_guard: &Storage, TextureId>, + texture: &Arc>, ) -> Result<(), TransferError> { - let _ = texture_guard - .get(source.texture) - .map_err(|_| TransferError::InvalidTexture(source.texture))?; - handle_texture_init( MemoryInitKind::NeedsInitializedMemory, - cmd_buf, + encoder, + trackers, + texture_memory_actions, device, source, copy_size, - texture_guard, - ); + texture, + )?; Ok(()) } @@ -510,16 +519,14 @@ fn handle_src_texture_init( /// Ensure the destination texture of a transfer is in the right initialization /// state, and record the state for after the transfer operation. fn handle_dst_texture_init( - cmd_buf: &mut CommandBuffer, + encoder: &mut CommandEncoder, + trackers: &mut Tracker, + texture_memory_actions: &mut CommandBufferTextureMemoryActions, device: &Device, destination: &ImageCopyTexture, copy_size: &Extent3d, - texture_guard: &Storage, TextureId>, + texture: &Arc>, ) -> Result<(), TransferError> { - let texture = texture_guard - .get(destination.texture) - .map_err(|_| TransferError::InvalidTexture(destination.texture))?; - // Attention: If we don't write full texture subresources, we need to a full // clear first since we don't track subrects. This means that in rare cases // even a *destination* texture of a transfer may need an immediate texture @@ -536,12 +543,14 @@ fn handle_dst_texture_init( handle_texture_init( dst_init_kind, - cmd_buf, + encoder, + trackers, + texture_memory_actions, device, destination, copy_size, - texture_guard, - ); + texture, + )?; Ok(()) } @@ -556,22 +565,26 @@ impl Global { size: BufferAddress, ) -> Result<(), CopyError> { profiling::scope!("CommandEncoder::copy_buffer_to_buffer"); + api_log!( + "CommandEncoder::copy_buffer_to_buffer {source:?} -> {destination:?} {size:?}bytes" + ); if source == destination { return Err(TransferError::SameSourceDestinationBuffer.into()); } let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id)?; - let (buffer_guard, _) = hub.buffers.read(&mut token); + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); - let device = &device_guard[cmd_buf.device_id.value]; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(TransferError::InvalidDevice(cmd_buf.device.as_info().id()).into()); + } #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(TraceCommand::CopyBufferToBuffer { src: source, src_offset: source_offset, @@ -581,34 +594,48 @@ impl Global { }); } - let (src_buffer, src_pending) = cmd_buf - .trackers - .buffers - .set_single(&*buffer_guard, source, hal::BufferUses::COPY_SRC) - .ok_or(TransferError::InvalidBuffer(source))?; + let snatch_guard = device.snatchable_lock.read(); + + let (src_buffer, src_pending) = { + let buffer_guard = hub.buffers.read(); + let src_buffer = buffer_guard + .get(source) + .map_err(|_| TransferError::InvalidBuffer(source))?; + cmd_buf_data + .trackers + .buffers + .set_single(src_buffer, hal::BufferUses::COPY_SRC) + .ok_or(TransferError::InvalidBuffer(source))? + }; let src_raw = src_buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(TransferError::InvalidBuffer(source))?; if !src_buffer.usage.contains(BufferUsages::COPY_SRC) { return Err(TransferError::MissingCopySrcUsageFlag.into()); } // expecting only a single barrier - let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer)); - - let (dst_buffer, dst_pending) = cmd_buf - .trackers - .buffers - .set_single(&*buffer_guard, destination, hal::BufferUses::COPY_DST) - .ok_or(TransferError::InvalidBuffer(destination))?; + let src_barrier = src_pending.map(|pending| pending.into_hal(&src_buffer, &snatch_guard)); + + let (dst_buffer, dst_pending) = { + let buffer_guard = hub.buffers.read(); + let dst_buffer = buffer_guard + .get(destination) + .map_err(|_| TransferError::InvalidBuffer(destination))?; + cmd_buf_data + .trackers + .buffers + .set_single(dst_buffer, hal::BufferUses::COPY_DST) + .ok_or(TransferError::InvalidBuffer(destination))? + }; let dst_raw = dst_buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(TransferError::InvalidBuffer(destination))?; if !dst_buffer.usage.contains(BufferUsages::COPY_DST) { return Err(TransferError::MissingCopyDstUsageFlag(Some(destination), None).into()); } - let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer)); + let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard)); if size % wgt::COPY_BUFFER_ALIGNMENT != 0 { return Err(TransferError::UnalignedCopySize(size).into()); @@ -667,27 +694,27 @@ impl Global { } // Make sure source is initialized memory and mark dest as initialized. - cmd_buf - .buffer_memory_init_actions - .extend(dst_buffer.initialization_status.create_action( - destination, + cmd_buf_data.buffer_memory_init_actions.extend( + dst_buffer.initialization_status.read().create_action( + &dst_buffer, destination_offset..(destination_offset + size), MemoryInitKind::ImplicitlyInitialized, - )); - cmd_buf - .buffer_memory_init_actions - .extend(src_buffer.initialization_status.create_action( - source, + ), + ); + cmd_buf_data.buffer_memory_init_actions.extend( + src_buffer.initialization_status.read().create_action( + &src_buffer, source_offset..(source_offset + size), MemoryInitKind::NeedsInitializedMemory, - )); + ), + ); let region = hal::BufferCopy { src_offset: source_offset, dst_offset: destination_offset, size: wgt::BufferSize::new(size).unwrap(), }; - let cmd_buf_raw = cmd_buf.encoder.open(); + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_buffers(src_barrier.into_iter().chain(dst_barrier)); cmd_buf_raw.copy_buffer_to_buffer(src_raw, dst_raw, iter::once(region)); @@ -703,20 +730,25 @@ impl Global { copy_size: &Extent3d, ) -> Result<(), CopyError> { profiling::scope!("CommandEncoder::copy_buffer_to_texture"); + api_log!( + "CommandEncoder::copy_buffer_to_texture {:?} -> {:?} {copy_size:?}", + source.buffer, + destination.texture + ); let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id)?; - let (buffer_guard, mut token) = hub.buffers.read(&mut token); - let (texture_guard, _) = hub.textures.read(&mut token); + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(TransferError::InvalidDevice(cmd_buf.device.as_info().id()).into()); + } - let device = &device_guard[cmd_buf.device_id.value]; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(TraceCommand::CopyBufferToTexture { src: *source, dst: *destination, @@ -724,12 +756,18 @@ impl Global { }); } + let encoder = &mut cmd_buf_data.encoder; + let tracker = &mut cmd_buf_data.trackers; + let buffer_memory_init_actions = &mut cmd_buf_data.buffer_memory_init_actions; + let texture_memory_actions = &mut cmd_buf_data.texture_memory_actions; + if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { log::trace!("Ignoring copy_buffer_to_texture of size 0"); return Ok(()); } - let dst_texture = texture_guard + let dst_texture = hub + .textures .get(destination.texture) .map_err(|_| TransferError::InvalidTexture(destination.texture))?; @@ -740,47 +778,55 @@ impl Global { copy_size, )?; - let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, dst_texture)?; + let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, &dst_texture)?; // Handle texture init *before* dealing with barrier transitions so we // have an easier time inserting "immediate-inits" that may be required // by prior discards in rare cases. - handle_dst_texture_init(cmd_buf, device, destination, copy_size, &texture_guard)?; + handle_dst_texture_init( + encoder, + tracker, + texture_memory_actions, + device, + destination, + copy_size, + &dst_texture, + )?; - let (src_buffer, src_pending) = cmd_buf - .trackers - .buffers - .set_single(&*buffer_guard, source.buffer, hal::BufferUses::COPY_SRC) - .ok_or(TransferError::InvalidBuffer(source.buffer))?; + let snatch_guard = device.snatchable_lock.read(); + + let (src_buffer, src_pending) = { + let buffer_guard = hub.buffers.read(); + let src_buffer = buffer_guard + .get(source.buffer) + .map_err(|_| TransferError::InvalidBuffer(source.buffer))?; + tracker + .buffers + .set_single(src_buffer, hal::BufferUses::COPY_SRC) + .ok_or(TransferError::InvalidBuffer(source.buffer))? + }; let src_raw = src_buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(TransferError::InvalidBuffer(source.buffer))?; if !src_buffer.usage.contains(BufferUsages::COPY_SRC) { return Err(TransferError::MissingCopySrcUsageFlag.into()); } - let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer)); + let src_barrier = src_pending.map(|pending| pending.into_hal(&src_buffer, &snatch_guard)); - let dst_pending = cmd_buf - .trackers + let dst_pending = tracker .textures - .set_single( - dst_texture, - destination.texture, - dst_range, - hal::TextureUses::COPY_DST, - ) + .set_single(&dst_texture, dst_range, hal::TextureUses::COPY_DST) .ok_or(TransferError::InvalidTexture(destination.texture))?; let dst_raw = dst_texture - .inner - .as_raw() + .raw(&snatch_guard) .ok_or(TransferError::InvalidTexture(destination.texture))?; if !dst_texture.desc.usage.contains(TextureUsages::COPY_DST) { return Err( TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(), ); } - let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_texture)); + let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_raw)); if !dst_base.aspect.is_one() { return Err(TransferError::CopyAspectNotOne.into()); @@ -810,13 +856,11 @@ impl Global { .map_err(TransferError::from)?; } - cmd_buf - .buffer_memory_init_actions - .extend(src_buffer.initialization_status.create_action( - source.buffer, - source.layout.offset..(source.layout.offset + required_buffer_bytes_in_copy), - MemoryInitKind::NeedsInitializedMemory, - )); + buffer_memory_init_actions.extend(src_buffer.initialization_status.read().create_action( + &src_buffer, + source.layout.offset..(source.layout.offset + required_buffer_bytes_in_copy), + MemoryInitKind::NeedsInitializedMemory, + )); let regions = (0..array_layer_count).map(|rel_array_layer| { let mut texture_base = dst_base.clone(); @@ -830,7 +874,7 @@ impl Global { } }); - let cmd_buf_raw = cmd_buf.encoder.open(); + let cmd_buf_raw = encoder.open()?; unsafe { cmd_buf_raw.transition_textures(dst_barrier.into_iter()); cmd_buf_raw.transition_buffers(src_barrier.into_iter()); @@ -847,59 +891,72 @@ impl Global { copy_size: &Extent3d, ) -> Result<(), CopyError> { profiling::scope!("CommandEncoder::copy_texture_to_buffer"); + api_log!( + "CommandEncoder::copy_texture_to_buffer {:?} -> {:?} {copy_size:?}", + source.texture, + destination.buffer + ); let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id)?; - let (buffer_guard, mut token) = hub.buffers.read(&mut token); - let (texture_guard, _) = hub.textures.read(&mut token); + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(TransferError::InvalidDevice(cmd_buf.device.as_info().id()).into()); + } - let device = &device_guard[cmd_buf.device_id.value]; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(TraceCommand::CopyTextureToBuffer { src: *source, dst: *destination, size: *copy_size, }); } + let encoder = &mut cmd_buf_data.encoder; + let tracker = &mut cmd_buf_data.trackers; + let buffer_memory_init_actions = &mut cmd_buf_data.buffer_memory_init_actions; + let texture_memory_actions = &mut cmd_buf_data.texture_memory_actions; if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { log::trace!("Ignoring copy_texture_to_buffer of size 0"); return Ok(()); } - let src_texture = texture_guard + let src_texture = hub + .textures .get(source.texture) .map_err(|_| TransferError::InvalidTexture(source.texture))?; let (hal_copy_size, array_layer_count) = validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?; - let (src_range, src_base) = extract_texture_selector(source, copy_size, src_texture)?; + let (src_range, src_base) = extract_texture_selector(source, copy_size, &src_texture)?; // Handle texture init *before* dealing with barrier transitions so we // have an easier time inserting "immediate-inits" that may be required // by prior discards in rare cases. - handle_src_texture_init(cmd_buf, device, source, copy_size, &texture_guard)?; + handle_src_texture_init( + encoder, + tracker, + texture_memory_actions, + device, + source, + copy_size, + &src_texture, + )?; - let src_pending = cmd_buf - .trackers + let snatch_guard = device.snatchable_lock.read(); + + let src_pending = tracker .textures - .set_single( - src_texture, - source.texture, - src_range, - hal::TextureUses::COPY_SRC, - ) + .set_single(&src_texture, src_range, hal::TextureUses::COPY_SRC) .ok_or(TransferError::InvalidTexture(source.texture))?; let src_raw = src_texture - .inner - .as_raw() + .raw(&snatch_guard) .ok_or(TransferError::InvalidTexture(source.texture))?; if !src_texture.desc.usage.contains(TextureUsages::COPY_SRC) { return Err(TransferError::MissingCopySrcUsageFlag.into()); @@ -917,27 +974,28 @@ impl Global { } .into()); } - let src_barrier = src_pending.map(|pending| pending.into_hal(src_texture)); - - let (dst_buffer, dst_pending) = cmd_buf - .trackers - .buffers - .set_single( - &*buffer_guard, - destination.buffer, - hal::BufferUses::COPY_DST, - ) - .ok_or(TransferError::InvalidBuffer(destination.buffer))?; + let src_barrier = src_pending.map(|pending| pending.into_hal(src_raw)); + + let (dst_buffer, dst_pending) = { + let buffer_guard = hub.buffers.read(); + let dst_buffer = buffer_guard + .get(destination.buffer) + .map_err(|_| TransferError::InvalidBuffer(destination.buffer))?; + tracker + .buffers + .set_single(dst_buffer, hal::BufferUses::COPY_DST) + .ok_or(TransferError::InvalidBuffer(destination.buffer))? + }; let dst_raw = dst_buffer .raw - .as_ref() + .get(&snatch_guard) .ok_or(TransferError::InvalidBuffer(destination.buffer))?; if !dst_buffer.usage.contains(BufferUsages::COPY_DST) { return Err( TransferError::MissingCopyDstUsageFlag(Some(destination.buffer), None).into(), ); } - let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer)); + let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard)); if !src_base.aspect.is_one() { return Err(TransferError::CopyAspectNotOne.into()); @@ -967,14 +1025,11 @@ impl Global { .map_err(TransferError::from)?; } - cmd_buf - .buffer_memory_init_actions - .extend(dst_buffer.initialization_status.create_action( - destination.buffer, - destination.layout.offset - ..(destination.layout.offset + required_buffer_bytes_in_copy), - MemoryInitKind::ImplicitlyInitialized, - )); + buffer_memory_init_actions.extend(dst_buffer.initialization_status.read().create_action( + &dst_buffer, + destination.layout.offset..(destination.layout.offset + required_buffer_bytes_in_copy), + MemoryInitKind::ImplicitlyInitialized, + )); let regions = (0..array_layer_count).map(|rel_array_layer| { let mut texture_base = src_base.clone(); @@ -987,7 +1042,7 @@ impl Global { size: hal_copy_size, } }); - let cmd_buf_raw = cmd_buf.encoder.open(); + let cmd_buf_raw = encoder.open()?; unsafe { cmd_buf_raw.transition_buffers(dst_barrier.into_iter()); cmd_buf_raw.transition_textures(src_barrier.into_iter()); @@ -1009,36 +1064,48 @@ impl Global { copy_size: &Extent3d, ) -> Result<(), CopyError> { profiling::scope!("CommandEncoder::copy_texture_to_texture"); + api_log!( + "CommandEncoder::copy_texture_to_texture {:?} -> {:?} {copy_size:?}", + source.texture, + destination.texture + ); let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); - let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id)?; - let (_, mut token) = hub.buffers.read(&mut token); // skip token - let (texture_guard, _) = hub.textures.read(&mut token); + let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(TransferError::InvalidDevice(cmd_buf.device.as_info().id()).into()); + } + + let snatch_guard = device.snatchable_lock.read(); - let device = &device_guard[cmd_buf.device_id.value]; + let mut cmd_buf_data = cmd_buf.data.lock(); + let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); #[cfg(feature = "trace")] - if let Some(ref mut list) = cmd_buf.commands { + if let Some(ref mut list) = cmd_buf_data.commands { list.push(TraceCommand::CopyTextureToTexture { src: *source, dst: *destination, size: *copy_size, }); } + let encoder = &mut cmd_buf_data.encoder; + let tracker = &mut cmd_buf_data.trackers; + let texture_memory_actions = &mut cmd_buf_data.texture_memory_actions; if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { log::trace!("Ignoring copy_texture_to_texture of size 0"); return Ok(()); } - let src_texture = texture_guard + let src_texture = hub + .textures .get(source.texture) .map_err(|_| TransferError::InvalidTexture(source.texture))?; - let dst_texture = texture_guard + let dst_texture = hub + .textures .get(destination.texture) .map_err(|_| TransferError::InvalidTexture(source.texture))?; @@ -1063,9 +1130,9 @@ impl Global { copy_size, )?; - let (src_range, src_tex_base) = extract_texture_selector(source, copy_size, src_texture)?; + let (src_range, src_tex_base) = extract_texture_selector(source, copy_size, &src_texture)?; let (dst_range, dst_tex_base) = - extract_texture_selector(destination, copy_size, dst_texture)?; + extract_texture_selector(destination, copy_size, &dst_texture)?; let src_texture_aspects = hal::FormatAspects::from(src_texture.desc.format); let dst_texture_aspects = hal::FormatAspects::from(dst_texture.desc.format); if src_tex_base.aspect != src_texture_aspects { @@ -1078,22 +1145,32 @@ impl Global { // Handle texture init *before* dealing with barrier transitions so we // have an easier time inserting "immediate-inits" that may be required // by prior discards in rare cases. - handle_src_texture_init(cmd_buf, device, source, copy_size, &texture_guard)?; - handle_dst_texture_init(cmd_buf, device, destination, copy_size, &texture_guard)?; + handle_src_texture_init( + encoder, + tracker, + texture_memory_actions, + device, + source, + copy_size, + &src_texture, + )?; + handle_dst_texture_init( + encoder, + tracker, + texture_memory_actions, + device, + destination, + copy_size, + &dst_texture, + )?; - let src_pending = cmd_buf + let src_pending = cmd_buf_data .trackers .textures - .set_single( - src_texture, - source.texture, - src_range, - hal::TextureUses::COPY_SRC, - ) + .set_single(&src_texture, src_range, hal::TextureUses::COPY_SRC) .ok_or(TransferError::InvalidTexture(source.texture))?; let src_raw = src_texture - .inner - .as_raw() + .raw(&snatch_guard) .ok_or(TransferError::InvalidTexture(source.texture))?; if !src_texture.desc.usage.contains(TextureUsages::COPY_SRC) { return Err(TransferError::MissingCopySrcUsageFlag.into()); @@ -1102,22 +1179,16 @@ impl Global { //TODO: try to avoid this the collection. It's needed because both // `src_pending` and `dst_pending` try to hold `trackers.textures` mutably. let mut barriers: ArrayVec<_, 2> = src_pending - .map(|pending| pending.into_hal(src_texture)) + .map(|pending| pending.into_hal(src_raw)) .collect(); - let dst_pending = cmd_buf + let dst_pending = cmd_buf_data .trackers .textures - .set_single( - dst_texture, - destination.texture, - dst_range, - hal::TextureUses::COPY_DST, - ) + .set_single(&dst_texture, dst_range, hal::TextureUses::COPY_DST) .ok_or(TransferError::InvalidTexture(destination.texture))?; let dst_raw = dst_texture - .inner - .as_raw() + .raw(&snatch_guard) .ok_or(TransferError::InvalidTexture(destination.texture))?; if !dst_texture.desc.usage.contains(TextureUsages::COPY_DST) { return Err( @@ -1125,7 +1196,7 @@ impl Global { ); } - barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_texture))); + barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_raw))); let hal_copy_size = hal::CopyExtent { width: src_copy_size.width.min(dst_copy_size.width), @@ -1143,7 +1214,7 @@ impl Global { size: hal_copy_size, } }); - let cmd_buf_raw = cmd_buf.encoder.open(); + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_textures(barriers.into_iter()); cmd_buf_raw.copy_texture_to_texture( @@ -1153,6 +1224,7 @@ impl Global { regions, ); } + Ok(()) } } diff --git a/wgpu-core/src/conv.rs b/wgpu-core/src/conv.rs index 72d09f036c..44ed750a43 100644 --- a/wgpu-core/src/conv.rs +++ b/wgpu-core/src/conv.rs @@ -1,4 +1,6 @@ -use crate::resource; +use wgt::TextureFormatFeatures; + +use crate::resource::{self, TextureDescriptor}; pub fn is_power_of_two_u16(val: u16) -> bool { val != 0 && (val & (val - 1)) == 0 @@ -143,6 +145,32 @@ pub fn map_texture_usage( u } +pub fn map_texture_usage_for_texture( + desc: &TextureDescriptor, + format_features: &TextureFormatFeatures, +) -> hal::TextureUses { + // Enforce having COPY_DST/DEPTH_STENCIL_WRITE/COLOR_TARGET otherwise we + // wouldn't be able to initialize the texture. + map_texture_usage(desc.usage, desc.format.into()) + | if desc.format.is_depth_stencil_format() { + hal::TextureUses::DEPTH_STENCIL_WRITE + } else if desc.usage.contains(wgt::TextureUsages::COPY_DST) { + hal::TextureUses::COPY_DST // (set already) + } else { + // Use COPY_DST only if we can't use COLOR_TARGET + if format_features + .allowed_usages + .contains(wgt::TextureUsages::RENDER_ATTACHMENT) + && desc.dimension == wgt::TextureDimension::D2 + // Render targets dimension must be 2d + { + hal::TextureUses::COLOR_TARGET + } else { + hal::TextureUses::COPY_DST + } + } +} + pub fn map_texture_usage_from_hal(uses: hal::TextureUses) -> wgt::TextureUsages { let mut u = wgt::TextureUsages::empty(); u.set( diff --git a/wgpu-core/src/device/any_device.rs b/wgpu-core/src/device/any_device.rs new file mode 100644 index 0000000000..b3c4a31839 --- /dev/null +++ b/wgpu-core/src/device/any_device.rs @@ -0,0 +1,76 @@ +use super::Device; +/// The `AnyDevice` type: a pointer to a `Device` for any backend `A`. +use crate::hal_api::HalApi; + +use std::any::Any; +use std::fmt; +use std::sync::Arc; + +/// A pointer to a `Device`, for any backend `A`. +/// +/// Any `AnyDevice` is just like an `Arc>`, except that the +/// `A` type parameter is erased. To access the `Device`, you must +/// downcast to a particular backend with the \[`downcast_ref`\] or +/// \[`downcast_clone`\] methods. +pub struct AnyDevice(Arc); + +impl AnyDevice { + /// Return an `AnyDevice` that holds an owning `Arc` pointer to `device`. + pub fn new(device: Arc>) -> AnyDevice { + AnyDevice(device) + } + + /// If `self` is an `Arc>`, return a reference to the + /// device. + pub fn downcast_ref(&self) -> Option<&Device> { + self.0.downcast_ref::>() + } + + /// If `self` is an `Arc>`, return a clone of that. + pub fn downcast_clone(&self) -> Option>> { + // `Arc::downcast` returns `Arc`, but requires that `T` be `Sync` and + // `Send`, and this is not the case for `Device` in wasm builds. + // + // But as far as I can see, `Arc::downcast` has no particular reason to + // require that `T` be `Sync` and `Send`; the steps used here are sound. + if (self.0).is::>() { + // Get an owned Arc. + let clone = self.0.clone(); + // Turn the `Arc`, which is a pointer to an `ArcInner` struct, into + // a pointer to the `ArcInner`'s `data` field. Carry along the + // vtable from the original `Arc`. + let raw_erased: *const (dyn Any + 'static) = Arc::into_raw(clone); + // Remove the vtable, and supply the concrete type of the `data`. + let raw_typed: *const Device = raw_erased.cast::>(); + // Convert the pointer to the `data` field back into a pointer to + // the `ArcInner`, and restore reference-counting behavior. + let arc_typed: Arc> = unsafe { + // Safety: + // - We checked that the `dyn Any` was indeed a `Device` above. + // - We're calling `Arc::from_raw` on the same pointer returned + // by `Arc::into_raw`, except that we stripped off the vtable + // pointer. + // - The pointer must still be live, because we've borrowed `self`, + // which holds another reference to it. + // - The format of a `ArcInner` must be the same as + // that of an `ArcInner>`, or else `AnyDevice::new` + // wouldn't be possible. + Arc::from_raw(raw_typed) + }; + Some(arc_typed) + } else { + None + } + } +} + +impl fmt::Debug for AnyDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("AnyDevice") + } +} + +#[cfg(send_sync)] +unsafe impl Send for AnyDevice {} +#[cfg(send_sync)] +unsafe impl Sync for AnyDevice {} diff --git a/wgpu-core/src/device/bgl.rs b/wgpu-core/src/device/bgl.rs new file mode 100644 index 0000000000..b97f87b168 --- /dev/null +++ b/wgpu-core/src/device/bgl.rs @@ -0,0 +1,129 @@ +use std::hash::{Hash, Hasher}; + +use crate::{ + binding_model::{self}, + FastIndexMap, +}; + +/// Where a given BGL came from. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Origin { + /// The bind group layout was created by the user and is present in the BGL resource pool. + Pool, + /// The bind group layout was derived and is not present in the BGL resource pool. + Derived, +} + +/// A HashMap-like structure that stores a BindGroupLayouts [`wgt::BindGroupLayoutEntry`]s. +/// +/// It is hashable, so bind group layouts can be deduplicated. +#[derive(Debug, Default, Clone, Eq)] +pub struct EntryMap { + /// We use a IndexMap here so that we can sort the entries by their binding index, + /// guarenteeing that the hash of equivilant layouts will be the same. + inner: FastIndexMap, + /// We keep track of whether the map is sorted or not, so that we can assert that + /// it is sorted, so that PartialEq and Hash will be stable. + /// + /// We only need sorted if it is used in a Hash or PartialEq, so we never need + /// to actively sort it. + sorted: bool, +} + +impl PartialEq for EntryMap { + fn eq(&self, other: &Self) -> bool { + self.assert_sorted(); + other.assert_sorted(); + + self.inner == other.inner + } +} + +impl Hash for EntryMap { + fn hash(&self, state: &mut H) { + self.assert_sorted(); + + // We don't need to hash the keys, since they are just extracted from the values. + // + // We know this is stable and will match the behavior of PartialEq as we ensure + // that the array is sorted. + for entry in self.inner.values() { + entry.hash(state); + } + } +} + +impl EntryMap { + fn assert_sorted(&self) { + assert!(self.sorted); + } + + /// Create a new [`BindGroupLayoutEntryMap`] from a slice of [`wgt::BindGroupLayoutEntry`]s. + /// + /// Errors if there are duplicate bindings or if any binding index is greater than + /// the device's limits. + pub fn from_entries( + device_limits: &wgt::Limits, + entries: &[wgt::BindGroupLayoutEntry], + ) -> Result { + let mut inner = FastIndexMap::with_capacity_and_hasher(entries.len(), Default::default()); + for entry in entries { + if entry.binding > device_limits.max_bindings_per_bind_group { + return Err( + binding_model::CreateBindGroupLayoutError::InvalidBindingIndex { + binding: entry.binding, + maximum: device_limits.max_bindings_per_bind_group, + }, + ); + } + if inner.insert(entry.binding, *entry).is_some() { + return Err(binding_model::CreateBindGroupLayoutError::ConflictBinding( + entry.binding, + )); + } + } + inner.sort_unstable_keys(); + + Ok(Self { + inner, + sorted: true, + }) + } + + /// Get the count of [`wgt::BindGroupLayoutEntry`]s in this map. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Get the [`wgt::BindGroupLayoutEntry`] for the given binding index. + pub fn get(&self, binding: u32) -> Option<&wgt::BindGroupLayoutEntry> { + self.inner.get(&binding) + } + + /// Iterator over all the binding indices in this map. + pub fn indices(&self) -> impl ExactSizeIterator + '_ { + self.inner.keys().copied() + } + + /// Iterator over all the [`wgt::BindGroupLayoutEntry`]s in this map. + pub fn values(&self) -> impl ExactSizeIterator + '_ { + self.inner.values() + } + + pub fn iter(&self) -> impl ExactSizeIterator + '_ { + self.inner.iter() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn contains_key(&self, key: u32) -> bool { + self.inner.contains_key(&key) + } + + pub fn entry(&mut self, key: u32) -> indexmap::map::Entry<'_, u32, wgt::BindGroupLayoutEntry> { + self.sorted = false; + self.inner.entry(key) + } +} diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs index 8fe5a6fcc9..fa0c4d7dbe 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -1,31 +1,39 @@ #[cfg(feature = "trace")] use crate::device::trace; use crate::{ - binding_model, command, conv, - device::{life::WaitIdleError, map_buffer, queue, Device, DeviceError, HostMap}, + api_log, binding_model, command, conv, + device::{ + bgl, life::WaitIdleError, map_buffer, queue, DeviceError, DeviceLostClosure, + DeviceLostReason, HostMap, IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL, + }, global::Global, hal_api::HalApi, - hub::Token, - id::{self, AdapterId, DeviceId, SurfaceId}, + id::{self, AdapterId, DeviceId, QueueId, SurfaceId}, identity::{GlobalIdentityHandlerFactory, Input}, init_tracker::TextureInitTracker, instance::{self, Adapter, Surface}, pipeline, present, - resource::{self, Buffer, BufferAccessResult, BufferMapState}, - resource::{BufferAccessError, BufferMapOperation, TextureClearMode}, - storage::InvalidId, + resource::{self, BufferAccessResult}, + resource::{BufferAccessError, BufferMapOperation, CreateBufferError, Resource}, validation::check_buffer_usage, - FastHashMap, Label, LabelHelpers as _, Stored, + Label, LabelHelpers as _, }; -use hal::{CommandEncoder as _, Device as _}; -use smallvec::SmallVec; +use arrayvec::ArrayVec; +use hal::Device as _; +use parking_lot::RwLock; use wgt::{BufferAddress, TextureFormat}; -use std::{borrow::Cow, iter, mem, ops::Range, ptr}; +use std::{ + borrow::Cow, + iter, + ops::Range, + ptr, + sync::{atomic::Ordering, Arc}, +}; -use super::{BufferMapPendingClosure, ImplicitPipelineIds, InvalidDevice, UserClosures}; +use super::{ImplicitPipelineIds, InvalidDevice, UserClosures}; impl Global { pub fn adapter_is_surface_supported( @@ -34,10 +42,9 @@ impl Global { surface_id: SurfaceId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (surface_guard, mut token) = self.surfaces.read(&mut token); - let (adapter_guard, mut _token) = hub.adapters.read(&mut token); + let surface_guard = self.surfaces.read(); + let adapter_guard = hub.adapters.read(); let adapter = adapter_guard .get(adapter_id) .map_err(|_| instance::IsSurfaceSupportedError::InvalidAdapter)?; @@ -80,10 +87,9 @@ impl Global { get_supported_callback: F, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (surface_guard, mut token) = self.surfaces.read(&mut token); - let (adapter_guard, mut _token) = hub.adapters.read(&mut token); + let surface_guard = self.surfaces.read(); + let adapter_guard = hub.adapters.read(); let adapter = adapter_guard .get(adapter_id) .map_err(|_| instance::GetSurfaceSupportError::InvalidAdapter)?; @@ -99,9 +105,11 @@ impl Global { device_id: DeviceId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, _) = hub.devices.read(&mut token); - let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?; + + let device = hub.devices.get(device_id).map_err(|_| InvalidDevice)?; + if !device.is_valid() { + return Err(InvalidDevice); + } Ok(device.features) } @@ -111,9 +119,11 @@ impl Global { device_id: DeviceId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, _) = hub.devices.read(&mut token); - let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?; + + let device = hub.devices.get(device_id).map_err(|_| InvalidDevice)?; + if !device.is_valid() { + return Err(InvalidDevice); + } Ok(device.limits.clone()) } @@ -123,9 +133,11 @@ impl Global { device_id: DeviceId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, _) = hub.devices.read(&mut token); - let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?; + + let device = hub.devices.get(device_id).map_err(|_| InvalidDevice)?; + if !device.is_valid() { + return Err(InvalidDevice); + } Ok(device.downlevel.clone()) } @@ -135,42 +147,45 @@ impl Global { device_id: DeviceId, desc: &resource::BufferDescriptor, id_in: Input, - ) -> (id::BufferId, Option) { + ) -> (id::BufferId, Option) { profiling::scope!("Device::create_buffer"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.buffers.prepare(id_in); + let fid = hub.buffers.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); + let mut to_destroy: ArrayVec, 2> = ArrayVec::new(); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, - Err(_) => break DeviceError::Invalid.into(), + Err(_) => { + break DeviceError::Invalid.into(); + } }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } if desc.usage.is_empty() { // Per spec, `usage` must not be zero. - break resource::CreateBufferError::InvalidUsage(desc.usage); + break CreateBufferError::InvalidUsage(desc.usage); } #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { + if let Some(ref mut trace) = *device.trace.lock() { let mut desc = desc.clone(); - let mapped_at_creation = mem::replace(&mut desc.mapped_at_creation, false); + let mapped_at_creation = std::mem::replace(&mut desc.mapped_at_creation, false); if mapped_at_creation && !desc.usage.contains(wgt::BufferUsages::MAP_WRITE) { desc.usage |= wgt::BufferUsages::COPY_DST; } - trace - .lock() - .add(trace::Action::CreateBuffer(fid.id(), desc)); + trace.add(trace::Action::CreateBuffer(fid.id(), desc)); } - let mut buffer = match device.create_buffer(device_id, desc, false) { + let buffer = match device.create_buffer(desc, false) { Ok(buffer) => buffer, - Err(e) => break e, + Err(e) => { + break e; + } }; - let ref_count = buffer.life_guard.add_ref(); let buffer_use = if !desc.mapped_at_creation { hal::BufferUses::empty() @@ -180,19 +195,15 @@ impl Global { let ptr = if map_size == 0 { std::ptr::NonNull::dangling() } else { - match map_buffer(&device.raw, &mut buffer, 0, map_size, HostMap::Write) { + match map_buffer(device.raw(), &buffer, 0, map_size, HostMap::Write) { Ok(ptr) => ptr, Err(e) => { - let raw = buffer.raw.unwrap(); - device.lock_life(&mut token).schedule_resource_destruction( - queue::TempResource::Buffer(raw), - !0, - ); + to_destroy.push(buffer); break e.into(); } } }; - buffer.map_state = resource::BufferMapState::Active { + *buffer.map_state.lock() = resource::BufferMapState::Active { ptr, range: 0..map_size, host: HostMap::Write, @@ -208,60 +219,65 @@ impl Global { usage: wgt::BufferUsages::MAP_WRITE | wgt::BufferUsages::COPY_SRC, mapped_at_creation: false, }; - let mut stage = match device.create_buffer(device_id, &stage_desc, true) { + let stage = match device.create_buffer(&stage_desc, true) { Ok(stage) => stage, Err(e) => { - let raw = buffer.raw.unwrap(); - device - .lock_life(&mut token) - .schedule_resource_destruction(queue::TempResource::Buffer(raw), !0); + to_destroy.push(buffer); break e; } }; - let stage_buffer = stage.raw.unwrap(); - let mapping = match unsafe { device.raw.map_buffer(&stage_buffer, 0..stage.size) } { + + let snatch_guard = device.snatchable_lock.read(); + let stage_raw = stage.raw(&snatch_guard).unwrap(); + let mapping = match unsafe { device.raw().map_buffer(stage_raw, 0..stage.size) } { Ok(mapping) => mapping, Err(e) => { - let raw = buffer.raw.unwrap(); - let mut life_lock = device.lock_life(&mut token); - life_lock - .schedule_resource_destruction(queue::TempResource::Buffer(raw), !0); - life_lock.schedule_resource_destruction( - queue::TempResource::Buffer(stage_buffer), - !0, - ); - break DeviceError::from(e).into(); + to_destroy.push(buffer); + to_destroy.push(stage); + break CreateBufferError::Device(e.into()); } }; + let stage_fid = hub.buffers.request(); + let stage = stage_fid.init(stage); + assert_eq!(buffer.size % wgt::COPY_BUFFER_ALIGNMENT, 0); // Zero initialize memory and then mark both staging and buffer as initialized // (it's guaranteed that this is the case by the time the buffer is usable) unsafe { ptr::write_bytes(mapping.ptr.as_ptr(), 0, buffer.size as usize) }; - buffer.initialization_status.drain(0..buffer.size); - stage.initialization_status.drain(0..buffer.size); + buffer.initialization_status.write().drain(0..buffer.size); + stage.initialization_status.write().drain(0..buffer.size); - buffer.map_state = resource::BufferMapState::Init { + *buffer.map_state.lock() = resource::BufferMapState::Init { ptr: mapping.ptr, needs_flush: !mapping.is_coherent, - stage_buffer, + stage_buffer: stage, }; hal::BufferUses::COPY_DST }; - let id = fid.assign(buffer, &mut token); - log::info!("Created buffer {:?} with {:?}", id, desc); + let (id, resource) = fid.assign(buffer); + api_log!("Device::create_buffer({desc:?}) -> {id:?}"); device .trackers .lock() .buffers - .insert_single(id, ref_count, buffer_use); + .insert_single(id, resource, buffer_use); - return (id.0, None); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + // Error path + + for buffer in to_destroy { + let device = Arc::clone(&buffer.device); + device + .lock_life() + .schedule_resource_destruction(queue::TempResource::Buffer(Arc::new(buffer)), !0); + } + + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -295,10 +311,9 @@ impl Global { /// [`wgpu_types::BufferUsages`]: wgt::BufferUsages pub fn create_buffer_error(&self, id_in: Input, label: Label) { let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.buffers.prepare(id_in); + let fid = hub.buffers.prepare::(id_in); - fid.assign_error(label.borrow_or_default(), &mut token); + fid.assign_error(label.borrow_or_default()); } pub fn create_render_bundle_error( @@ -307,11 +322,9 @@ impl Global { label: Label, ) { let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.render_bundles.prepare(id_in); + let fid = hub.render_bundles.prepare::(id_in); - let (_, mut token) = hub.devices.read(&mut token); - fid.assign_error(label.borrow_or_default(), &mut token); + fid.assign_error(label.borrow_or_default()); } /// Assign `id_in` an error with the given `label`. @@ -319,10 +332,9 @@ impl Global { /// See `create_buffer_error` for more context and explaination. pub fn create_texture_error(&self, id_in: Input, label: Label) { let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.textures.prepare(id_in); + let fid = hub.textures.prepare::(id_in); - fid.assign_error(label.borrow_or_default(), &mut token); + fid.assign_error(label.borrow_or_default()); } #[cfg(feature = "replay")] @@ -332,20 +344,19 @@ impl Global { buffer_id: id::BufferId, ) -> Result<(), WaitIdleError> { let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); + let last_submission = { - let (buffer_guard, _) = hub.buffers.write(&mut token); + let buffer_guard = hub.buffers.write(); match buffer_guard.get(buffer_id) { - Ok(buffer) => buffer.life_guard.life_count(), + Ok(buffer) => buffer.info.submission_index(), Err(_) => return Ok(()), } }; - device_guard + hub.devices .get(device_id) .map_err(|_| DeviceError::Invalid)? - .wait_for_submit(last_submission, &mut token) + .wait_for_submit(last_submission) } #[doc(hidden)] @@ -359,22 +370,25 @@ impl Global { profiling::scope!("Device::set_buffer_sub_data"); let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - let (mut buffer_guard, _) = hub.buffers.write(&mut token); - let device = device_guard + let device = hub + .devices .get(device_id) .map_err(|_| DeviceError::Invalid)?; - let buffer = buffer_guard - .get_mut(buffer_id) + let snatch_guard = device.snatchable_lock.read(); + if !device.is_valid() { + return Err(DeviceError::Lost.into()); + } + + let buffer = hub + .buffers + .get(buffer_id) .map_err(|_| BufferAccessError::Invalid)?; check_buffer_usage(buffer.usage, wgt::BufferUsages::MAP_WRITE)?; //assert!(buffer isn't used by the GPU); #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - let mut trace = trace.lock(); + if let Some(ref mut trace) = *device.trace.lock() { let data_path = trace.make_binary("bin", data); trace.add(trace::Action::WriteBuffer { id: buffer_id, @@ -384,20 +398,22 @@ impl Global { }); } - let raw_buf = buffer.raw.as_ref().unwrap(); + let raw_buf = buffer + .raw(&snatch_guard) + .ok_or(BufferAccessError::Destroyed)?; unsafe { let mapping = device - .raw + .raw() .map_buffer(raw_buf, offset..offset + data.len() as u64) .map_err(DeviceError::from)?; ptr::copy_nonoverlapping(data.as_ptr(), mapping.ptr.as_ptr(), data.len()); if !mapping.is_coherent { device - .raw + .raw() .flush_mapped_ranges(raw_buf, iter::once(offset..offset + data.len() as u64)); } device - .raw + .raw() .unmap_buffer(raw_buf) .map_err(DeviceError::from)?; } @@ -416,34 +432,41 @@ impl Global { profiling::scope!("Device::get_buffer_sub_data"); let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - let (mut buffer_guard, _) = hub.buffers.write(&mut token); - let device = device_guard + let device = hub + .devices .get(device_id) .map_err(|_| DeviceError::Invalid)?; - let buffer = buffer_guard - .get_mut(buffer_id) + if !device.is_valid() { + return Err(DeviceError::Lost.into()); + } + + let snatch_guard = device.snatchable_lock.read(); + + let buffer = hub + .buffers + .get(buffer_id) .map_err(|_| BufferAccessError::Invalid)?; check_buffer_usage(buffer.usage, wgt::BufferUsages::MAP_READ)?; //assert!(buffer isn't used by the GPU); - let raw_buf = buffer.raw.as_ref().unwrap(); + let raw_buf = buffer + .raw(&snatch_guard) + .ok_or(BufferAccessError::Destroyed)?; unsafe { let mapping = device - .raw + .raw() .map_buffer(raw_buf, offset..offset + data.len() as u64) .map_err(DeviceError::from)?; if !mapping.is_coherent { - device.raw.invalidate_mapped_ranges( + device.raw().invalidate_mapped_ranges( raw_buf, iter::once(offset..offset + data.len() as u64), ); } ptr::copy_nonoverlapping(mapping.ptr.as_ptr(), data.as_mut_ptr(), data.len()); device - .raw + .raw() .unmap_buffer(raw_buf) .map_err(DeviceError::from)?; } @@ -460,109 +483,60 @@ impl Global { buffer_id: id::BufferId, ) -> Result<(), resource::DestroyError> { profiling::scope!("Buffer::destroy"); + api_log!("Buffer::destroy {buffer_id:?}"); - let map_closure; - // Restrict the locks to this scope. - { - let hub = A::hub(self); - let mut token = Token::root(); - - //TODO: lock pending writes separately, keep the device read-only - let (mut device_guard, mut token) = hub.devices.write(&mut token); - - log::info!("Buffer {:?} is destroyed", buffer_id); - let (mut buffer_guard, _) = hub.buffers.write(&mut token); - let buffer = buffer_guard - .get_mut(buffer_id) - .map_err(|_| resource::DestroyError::Invalid)?; - - let device = &mut device_guard[buffer.device_id.value]; - - map_closure = match &buffer.map_state { - &BufferMapState::Waiting(..) // To get the proper callback behavior. - | &BufferMapState::Init { .. } - | &BufferMapState::Active { .. } - => { - self.buffer_unmap_inner(buffer_id, buffer, device) - .unwrap_or(None) - } - _ => None, - }; - - #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::FreeBuffer(buffer_id)); - } - - let raw = buffer - .raw - .take() - .ok_or(resource::DestroyError::AlreadyDestroyed)?; - let temp = queue::TempResource::Buffer(raw); + let hub = A::hub(self); - if device.pending_writes.dst_buffers.contains(&buffer_id) { - device.pending_writes.temp_resources.push(temp); - } else { - let last_submit_index = buffer.life_guard.life_count(); - drop(buffer_guard); - device - .lock_life(&mut token) - .schedule_resource_destruction(temp, last_submit_index); - } - } + let buffer = hub + .buffers + .get(buffer_id) + .map_err(|_| resource::DestroyError::Invalid)?; - // Note: outside the scope where locks are held when calling the callback - if let Some((operation, status)) = map_closure { - operation.callback.call(status); - } + let _ = buffer.unmap(); - Ok(()) + buffer.destroy() } pub fn buffer_drop(&self, buffer_id: id::BufferId, wait: bool) { profiling::scope!("Buffer::drop"); - log::debug!("buffer {:?} is dropped", buffer_id); + api_log!("Buffer::drop {buffer_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - - let (ref_count, last_submit_index, device_id) = { - let (mut buffer_guard, _) = hub.buffers.write(&mut token); - match buffer_guard.get_mut(buffer_id) { - Ok(buffer) => { - let ref_count = buffer.life_guard.ref_count.take().unwrap(); - let last_submit_index = buffer.life_guard.life_count(); - (ref_count, last_submit_index, buffer.device_id.value) - } - Err(InvalidId) => { - hub.buffers.unregister_locked(buffer_id, &mut *buffer_guard); - return; - } + + let buffer = match hub.buffers.unregister(buffer_id) { + Some(buffer) => buffer, + None => { + return; } }; - let (device_guard, mut token) = hub.devices.read(&mut token); - let device = &device_guard[device_id]; + let _ = buffer.unmap(); + + let last_submit_index = buffer.info.submission_index(); + + let device = buffer.device.clone(); + + if device + .pending_writes + .lock() + .as_ref() + .unwrap() + .dst_buffers + .contains_key(&buffer_id) { - let mut life_lock = device.lock_life(&mut token); - if device.pending_writes.dst_buffers.contains(&buffer_id) { - life_lock.future_suspected_buffers.push(Stored { - value: id::Valid(buffer_id), - ref_count, - }); - } else { - drop(ref_count); - life_lock - .suspected_resources - .buffers - .push(id::Valid(buffer_id)); - } + device.lock_life().future_suspected_buffers.push(buffer); + } else { + device + .lock_life() + .suspected_resources + .buffers + .insert(buffer_id, buffer); } if wait { - match device.wait_for_submit(last_submit_index, &mut token) { + match device.wait_for_submit(last_submit_index) { Ok(()) => (), - Err(e) => log::error!("Failed to wait for buffer {:?}: {:?}", buffer_id, e), + Err(e) => log::error!("Failed to wait for buffer {:?}: {}", buffer_id, e), } } } @@ -576,43 +550,42 @@ impl Global { profiling::scope!("Device::create_texture"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.textures.prepare(id_in); - let (adapter_guard, mut token) = hub.adapters.read(&mut token); - let (device_guard, mut token) = hub.devices.read(&mut token); + let fid = hub.textures.prepare::(id_in); + let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::CreateTexture(fid.id(), desc.clone())); + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateTexture(fid.id(), desc.clone())); } - let adapter = &adapter_guard[device.adapter_id.value]; - let texture = match device.create_texture(device_id, adapter, desc) { + let texture = match device.create_texture(&device.adapter, desc) { Ok(texture) => texture, Err(error) => break error, }; - let ref_count = texture.life_guard.add_ref(); - let id = fid.assign(texture, &mut token); - log::info!("Created texture {:?} with {:?}", id, desc); + let (id, resource) = fid.assign(texture); + api_log!("Device::create_texture({desc:?}) -> {id:?}"); device.trackers.lock().textures.insert_single( - id.0, - ref_count, + id, + resource, hal::TextureUses::UNINITIALIZED, ); - return (id.0, None); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + log::error!("Device::create_texture error: {error}"); + + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -628,33 +601,30 @@ impl Global { desc: &resource::TextureDescriptor, id_in: Input, ) -> (id::TextureId, Option) { - profiling::scope!("Device::create_texture"); + profiling::scope!("Device::create_texture_from_hal"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.textures.prepare(id_in); - let (adapter_guard, mut token) = hub.adapters.read(&mut token); - let (device_guard, mut token) = hub.devices.read(&mut token); + let fid = hub.textures.prepare::(id_in); + let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } // NB: Any change done through the raw texture handle will not be // recorded in the replay #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::CreateTexture(fid.id(), desc.clone())); + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateTexture(fid.id(), desc.clone())); } - let adapter = &adapter_guard[device.adapter_id.value]; - let format_features = match device - .describe_format_features(adapter, desc.format) + .describe_format_features(&device.adapter, desc.format) .map_err(|error| resource::CreateTextureError::MissingFeatures(desc.format, error)) { Ok(features) => features, @@ -664,32 +634,32 @@ impl Global { let mut texture = device.create_texture_from_hal( hal_texture, conv::map_texture_usage(desc.usage, desc.format.into()), - device_id, desc, format_features, - TextureClearMode::None, + resource::TextureClearMode::None, ); if desc.usage.contains(wgt::TextureUsages::COPY_DST) { texture.hal_usage |= hal::TextureUses::COPY_DST; } - texture.initialization_status = TextureInitTracker::new(desc.mip_level_count, 0); - - let ref_count = texture.life_guard.add_ref(); + texture.initialization_status = + RwLock::new(TextureInitTracker::new(desc.mip_level_count, 0)); - let id = fid.assign(texture, &mut token); - log::info!("Created texture {:?} with {:?}", id, desc); + let (id, resource) = fid.assign(texture); + api_log!("Device::create_texture -> {id:?}"); device.trackers.lock().textures.insert_single( - id.0, - ref_count, + id, + resource, hal::TextureUses::UNINITIALIZED, ); - return (id.0, None); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + log::error!("Device::create_texture error: {error}"); + + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -704,49 +674,45 @@ impl Global { device_id: DeviceId, desc: &resource::BufferDescriptor, id_in: Input, - ) -> (id::BufferId, Option) { + ) -> (id::BufferId, Option) { profiling::scope!("Device::create_buffer"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.buffers.prepare(id_in); + let fid = hub.buffers.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } // NB: Any change done through the raw buffer handle will not be // recorded in the replay #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::CreateBuffer(fid.id(), desc.clone())); + if let Some(trace) = device.trace.lock().as_mut() { + trace.add(trace::Action::CreateBuffer(fid.id(), desc.clone())); } - let mut buffer = device.create_buffer_from_hal(hal_buffer, device_id, desc); - - // Assume external buffers are initialized - buffer.initialization_status = crate::init_tracker::BufferInitTracker::new(0); - - let ref_count = buffer.life_guard.add_ref(); + let buffer = device.create_buffer_from_hal(hal_buffer, desc); - let id = fid.assign(buffer, &mut token); - log::info!("Created buffer {:?} with {:?}", id, desc); + let (id, buffer) = fid.assign(buffer); + api_log!("Device::create_buffer -> {id:?}"); device .trackers .lock() .buffers - .insert_single(id, ref_count, hal::BufferUses::empty()); + .insert_single(id, buffer, hal::BufferUses::empty()); - return (id.0, None); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + log::error!("Device::create_buffer error: {error}"); + + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -759,110 +725,60 @@ impl Global { texture_id: id::TextureId, ) -> Result<(), resource::DestroyError> { profiling::scope!("Texture::destroy"); + api_log!("Texture::destroy {texture_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - - //TODO: lock pending writes separately, keep the device read-only - let (mut device_guard, mut token) = hub.devices.write(&mut token); - log::info!("Buffer {:?} is destroyed", texture_id); - let (mut texture_guard, _) = hub.textures.write(&mut token); - let texture = texture_guard - .get_mut(texture_id) + let texture = hub + .textures + .get(texture_id) .map_err(|_| resource::DestroyError::Invalid)?; - let device = &mut device_guard[texture.device_id.value]; - - #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::FreeTexture(texture_id)); - } + texture.destroy() + } - let last_submit_index = texture.life_guard.life_count(); + pub fn texture_drop(&self, texture_id: id::TextureId, wait: bool) { + profiling::scope!("Texture::drop"); + api_log!("Texture::drop {texture_id:?}"); - let clear_views = match std::mem::replace(&mut texture.clear_mode, TextureClearMode::None) { - TextureClearMode::BufferCopy => SmallVec::new(), - TextureClearMode::RenderPass { clear_views, .. } => clear_views, - TextureClearMode::None => SmallVec::new(), - }; + let hub = A::hub(self); - match texture.inner { - resource::TextureInner::Native { ref mut raw } => { - let raw = raw.take().ok_or(resource::DestroyError::AlreadyDestroyed)?; - let temp = queue::TempResource::Texture(raw, clear_views); + if let Some(texture) = hub.textures.unregister(texture_id) { + let last_submit_index = texture.info.submission_index(); - if device.pending_writes.dst_textures.contains(&texture_id) { - device.pending_writes.temp_resources.push(temp); + let device = &texture.device; + { + if device + .pending_writes + .lock() + .as_ref() + .unwrap() + .dst_textures + .contains_key(&texture_id) + { + device + .lock_life() + .future_suspected_textures + .push(texture.clone()); } else { - drop(texture_guard); device - .lock_life(&mut token) - .schedule_resource_destruction(temp, last_submit_index); + .lock_life() + .suspected_resources + .textures + .insert(texture_id, texture.clone()); } } - resource::TextureInner::Surface { .. } => { - for clear_view in clear_views { - unsafe { - device.raw.destroy_texture_view(clear_view); - } - } - // TODO? - } - } - - Ok(()) - } - pub fn texture_drop(&self, texture_id: id::TextureId, wait: bool) { - profiling::scope!("Texture::drop"); - log::debug!("texture {:?} is dropped", texture_id); - - let hub = A::hub(self); - let mut token = Token::root(); - - let (ref_count, last_submit_index, device_id) = { - let (mut texture_guard, _) = hub.textures.write(&mut token); - match texture_guard.get_mut(texture_id) { - Ok(texture) => { - let ref_count = texture.life_guard.ref_count.take().unwrap(); - let last_submit_index = texture.life_guard.life_count(); - (ref_count, last_submit_index, texture.device_id.value) - } - Err(InvalidId) => { - hub.textures - .unregister_locked(texture_id, &mut *texture_guard); - return; + if wait { + match device.wait_for_submit(last_submit_index) { + Ok(()) => (), + Err(e) => log::error!("Failed to wait for texture {texture_id:?}: {e}"), } } - }; - - let (device_guard, mut token) = hub.devices.read(&mut token); - let device = &device_guard[device_id]; - { - let mut life_lock = device.lock_life(&mut token); - if device.pending_writes.dst_textures.contains(&texture_id) { - life_lock.future_suspected_textures.push(Stored { - value: id::Valid(texture_id), - ref_count, - }); - } else { - drop(ref_count); - life_lock - .suspected_resources - .textures - .push(id::Valid(texture_id)); - } - } - - if wait { - match device.wait_for_submit(last_submit_index, &mut token) { - Ok(()) => (), - Err(e) => log::error!("Failed to wait for texture {:?}: {:?}", texture_id, e), - } } } + #[allow(unused_unsafe)] pub fn texture_create_view( &self, texture_id: id::TextureId, @@ -872,38 +788,43 @@ impl Global { profiling::scope!("Texture::create_view"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.texture_views.prepare(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); - let (texture_guard, mut token) = hub.textures.read(&mut token); + let fid = hub.texture_views.prepare::(id_in); + let error = loop { - let texture = match texture_guard.get(texture_id) { + let texture = match hub.textures.get(texture_id) { Ok(texture) => texture, Err(_) => break resource::CreateTextureViewError::InvalidTexture, }; - let device = &device_guard[texture.device_id.value]; + let device = &texture.device; + { + let snatch_guard = device.snatchable_lock.read(); + if texture.is_destroyed(&snatch_guard) { + break resource::CreateTextureViewError::InvalidTexture; + } + } #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::CreateTextureView { + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateTextureView { id: fid.id(), parent_id: texture_id, desc: desc.clone(), }); } - let view = match device.create_texture_view(texture, texture_id, desc) { + let view = match unsafe { device.create_texture_view(&texture, desc) } { Ok(view) => view, Err(e) => break e, }; - let ref_count = view.life_guard.add_ref(); - let id = fid.assign(view, &mut token); - device.trackers.lock().views.insert_single(id, ref_count); - return (id.0, None); + let (id, resource) = fid.assign(view); + api_log!("Texture::create_view({texture_id:?}) -> {id:?}"); + device.trackers.lock().views.insert_single(id, resource); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + log::error!("Texture::create_view({texture_id:?}) error: {error}"); + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -917,44 +838,26 @@ impl Global { wait: bool, ) -> Result<(), resource::TextureViewDestroyError> { profiling::scope!("TextureView::drop"); - log::debug!("texture view {:?} is dropped", texture_view_id); + api_log!("TextureView::drop {texture_view_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - let (last_submit_index, device_id) = { - let (mut texture_view_guard, _) = hub.texture_views.write(&mut token); + if let Some(view) = hub.texture_views.unregister(texture_view_id) { + let last_submit_index = view.info.submission_index(); - match texture_view_guard.get_mut(texture_view_id) { - Ok(view) => { - let _ref_count = view.life_guard.ref_count.take(); - let last_submit_index = view.life_guard.life_count(); - (last_submit_index, view.device_id.value) - } - Err(InvalidId) => { - hub.texture_views - .unregister_locked(texture_view_id, &mut *texture_view_guard); - return Ok(()); - } - } - }; - - let (device_guard, mut token) = hub.devices.read(&mut token); - let device = &device_guard[device_id]; - device - .lock_life(&mut token) - .suspected_resources - .texture_views - .push(id::Valid(texture_view_id)); + view.device + .lock_life() + .suspected_resources + .texture_views + .insert(texture_view_id, view.clone()); - if wait { - match device.wait_for_submit(last_submit_index, &mut token) { - Ok(()) => (), - Err(e) => log::error!( - "Failed to wait for texture view {:?}: {:?}", - texture_view_id, - e - ), + if wait { + match view.device.wait_for_submit(last_submit_index) { + Ok(()) => (), + Err(e) => { + log::error!("Failed to wait for texture view {texture_view_id:?}: {e}") + } + } } } Ok(()) @@ -969,35 +872,35 @@ impl Global { profiling::scope!("Device::create_sampler"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.samplers.prepare(id_in); + let fid = hub.samplers.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::CreateSampler(fid.id(), desc.clone())); + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateSampler(fid.id(), desc.clone())); } - let sampler = match device.create_sampler(device_id, desc) { + let sampler = match device.create_sampler(desc) { Ok(sampler) => sampler, Err(e) => break e, }; - let ref_count = sampler.life_guard.add_ref(); - let id = fid.assign(sampler, &mut token); - device.trackers.lock().samplers.insert_single(id, ref_count); + let (id, resource) = fid.assign(sampler); + api_log!("Device::create_sampler -> {id:?}"); + device.trackers.lock().samplers.insert_single(id, resource); - return (id.0, None); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -1007,32 +910,18 @@ impl Global { pub fn sampler_drop(&self, sampler_id: id::SamplerId) { profiling::scope!("Sampler::drop"); - log::debug!("sampler {:?} is dropped", sampler_id); + api_log!("Sampler::drop {sampler_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - - let device_id = { - let (mut sampler_guard, _) = hub.samplers.write(&mut token); - match sampler_guard.get_mut(sampler_id) { - Ok(sampler) => { - sampler.life_guard.ref_count.take(); - sampler.device_id.value - } - Err(InvalidId) => { - hub.samplers - .unregister_locked(sampler_id, &mut *sampler_guard); - return; - } - } - }; - let (device_guard, mut token) = hub.devices.read(&mut token); - device_guard[device_id] - .lock_life(&mut token) - .suspected_resources - .samplers - .push(id::Valid(sampler_id)); + if let Some(sampler) = hub.samplers.unregister(sampler_id) { + sampler + .device + .lock_life() + .suspected_resources + .samplers + .insert(sampler_id, sampler.clone()); + } } pub fn device_create_bind_group_layout( @@ -1046,76 +935,71 @@ impl Global { ) { profiling::scope!("Device::create_bind_group_layout"); - let mut token = Token::root(); let hub = A::hub(self); - let fid = hub.bind_group_layouts.prepare(id_in); + let fid = hub.bind_group_layouts.prepare::(id_in); - let error = 'outer: loop { - let (device_guard, mut token) = hub.devices.read(&mut token); - let device = match device_guard.get(device_id) { + let error = loop { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; - #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::CreateBindGroupLayout(fid.id(), desc.clone())); + if !device.is_valid() { + break DeviceError::Lost.into(); } - let mut entry_map = FastHashMap::default(); - for entry in desc.entries.iter() { - if entry.binding > device.limits.max_bindings_per_bind_group { - break 'outer binding_model::CreateBindGroupLayoutError::InvalidBindingIndex { - binding: entry.binding, - maximum: device.limits.max_bindings_per_bind_group, - }; - } - if entry_map.insert(entry.binding, *entry).is_some() { - break 'outer binding_model::CreateBindGroupLayoutError::ConflictBinding( - entry.binding, - ); - } + #[cfg(feature = "trace")] + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateBindGroupLayout(fid.id(), desc.clone())); } - let mut compatible_layout = None; - { - let (bgl_guard, _) = hub.bind_group_layouts.read(&mut token); - if let Some(id) = - Device::deduplicate_bind_group_layout(device_id, &entry_map, &*bgl_guard) - { - // If there is an equivalent BGL, just bump the refcount and return it. - // This is only applicable if ids are generated in wgpu. In practice: - // - wgpu users take this branch and return the existing - // id without using the indirection layer in BindGroupLayout. - // - Other users like gecko or the replay tool use don't take - // the branch and instead rely on the indirection to use the - // proper bind group layout id. - if G::ids_are_generated_in_wgpu() { - return (id, None); - } + let entry_map = match bgl::EntryMap::from_entries(&device.limits, &desc.entries) { + Ok(map) => map, + Err(e) => break e, + }; - compatible_layout = Some(id::Valid(id)); - } - } + // Currently we make a distinction between fid.assign and fid.assign_existing. This distinction is incorrect, + // but see https://github.com/gfx-rs/wgpu/issues/4912. + // + // `assign` also registers the ID with the resource info, so it can be automatically reclaimed. This needs to + // happen with a mutable reference, which means it can only happen on creation. + // + // Because we need to call `assign` inside the closure (to get mut access), we need to "move" the future id into the closure. + // Rust cannot figure out at compile time that we only ever consume the ID once, so we need to move the check + // to runtime using an Option. + let mut fid = Some(fid); + + // The closure might get called, and it might give us an ID. Side channel it out of the closure. + let mut id = None; + + let bgl_result = device.bgl_pool.get_or_init(entry_map, |entry_map| { + let bgl = + device.create_bind_group_layout(&desc.label, entry_map, bgl::Origin::Pool)?; + + let (id_inner, arc) = fid.take().unwrap().assign(bgl); + id = Some(id_inner); - let mut layout = match device.create_bind_group_layout( - device_id, - desc.label.borrow_option(), - entry_map, - ) { + Ok(arc) + }); + + let layout = match bgl_result { Ok(layout) => layout, Err(e) => break e, }; - layout.compatible_layout = compatible_layout; - - let id = fid.assign(layout, &mut token); + // If the ID was not assigned, and we survived the above check, + // it means that the bind group layout already existed and we need to call `assign_existing`. + // + // Calling this function _will_ leak the ID. See https://github.com/gfx-rs/wgpu/issues/4912. + if id.is_none() { + id = Some(fid.take().unwrap().assign_existing(&layout)) + } - return (id.0, None); + api_log!("Device::create_bind_group_layout -> {id:?}"); + return (id.unwrap(), None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let fid = hub.bind_group_layouts.prepare::(id_in); + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -1125,28 +1009,18 @@ impl Global { pub fn bind_group_layout_drop(&self, bind_group_layout_id: id::BindGroupLayoutId) { profiling::scope!("BindGroupLayout::drop"); - log::debug!("bind group layout {:?} is dropped", bind_group_layout_id); + api_log!("BindGroupLayout::drop {bind_group_layout_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - let device_id = { - let (mut bind_group_layout_guard, _) = hub.bind_group_layouts.write(&mut token); - match bind_group_layout_guard.get_mut(bind_group_layout_id) { - Ok(layout) => layout.device_id.value, - Err(InvalidId) => { - hub.bind_group_layouts - .unregister_locked(bind_group_layout_id, &mut *bind_group_layout_guard); - return; - } - } - }; - let (device_guard, mut token) = hub.devices.read(&mut token); - device_guard[device_id] - .lock_life(&mut token) - .suspected_resources - .bind_group_layouts - .push(id::Valid(bind_group_layout_id)); + if let Some(layout) = hub.bind_group_layouts.unregister(bind_group_layout_id) { + layout + .device + .lock_life() + .suspected_resources + .bind_group_layouts + .insert(bind_group_layout_id, layout.clone()); + } } pub fn device_create_pipeline_layout( @@ -1161,35 +1035,33 @@ impl Global { profiling::scope!("Device::create_pipeline_layout"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.pipeline_layouts.prepare(id_in); + let fid = hub.pipeline_layouts.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::CreatePipelineLayout(fid.id(), desc.clone())); + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreatePipelineLayout(fid.id(), desc.clone())); } - let layout = { - let (bgl_guard, _) = hub.bind_group_layouts.read(&mut token); - match device.create_pipeline_layout(device_id, desc, &*bgl_guard) { - Ok(layout) => layout, - Err(e) => break e, - } + let layout = match device.create_pipeline_layout(desc, &hub.bind_group_layouts) { + Ok(layout) => layout, + Err(e) => break e, }; - let id = fid.assign(layout, &mut token); - return (id.0, None); + let (id, _) = fid.assign(layout); + api_log!("Device::create_pipeline_layout -> {id:?}"); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -1199,34 +1071,17 @@ impl Global { pub fn pipeline_layout_drop(&self, pipeline_layout_id: id::PipelineLayoutId) { profiling::scope!("PipelineLayout::drop"); - log::debug!("pipeline layout {:?} is dropped", pipeline_layout_id); + api_log!("PipelineLayout::drop {pipeline_layout_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - let (device_id, ref_count) = { - let (mut pipeline_layout_guard, _) = hub.pipeline_layouts.write(&mut token); - match pipeline_layout_guard.get_mut(pipeline_layout_id) { - Ok(layout) => ( - layout.device_id.value, - layout.life_guard.ref_count.take().unwrap(), - ), - Err(InvalidId) => { - hub.pipeline_layouts - .unregister_locked(pipeline_layout_id, &mut *pipeline_layout_guard); - return; - } - } - }; - - let (device_guard, mut token) = hub.devices.read(&mut token); - device_guard[device_id] - .lock_life(&mut token) - .suspected_resources - .pipeline_layouts - .push(Stored { - value: id::Valid(pipeline_layout_id), - ref_count, - }); + if let Some(layout) = hub.pipeline_layouts.unregister(pipeline_layout_id) { + layout + .device + .lock_life() + .suspected_resources + .pipeline_layouts + .insert(pipeline_layout_id, layout.clone()); + } } pub fn device_create_bind_group( @@ -1238,60 +1093,48 @@ impl Global { profiling::scope!("Device::create_bind_group"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.bind_groups.prepare(id_in); - - let (device_guard, mut token) = hub.devices.read(&mut token); - let (bind_group_layout_guard, mut token) = hub.bind_group_layouts.read(&mut token); + let fid = hub.bind_groups.prepare::(id_in); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::CreateBindGroup(fid.id(), desc.clone())); + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateBindGroup(fid.id(), desc.clone())); } - let mut bind_group_layout = match bind_group_layout_guard.get(desc.layout) { + let bind_group_layout = match hub.bind_group_layouts.get(desc.layout) { Ok(layout) => layout, Err(..) => break binding_model::CreateBindGroupError::InvalidLayout, }; - let mut layout_id = id::Valid(desc.layout); - if let Some(id) = bind_group_layout.compatible_layout { - layout_id = id; - bind_group_layout = &bind_group_layout_guard[id]; + if bind_group_layout.device.as_info().id() != device.as_info().id() { + break DeviceError::WrongDevice.into(); } - let bind_group = match device.create_bind_group( - device_id, - bind_group_layout, - layout_id, - desc, - hub, - &mut token, - ) { + let bind_group = match device.create_bind_group(&bind_group_layout, desc, hub) { Ok(bind_group) => bind_group, Err(e) => break e, }; - let ref_count = bind_group.life_guard.add_ref(); - let id = fid.assign(bind_group, &mut token); - log::debug!("Bind group {:?}", id,); + let (id, resource) = fid.assign(bind_group); + api_log!("Device::create_bind_group -> {id:?}"); device .trackers .lock() .bind_groups - .insert_single(id, ref_count); - return (id.0, None); + .insert_single(id, resource); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -1301,32 +1144,18 @@ impl Global { pub fn bind_group_drop(&self, bind_group_id: id::BindGroupId) { profiling::scope!("BindGroup::drop"); - log::debug!("bind group {:?} is dropped", bind_group_id); + api_log!("BindGroup::drop {bind_group_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - - let device_id = { - let (mut bind_group_guard, _) = hub.bind_groups.write(&mut token); - match bind_group_guard.get_mut(bind_group_id) { - Ok(bind_group) => { - bind_group.life_guard.ref_count.take(); - bind_group.device_id.value - } - Err(InvalidId) => { - hub.bind_groups - .unregister_locked(bind_group_id, &mut *bind_group_guard); - return; - } - } - }; - let (device_guard, mut token) = hub.devices.read(&mut token); - device_guard[device_id] - .lock_life(&mut token) - .suspected_resources - .bind_groups - .push(id::Valid(bind_group_id)); + if let Some(bind_group) = hub.bind_groups.unregister(bind_group_id) { + bind_group + .device + .lock_life() + .suspected_resources + .bind_groups + .insert(bind_group_id, bind_group.clone()); + } } pub fn device_create_shader_module( @@ -1342,18 +1171,19 @@ impl Global { profiling::scope!("Device::create_shader_module"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.shader_modules.prepare(id_in); + let fid = hub.shader_modules.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - let mut trace = trace.lock(); + if let Some(ref mut trace) = *device.trace.lock() { let data = match source { #[cfg(feature = "wgsl")] pipeline::ShaderModuleSource::Wgsl(ref code) => { @@ -1376,15 +1206,19 @@ impl Global { }); }; - let shader = match device.create_shader_module(device_id, desc, source) { + let shader = match device.create_shader_module(desc, source) { Ok(shader) => shader, Err(e) => break e, }; - let id = fid.assign(shader, &mut token); - return (id.0, None); + + let (id, _) = fid.assign(shader); + api_log!("Device::create_shader_module -> {id:?}"); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + log::error!("Device::create_shader_module error: {error}"); + + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -1407,18 +1241,19 @@ impl Global { profiling::scope!("Device::create_shader_module"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.shader_modules.prepare(id_in); + let fid = hub.shader_modules.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - let mut trace = trace.lock(); + if let Some(ref mut trace) = *device.trace.lock() { let data = trace.make_binary("spv", unsafe { std::slice::from_raw_parts(source.as_ptr() as *const u8, source.len() * 4) }); @@ -1429,16 +1264,18 @@ impl Global { }); }; - let shader = - match unsafe { device.create_shader_module_spirv(device_id, desc, &source) } { - Ok(shader) => shader, - Err(e) => break e, - }; - let id = fid.assign(shader, &mut token); - return (id.0, None); + let shader = match unsafe { device.create_shader_module_spirv(desc, &source) } { + Ok(shader) => shader, + Err(e) => break e, + }; + let (id, _) = fid.assign(shader); + api_log!("Device::create_shader_module_spirv -> {id:?}"); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + log::error!("Device::create_shader_module_spirv error: {error}"); + + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -1448,24 +1285,10 @@ impl Global { pub fn shader_module_drop(&self, shader_module_id: id::ShaderModuleId) { profiling::scope!("ShaderModule::drop"); - log::debug!("shader module {:?} is dropped", shader_module_id); + api_log!("ShaderModule::drop {shader_module_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - let (module, _) = hub.shader_modules.unregister(shader_module_id, &mut token); - if let Some(module) = module { - let device = &device_guard[module.device_id.value]; - #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::DestroyShaderModule(shader_module_id)); - } - unsafe { - device.raw.destroy_shader_module(module.raw); - } - } + hub.shader_modules.unregister(shader_module_id); } pub fn device_create_command_encoder( @@ -1477,43 +1300,46 @@ impl Global { profiling::scope!("Device::create_command_encoder"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.command_buffers.prepare(id_in); + let fid = hub.command_buffers.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid, }; - let dev_stored = Stored { - value: id::Valid(device_id), - ref_count: device.life_guard.add_ref(), + if !device.is_valid() { + break DeviceError::Lost; + } + let queue = match hub.queues.get(device.queue_id.read().unwrap()) { + Ok(queue) => queue, + Err(_) => break DeviceError::InvalidQueueId, }; let encoder = match device .command_allocator .lock() - .acquire_encoder(&device.raw, &device.queue) + .as_mut() + .unwrap() + .acquire_encoder(device.raw(), queue.raw.as_ref().unwrap()) { Ok(raw) => raw, Err(_) => break DeviceError::OutOfMemory, }; let command_buffer = command::CommandBuffer::new( encoder, - dev_stored, - device.limits.clone(), - device.downlevel.clone(), - device.features, + &device, #[cfg(feature = "trace")] - device.trace.is_some(), - &desc.label, + device.trace.lock().is_some(), + desc.label + .to_hal(device.instance_flags) + .map(|s| s.to_string()), ); - let id = fid.assign(command_buffer, &mut token); - return (id.0, None); + let (id, _) = fid.assign(command_buffer); + api_log!("Device::create_command_encoder -> {id:?}"); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -1523,25 +1349,20 @@ impl Global { pub fn command_encoder_drop(&self, command_encoder_id: id::CommandEncoderId) { profiling::scope!("CommandEncoder::drop"); - log::debug!("command encoder {:?} is dropped", command_encoder_id); + api_log!("CommandEncoder::drop {command_encoder_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - - let (mut device_guard, mut token) = hub.devices.write(&mut token); - let (cmdbuf, _) = hub - .command_buffers - .unregister(command_encoder_id, &mut token); - if let Some(cmdbuf) = cmdbuf { - let device = &mut device_guard[cmdbuf.device_id.value]; - device.untrack::(hub, &cmdbuf.trackers, &mut token); - device.destroy_command_buffer(cmdbuf); + + if let Some(cmd_buf) = hub.command_buffers.unregister(command_encoder_id) { + cmd_buf + .device + .untrack(&cmd_buf.data.lock().as_ref().unwrap().trackers); } } pub fn command_buffer_drop(&self, command_buffer_id: id::CommandBufferId) { profiling::scope!("CommandBuffer::drop"); - log::debug!("command buffer {:?} is dropped", command_buffer_id); + api_log!("CommandBuffer::drop {command_buffer_id:?}"); self.command_encoder_drop::(command_buffer_id) } @@ -1554,6 +1375,7 @@ impl Global { Option, ) { profiling::scope!("Device::create_render_bundle_encoder"); + api_log!("Device::device_create_render_bundle_encoder"); let (encoder, error) = match command::RenderBundleEncoder::new(desc, device_id, None) { Ok(encoder) => (encoder, None), Err(e) => (command::RenderBundleEncoder::dummy(device_id), Some(e)), @@ -1570,18 +1392,21 @@ impl Global { profiling::scope!("RenderBundleEncoder::finish"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.render_bundles.prepare(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); + let fid = hub.render_bundles.prepare::(id_in); + let error = loop { - let device = match device_guard.get(bundle_encoder.parent()) { + let device = match hub.devices.get(bundle_encoder.parent()) { Ok(device) => device, Err(_) => break command::RenderBundleError::INVALID_DEVICE, }; + if !device.is_valid() { + break command::RenderBundleError::INVALID_DEVICE; + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::CreateRenderBundle { + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateRenderBundle { id: fid.id(), desc: trace::new_render_bundle_encoder_descriptor( desc.label.clone(), @@ -1593,20 +1418,18 @@ impl Global { }); } - let render_bundle = match bundle_encoder.finish(desc, device, hub, &mut token) { + let render_bundle = match bundle_encoder.finish(desc, &device, hub) { Ok(bundle) => bundle, Err(e) => break e, }; - log::debug!("Render bundle"); - let ref_count = render_bundle.life_guard.add_ref(); - let id = fid.assign(render_bundle, &mut token); - - device.trackers.lock().bundles.insert_single(id, ref_count); - return (id.0, None); + let (id, resource) = fid.assign(render_bundle); + api_log!("RenderBundleEncoder::finish -> {id:?}"); + device.trackers.lock().bundles.insert_single(id, resource); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -1616,31 +1439,18 @@ impl Global { pub fn render_bundle_drop(&self, render_bundle_id: id::RenderBundleId) { profiling::scope!("RenderBundle::drop"); - log::debug!("render bundle {:?} is dropped", render_bundle_id); + api_log!("RenderBundle::drop {render_bundle_id:?}"); + let hub = A::hub(self); - let mut token = Token::root(); - - let (device_guard, mut token) = hub.devices.read(&mut token); - let device_id = { - let (mut bundle_guard, _) = hub.render_bundles.write(&mut token); - match bundle_guard.get_mut(render_bundle_id) { - Ok(bundle) => { - bundle.life_guard.ref_count.take(); - bundle.device_id.value - } - Err(InvalidId) => { - hub.render_bundles - .unregister_locked(render_bundle_id, &mut *bundle_guard); - return; - } - } - }; - device_guard[device_id] - .lock_life(&mut token) - .suspected_resources - .render_bundles - .push(id::Valid(render_bundle_id)); + if let Some(bundle) = hub.render_bundles.unregister(render_bundle_id) { + bundle + .device + .lock_life() + .suspected_resources + .render_bundles + .insert(render_bundle_id, bundle.clone()); + } } pub fn device_create_query_set( @@ -1652,73 +1462,65 @@ impl Global { profiling::scope!("Device::create_query_set"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.query_sets.prepare(id_in); + let fid = hub.query_sets.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::CreateQuerySet { + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateQuerySet { id: fid.id(), desc: desc.clone(), }); } - let query_set = match device.create_query_set(device_id, desc) { + let query_set = match device.create_query_set(desc) { Ok(query_set) => query_set, Err(err) => break err, }; - let ref_count = query_set.life_guard.add_ref(); - let id = fid.assign(query_set, &mut token); - + let (id, resource) = fid.assign(query_set); + api_log!("Device::create_query_set -> {id:?}"); device .trackers .lock() .query_sets - .insert_single(id, ref_count); + .insert_single(id, resource); - return (id.0, None); + return (id, None); }; - let id = fid.assign_error("", &mut token); + let id = fid.assign_error(""); (id, Some(error)) } pub fn query_set_drop(&self, query_set_id: id::QuerySetId) { profiling::scope!("QuerySet::drop"); - log::debug!("query set {:?} is dropped", query_set_id); + api_log!("QuerySet::drop {query_set_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - let device_id = { - let (mut query_set_guard, _) = hub.query_sets.write(&mut token); - let query_set = query_set_guard.get_mut(query_set_id).unwrap(); - query_set.life_guard.ref_count.take(); - query_set.device_id.value - }; + if let Some(query_set) = hub.query_sets.unregister(query_set_id) { + let device = &query_set.device; - let (device_guard, mut token) = hub.devices.read(&mut token); - let device = &device_guard[device_id]; + #[cfg(feature = "trace")] + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::DestroyQuerySet(query_set_id)); + } - #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::DestroyQuerySet(query_set_id)); + device + .lock_life() + .suspected_resources + .query_sets + .insert(query_set_id, query_set.clone()); } - - device - .lock_life(&mut token) - .suspected_resources - .query_sets - .push(id::Valid(query_set_id)); } pub fn query_set_label(&self, id: id::QuerySetId) -> String { @@ -1738,54 +1540,67 @@ impl Global { profiling::scope!("Device::create_render_pipeline"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.render_pipelines.prepare(id_in); + let fid = hub.render_pipelines.prepare::(id_in); let implicit_context = implicit_pipeline_ids.map(|ipi| ipi.prepare(hub)); + let implicit_error_context = implicit_context.clone(); - let (adapter_guard, mut token) = hub.adapters.read(&mut token); - let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; - let adapter = &adapter_guard[device.adapter_id.value]; + if !device.is_valid() { + break DeviceError::Lost.into(); + } #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::CreateRenderPipeline { + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateRenderPipeline { id: fid.id(), desc: desc.clone(), implicit_context: implicit_context.clone(), }); } - let pipeline = match device.create_render_pipeline( - device_id, - adapter, - desc, - implicit_context, - hub, - &mut token, - ) { - Ok(pair) => pair, - Err(e) => break e, - }; - let ref_count = pipeline.life_guard.add_ref(); + let pipeline = + match device.create_render_pipeline(&device.adapter, desc, implicit_context, hub) { + Ok(pair) => pair, + Err(e) => break e, + }; - let id = fid.assign(pipeline, &mut token); - log::info!("Created render pipeline {:?} with {:?}", id, desc); + let (id, resource) = fid.assign(pipeline); + api_log!("Device::create_render_pipeline -> {id:?}"); device .trackers .lock() .render_pipelines - .insert_single(id, ref_count); + .insert_single(id, resource); - return (id.0, None); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let id = fid.assign_error(desc.label.borrow_or_default()); + + // We also need to assign errors to the implicit pipeline layout and the + // implicit bind group layout. We have to remove any existing entries first. + let mut pipeline_layout_guard = hub.pipeline_layouts.write(); + let mut bgl_guard = hub.bind_group_layouts.write(); + if let Some(ref ids) = implicit_error_context { + if pipeline_layout_guard.contains(ids.root_id) { + pipeline_layout_guard.remove(ids.root_id); + } + pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL); + for &bgl_id in ids.group_ids.iter() { + if bgl_guard.contains(bgl_id) { + bgl_guard.remove(bgl_id); + } + bgl_guard.insert_error(bgl_id, IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL); + } + } + + log::error!("Device::create_render_pipeline error: {error}"); + (id, Some(error)) } @@ -1801,34 +1616,26 @@ impl Global { Option, ) { let hub = A::hub(self); - let mut token = Token::root(); - let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(&mut token); let error = loop { - let (bgl_guard, mut token) = hub.bind_group_layouts.read(&mut token); - let (_, mut token) = hub.bind_groups.read(&mut token); - let (pipeline_guard, _) = hub.render_pipelines.read(&mut token); - - let pipeline = match pipeline_guard.get(pipeline_id) { + let pipeline = match hub.render_pipelines.get(pipeline_id) { Ok(pipeline) => pipeline, Err(_) => break binding_model::GetBindGroupLayoutError::InvalidPipeline, }; - let id = match pipeline_layout_guard[pipeline.layout_id.value] - .bind_group_layout_ids - .get(index as usize) - { - Some(id) => id, + let id = match pipeline.layout.bind_group_layouts.get(index as usize) { + Some(bg) => hub + .bind_group_layouts + .prepare::(id_in) + .assign_existing(bg), None => break binding_model::GetBindGroupLayoutError::InvalidGroupIndex(index), }; - - bgl_guard[*id].multi_ref_count.inc(); - return (id.0, None); + return (id, None); }; let id = hub .bind_group_layouts - .prepare(id_in) - .assign_error("", &mut token); + .prepare::(id_in) + .assign_error(""); (id, Some(error)) } @@ -1838,35 +1645,24 @@ impl Global { pub fn render_pipeline_drop(&self, render_pipeline_id: id::RenderPipelineId) { profiling::scope!("RenderPipeline::drop"); - log::debug!("render pipeline {:?} is dropped", render_pipeline_id); + api_log!("RenderPipeline::drop {render_pipeline_id:?}"); + let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - - let (device_id, layout_id) = { - let (mut pipeline_guard, _) = hub.render_pipelines.write(&mut token); - match pipeline_guard.get_mut(render_pipeline_id) { - Ok(pipeline) => { - pipeline.life_guard.ref_count.take(); - (pipeline.device_id.value, pipeline.layout_id.clone()) - } - Err(InvalidId) => { - hub.render_pipelines - .unregister_locked(render_pipeline_id, &mut *pipeline_guard); - return; - } - } - }; - let mut life_lock = device_guard[device_id].lock_life(&mut token); - life_lock - .suspected_resources - .render_pipelines - .push(id::Valid(render_pipeline_id)); - life_lock - .suspected_resources - .pipeline_layouts - .push(layout_id); + if let Some(pipeline) = hub.render_pipelines.unregister(render_pipeline_id) { + let layout_id = pipeline.layout.as_info().id(); + let device = &pipeline.device; + let mut life_lock = device.lock_life(); + life_lock + .suspected_resources + .render_pipelines + .insert(render_pipeline_id, pipeline.clone()); + + life_lock + .suspected_resources + .pipeline_layouts + .insert(layout_id, pipeline.layout.clone()); + } } pub fn device_create_compute_pipeline( @@ -1882,50 +1678,62 @@ impl Global { profiling::scope!("Device::create_compute_pipeline"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.compute_pipelines.prepare(id_in); + let fid = hub.compute_pipelines.prepare::(id_in); let implicit_context = implicit_pipeline_ids.map(|ipi| ipi.prepare(hub)); + let implicit_error_context = implicit_context.clone(); - let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { - let device = match device_guard.get(device_id) { + let device = match hub.devices.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::CreateComputePipeline { + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::CreateComputePipeline { id: fid.id(), desc: desc.clone(), implicit_context: implicit_context.clone(), }); } - - let pipeline = match device.create_compute_pipeline( - device_id, - desc, - implicit_context, - hub, - &mut token, - ) { + let pipeline = match device.create_compute_pipeline(desc, implicit_context, hub) { Ok(pair) => pair, Err(e) => break e, }; - let ref_count = pipeline.life_guard.add_ref(); - let id = fid.assign(pipeline, &mut token); - log::info!("Created compute pipeline {:?} with {:?}", id, desc); + let (id, resource) = fid.assign(pipeline); + api_log!("Device::create_compute_pipeline -> {id:?}"); device .trackers .lock() .compute_pipelines - .insert_single(id, ref_count); - return (id.0, None); + .insert_single(id, resource); + return (id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let id = fid.assign_error(desc.label.borrow_or_default()); + + // We also need to assign errors to the implicit pipeline layout and the + // implicit bind group layout. We have to remove any existing entries first. + let mut pipeline_layout_guard = hub.pipeline_layouts.write(); + let mut bgl_guard = hub.bind_group_layouts.write(); + if let Some(ref ids) = implicit_error_context { + if pipeline_layout_guard.contains(ids.root_id) { + pipeline_layout_guard.remove(ids.root_id); + } + pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL); + for &bgl_id in ids.group_ids.iter() { + if bgl_guard.contains(bgl_id) { + bgl_guard.remove(bgl_id); + } + bgl_guard.insert_error(bgl_id, IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL); + } + } (id, Some(error)) } @@ -1941,34 +1749,28 @@ impl Global { Option, ) { let hub = A::hub(self); - let mut token = Token::root(); - let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(&mut token); let error = loop { - let (bgl_guard, mut token) = hub.bind_group_layouts.read(&mut token); - let (_, mut token) = hub.bind_groups.read(&mut token); - let (pipeline_guard, _) = hub.compute_pipelines.read(&mut token); - - let pipeline = match pipeline_guard.get(pipeline_id) { + let pipeline = match hub.compute_pipelines.get(pipeline_id) { Ok(pipeline) => pipeline, Err(_) => break binding_model::GetBindGroupLayoutError::InvalidPipeline, }; - let id = match pipeline_layout_guard[pipeline.layout_id.value] - .bind_group_layout_ids - .get(index as usize) - { - Some(id) => id, + + let id = match pipeline.layout.bind_group_layouts.get(index as usize) { + Some(bg) => hub + .bind_group_layouts + .prepare::(id_in) + .assign_existing(bg), None => break binding_model::GetBindGroupLayoutError::InvalidGroupIndex(index), }; - bgl_guard[*id].multi_ref_count.inc(); - return (id.0, None); + return (id, None); }; let id = hub .bind_group_layouts - .prepare(id_in) - .assign_error("", &mut token); + .prepare::(id_in) + .assign_error(""); (id, Some(error)) } @@ -1978,35 +1780,23 @@ impl Global { pub fn compute_pipeline_drop(&self, compute_pipeline_id: id::ComputePipelineId) { profiling::scope!("ComputePipeline::drop"); - log::debug!("compute pipeline {:?} is dropped", compute_pipeline_id); + api_log!("ComputePipeline::drop {compute_pipeline_id:?}"); + let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - - let (device_id, layout_id) = { - let (mut pipeline_guard, _) = hub.compute_pipelines.write(&mut token); - match pipeline_guard.get_mut(compute_pipeline_id) { - Ok(pipeline) => { - pipeline.life_guard.ref_count.take(); - (pipeline.device_id.value, pipeline.layout_id.clone()) - } - Err(InvalidId) => { - hub.compute_pipelines - .unregister_locked(compute_pipeline_id, &mut *pipeline_guard); - return; - } - } - }; - let mut life_lock = device_guard[device_id].lock_life(&mut token); - life_lock - .suspected_resources - .compute_pipelines - .push(id::Valid(compute_pipeline_id)); - life_lock - .suspected_resources - .pipeline_layouts - .push(layout_id); + if let Some(pipeline) = hub.compute_pipelines.unregister(compute_pipeline_id) { + let layout_id = pipeline.layout.as_info().id(); + let device = &pipeline.device; + let mut life_lock = device.lock_life(); + life_lock + .suspected_resources + .compute_pipelines + .insert(compute_pipeline_id, pipeline.clone()); + life_lock + .suspected_resources + .pipeline_layouts + .insert(layout_id, pipeline.layout.clone()); + } } pub fn surface_configure( @@ -2022,21 +1812,19 @@ impl Global { fn validate_surface_configuration( config: &mut hal::SurfaceConfiguration, caps: &hal::SurfaceCapabilities, + max_texture_dimension_2d: u32, ) -> Result<(), E> { let width = config.extent.width; let height = config.extent.height; - if width < caps.extents.start().width - || width > caps.extents.end().width - || height < caps.extents.start().height - || height > caps.extents.end().height - { - log::warn!( - "Requested size {}x{} is outside of the supported range: {:?}", + + if width > max_texture_dimension_2d || height > max_texture_dimension_2d { + return Err(E::TooLarge { width, height, - caps.extents - ); + max_texture_dimension_2d, + }); } + if !caps.present_modes.contains(&config.present_mode) { let new_mode = 'b: loop { // Automatic present mode checks. @@ -2069,7 +1857,7 @@ impl Global { unreachable!("Fallback system failed to choose present mode. This is a bug. Mode: {:?}, Options: {:?}", config.present_mode, &caps.present_modes); }; - log::info!( + api_log!( "Automatically choosing presentation mode by rule {:?}. Chose {new_mode:?}", config.present_mode ); @@ -2113,7 +1901,7 @@ impl Global { ); }; - log::info!( + api_log!( "Automatically choosing alpha mode by rule {:?}. Chose {new_alpha_mode:?}", config.composite_alpha_mode ); @@ -2128,120 +1916,152 @@ impl Global { Ok(()) } - log::info!("configuring surface with {:?}", config); - let hub = A::hub(self); - let mut token = Token::root(); - - let (mut surface_guard, mut token) = self.surfaces.write(&mut token); - let (adapter_guard, mut token) = hub.adapters.read(&mut token); - let (device_guard, _token) = hub.devices.read(&mut token); + log::debug!("configuring surface with {:?}", config); let error = 'outer: loop { - let device = match device_guard.get(device_id) { - Ok(device) => device, - Err(_) => break DeviceError::Invalid.into(), - }; - #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace - .lock() - .add(trace::Action::ConfigureSurface(surface_id, config.clone())); - } + // User callbacks must not be called while we are holding locks. + let user_callbacks; + { + let hub = A::hub(self); + let surface_guard = self.surfaces.read(); + let device_guard = hub.devices.read(); - let surface = match surface_guard.get_mut(surface_id) { - Ok(surface) => surface, - Err(_) => break E::InvalidSurface, - }; + let device = match device_guard.get(device_id) { + Ok(device) => device, + Err(_) => break DeviceError::Invalid.into(), + }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } - let caps = unsafe { - let suf = A::get_surface(surface); - let adapter = &adapter_guard[device.adapter_id.value]; - match adapter.raw.adapter.surface_capabilities(&suf.unwrap().raw) { - Some(caps) => caps, - None => break E::UnsupportedQueueFamily, + #[cfg(feature = "trace")] + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::ConfigureSurface(surface_id, config.clone())); } - }; - let mut hal_view_formats = vec![]; - for format in config.view_formats.iter() { - if *format == config.format { - continue; + let surface = match surface_guard.get(surface_id) { + Ok(surface) => surface, + Err(_) => break E::InvalidSurface, + }; + + let caps = unsafe { + let suf = A::get_surface(surface); + let adapter = &device.adapter; + match adapter + .raw + .adapter + .surface_capabilities(suf.unwrap().raw.as_ref()) + { + Some(caps) => caps, + None => break E::UnsupportedQueueFamily, + } + }; + + let mut hal_view_formats = vec![]; + for format in config.view_formats.iter() { + if *format == config.format { + continue; + } + if !caps.formats.contains(&config.format) { + break 'outer E::UnsupportedFormat { + requested: config.format, + available: caps.formats, + }; + } + if config.format.remove_srgb_suffix() != format.remove_srgb_suffix() { + break 'outer E::InvalidViewFormat(*format, config.format); + } + hal_view_formats.push(*format); } - if !caps.formats.contains(&config.format) { - break 'outer E::UnsupportedFormat { - requested: config.format, - available: caps.formats, - }; + + if !hal_view_formats.is_empty() { + if let Err(missing_flag) = + device.require_downlevel_flags(wgt::DownlevelFlags::SURFACE_VIEW_FORMATS) + { + break 'outer E::MissingDownlevelFlags(missing_flag); + } } - if config.format.remove_srgb_suffix() != format.remove_srgb_suffix() { - break 'outer E::InvalidViewFormat(*format, config.format); + + let num_frames = present::DESIRED_NUM_FRAMES + .clamp(*caps.swap_chain_sizes.start(), *caps.swap_chain_sizes.end()); + let mut hal_config = hal::SurfaceConfiguration { + swap_chain_size: num_frames, + present_mode: config.present_mode, + composite_alpha_mode: config.alpha_mode, + format: config.format, + extent: wgt::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + usage: conv::map_texture_usage(config.usage, hal::FormatAspects::COLOR), + view_formats: hal_view_formats, + }; + + if let Err(error) = validate_surface_configuration( + &mut hal_config, + &caps, + device.limits.max_texture_dimension_2d, + ) { + break error; } - hal_view_formats.push(*format); - } - if !hal_view_formats.is_empty() { - if let Err(missing_flag) = - device.require_downlevel_flags(wgt::DownlevelFlags::SURFACE_VIEW_FORMATS) - { - break 'outer E::MissingDownlevelFlags(missing_flag); + // Wait for all work to finish before configuring the surface. + let fence = device.fence.read(); + let fence = fence.as_ref().unwrap(); + match device.maintain(fence, wgt::Maintain::Wait) { + Ok((closures, _)) => { + user_callbacks = closures; + } + Err(e) => { + break e.into(); + } } - } - let num_frames = present::DESIRED_NUM_FRAMES - .clamp(*caps.swap_chain_sizes.start(), *caps.swap_chain_sizes.end()); - let mut hal_config = hal::SurfaceConfiguration { - swap_chain_size: num_frames, - present_mode: config.present_mode, - composite_alpha_mode: config.alpha_mode, - format: config.format, - extent: wgt::Extent3d { - width: config.width, - height: config.height, - depth_or_array_layers: 1, - }, - usage: conv::map_texture_usage(config.usage, hal::FormatAspects::COLOR), - view_formats: hal_view_formats, - }; + // All textures must be destroyed before the surface can be re-configured. + if let Some(present) = surface.presentation.lock().take() { + if present.acquired_texture.is_some() { + break E::PreviousOutputExists; + } + } - if let Err(error) = validate_surface_configuration(&mut hal_config, &caps) { - break error; - } + // TODO: Texture views may still be alive that point to the texture. + // this will allow the user to render to the surface texture, long after + // it has been removed. + // + // https://github.com/gfx-rs/wgpu/issues/4105 - match unsafe { - A::get_surface_mut(surface) - .unwrap() - .raw - .configure(&device.raw, &hal_config) - } { - Ok(()) => (), - Err(error) => { - break match error { - hal::SurfaceError::Outdated | hal::SurfaceError::Lost => E::InvalidSurface, - hal::SurfaceError::Device(error) => E::Device(error.into()), - hal::SurfaceError::Other(message) => { - log::error!("surface configuration failed: {}", message); - E::InvalidSurface + match unsafe { + A::get_surface(surface) + .unwrap() + .raw + .configure(device.raw(), &hal_config) + } { + Ok(()) => (), + Err(error) => { + break match error { + hal::SurfaceError::Outdated | hal::SurfaceError::Lost => { + E::InvalidSurface + } + hal::SurfaceError::Device(error) => E::Device(error.into()), + hal::SurfaceError::Other(message) => { + log::error!("surface configuration failed: {}", message); + E::InvalidSurface + } } } } - } - if let Some(present) = surface.presentation.take() { - if present.acquired_texture.is_some() { - break E::PreviousOutputExists; - } + let mut presentation = surface.presentation.lock(); + *presentation = Some(present::Presentation { + device: super::any_device::AnyDevice::new(device.clone()), + config: config.clone(), + num_frames, + acquired_texture: None, + }); } - surface.presentation = Some(present::Presentation { - device_id: Stored { - value: id::Valid(device_id), - ref_count: device.life_guard.add_ref(), - }, - config: config.clone(), - num_frames, - acquired_texture: None, - }); - + user_callbacks.fire(); return None; }; @@ -2253,16 +2073,12 @@ impl Global { /// upon creating new resources when re-playing a trace. pub fn device_maintain_ids(&self, device_id: DeviceId) -> Result<(), InvalidDevice> { let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?; - device.lock_life(&mut token).triage_suspected( - hub, - &device.trackers, - #[cfg(feature = "trace")] - None, - &mut token, - ); + + let device = hub.devices.get(device_id).map_err(|_| InvalidDevice)?; + if !device.is_valid() { + return Err(InvalidDevice); + } + device.lock_life().triage_suspected(&device.trackers); Ok(()) } @@ -2274,6 +2090,8 @@ impl Global { device_id: DeviceId, maintain: wgt::Maintain, ) -> Result { + api_log!("Device::poll"); + let (closures, queue_empty) = { if let wgt::Maintain::WaitForSubmissionIndex(submission_index) = maintain { if submission_index.queue_id != device_id { @@ -2285,12 +2103,13 @@ impl Global { } let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - device_guard + let device = hub + .devices .get(device_id) - .map_err(|_| DeviceError::Invalid)? - .maintain(hub, maintain, &mut token)? + .map_err(|_| DeviceError::Invalid)?; + let fence = device.fence.read(); + let fence = fence.as_ref().unwrap(); + device.maintain(fence, maintain)? }; closures.fire(); @@ -2304,42 +2123,33 @@ impl Global { /// /// Return `all_queue_empty` indicating whether there are more queue /// submissions still in flight. - fn poll_devices( + fn poll_device( &self, force_wait: bool, closures: &mut UserClosures, ) -> Result { - profiling::scope!("poll_devices"); + profiling::scope!("poll_device"); let hub = A::hub(self); - let mut devices_to_drop = vec![]; let mut all_queue_empty = true; { - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); + let device_guard = hub.devices.read(); - for (id, device) in device_guard.iter(A::VARIANT) { + for (_id, device) in device_guard.iter(A::VARIANT) { let maintain = if force_wait { wgt::Maintain::Wait } else { wgt::Maintain::Poll }; - let (cbs, queue_empty) = device.maintain(hub, maintain, &mut token)?; + let fence = device.fence.read(); + let fence = fence.as_ref().unwrap(); + let (cbs, queue_empty) = device.maintain(fence, maintain)?; all_queue_empty = all_queue_empty && queue_empty; - // If the device's own `RefCount` clone is the only one left, and - // its submission queue is empty, then it can be freed. - if queue_empty && device.ref_count.load() == 1 { - devices_to_drop.push(id); - } closures.extend(cbs); } } - for device_id in devices_to_drop { - self.exit_device::(device_id); - } - Ok(all_queue_empty) } @@ -2350,33 +2160,29 @@ impl Global { /// Return `all_queue_empty` indicating whether there are more queue /// submissions still in flight. pub fn poll_all_devices(&self, force_wait: bool) -> Result { + api_log!("poll_all_devices"); let mut closures = UserClosures::default(); let mut all_queue_empty = true; - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - { - all_queue_empty = self.poll_devices::(force_wait, &mut closures)? - && all_queue_empty; - } - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(vulkan)] { all_queue_empty = - self.poll_devices::(force_wait, &mut closures)? && all_queue_empty; + self.poll_device::(force_wait, &mut closures)? && all_queue_empty; } - #[cfg(all(feature = "dx12", windows))] + #[cfg(metal)] { all_queue_empty = - self.poll_devices::(force_wait, &mut closures)? && all_queue_empty; + self.poll_device::(force_wait, &mut closures)? && all_queue_empty; } - #[cfg(all(feature = "dx11", windows))] + #[cfg(dx12)] { all_queue_empty = - self.poll_devices::(force_wait, &mut closures)? && all_queue_empty; + self.poll_device::(force_wait, &mut closures)? && all_queue_empty; } - #[cfg(feature = "gles")] + #[cfg(gles)] { all_queue_empty = - self.poll_devices::(force_wait, &mut closures)? && all_queue_empty; + self.poll_device::(force_wait, &mut closures)? && all_queue_empty; } closures.fire(); @@ -2389,68 +2195,115 @@ impl Global { } pub fn device_start_capture(&self, id: DeviceId) { + api_log!("Device::start_capture"); + let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, _) = hub.devices.read(&mut token); - if let Ok(device) = device_guard.get(id) { - unsafe { device.raw.start_capture() }; + + if let Ok(device) = hub.devices.get(id) { + if !device.is_valid() { + return; + } + unsafe { device.raw().start_capture() }; } } pub fn device_stop_capture(&self, id: DeviceId) { + api_log!("Device::stop_capture"); + let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, _) = hub.devices.read(&mut token); - if let Ok(device) = device_guard.get(id) { - unsafe { device.raw.stop_capture() }; + + if let Ok(device) = hub.devices.get(id) { + if !device.is_valid() { + return; + } + unsafe { device.raw().stop_capture() }; } } pub fn device_drop(&self, device_id: DeviceId) { profiling::scope!("Device::drop"); - log::debug!("device {:?} is dropped", device_id); + api_log!("Device::drop {device_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - - // For now, just drop the `RefCount` in `device.life_guard`, which - // stands for the user's reference to the device. We'll take care of - // cleaning up the device when we're polled, once its queue submissions - // have completed and it is no longer needed by other resources. - let (mut device_guard, _) = hub.devices.write(&mut token); - if let Ok(device) = device_guard.get_mut(device_id) { - device.life_guard.ref_count.take().unwrap(); + if let Some(device) = hub.devices.unregister(device_id) { + let device_lost_closure = device.lock_life().device_lost_closure.take(); + if let Some(closure) = device_lost_closure { + closure.call(DeviceLostReason::Unknown, String::from("Device dropped.")); + } + + // The things `Device::prepare_to_die` takes care are mostly + // unnecessary here. We know our queue is empty, so we don't + // need to wait for submissions or triage them. We know we were + // just polled, so `life_tracker.free_resources` is empty. + debug_assert!(device.lock_life().queue_empty()); + { + let mut pending_writes = device.pending_writes.lock(); + let pending_writes = pending_writes.as_mut().unwrap(); + pending_writes.deactivate(); + } + + drop(device); } } - /// Exit the unreferenced, inactive device `device_id`. - fn exit_device(&self, device_id: DeviceId) { + // This closure will be called exactly once during "lose the device" + // or when the device is dropped, if it was never lost. + pub fn device_set_device_lost_closure( + &self, + device_id: DeviceId, + device_lost_closure: DeviceLostClosure, + ) { let hub = A::hub(self); - let mut token = Token::root(); - let mut free_adapter_id = None; - { - let (device, mut _token) = hub.devices.unregister(device_id, &mut token); - if let Some(mut device) = device { - // The things `Device::prepare_to_die` takes care are mostly - // unnecessary here. We know our queue is empty, so we don't - // need to wait for submissions or triage them. We know we were - // just polled, so `life_tracker.free_resources` is empty. - debug_assert!(device.lock_life(&mut _token).queue_empty()); - device.pending_writes.deactivate(); - - // Adapter is only referenced by the device and itself. - // This isn't a robust way to destroy them, we should find a better one. - if device.adapter_id.ref_count.load() == 1 { - free_adapter_id = Some(device.adapter_id.value.0); - } - device.dispose(); - } + if let Ok(device) = hub.devices.get(device_id) { + let mut life_tracker = device.lock_life(); + life_tracker.device_lost_closure = Some(device_lost_closure); } + } + + pub fn device_destroy(&self, device_id: DeviceId) { + api_log!("Device::destroy {device_id:?}"); + + let hub = A::hub(self); + + if let Ok(device) = hub.devices.get(device_id) { + // Follow the steps at + // https://gpuweb.github.io/gpuweb/#dom-gpudevice-destroy. + // It's legal to call destroy multiple times, but if the device + // is already invalid, there's nothing more to do. There's also + // no need to return an error. + if !device.is_valid() { + return; + } + + // The last part of destroy is to lose the device. The spec says + // delay that until all "currently-enqueued operations on any + // queue on this device are completed." This is accomplished by + // setting valid to false, and then relying upon maintain to + // check for empty queues and a DeviceLostClosure. At that time, + // the DeviceLostClosure will be called with "destroyed" as the + // reason. + device.valid.store(false, Ordering::Relaxed); + } + } - // Free the adapter now that we've dropped the `Device` token. - if let Some(free_adapter_id) = free_adapter_id { - let _ = hub.adapters.unregister(free_adapter_id, &mut token); + pub fn device_mark_lost(&self, device_id: DeviceId, message: &str) { + api_log!("Device::mark_lost {device_id:?}"); + + let hub = A::hub(self); + + if let Ok(device) = hub.devices.get(device_id) { + device.lose(message); + } + } + + pub fn queue_drop(&self, queue_id: QueueId) { + profiling::scope!("Queue::drop"); + api_log!("Queue::drop {queue_id:?}"); + + let hub = A::hub(self); + if let Some(queue) = hub.queues.unregister(queue_id) { + drop(queue); } } @@ -2460,12 +2313,16 @@ impl Global { range: Range, op: BufferMapOperation, ) -> BufferAccessResult { + api_log!("Buffer::map_async {buffer_id:?}"); + // User callbacks must not be called while holding buffer_map_async_inner's locks, so we // defer the error callback if it needs to be called immediately (typically when running // into errors). - if let Err((op, err)) = self.buffer_map_async_inner::(buffer_id, range, op) { - op.callback.call(Err(err.clone())); - + if let Err((mut operation, err)) = self.buffer_map_async_inner::(buffer_id, range, op) { + if let Some(callback) = operation.callback.take() { + callback.call(Err(err.clone())); + } + log::error!("Buffer::map_async error: {err}"); return Err(err); } @@ -2483,8 +2340,7 @@ impl Global { profiling::scope!("Buffer::map_async"); let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); + let (pub_usage, internal_use) = match op.host { HostMap::Read => (wgt::BufferUsages::MAP_READ, hal::BufferUses::MAP_READ), HostMap::Write => (wgt::BufferUsages::MAP_WRITE, hal::BufferUses::MAP_WRITE), @@ -2494,10 +2350,10 @@ impl Global { return Err((op, BufferAccessError::UnalignedRange)); } - let (device_id, ref_count) = { - let (mut buffer_guard, _) = hub.buffers.write(&mut token); - let buffer = buffer_guard - .get_mut(buffer_id) + let buffer = { + let buffer = hub + .buffers + .get(buffer_id) .map_err(|_| BufferAccessError::Invalid); let buffer = match buffer { @@ -2507,6 +2363,11 @@ impl Global { } }; + let device = &buffer.device; + if !device.is_valid() { + return Err((op, DeviceError::Lost.into())); + } + if let Err(e) = check_buffer_usage(buffer.usage, pub_usage) { return Err((op, e.into())); } @@ -2530,41 +2391,44 @@ impl Global { )); } - buffer.map_state = match buffer.map_state { - resource::BufferMapState::Init { .. } | resource::BufferMapState::Active { .. } => { - return Err((op, BufferAccessError::AlreadyMapped)); - } - resource::BufferMapState::Waiting(_) => { - return Err((op, BufferAccessError::MapAlreadyPending)); - } - resource::BufferMapState::Idle => { - resource::BufferMapState::Waiting(resource::BufferPendingMapping { - range, - op, - _parent_ref_count: buffer.life_guard.add_ref(), - }) - } - }; - log::debug!("Buffer {:?} map state -> Waiting", buffer_id); + let snatch_guard = device.snatchable_lock.read(); + if buffer.is_destroyed(&snatch_guard) { + return Err((op, BufferAccessError::Destroyed)); + } - let device = &device_guard[buffer.device_id.value]; + { + let map_state = &mut *buffer.map_state.lock(); + *map_state = match *map_state { + resource::BufferMapState::Init { .. } + | resource::BufferMapState::Active { .. } => { + return Err((op, BufferAccessError::AlreadyMapped)); + } + resource::BufferMapState::Waiting(_) => { + return Err((op, BufferAccessError::MapAlreadyPending)); + } + resource::BufferMapState::Idle => { + resource::BufferMapState::Waiting(resource::BufferPendingMapping { + range, + op, + _parent_buffer: buffer.clone(), + }) + } + }; + } - let ret = (buffer.device_id.value, buffer.life_guard.add_ref()); + { + let mut trackers = buffer.device.as_ref().trackers.lock(); + trackers.buffers.set_single(&buffer, internal_use); + //TODO: Check if draining ALL buffers is correct! + let _ = trackers.buffers.drain_transitions(&snatch_guard); + } - let mut trackers = device.trackers.lock(); - trackers - .buffers - .set_single(&*buffer_guard, buffer_id, internal_use); - trackers.buffers.drain(); + drop(snatch_guard); - ret + buffer }; - let device = &device_guard[device_id]; - - device - .lock_life(&mut token) - .map(id::Valid(buffer_id), ref_count); + buffer.device.lock_life().map(&buffer); Ok(()) } @@ -2576,14 +2440,22 @@ impl Global { size: Option, ) -> Result<(*mut u8, u64), BufferAccessError> { profiling::scope!("Buffer::get_mapped_range"); + api_log!("Buffer::get_mapped_range {buffer_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - let (buffer_guard, _) = hub.buffers.read(&mut token); - let buffer = buffer_guard + + let buffer = hub + .buffers .get(buffer_id) .map_err(|_| BufferAccessError::Invalid)?; + { + let snatch_guard = buffer.device.snatchable_lock.read(); + if buffer.is_destroyed(&snatch_guard) { + return Err(BufferAccessError::Destroyed); + } + } + let range_size = if let Some(size) = size { size } else if offset > buffer.size { @@ -2598,9 +2470,9 @@ impl Global { if range_size % wgt::COPY_BUFFER_ALIGNMENT != 0 { return Err(BufferAccessError::UnalignedRangeSize { range_size }); } - - match buffer.map_state { - resource::BufferMapState::Init { ptr, .. } => { + let map_state = &*buffer.map_state.lock(); + match *map_state { + resource::BufferMapState::Init { ref ptr, .. } => { // offset (u64) can not be < 0, so no need to validate the lower bound if offset + range_size > buffer.size { return Err(BufferAccessError::OutOfBoundsOverrun { @@ -2610,7 +2482,9 @@ impl Global { } unsafe { Ok((ptr.as_ptr().offset(offset as isize), range_size)) } } - resource::BufferMapState::Active { ptr, ref range, .. } => { + resource::BufferMapState::Active { + ref ptr, ref range, .. + } => { if offset < range.start { return Err(BufferAccessError::OutOfBoundsUnderrun { index: offset, @@ -2633,130 +2507,27 @@ impl Global { } } } - - fn buffer_unmap_inner( - &self, - buffer_id: id::BufferId, - buffer: &mut Buffer, - device: &mut Device, - ) -> Result, BufferAccessError> { - log::debug!("Buffer {:?} map state -> Idle", buffer_id); - match mem::replace(&mut buffer.map_state, resource::BufferMapState::Idle) { - resource::BufferMapState::Init { - ptr, - stage_buffer, - needs_flush, - } => { - #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - let mut trace = trace.lock(); - let data = trace.make_binary("bin", unsafe { - std::slice::from_raw_parts(ptr.as_ptr(), buffer.size as usize) - }); - trace.add(trace::Action::WriteBuffer { - id: buffer_id, - data, - range: 0..buffer.size, - queued: true, - }); - } - let _ = ptr; - if needs_flush { - unsafe { - device - .raw - .flush_mapped_ranges(&stage_buffer, iter::once(0..buffer.size)); - } - } - - let raw_buf = buffer.raw.as_ref().ok_or(BufferAccessError::Destroyed)?; - - buffer.life_guard.use_at(device.active_submission_index + 1); - let region = wgt::BufferSize::new(buffer.size).map(|size| hal::BufferCopy { - src_offset: 0, - dst_offset: 0, - size, - }); - let transition_src = hal::BufferBarrier { - buffer: &stage_buffer, - usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, - }; - let transition_dst = hal::BufferBarrier { - buffer: raw_buf, - usage: hal::BufferUses::empty()..hal::BufferUses::COPY_DST, - }; - let encoder = device.pending_writes.activate(); - unsafe { - encoder.transition_buffers( - iter::once(transition_src).chain(iter::once(transition_dst)), - ); - if buffer.size > 0 { - encoder.copy_buffer_to_buffer(&stage_buffer, raw_buf, region.into_iter()); - } - } - device - .pending_writes - .consume_temp(queue::TempResource::Buffer(stage_buffer)); - device.pending_writes.dst_buffers.insert(buffer_id); - } - resource::BufferMapState::Idle => { - return Err(BufferAccessError::NotMapped); - } - resource::BufferMapState::Waiting(pending) => { - return Ok(Some((pending.op, Err(BufferAccessError::MapAborted)))); - } - resource::BufferMapState::Active { ptr, range, host } => { - if host == HostMap::Write { - #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - let mut trace = trace.lock(); - let size = range.end - range.start; - let data = trace.make_binary("bin", unsafe { - std::slice::from_raw_parts(ptr.as_ptr(), size as usize) - }); - trace.add(trace::Action::WriteBuffer { - id: buffer_id, - data, - range: range.clone(), - queued: false, - }); - } - let _ = (ptr, range); - } - unsafe { - device - .raw - .unmap_buffer(buffer.raw.as_ref().unwrap()) - .map_err(DeviceError::from)? - }; - } - } - Ok(None) - } - pub fn buffer_unmap(&self, buffer_id: id::BufferId) -> BufferAccessResult { profiling::scope!("unmap", "Buffer"); + api_log!("Buffer::unmap {buffer_id:?}"); - let closure; - { - // Restrict the locks to this scope. - let hub = A::hub(self); - let mut token = Token::root(); + let hub = A::hub(self); - let (mut device_guard, mut token) = hub.devices.write(&mut token); - let (mut buffer_guard, _) = hub.buffers.write(&mut token); - let buffer = buffer_guard - .get_mut(buffer_id) - .map_err(|_| BufferAccessError::Invalid)?; - let device = &mut device_guard[buffer.device_id.value]; + let buffer = hub + .buffers + .get(buffer_id) + .map_err(|_| BufferAccessError::Invalid)?; - closure = self.buffer_unmap_inner(buffer_id, buffer, device) + let snatch_guard = buffer.device.snatchable_lock.read(); + if buffer.is_destroyed(&snatch_guard) { + return Err(BufferAccessError::Destroyed); } + drop(snatch_guard); - // Note: outside the scope where locks are held when calling the callback - if let Some((operation, status)) = closure? { - operation.callback.call(status); + if !buffer.device.is_valid() { + return Err(DeviceError::Lost.into()); } - Ok(()) + + buffer.unmap() } } diff --git a/wgpu-core/src/device/life.rs b/wgpu-core/src/device/life.rs index 7ab3c2f068..537ea3be0d 100644 --- a/wgpu-core/src/device/life.rs +++ b/wgpu-core/src/device/life.rs @@ -1,219 +1,152 @@ -#[cfg(feature = "trace")] -use crate::device::trace; use crate::{ + binding_model::{BindGroup, BindGroupLayout, PipelineLayout}, + command::RenderBundle, device::{ queue::{EncoderInFlight, SubmittedWorkDoneClosure, TempResource}, - DeviceError, + DeviceError, DeviceLostClosure, }, hal_api::HalApi, - hub::{Hub, Token}, - id, - identity::GlobalIdentityHandlerFactory, - resource, - track::{BindGroupStates, RenderBundleScope, Tracker}, - RefCount, Stored, SubmissionIndex, + id::{ + self, BindGroupId, BindGroupLayoutId, BufferId, ComputePipelineId, PipelineLayoutId, + QuerySetId, RenderBundleId, RenderPipelineId, SamplerId, StagingBufferId, TextureId, + TextureViewId, + }, + pipeline::{ComputePipeline, RenderPipeline}, + resource::{ + self, Buffer, DestroyedBuffer, DestroyedTexture, QuerySet, Resource, Sampler, + StagingBuffer, Texture, TextureView, + }, + track::{ResourceTracker, Tracker}, + FastHashMap, SubmissionIndex, }; use smallvec::SmallVec; -use hal::Device as _; +use crate::id::{BlasId, TlasId}; +use crate::resource::{Blas, Tlas}; use parking_lot::Mutex; +use std::sync::Arc; use thiserror::Error; -use std::mem; - /// A struct that keeps lists of resources that are no longer needed by the user. -#[derive(Debug, Default)] -pub(super) struct SuspectedResources { - pub(super) buffers: Vec>, - pub(super) textures: Vec>, - pub(super) texture_views: Vec>, - pub(super) samplers: Vec>, - pub(super) bind_groups: Vec>, - pub(super) compute_pipelines: Vec>, - pub(super) render_pipelines: Vec>, - pub(super) bind_group_layouts: Vec>, - pub(super) pipeline_layouts: Vec>, - pub(super) render_bundles: Vec>, - pub(super) query_sets: Vec>, - pub(super) blas_s: Vec>, - pub(super) tlas_s: Vec>, +#[derive(Default)] +pub(crate) struct ResourceMaps { + pub buffers: FastHashMap>>, + pub staging_buffers: FastHashMap>>, + pub textures: FastHashMap>>, + pub texture_views: FastHashMap>>, + pub samplers: FastHashMap>>, + pub bind_groups: FastHashMap>>, + pub bind_group_layouts: FastHashMap>>, + pub render_pipelines: FastHashMap>>, + pub compute_pipelines: FastHashMap>>, + pub pipeline_layouts: FastHashMap>>, + pub render_bundles: FastHashMap>>, + pub query_sets: FastHashMap>>, + pub blas_s: FastHashMap>>, + pub tlas_s: FastHashMap>>, + pub destroyed_buffers: FastHashMap>>, + pub destroyed_textures: FastHashMap>>, } -impl SuspectedResources { - pub(super) fn clear(&mut self) { - self.buffers.clear(); - self.textures.clear(); - self.texture_views.clear(); - self.samplers.clear(); - self.bind_groups.clear(); - self.compute_pipelines.clear(); - self.render_pipelines.clear(); - self.bind_group_layouts.clear(); - self.pipeline_layouts.clear(); - self.render_bundles.clear(); - self.query_sets.clear(); - self.blas_s.clear(); - self.tlas_s.clear(); - } - - pub(super) fn extend(&mut self, other: &Self) { - self.buffers.extend_from_slice(&other.buffers); - self.textures.extend_from_slice(&other.textures); - self.texture_views.extend_from_slice(&other.texture_views); - self.samplers.extend_from_slice(&other.samplers); - self.bind_groups.extend_from_slice(&other.bind_groups); - self.compute_pipelines - .extend_from_slice(&other.compute_pipelines); - self.render_pipelines - .extend_from_slice(&other.render_pipelines); - self.bind_group_layouts - .extend_from_slice(&other.bind_group_layouts); - self.pipeline_layouts - .extend_from_slice(&other.pipeline_layouts); - self.render_bundles.extend_from_slice(&other.render_bundles); - self.query_sets.extend_from_slice(&other.query_sets); - self.blas_s.extend_from_slice(&other.blas_s); - self.tlas_s.extend_from_slice(&other.tlas_s); - } - - pub(super) fn add_render_bundle_scope(&mut self, trackers: &RenderBundleScope) { - self.buffers.extend(trackers.buffers.used()); - self.textures.extend(trackers.textures.used()); - self.bind_groups.extend(trackers.bind_groups.used()); - self.render_pipelines - .extend(trackers.render_pipelines.used()); - self.query_sets.extend(trackers.query_sets.used()); - } - - pub(super) fn add_bind_group_states(&mut self, trackers: &BindGroupStates) { - self.buffers.extend(trackers.buffers.used()); - self.textures.extend(trackers.textures.used()); - self.texture_views.extend(trackers.views.used()); - self.samplers.extend(trackers.samplers.used()); - self.tlas_s.extend(trackers.acceleration_structures.used()); +impl ResourceMaps { + pub(crate) fn new() -> Self { + ResourceMaps { + buffers: FastHashMap::default(), + staging_buffers: FastHashMap::default(), + textures: FastHashMap::default(), + texture_views: FastHashMap::default(), + samplers: FastHashMap::default(), + bind_groups: FastHashMap::default(), + bind_group_layouts: FastHashMap::default(), + render_pipelines: FastHashMap::default(), + compute_pipelines: FastHashMap::default(), + pipeline_layouts: FastHashMap::default(), + render_bundles: FastHashMap::default(), + query_sets: FastHashMap::default(), + blas_s: FastHashMap::default(), + tlas_s: FastHashMap::default(), + destroyed_buffers: FastHashMap::default(), + destroyed_textures: FastHashMap::default(), + } } -} -/// Raw backend resources that should be freed shortly. -#[derive(Debug)] -struct NonReferencedResources { - buffers: Vec, - textures: Vec, - texture_views: Vec, - samplers: Vec, - bind_groups: Vec, - compute_pipes: Vec, - render_pipes: Vec, - bind_group_layouts: Vec, - pipeline_layouts: Vec, - query_sets: Vec, - acceleration_structures: Vec, -} - -impl NonReferencedResources { - fn new() -> Self { - Self { - buffers: Vec::new(), - textures: Vec::new(), - texture_views: Vec::new(), - samplers: Vec::new(), - bind_groups: Vec::new(), - compute_pipes: Vec::new(), - render_pipes: Vec::new(), - bind_group_layouts: Vec::new(), - pipeline_layouts: Vec::new(), - query_sets: Vec::new(), - acceleration_structures: Vec::new(), - } + pub(crate) fn clear(&mut self) { + let ResourceMaps { + buffers, + staging_buffers, + textures, + texture_views, + samplers, + bind_groups, + bind_group_layouts, + render_pipelines, + compute_pipelines, + pipeline_layouts, + render_bundles, + query_sets, + destroyed_buffers, + tlas_s, + blas_s, + destroyed_textures, + } = self; + buffers.clear(); + staging_buffers.clear(); + textures.clear(); + texture_views.clear(); + samplers.clear(); + bind_groups.clear(); + bind_group_layouts.clear(); + render_pipelines.clear(); + compute_pipelines.clear(); + pipeline_layouts.clear(); + render_bundles.clear(); + query_sets.clear(); + blas_s.clear(); + tlas_s.clear(); + destroyed_buffers.clear(); + destroyed_textures.clear(); } - fn extend(&mut self, other: Self) { - self.buffers.extend(other.buffers); - self.textures.extend(other.textures); - self.texture_views.extend(other.texture_views); - self.samplers.extend(other.samplers); - self.bind_groups.extend(other.bind_groups); - self.compute_pipes.extend(other.compute_pipes); - self.render_pipes.extend(other.render_pipes); - self.query_sets.extend(other.query_sets); - self.acceleration_structures - .extend(other.acceleration_structures); - assert!(other.bind_group_layouts.is_empty()); - assert!(other.pipeline_layouts.is_empty()); - } - - unsafe fn clean(&mut self, device: &A::Device) { - if !self.buffers.is_empty() { - profiling::scope!("destroy_buffers"); - for raw in self.buffers.drain(..) { - unsafe { device.destroy_buffer(raw) }; - } - } - if !self.textures.is_empty() { - profiling::scope!("destroy_textures"); - for raw in self.textures.drain(..) { - unsafe { device.destroy_texture(raw) }; - } - } - if !self.texture_views.is_empty() { - profiling::scope!("destroy_texture_views"); - for raw in self.texture_views.drain(..) { - unsafe { device.destroy_texture_view(raw) }; - } - } - if !self.samplers.is_empty() { - profiling::scope!("destroy_samplers"); - for raw in self.samplers.drain(..) { - unsafe { device.destroy_sampler(raw) }; - } - } - if !self.bind_groups.is_empty() { - profiling::scope!("destroy_bind_groups"); - for raw in self.bind_groups.drain(..) { - unsafe { device.destroy_bind_group(raw) }; - } - } - if !self.compute_pipes.is_empty() { - profiling::scope!("destroy_compute_pipelines"); - for raw in self.compute_pipes.drain(..) { - unsafe { device.destroy_compute_pipeline(raw) }; - } - } - if !self.render_pipes.is_empty() { - profiling::scope!("destroy_render_pipelines"); - for raw in self.render_pipes.drain(..) { - unsafe { device.destroy_render_pipeline(raw) }; - } - } - if !self.bind_group_layouts.is_empty() { - profiling::scope!("destroy_bind_group_layouts"); - for raw in self.bind_group_layouts.drain(..) { - unsafe { device.destroy_bind_group_layout(raw) }; - } - } - if !self.pipeline_layouts.is_empty() { - profiling::scope!("destroy_pipeline_layouts"); - for raw in self.pipeline_layouts.drain(..) { - unsafe { device.destroy_pipeline_layout(raw) }; - } - } - if !self.query_sets.is_empty() { - profiling::scope!("destroy_query_sets"); - for raw in self.query_sets.drain(..) { - unsafe { device.destroy_query_set(raw) }; - } - } - if !self.acceleration_structures.is_empty() { - profiling::scope!("destroy_acceleration_structures"); - for raw in self.acceleration_structures.drain(..) { - unsafe { device.destroy_acceleration_structure(raw) }; - } - } + pub(crate) fn extend(&mut self, mut other: Self) { + let ResourceMaps { + buffers, + staging_buffers, + textures, + texture_views, + samplers, + bind_groups, + bind_group_layouts, + render_pipelines, + compute_pipelines, + pipeline_layouts, + render_bundles, + query_sets, + tlas_s, + blas_s, + destroyed_buffers, + destroyed_textures, + } = self; + buffers.extend(other.buffers.drain()); + staging_buffers.extend(other.staging_buffers.drain()); + textures.extend(other.textures.drain()); + texture_views.extend(other.texture_views.drain()); + samplers.extend(other.samplers.drain()); + bind_groups.extend(other.bind_groups.drain()); + bind_group_layouts.extend(other.bind_group_layouts.drain()); + render_pipelines.extend(other.render_pipelines.drain()); + compute_pipelines.extend(other.compute_pipelines.drain()); + pipeline_layouts.extend(other.pipeline_layouts.drain()); + render_bundles.extend(other.render_bundles.drain()); + query_sets.extend(other.query_sets.drain()); + tlas_s.extend(other.tlas_s.drain()); + blas_s.extend(other.blas_s.drain()); + destroyed_buffers.extend(other.destroyed_buffers.drain()); + destroyed_textures.extend(other.destroyed_textures.drain()); } } /// Resources used by a queue submission, and work to be done once it completes. -struct ActiveSubmission { +struct ActiveSubmission { /// The index of the submission we track. /// /// When `Device::fence`'s value is greater than or equal to this, our queue @@ -223,17 +156,16 @@ struct ActiveSubmission { /// Resources to be freed once this queue submission has completed. /// /// When the device is polled, for completed submissions, - /// `triage_submissions` merges these into - /// `LifetimeTracker::free_resources`. From there, - /// `LifetimeTracker::cleanup` passes them to the hal to be freed. + /// `triage_submissions` removes resources that don't need to be held alive any longer + /// from there. /// /// This includes things like temporary resources and resources that are /// used by submitted commands but have been dropped by the user (meaning that /// this submission is their last reference.) - last_resources: NonReferencedResources, + last_resources: ResourceMaps, /// Buffers to be mapped once this submission has completed. - mapped: Vec>, + mapped: Vec>>, encoders: Vec>, @@ -260,12 +192,12 @@ pub enum WaitIdleError { /// A buffer cannot be mapped until all active queue submissions that use it /// have completed. To that end: /// -/// - Each buffer's `LifeGuard::submission_index` records the index of the +/// - Each buffer's `ResourceInfo::submission_index` records the index of the /// most recent queue submission that uses that buffer. /// -/// - Calling `map_async` adds the buffer to `self.mapped`, and changes -/// `Buffer::map_state` to prevent it from being used in any new -/// submissions. +/// - Calling `Global::buffer_map_async` adds the buffer to +/// `self.mapped`, and changes `Buffer::map_state` to prevent it +/// from being used in any new submissions. /// /// - When the device is polled, the following `LifetimeTracker` methods decide /// what should happen next: @@ -286,25 +218,23 @@ pub enum WaitIdleError { /// buffers that were dropped by the user get moved to /// `self.free_resources`. /// -/// 4) `cleanup` frees everything in `free_resources`. -/// -/// Only `self.mapped` holds a `RefCount` for the buffer; it is dropped by -/// `triage_mapped`. -pub(super) struct LifetimeTracker { +/// Only calling `Global::buffer_map_async` clones a new `Arc` for the +/// buffer. This new `Arc` is only dropped by `handle_mapping`. +pub(crate) struct LifetimeTracker { /// Resources that the user has requested be mapped, but which are used by /// queue submissions still in flight. - mapped: Vec>, + mapped: Vec>>, /// Buffers can be used in a submission that is yet to be made, by the /// means of `write_buffer()`, so we have a special place for them. - pub future_suspected_buffers: Vec>, + pub future_suspected_buffers: Vec>>, /// Textures can be used in the upcoming submission by `write_texture`. - pub future_suspected_textures: Vec>, + pub future_suspected_textures: Vec>>, /// Resources whose user handle has died (i.e. drop/destroy has been called) /// and will likely be ready for destruction soon. - pub suspected_resources: SuspectedResources, + pub suspected_resources: ResourceMaps, /// Resources used by queue submissions still in flight. One entry per /// submission, with older submissions appearing before younger. @@ -314,35 +244,33 @@ pub(super) struct LifetimeTracker { /// to particular entries. active: Vec>, - /// Raw backend resources that are neither referenced nor used. - /// - /// These are freed by `LifeTracker::cleanup`, which is called from periodic - /// maintenance functions like `Global::device_poll`, and when a device is - /// destroyed. - free_resources: NonReferencedResources, - /// Buffers the user has asked us to map, and which are not used by any /// queue submission still in flight. - ready_to_map: Vec>, + ready_to_map: Vec>>, /// Queue "on_submitted_work_done" closures that were initiated for while there is no - /// currently pending submissions. These cannot be immeidately invoked as they + /// currently pending submissions. These cannot be immediately invoked as they /// must happen _after_ all mapped buffer callbacks are mapped, so we defer them /// here until the next time the device is maintained. work_done_closures: SmallVec<[SubmittedWorkDoneClosure; 1]>, + + /// Closure to be called on "lose the device". This is invoked directly by + /// device.lose or by the UserCallbacks returned from maintain when the device + /// has been destroyed and its queues are empty. + pub device_lost_closure: Option, } -impl LifetimeTracker { +impl LifetimeTracker { pub fn new() -> Self { Self { mapped: Vec::new(), future_suspected_buffers: Vec::new(), future_suspected_textures: Vec::new(), - suspected_resources: SuspectedResources::default(), + suspected_resources: ResourceMaps::new(), active: Vec::new(), - free_resources: NonReferencedResources::new(), ready_to_map: Vec::new(), work_done_closures: SmallVec::new(), + device_lost_closure: None, } } @@ -358,16 +286,35 @@ impl LifetimeTracker { temp_resources: impl Iterator>, encoders: Vec>, ) { - let mut last_resources = NonReferencedResources::new(); + let mut last_resources = ResourceMaps::new(); for res in temp_resources { match res { - TempResource::Buffer(raw) => last_resources.buffers.push(raw), - TempResource::Texture(raw, views) => { - last_resources.textures.push(raw); - last_resources.texture_views.extend(views); + TempResource::Buffer(raw) => { + last_resources.buffers.insert(raw.as_info().id(), raw); + } + TempResource::StagingBuffer(raw) => { + last_resources + .staging_buffers + .insert(raw.as_info().id(), raw); + } + TempResource::DestroyedBuffer(destroyed) => { + last_resources + .destroyed_buffers + .insert(destroyed.id, destroyed); + } + TempResource::Texture(raw) => { + last_resources.textures.insert(raw.as_info().id(), raw); } - TempResource::AccelerationStructure(raw) => { - last_resources.acceleration_structures.push(raw) + TempResource::DestroyedTexture(destroyed) => { + last_resources + .destroyed_textures + .insert(destroyed.id, destroyed); + } + TempResource::Tlas(raw) => { + last_resources.tlas_s.insert(raw.as_info().id(), raw); + } + TempResource::Blas(raw) => { + last_resources.blas_s.insert(raw.as_info().id(), raw); } } } @@ -382,20 +329,18 @@ impl LifetimeTracker { } pub fn post_submit(&mut self) { - self.suspected_resources.buffers.extend( - self.future_suspected_buffers - .drain(..) - .map(|stored| stored.value), - ); - self.suspected_resources.textures.extend( - self.future_suspected_textures - .drain(..) - .map(|stored| stored.value), - ); + for v in self.future_suspected_buffers.drain(..).take(1) { + self.suspected_resources.buffers.insert(v.as_info().id(), v); + } + for v in self.future_suspected_textures.drain(..).take(1) { + self.suspected_resources + .textures + .insert(v.as_info().id(), v); + } } - pub(crate) fn map(&mut self, value: id::Valid, ref_count: RefCount) { - self.mapped.push(Stored { value, ref_count }); + pub(crate) fn map(&mut self, value: &Arc>) { + self.mapped.push(value.clone()); } /// Sort out the consequences of completed submissions. @@ -423,7 +368,7 @@ impl LifetimeTracker { pub fn triage_submissions( &mut self, last_done: SubmissionIndex, - command_allocator: &Mutex>, + command_allocator: &mut super::CommandAllocator, ) -> SmallVec<[SubmittedWorkDoneClosure; 1]> { profiling::scope!("triage_submissions"); @@ -437,25 +382,17 @@ impl LifetimeTracker { let mut work_done_closures: SmallVec<_> = self.work_done_closures.drain(..).collect(); for a in self.active.drain(..done_count) { - log::trace!("Active submission {} is done", a.index); - self.free_resources.extend(a.last_resources); + log::debug!("Active submission {} is done", a.index); self.ready_to_map.extend(a.mapped); for encoder in a.encoders { let raw = unsafe { encoder.land() }; - command_allocator.lock().release_encoder(raw); + command_allocator.release_encoder(raw); } work_done_closures.extend(a.work_done_closures); } work_done_closures } - pub fn cleanup(&mut self, device: &A::Device) { - profiling::scope!("LifetimeTracker::cleanup"); - unsafe { - self.free_resources.clean(device); - } - } - pub fn schedule_resource_destruction( &mut self, temp_resource: TempResource, @@ -465,14 +402,31 @@ impl LifetimeTracker { .active .iter_mut() .find(|a| a.index == last_submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources); - match temp_resource { - TempResource::Buffer(raw) => resources.buffers.push(raw), - TempResource::Texture(raw, views) => { - resources.texture_views.extend(views); - resources.textures.push(raw); + .map(|a| &mut a.last_resources); + if let Some(resources) = resources { + match temp_resource { + TempResource::Buffer(raw) => { + resources.buffers.insert(raw.as_info().id(), raw); + } + TempResource::StagingBuffer(raw) => { + resources.staging_buffers.insert(raw.as_info().id(), raw); + } + TempResource::DestroyedBuffer(destroyed) => { + resources.destroyed_buffers.insert(destroyed.id, destroyed); + } + TempResource::Texture(raw) => { + resources.textures.insert(raw.as_info().id(), raw); + } + TempResource::DestroyedTexture(destroyed) => { + resources.destroyed_textures.insert(destroyed.id, destroyed); + } + TempResource::Tlas(raw) => { + resources.tlas_s.insert(raw.as_info().id(), raw); + } + TempResource::Blas(raw) => { + resources.blas_s.insert(raw.as_info().id(), raw); + } } - TempResource::AccelerationStructure(raw) => resources.acceleration_structures.push(raw), } } @@ -491,6 +445,298 @@ impl LifetimeTracker { } impl LifetimeTracker { + fn triage_resources( + resources_map: &mut FastHashMap>, + active: &mut [ActiveSubmission], + trackers: &mut impl ResourceTracker, + get_resource_map: impl Fn(&mut ResourceMaps) -> &mut FastHashMap>, + ) -> Vec> + where + Id: id::TypedId, + R: Resource, + { + let mut removed_resources = Vec::new(); + resources_map.retain(|&id, resource| { + let submit_index = resource.as_info().submission_index(); + let non_referenced_resources = active + .iter_mut() + .find(|a| a.index == submit_index) + .map(|a| &mut a.last_resources); + + let is_removed = trackers.remove_abandoned(id); + if is_removed { + removed_resources.push(resource.clone()); + if let Some(ressources) = non_referenced_resources { + get_resource_map(ressources).insert(id, resource.clone()); + } + } + !is_removed + }); + removed_resources + } + + fn triage_suspected_render_bundles(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.render_bundles; + let mut removed_resources = Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.bundles, + |maps| &mut maps.render_bundles, + ); + removed_resources.drain(..).for_each(|bundle| { + for v in bundle.used.buffers.write().drain_resources() { + self.suspected_resources.buffers.insert(v.as_info().id(), v); + } + for v in bundle.used.textures.write().drain_resources() { + self.suspected_resources + .textures + .insert(v.as_info().id(), v); + } + for v in bundle.used.bind_groups.write().drain_resources() { + self.suspected_resources + .bind_groups + .insert(v.as_info().id(), v); + } + for v in bundle.used.render_pipelines.write().drain_resources() { + self.suspected_resources + .render_pipelines + .insert(v.as_info().id(), v); + } + for v in bundle.used.query_sets.write().drain_resources() { + self.suspected_resources + .query_sets + .insert(v.as_info().id(), v); + } + }); + self + } + + fn triage_suspected_bind_groups(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.bind_groups; + let mut removed_resource = Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.bind_groups, + |maps| &mut maps.bind_groups, + ); + removed_resource.drain(..).for_each(|bind_group| { + for v in bind_group.used.buffers.drain_resources() { + self.suspected_resources.buffers.insert(v.as_info().id(), v); + } + for v in bind_group.used.textures.drain_resources() { + self.suspected_resources + .textures + .insert(v.as_info().id(), v); + } + for v in bind_group.used.views.drain_resources() { + self.suspected_resources + .texture_views + .insert(v.as_info().id(), v); + } + for v in bind_group.used.samplers.drain_resources() { + self.suspected_resources + .samplers + .insert(v.as_info().id(), v); + } + for v in bind_group.used.acceleration_structures.drain_resources() { + self.suspected_resources.tlas_s.insert(v.as_info().id(), v); + } + + self.suspected_resources + .bind_group_layouts + .insert(bind_group.layout.as_info().id(), bind_group.layout.clone()); + }); + self + } + + fn triage_suspected_texture_views(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.texture_views; + let mut removed_resources = Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.views, + |maps| &mut maps.texture_views, + ); + removed_resources.drain(..).for_each(|texture_view| { + let mut lock = texture_view.parent.write(); + if let Some(parent_texture) = lock.take() { + self.suspected_resources + .textures + .insert(parent_texture.as_info().id(), parent_texture); + } + }); + self + } + + fn triage_suspected_textures(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.textures; + Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.textures, + |maps| &mut maps.textures, + ); + self + } + + fn triage_suspected_samplers(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.samplers; + Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.samplers, + |maps| &mut maps.samplers, + ); + self + } + + fn triage_suspected_buffers(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.buffers; + Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.buffers, + |maps| &mut maps.buffers, + ); + + self + } + + fn triage_suspected_destroyed_buffers(&mut self) { + for (id, buffer) in self.suspected_resources.destroyed_buffers.drain() { + let submit_index = buffer.submission_index; + if let Some(resources) = self.active.iter_mut().find(|a| a.index == submit_index) { + resources + .last_resources + .destroyed_buffers + .insert(id, buffer); + } + } + } + + fn triage_suspected_destroyed_textures(&mut self) { + for (id, texture) in self.suspected_resources.destroyed_textures.drain() { + let submit_index = texture.submission_index; + if let Some(resources) = self.active.iter_mut().find(|a| a.index == submit_index) { + resources + .last_resources + .destroyed_textures + .insert(id, texture); + } + } + } + + fn triage_suspected_compute_pipelines(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.compute_pipelines; + let mut removed_resources = Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.compute_pipelines, + |maps| &mut maps.compute_pipelines, + ); + removed_resources.drain(..).for_each(|compute_pipeline| { + self.suspected_resources.pipeline_layouts.insert( + compute_pipeline.layout.as_info().id(), + compute_pipeline.layout.clone(), + ); + }); + self + } + + fn triage_suspected_render_pipelines(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.render_pipelines; + let mut removed_resources = Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.render_pipelines, + |maps| &mut maps.render_pipelines, + ); + removed_resources.drain(..).for_each(|render_pipeline| { + self.suspected_resources.pipeline_layouts.insert( + render_pipeline.layout.as_info().id(), + render_pipeline.layout.clone(), + ); + }); + self + } + + fn triage_suspected_pipeline_layouts(&mut self) -> &mut Self { + let mut removed_resources = Vec::new(); + self.suspected_resources + .pipeline_layouts + .retain(|_pipeline_layout_id, pipeline_layout| { + removed_resources.push(pipeline_layout.clone()); + false + }); + removed_resources.drain(..).for_each(|pipeline_layout| { + for bgl in &pipeline_layout.bind_group_layouts { + self.suspected_resources + .bind_group_layouts + .insert(bgl.as_info().id(), bgl.clone()); + } + }); + self + } + + fn triage_suspected_bind_group_layouts(&mut self) -> &mut Self { + //Note: this has to happen after all the suspected pipelines are destroyed + //Note: nothing else can bump the refcount since the guard is locked exclusively + //Note: same BGL can appear multiple times in the list, but only the last + self.suspected_resources.bind_group_layouts.clear(); + + self + } + + fn triage_suspected_blas(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.blas_s; + let _ = Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.blas_s, + |maps| &mut maps.blas_s, + ); + self + } + + fn triage_suspected_tlas(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.tlas_s; + let _ = Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.tlas_s, + |maps| &mut maps.tlas_s, + ); + self + } + + fn triage_suspected_query_sets(&mut self, trackers: &Mutex>) -> &mut Self { + let mut trackers = trackers.lock(); + let resource_map = &mut self.suspected_resources.query_sets; + Self::triage_resources( + resource_map, + self.active.as_mut_slice(), + &mut trackers.query_sets, + |maps| &mut maps.query_sets, + ); + self + } + + fn triage_suspected_staging_buffers(&mut self) -> &mut Self { + self.suspected_resources.staging_buffers.clear(); + + self + } + /// Identify resources to free, according to `trackers` and `self.suspected_resources`. /// /// Given `trackers`, the [`Tracker`] belonging to same [`Device`] as @@ -504,12 +750,8 @@ impl LifetimeTracker { /// - Add resources used by queue submissions still in flight to the /// [`last_resources`] table of the last such submission's entry in /// [`self.active`]. When that submission has finished execution. the - /// [`triage_submissions`] method will move them to - /// [`self.free_resources`]. - /// - /// - Add resources that can be freed right now to [`self.free_resources`] - /// directly. [`LifetimeTracker::cleanup`] will take care of them as - /// part of this poll. + /// [`triage_submissions`] method will remove from the tracker and the + /// resource reference count will be responsible carrying out deallocation. /// /// ## Entrained resources /// @@ -529,393 +771,42 @@ impl LifetimeTracker { /// [`last_resources`]: ActiveSubmission::last_resources /// [`self.active`]: LifetimeTracker::active /// [`triage_submissions`]: LifetimeTracker::triage_submissions - /// [`self.free_resources`]: LifetimeTracker::free_resources - pub(super) fn triage_suspected( - &mut self, - hub: &Hub, - trackers: &Mutex>, - #[cfg(feature = "trace")] trace: Option<&Mutex>, - token: &mut Token>, - ) { + pub(crate) fn triage_suspected(&mut self, trackers: &Mutex>) { profiling::scope!("triage_suspected"); - if !self.suspected_resources.render_bundles.is_empty() { - let (mut guard, _) = hub.render_bundles.write(token); - let mut trackers = trackers.lock(); - - while let Some(id) = self.suspected_resources.render_bundles.pop() { - if trackers.bundles.remove_abandoned(id) { - log::debug!("Bundle {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyRenderBundle(id.0)); - } - - if let Some(res) = hub.render_bundles.unregister_locked(id.0, &mut *guard) { - self.suspected_resources.add_render_bundle_scope(&res.used); - } - } - } - } - - if !self.suspected_resources.bind_groups.is_empty() { - let (mut guard, _) = hub.bind_groups.write(token); - let mut trackers = trackers.lock(); - - while let Some(id) = self.suspected_resources.bind_groups.pop() { - if trackers.bind_groups.remove_abandoned(id) { - log::debug!("Bind group {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyBindGroup(id.0)); - } - - if let Some(res) = hub.bind_groups.unregister_locked(id.0, &mut *guard) { - self.suspected_resources.add_bind_group_states(&res.used); - - self.suspected_resources - .bind_group_layouts - .push(res.layout_id); - - let submit_index = res.life_guard.life_count(); - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .bind_groups - .push(res.raw); - } - } - } - } - - if !self.suspected_resources.texture_views.is_empty() { - let (mut guard, _) = hub.texture_views.write(token); - let mut trackers = trackers.lock(); - - let mut list = mem::take(&mut self.suspected_resources.texture_views); - for id in list.drain(..) { - if trackers.views.remove_abandoned(id) { - log::debug!("Texture view {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyTextureView(id.0)); - } - - if let Some(res) = hub.texture_views.unregister_locked(id.0, &mut *guard) { - self.suspected_resources.textures.push(res.parent_id.value); - let submit_index = res.life_guard.life_count(); - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .texture_views - .push(res.raw); - } - } - } - self.suspected_resources.texture_views = list; - } - - if !self.suspected_resources.textures.is_empty() { - let (mut guard, _) = hub.textures.write(token); - let mut trackers = trackers.lock(); - - for id in self.suspected_resources.textures.drain(..) { - if trackers.textures.remove_abandoned(id) { - log::debug!("Texture {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyTexture(id.0)); - } - - if let Some(res) = hub.textures.unregister_locked(id.0, &mut *guard) { - let submit_index = res.life_guard.life_count(); - let raw = match res.inner { - resource::TextureInner::Native { raw: Some(raw) } => raw, - _ => continue, - }; - let non_referenced_resources = self - .active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources); - - non_referenced_resources.textures.push(raw); - if let resource::TextureClearMode::RenderPass { clear_views, .. } = - res.clear_mode - { - non_referenced_resources - .texture_views - .extend(clear_views.into_iter()); - } - } - } - } - } - - if !self.suspected_resources.samplers.is_empty() { - let (mut guard, _) = hub.samplers.write(token); - let mut trackers = trackers.lock(); - - for id in self.suspected_resources.samplers.drain(..) { - if trackers.samplers.remove_abandoned(id) { - log::debug!("Sampler {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroySampler(id.0)); - } - - if let Some(res) = hub.samplers.unregister_locked(id.0, &mut *guard) { - let submit_index = res.life_guard.life_count(); - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .samplers - .push(res.raw); - } - } - } - } - - if !self.suspected_resources.buffers.is_empty() { - let (mut guard, _) = hub.buffers.write(token); - let mut trackers = trackers.lock(); - - for id in self.suspected_resources.buffers.drain(..) { - if trackers.buffers.remove_abandoned(id) { - log::debug!("Buffer {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyBuffer(id.0)); - } - - if let Some(res) = hub.buffers.unregister_locked(id.0, &mut *guard) { - let submit_index = res.life_guard.life_count(); - if let resource::BufferMapState::Init { stage_buffer, .. } = res.map_state { - self.free_resources.buffers.push(stage_buffer); - } - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .buffers - .extend(res.raw); - } - } - } - } - - if !self.suspected_resources.compute_pipelines.is_empty() { - let (mut guard, _) = hub.compute_pipelines.write(token); - let mut trackers = trackers.lock(); - - for id in self.suspected_resources.compute_pipelines.drain(..) { - if trackers.compute_pipelines.remove_abandoned(id) { - log::debug!("Compute pipeline {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyComputePipeline(id.0)); - } - - if let Some(res) = hub.compute_pipelines.unregister_locked(id.0, &mut *guard) { - let submit_index = res.life_guard.life_count(); - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .compute_pipes - .push(res.raw); - } - } - } - } - - if !self.suspected_resources.render_pipelines.is_empty() { - let (mut guard, _) = hub.render_pipelines.write(token); - let mut trackers = trackers.lock(); - - for id in self.suspected_resources.render_pipelines.drain(..) { - if trackers.render_pipelines.remove_abandoned(id) { - log::debug!("Render pipeline {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyRenderPipeline(id.0)); - } - - if let Some(res) = hub.render_pipelines.unregister_locked(id.0, &mut *guard) { - let submit_index = res.life_guard.life_count(); - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .render_pipes - .push(res.raw); - } - } - } - } - - if !self.suspected_resources.pipeline_layouts.is_empty() { - let (mut guard, _) = hub.pipeline_layouts.write(token); - - for Stored { - value: id, - ref_count, - } in self.suspected_resources.pipeline_layouts.drain(..) - { - //Note: this has to happen after all the suspected pipelines are destroyed - if ref_count.load() == 1 { - log::debug!("Pipeline layout {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyPipelineLayout(id.0)); - } - - if let Some(lay) = hub.pipeline_layouts.unregister_locked(id.0, &mut *guard) { - self.suspected_resources - .bind_group_layouts - .extend_from_slice(&lay.bind_group_layout_ids); - self.free_resources.pipeline_layouts.push(lay.raw); - } - } - } - } - - if !self.suspected_resources.bind_group_layouts.is_empty() { - let (mut guard, _) = hub.bind_group_layouts.write(token); - - for id in self.suspected_resources.bind_group_layouts.drain(..) { - //Note: this has to happen after all the suspected pipelines are destroyed - //Note: nothing else can bump the refcount since the guard is locked exclusively - //Note: same BGL can appear multiple times in the list, but only the last - // encounter could drop the refcount to 0. - let mut bgl_to_check = Some(id); - while let Some(id) = bgl_to_check.take() { - let bgl = &guard[id]; - if bgl.multi_ref_count.dec_and_check_empty() { - // If This layout points to a compatible one, go over the latter - // to decrement the ref count and potentially destroy it. - bgl_to_check = bgl.compatible_layout; - - log::debug!("Bind group layout {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyBindGroupLayout(id.0)); - } - if let Some(lay) = - hub.bind_group_layouts.unregister_locked(id.0, &mut *guard) - { - self.free_resources.bind_group_layouts.push(lay.raw); - } - } - } - } - } - - if !self.suspected_resources.query_sets.is_empty() { - let (mut guard, _) = hub.query_sets.write(token); - let mut trackers = trackers.lock(); - - for id in self.suspected_resources.query_sets.drain(..) { - if trackers.query_sets.remove_abandoned(id) { - log::debug!("Query set {:?} will be destroyed", id); - // #[cfg(feature = "trace")] - // trace.map(|t| t.lock().add(trace::Action::DestroyComputePipeline(id.0))); - if let Some(res) = hub.query_sets.unregister_locked(id.0, &mut *guard) { - let submit_index = res.life_guard.life_count(); - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .query_sets - .push(res.raw); - } - } - } - } - - if !self.suspected_resources.blas_s.is_empty() { - let (mut guard, _) = hub.blas_s.write(token); - let mut trackers = trackers.lock(); - - for id in self.suspected_resources.blas_s.drain(..) { - if trackers.blas_s.remove_abandoned(id) { - log::debug!("Blas {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyBlas(id.0)); - } - - if let Some(res) = hub.blas_s.unregister_locked(id.0, &mut *guard) { - let submit_index = res.life_guard.life_count(); - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .acceleration_structures - .extend(res.raw); - } - } - } - } - - if !self.suspected_resources.tlas_s.is_empty() { - let (mut guard, _) = hub.tlas_s.write(token); - let mut trackers = trackers.lock(); - - for id in self.suspected_resources.tlas_s.drain(..) { - if trackers.tlas_s.remove_abandoned(id) { - log::debug!("Tlas {:?} will be destroyed", id); - #[cfg(feature = "trace")] - if let Some(t) = trace { - t.lock().add(trace::Action::DestroyTlas(id.0)); - } - - if let Some(res) = hub.tlas_s.unregister_locked(id.0, &mut *guard) { - let submit_index = res.life_guard.life_count(); - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .acceleration_structures - .extend(res.raw); - - self.active - .iter_mut() - .find(|a| a.index == submit_index) - .map_or(&mut self.free_resources, |a| &mut a.last_resources) - .buffers - .extend(res.instance_buffer); - } - } - } - } + //NOTE: the order is important to release resources that depends between each other! + self.triage_suspected_render_bundles(trackers); + self.triage_suspected_compute_pipelines(trackers); + self.triage_suspected_render_pipelines(trackers); + self.triage_suspected_bind_groups(trackers); + self.triage_suspected_pipeline_layouts(); + self.triage_suspected_bind_group_layouts(); + self.triage_suspected_query_sets(trackers); + self.triage_suspected_samplers(trackers); + self.triage_suspected_staging_buffers(); + self.triage_suspected_texture_views(trackers); + self.triage_suspected_textures(trackers); + self.triage_suspected_buffers(trackers); + self.triage_suspected_tlas(trackers); + self.triage_suspected_blas(trackers); + self.triage_suspected_destroyed_buffers(); + self.triage_suspected_destroyed_textures(); } /// Determine which buffers are ready to map, and which must wait for the /// GPU. /// /// See the documentation for [`LifetimeTracker`] for details. - pub(super) fn triage_mapped( - &mut self, - hub: &Hub, - token: &mut Token>, - ) { + pub(crate) fn triage_mapped(&mut self) { if self.mapped.is_empty() { return; } - let (buffer_guard, _) = hub.buffers.read(token); - for stored in self.mapped.drain(..) { - let resource_id = stored.value; - let buf = &buffer_guard[resource_id]; - - let submit_index = buf.life_guard.life_count(); + for buffer in self.mapped.drain(..) { + let submit_index = buffer.info.submission_index(); log::trace!( "Mapping of {:?} at submission {:?} gets assigned to active {:?}", - resource_id, + buffer.info.id(), submit_index, self.active.iter().position(|a| a.index == submit_index) ); @@ -924,7 +815,7 @@ impl LifetimeTracker { .iter_mut() .find(|a| a.index == submit_index) .map_or(&mut self.ready_to_map, |a| &mut a.mapped) - .push(resource_id); + .push(buffer); } } @@ -934,35 +825,29 @@ impl LifetimeTracker { /// /// See the documentation for [`LifetimeTracker`] for details. #[must_use] - pub(super) fn handle_mapping( + pub(crate) fn handle_mapping( &mut self, - hub: &Hub, raw: &A::Device, trackers: &Mutex>, - token: &mut Token>, ) -> Vec { if self.ready_to_map.is_empty() { return Vec::new(); } - let (mut buffer_guard, _) = hub.buffers.write(token); let mut pending_callbacks: Vec = Vec::with_capacity(self.ready_to_map.len()); - let mut trackers = trackers.lock(); - for buffer_id in self.ready_to_map.drain(..) { - let buffer = &mut buffer_guard[buffer_id]; - if buffer.life_guard.ref_count.is_none() && trackers.buffers.remove_abandoned(buffer_id) - { - buffer.map_state = resource::BufferMapState::Idle; - log::debug!("Mapping request is dropped because the buffer is destroyed."); - if let Some(buf) = hub - .buffers - .unregister_locked(buffer_id.0, &mut *buffer_guard) - { - self.free_resources.buffers.extend(buf.raw); - } + + for buffer in self.ready_to_map.drain(..) { + let buffer_id = buffer.info.id(); + let is_removed = { + let mut trackers = trackers.lock(); + trackers.buffers.remove_abandoned(buffer_id) + }; + if is_removed { + *buffer.map_state.lock() = resource::BufferMapState::Idle; + log::trace!("Buffer ready to map {:?} is not tracked anymore", buffer_id); } else { let mapping = match std::mem::replace( - &mut buffer.map_state, + &mut *buffer.map_state.lock(), resource::BufferMapState::Idle, ) { resource::BufferMapState::Waiting(pending_mapping) => pending_mapping, @@ -971,7 +856,7 @@ impl LifetimeTracker { // Mapping queued at least twice by map -> unmap -> map // and was already successfully mapped below active @ resource::BufferMapState::Active { .. } => { - buffer.map_state = active; + *buffer.map_state.lock() = active; continue; } _ => panic!("No pending mapping."), @@ -980,9 +865,9 @@ impl LifetimeTracker { log::debug!("Buffer {:?} map state -> Active", buffer_id); let host = mapping.op.host; let size = mapping.range.end - mapping.range.start; - match super::map_buffer(raw, buffer, mapping.range.start, size, host) { + match super::map_buffer(raw, &buffer, mapping.range.start, size, host) { Ok(ptr) => { - buffer.map_state = resource::BufferMapState::Active { + *buffer.map_state.lock() = resource::BufferMapState::Active { ptr, range: mapping.range.start..mapping.range.start + size, host, @@ -990,12 +875,12 @@ impl LifetimeTracker { Ok(()) } Err(e) => { - log::error!("Mapping failed {:?}", e); + log::error!("Mapping failed: {e}"); Err(e) } } } else { - buffer.map_state = resource::BufferMapState::Active { + *buffer.map_state.lock() = resource::BufferMapState::Active { ptr: std::ptr::NonNull::dangling(), range: mapping.range, host: mapping.op.host, @@ -1007,4 +892,47 @@ impl LifetimeTracker { } pending_callbacks } + + pub(crate) fn release_gpu_resources(&mut self) { + // This is called when the device is lost, which makes every associated + // resource invalid and unusable. This is an opportunity to release all of + // the underlying gpu resources, even though the objects remain visible to + // the user agent. We purge this memory naturally when resources have been + // moved into the appropriate buckets, so this function just needs to + // initiate movement into those buckets, and it can do that by calling + // "destroy" on all the resources we know about which aren't already marked + // for cleanup. + + // During these iterations, we discard all errors. We don't care! + + // Destroy all the mapped buffers. + for buffer in &self.mapped { + let _ = buffer.destroy(); + } + + // Destroy all the unmapped buffers. + for buffer in &self.ready_to_map { + let _ = buffer.destroy(); + } + + // Destroy all the future_suspected_buffers. + for buffer in &self.future_suspected_buffers { + let _ = buffer.destroy(); + } + + // Destroy the buffers in all active submissions. + for submission in &self.active { + for buffer in &submission.mapped { + let _ = buffer.destroy(); + } + } + + // Destroy all the future_suspected_textures. + // TODO: texture.destroy is not implemented + /* + for texture in &self.future_suspected_textures { + let _ = texture.destroy(); + } + */ + } } diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 23b7764cd3..4a263f3809 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -1,23 +1,25 @@ use crate::{ binding_model, - device::life::WaitIdleError, hal_api::HalApi, hub::Hub, - id, + id::{self}, identity::{GlobalIdentityHandlerFactory, Input}, resource::{Buffer, BufferAccessResult}, resource::{BufferAccessError, BufferMapOperation}, - Label, DOWNLEVEL_ERROR_MESSAGE, + resource_log, Label, DOWNLEVEL_ERROR_MESSAGE, }; use arrayvec::ArrayVec; use hal::Device as _; use smallvec::SmallVec; +use std::os::raw::c_char; use thiserror::Error; -use wgt::{BufferAddress, TextureFormat}; +use wgt::{BufferAddress, DeviceLostReason, TextureFormat}; use std::{iter, num::NonZeroU32, ptr}; +pub mod any_device; +pub(crate) mod bgl; pub mod global; mod life; pub mod queue; @@ -25,17 +27,17 @@ pub mod ray_tracing; pub mod resource; #[cfg(any(feature = "trace", feature = "replay"))] pub mod trace; -pub use resource::Device; +pub use {life::WaitIdleError, resource::Device}; -pub const SHADER_STAGE_COUNT: usize = 3; +pub const SHADER_STAGE_COUNT: usize = hal::MAX_CONCURRENT_SHADER_STAGES; // Should be large enough for the largest possible texture row. This // value is enough for a 16k texture with float4 format. pub(crate) const ZERO_BUFFER_SIZE: BufferAddress = 512 << 10; const CLEANUP_WAIT_MS: u32 = 5000; -const IMPLICIT_FAILURE: &str = "failed implicit"; -const EP_FAILURE: &str = "EP is invalid"; +const IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL: &str = "Implicit BindGroupLayout in the Error State"; +const ENTRYPOINT_FAILURE_ERROR: &str = "The given EntryPoint is Invalid"; pub type DeviceDescriptor<'a> = wgt::DeviceDescriptor>; @@ -171,12 +173,15 @@ pub type BufferMapPendingClosure = (BufferMapOperation, BufferAccessResult); pub struct UserClosures { pub mappings: Vec, pub submissions: SmallVec<[queue::SubmittedWorkDoneClosure; 1]>, + pub device_lost_invocations: SmallVec<[DeviceLostInvocation; 1]>, } impl UserClosures { fn extend(&mut self, other: Self) { self.mappings.extend(other.mappings); self.submissions.extend(other.submissions); + self.device_lost_invocations + .extend(other.device_lost_invocations); } fn fire(self) { @@ -185,33 +190,149 @@ impl UserClosures { // Mappings _must_ be fired before submissions, as the spec requires all mapping callbacks that are registered before // a on_submitted_work_done callback to be fired before the on_submitted_work_done callback. - for (operation, status) in self.mappings { - operation.callback.call(status); + for (mut operation, status) in self.mappings { + if let Some(callback) = operation.callback.take() { + callback.call(status); + } } for closure in self.submissions { closure.call(); } + for invocation in self.device_lost_invocations { + invocation + .closure + .call(invocation.reason, invocation.message); + } + } +} + +#[cfg(send_sync)] +pub type DeviceLostCallback = Box; +#[cfg(not(send_sync))] +pub type DeviceLostCallback = Box; + +pub struct DeviceLostClosureRust { + pub callback: DeviceLostCallback, + consumed: bool, +} + +impl Drop for DeviceLostClosureRust { + fn drop(&mut self) { + if !self.consumed { + panic!("DeviceLostClosureRust must be consumed before it is dropped."); + } + } +} + +#[repr(C)] +pub struct DeviceLostClosureC { + pub callback: unsafe extern "C" fn(user_data: *mut u8, reason: u8, message: *const c_char), + pub user_data: *mut u8, + consumed: bool, +} + +#[cfg(send_sync)] +unsafe impl Send for DeviceLostClosureC {} + +impl Drop for DeviceLostClosureC { + fn drop(&mut self) { + if !self.consumed { + panic!("DeviceLostClosureC must be consumed before it is dropped."); + } } } -fn map_buffer( +pub struct DeviceLostClosure { + // We wrap this so creating the enum in the C variant can be unsafe, + // allowing our call function to be safe. + inner: DeviceLostClosureInner, +} + +pub struct DeviceLostInvocation { + closure: DeviceLostClosure, + reason: DeviceLostReason, + message: String, +} + +enum DeviceLostClosureInner { + Rust { inner: DeviceLostClosureRust }, + C { inner: DeviceLostClosureC }, +} + +impl DeviceLostClosure { + pub fn from_rust(callback: DeviceLostCallback) -> Self { + let inner = DeviceLostClosureRust { + callback, + consumed: false, + }; + Self { + inner: DeviceLostClosureInner::Rust { inner }, + } + } + + /// # Safety + /// + /// - The callback pointer must be valid to call with the provided `user_data` + /// pointer. + /// + /// - Both pointers must point to `'static` data, as the callback may happen at + /// an unspecified time. + pub unsafe fn from_c(mut closure: DeviceLostClosureC) -> Self { + // Build an inner with the values from closure, ensuring that + // inner.consumed is false. + let inner = DeviceLostClosureC { + callback: closure.callback, + user_data: closure.user_data, + consumed: false, + }; + + // Mark the original closure as consumed, so we can safely drop it. + closure.consumed = true; + + Self { + inner: DeviceLostClosureInner::C { inner }, + } + } + + pub(crate) fn call(self, reason: DeviceLostReason, message: String) { + match self.inner { + DeviceLostClosureInner::Rust { mut inner } => { + inner.consumed = true; + + (inner.callback)(reason, message) + } + // SAFETY: the contract of the call to from_c says that this unsafe is sound. + DeviceLostClosureInner::C { mut inner } => unsafe { + inner.consumed = true; + + // Ensure message is structured as a null-terminated C string. It only + // needs to live as long as the callback invocation. + let message = std::ffi::CString::new(message).unwrap(); + (inner.callback)(inner.user_data, reason as u8, message.as_ptr()) + }, + } + } +} + +fn map_buffer( raw: &A::Device, - buffer: &mut Buffer, + buffer: &Buffer, offset: BufferAddress, size: BufferAddress, kind: HostMap, ) -> Result, BufferAccessError> { + let snatch_guard = buffer.device.snatchable_lock.read(); + let raw_buffer = buffer + .raw(&snatch_guard) + .ok_or(BufferAccessError::Destroyed)?; let mapping = unsafe { - raw.map_buffer(buffer.raw.as_ref().unwrap(), offset..offset + size) + raw.map_buffer(raw_buffer, offset..offset + size) .map_err(DeviceError::from)? }; - buffer.sync_mapped_writes = match kind { + *buffer.sync_mapped_writes.lock() = match kind { HostMap::Read if !mapping.is_coherent => unsafe { - raw.invalidate_mapped_ranges( - buffer.raw.as_ref().unwrap(), - iter::once(offset..offset + size), - ); + raw.invalidate_mapped_ranges(raw_buffer, iter::once(offset..offset + size)); None }, HostMap::Write if !mapping.is_coherent => Some(offset..offset + size), @@ -235,10 +356,15 @@ fn map_buffer( // reasonable way as all data is pushed to GPU anyways. // No need to flush if it is flushed later anyways. - let zero_init_needs_flush_now = mapping.is_coherent && buffer.sync_mapped_writes.is_none(); + let zero_init_needs_flush_now = + mapping.is_coherent && buffer.sync_mapped_writes.lock().is_none(); let mapped = unsafe { std::slice::from_raw_parts_mut(mapping.ptr.as_ptr(), size as usize) }; - for uninitialized in buffer.initialization_status.drain(offset..(size + offset)) { + for uninitialized in buffer + .initialization_status + .write() + .drain(offset..(size + offset)) + { // The mapping's pointer is already offset, however we track the // uninitialized range relative to the buffer's start. let fill_range = @@ -246,20 +372,18 @@ fn map_buffer( mapped[fill_range].fill(0); if zero_init_needs_flush_now { - unsafe { - raw.flush_mapped_ranges(buffer.raw.as_ref().unwrap(), iter::once(uninitialized)) - }; + unsafe { raw.flush_mapped_ranges(raw_buffer, iter::once(uninitialized)) }; } } Ok(mapping.ptr) } -struct CommandAllocator { +pub(crate) struct CommandAllocator { free_encoders: Vec, } -impl CommandAllocator { +impl CommandAllocator { fn acquire_encoder( &mut self, device: &A::Device, @@ -279,7 +403,10 @@ impl CommandAllocator { } fn dispose(self, device: &A::Device) { - log::info!("Destroying {} command encoders", self.free_encoders.len()); + resource_log!( + "CommandAllocator::dispose encoders {}", + self.free_encoders.len() + ); for cmd_encoder in self.free_encoders { unsafe { device.destroy_command_encoder(cmd_encoder); @@ -293,15 +420,20 @@ impl CommandAllocator { pub struct InvalidDevice; #[derive(Clone, Debug, Error)] +#[non_exhaustive] pub enum DeviceError { - #[error("Parent device is invalid")] + #[error("Parent device is invalid.")] Invalid, #[error("Parent device is lost")] Lost, - #[error("Not enough memory left")] + #[error("Not enough memory left.")] OutOfMemory, #[error("Creation of a resource failed for a reason other than running out of memory.")] ResourceCreationFailed, + #[error("QueueId is invalid")] + InvalidQueueId, + #[error("Attempt to use a resource with a different device from the one that created it")] + WrongDevice, } impl From for DeviceError { @@ -339,13 +471,13 @@ pub struct ImplicitPipelineIds<'a, G: GlobalIdentityHandlerFactory> { } impl ImplicitPipelineIds<'_, G> { - fn prepare(self, hub: &Hub) -> ImplicitPipelineContext { + fn prepare(self, hub: &Hub) -> ImplicitPipelineContext { ImplicitPipelineContext { - root_id: hub.pipeline_layouts.prepare(self.root_id).into_id(), + root_id: hub.pipeline_layouts.prepare::(self.root_id).into_id(), group_ids: self .group_ids .iter() - .map(|id_in| hub.bind_group_layouts.prepare(id_in.clone()).into_id()) + .map(|id_in| hub.bind_group_layouts.prepare::(*id_in).into_id()) .collect(), } } diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 490dc73172..f50d506d30 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -1,29 +1,64 @@ #[cfg(feature = "trace")] use crate::device::trace::Action; use crate::{ + api_log, command::{ extract_texture_selector, validate_linear_texture_data, validate_texture_copy_range, ClearError, CommandBuffer, CopySide, ImageCopyTexture, TransferError, }, conv, - device::{DeviceError, WaitIdleError}, + device::{life::ResourceMaps, DeviceError, WaitIdleError}, get_lowest_common_denom, global::Global, hal_api::HalApi, - hub::Token, - id, + hal_label, + id::{self, QueueId}, identity::{GlobalIdentityHandlerFactory, Input}, init_tracker::{has_copy_partial_init_tracker_coverage, TextureInitRange}, - resource::{BufferAccessError, BufferMapState, StagingBuffer, TextureInner}, - track, FastHashSet, SubmissionIndex, + resource::{ + Buffer, BufferAccessError, BufferMapState, DestroyedBuffer, DestroyedTexture, Resource, + ResourceInfo, ResourceType, StagingBuffer, Texture, TextureInner, + }, + resource_log, track, FastHashMap, SubmissionIndex, }; use hal::{CommandEncoder as _, Device as _, Queue as _}; use parking_lot::Mutex; -use smallvec::SmallVec; -use std::{iter, mem, ptr}; + +use crate::resource::{Blas, Tlas}; +use std::{ + iter, mem, ptr, + sync::{atomic::Ordering, Arc}, +}; use thiserror::Error; +use super::Device; + +pub struct Queue { + pub device: Option>>, + pub raw: Option, + pub info: ResourceInfo, +} + +impl Resource for Queue { + const TYPE: ResourceType = "Queue"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info + } +} + +impl Drop for Queue { + fn drop(&mut self) { + let queue = self.raw.take().unwrap(); + self.device.as_ref().unwrap().release_queue(queue); + } +} + /// Number of command buffers that we generate from the same pool /// for the write_xxx commands, before the pool is recycled. /// @@ -37,13 +72,7 @@ pub struct SubmittedWorkDoneClosureC { pub user_data: *mut u8, } -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] +#[cfg(send_sync)] unsafe impl Send for SubmittedWorkDoneClosureC {} pub struct SubmittedWorkDoneClosure { @@ -52,21 +81,9 @@ pub struct SubmittedWorkDoneClosure { inner: SubmittedWorkDoneClosureInner, } -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] +#[cfg(send_sync)] type SubmittedWorkDoneCallback = Box; -#[cfg(not(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -)))] +#[cfg(not(send_sync))] type SubmittedWorkDoneCallback = Box; enum SubmittedWorkDoneClosureInner { @@ -108,7 +125,7 @@ impl SubmittedWorkDoneClosure { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct WrappedSubmissionIndex { - pub queue_id: id::QueueId, + pub queue_id: QueueId, pub index: SubmissionIndex, } @@ -126,20 +143,24 @@ pub struct WrappedSubmissionIndex { /// - `LifetimeTracker::free_resources`: resources to be freed in the next /// `maintain` call, no longer used anywhere #[derive(Debug)] -pub enum TempResource { - Buffer(A::Buffer), - Texture(A::Texture, SmallVec<[A::TextureView; 1]>), - AccelerationStructure(A::AccelerationStructure), +pub enum TempResource { + Buffer(Arc>), + StagingBuffer(Arc>), + DestroyedBuffer(Arc>), + DestroyedTexture(Arc>), + Texture(Arc>), + Tlas(Arc>), + Blas(Arc>), } /// A queue execution for a particular command encoder. -pub(super) struct EncoderInFlight { +pub(crate) struct EncoderInFlight { raw: A::CommandEncoder, cmd_buffers: Vec, } -impl EncoderInFlight { - pub(super) unsafe fn land(mut self) -> A::CommandEncoder { +impl EncoderInFlight { + pub(crate) unsafe fn land(mut self) -> A::CommandEncoder { unsafe { self.raw.reset_all(self.cmd_buffers.into_iter()) }; self.raw } @@ -160,25 +181,29 @@ impl EncoderInFlight { /// time the user submits a wgpu command buffer, ahead of the user's /// commands. /// +/// Important: +/// When locking pending_writes be sure that tracker is not locked +/// and try to lock trackers for the minimum timespan possible +/// /// All uses of [`StagingBuffer`]s end up here. #[derive(Debug)] -pub(crate) struct PendingWrites { +pub(crate) struct PendingWrites { pub command_encoder: A::CommandEncoder, pub is_active: bool, pub temp_resources: Vec>, - pub dst_buffers: FastHashSet, - pub dst_textures: FastHashSet, + pub dst_buffers: FastHashMap>>, + pub dst_textures: FastHashMap>>, pub executing_command_buffers: Vec, } -impl PendingWrites { +impl PendingWrites { pub fn new(command_encoder: A::CommandEncoder) -> Self { Self { command_encoder, is_active: false, temp_resources: Vec::new(), - dst_buffers: FastHashSet::default(), - dst_textures: FastHashSet::default(), + dst_buffers: FastHashMap::default(), + dst_textures: FastHashMap::default(), executing_command_buffers: Vec::new(), } } @@ -193,30 +218,16 @@ impl PendingWrites { device.destroy_command_encoder(self.command_encoder); } - for resource in self.temp_resources { - match resource { - TempResource::Buffer(buffer) => unsafe { - device.destroy_buffer(buffer); - }, - TempResource::Texture(texture, views) => unsafe { - for view in views.into_iter() { - device.destroy_texture_view(view); - } - device.destroy_texture(texture); - }, - TempResource::AccelerationStructure(acceleration_structure) => unsafe { - device.destroy_acceleration_structure(acceleration_structure); - }, - } - } + self.temp_resources.clear(); } pub fn consume_temp(&mut self, resource: TempResource) { self.temp_resources.push(resource); } - fn consume(&mut self, buffer: StagingBuffer) { - self.temp_resources.push(TempResource::Buffer(buffer.raw)); + fn consume(&mut self, buffer: Arc>) { + self.temp_resources + .push(TempResource::StagingBuffer(buffer)); } #[must_use] @@ -236,15 +247,12 @@ impl PendingWrites { #[must_use] fn post_submit( &mut self, - command_allocator: &Mutex>, + command_allocator: &mut super::CommandAllocator, device: &A::Device, queue: &A::Queue, ) -> Option> { if self.executing_command_buffers.len() >= WRITE_COMMAND_BUFFERS_PER_POOL { - let new_encoder = command_allocator - .lock() - .acquire_encoder(device, queue) - .unwrap(); + let new_encoder = command_allocator.acquire_encoder(device, queue).unwrap(); Some(EncoderInFlight { raw: mem::replace(&mut self.command_encoder, new_encoder), cmd_buffers: mem::take(&mut self.executing_command_buffers), @@ -277,35 +285,43 @@ impl PendingWrites { } fn prepare_staging_buffer( - device: &mut A::Device, + device: &Arc>, size: wgt::BufferAddress, + instance_flags: wgt::InstanceFlags, ) -> Result<(StagingBuffer, *mut u8), DeviceError> { profiling::scope!("prepare_staging_buffer"); let stage_desc = hal::BufferDescriptor { - label: Some("(wgpu internal) Staging"), + label: hal_label(Some("(wgpu internal) Staging"), instance_flags), size, usage: hal::BufferUses::MAP_WRITE | hal::BufferUses::COPY_SRC, memory_flags: hal::MemoryFlags::TRANSIENT, }; - let buffer = unsafe { device.create_buffer(&stage_desc)? }; - let mapping = unsafe { device.map_buffer(&buffer, 0..size) }?; + let buffer = unsafe { device.raw().create_buffer(&stage_desc)? }; + let mapping = unsafe { device.raw().map_buffer(&buffer, 0..size) }?; let staging_buffer = StagingBuffer { - raw: buffer, + raw: Mutex::new(Some(buffer)), + device: device.clone(), size, + info: ResourceInfo::new(""), is_coherent: mapping.is_coherent, }; Ok((staging_buffer, mapping.ptr.as_ptr())) } -impl StagingBuffer { +impl StagingBuffer { unsafe fn flush(&self, device: &A::Device) -> Result<(), DeviceError> { if !self.is_coherent { - unsafe { device.flush_mapped_ranges(&self.raw, iter::once(0..self.size)) }; + unsafe { + device.flush_mapped_ranges( + self.raw.lock().as_ref().unwrap(), + iter::once(0..self.size), + ) + }; } - unsafe { device.unmap_buffer(&self.raw)? }; + unsafe { device.unmap_buffer(self.raw.lock().as_ref().unwrap())? }; Ok(()) } } @@ -355,26 +371,27 @@ pub enum QueueSubmitError { impl Global { pub fn queue_write_buffer( &self, - queue_id: id::QueueId, + queue_id: QueueId, buffer_id: id::BufferId, buffer_offset: wgt::BufferAddress, data: &[u8], ) -> Result<(), QueueWriteError> { profiling::scope!("Queue::write_buffer"); + api_log!("Queue::write_buffer {buffer_id:?} {}bytes", data.len()); let hub = A::hub(self); - let root_token = &mut Token::root(); - let (mut device_guard, ref mut device_token) = hub.devices.write(root_token); - let device = device_guard - .get_mut(queue_id) - .map_err(|_| DeviceError::Invalid)?; + let queue = hub + .queues + .get(queue_id) + .map_err(|_| DeviceError::InvalidQueueId)?; + + let device = queue.device.as_ref().unwrap(); let data_size = data.len() as wgt::BufferAddress; #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - let mut trace = trace.lock(); + if let Some(ref mut trace) = *device.trace.lock() { let data_path = trace.make_binary("bin", data); trace.add(Action::WriteBuffer { id: buffer_id, @@ -393,122 +410,131 @@ impl Global { // freed, even if an error occurs. All paths from here must call // `device.pending_writes.consume`. let (staging_buffer, staging_buffer_ptr) = - prepare_staging_buffer(&mut device.raw, data_size)?; + prepare_staging_buffer(device, data_size, device.instance_flags)?; + let mut pending_writes = device.pending_writes.lock(); + let pending_writes = pending_writes.as_mut().unwrap(); + + let stage_fid = hub.staging_buffers.request(); + let staging_buffer = stage_fid.init(staging_buffer); if let Err(flush_error) = unsafe { profiling::scope!("copy"); ptr::copy_nonoverlapping(data.as_ptr(), staging_buffer_ptr, data.len()); - staging_buffer.flush(&device.raw) + staging_buffer.flush(device.raw()) } { - device.pending_writes.consume(staging_buffer); + pending_writes.consume(staging_buffer); return Err(flush_error.into()); } let result = self.queue_write_staging_buffer_impl( device, - device_token, + pending_writes, &staging_buffer, buffer_id, buffer_offset, ); - device.pending_writes.consume(staging_buffer); + pending_writes.consume(staging_buffer); result } pub fn queue_create_staging_buffer( &self, - queue_id: id::QueueId, + queue_id: QueueId, buffer_size: wgt::BufferSize, id_in: Input, ) -> Result<(id::StagingBufferId, *mut u8), QueueWriteError> { profiling::scope!("Queue::create_staging_buffer"); let hub = A::hub(self); - let root_token = &mut Token::root(); - let (mut device_guard, ref mut device_token) = hub.devices.write(root_token); - let device = device_guard - .get_mut(queue_id) - .map_err(|_| DeviceError::Invalid)?; + let queue = hub + .queues + .get(queue_id) + .map_err(|_| DeviceError::InvalidQueueId)?; + + let device = queue.device.as_ref().unwrap(); let (staging_buffer, staging_buffer_ptr) = - prepare_staging_buffer(&mut device.raw, buffer_size.get())?; + prepare_staging_buffer(device, buffer_size.get(), device.instance_flags)?; - let fid = hub.staging_buffers.prepare(id_in); - let id = fid.assign(staging_buffer, device_token); + let fid = hub.staging_buffers.prepare::(id_in); + let (id, _) = fid.assign(staging_buffer); + resource_log!("Queue::create_staging_buffer {id:?}"); - Ok((id.0, staging_buffer_ptr)) + Ok((id, staging_buffer_ptr)) } pub fn queue_write_staging_buffer( &self, - queue_id: id::QueueId, + queue_id: QueueId, buffer_id: id::BufferId, buffer_offset: wgt::BufferAddress, staging_buffer_id: id::StagingBufferId, ) -> Result<(), QueueWriteError> { profiling::scope!("Queue::write_staging_buffer"); let hub = A::hub(self); - let root_token = &mut Token::root(); - let (mut device_guard, ref mut device_token) = hub.devices.write(root_token); - let device = device_guard - .get_mut(queue_id) - .map_err(|_| DeviceError::Invalid)?; + let queue = hub + .queues + .get(queue_id) + .map_err(|_| DeviceError::InvalidQueueId)?; - let staging_buffer = hub - .staging_buffers - .unregister(staging_buffer_id, device_token) - .0 - .ok_or(TransferError::InvalidBuffer(buffer_id))?; + let device = queue.device.as_ref().unwrap(); + + let staging_buffer = hub.staging_buffers.unregister(staging_buffer_id); + if staging_buffer.is_none() { + return Err(QueueWriteError::Transfer(TransferError::InvalidBuffer( + buffer_id, + ))); + } + let staging_buffer = staging_buffer.unwrap(); + let mut pending_writes = device.pending_writes.lock(); + let pending_writes = pending_writes.as_mut().unwrap(); // At this point, we have taken ownership of the staging_buffer from the // user. Platform validation requires that the staging buffer always // be freed, even if an error occurs. All paths from here must call // `device.pending_writes.consume`. - if let Err(flush_error) = unsafe { staging_buffer.flush(&device.raw) } { - device.pending_writes.consume(staging_buffer); + if let Err(flush_error) = unsafe { staging_buffer.flush(device.raw()) } { + pending_writes.consume(staging_buffer); return Err(flush_error.into()); } let result = self.queue_write_staging_buffer_impl( device, - device_token, + pending_writes, &staging_buffer, buffer_id, buffer_offset, ); - device.pending_writes.consume(staging_buffer); + pending_writes.consume(staging_buffer); result } pub fn queue_validate_write_buffer( &self, - _queue_id: id::QueueId, + _queue_id: QueueId, buffer_id: id::BufferId, buffer_offset: u64, buffer_size: u64, ) -> Result<(), QueueWriteError> { profiling::scope!("Queue::validate_write_buffer"); let hub = A::hub(self); - let root_token = &mut Token::root(); - - let (_, ref mut device_token) = hub.devices.read(root_token); - let buffer_guard = hub.buffers.read(device_token).0; - let buffer = buffer_guard + let buffer = hub + .buffers .get(buffer_id) .map_err(|_| TransferError::InvalidBuffer(buffer_id))?; - self.queue_validate_write_buffer_impl(buffer, buffer_id, buffer_offset, buffer_size)?; + self.queue_validate_write_buffer_impl(&buffer, buffer_id, buffer_offset, buffer_size)?; Ok(()) } fn queue_validate_write_buffer_impl( &self, - buffer: &crate::resource::Buffer, + buffer: &Buffer, buffer_id: id::BufferId, buffer_offset: u64, buffer_size: u64, @@ -539,57 +565,69 @@ impl Global { fn queue_write_staging_buffer_impl( &self, - device: &mut super::Device, - device_token: &mut Token>, + device: &Device, + pending_writes: &mut PendingWrites, staging_buffer: &StagingBuffer, buffer_id: id::BufferId, buffer_offset: u64, ) -> Result<(), QueueWriteError> { let hub = A::hub(self); - let buffer_guard = hub.buffers.read(device_token).0; - - let mut trackers = device.trackers.lock(); - let (dst, transition) = trackers - .buffers - .set_single(&buffer_guard, buffer_id, hal::BufferUses::COPY_DST) - .ok_or(TransferError::InvalidBuffer(buffer_id))?; + let (dst, transition) = { + let buffer_guard = hub.buffers.read(); + let dst = buffer_guard + .get(buffer_id) + .map_err(|_| TransferError::InvalidBuffer(buffer_id))?; + let mut trackers = device.trackers.lock(); + trackers + .buffers + .set_single(dst, hal::BufferUses::COPY_DST) + .ok_or(TransferError::InvalidBuffer(buffer_id))? + }; + let snatch_guard = device.snatchable_lock.read(); let dst_raw = dst .raw - .as_ref() + .get(&snatch_guard) .ok_or(TransferError::InvalidBuffer(buffer_id))?; + if dst.device.as_info().id() != device.as_info().id() { + return Err(DeviceError::WrongDevice.into()); + } + let src_buffer_size = staging_buffer.size; - self.queue_validate_write_buffer_impl(dst, buffer_id, buffer_offset, src_buffer_size)?; + self.queue_validate_write_buffer_impl(&dst, buffer_id, buffer_offset, src_buffer_size)?; - dst.life_guard.use_at(device.active_submission_index + 1); + dst.info + .use_at(device.active_submission_index.load(Ordering::Relaxed) + 1); let region = wgt::BufferSize::new(src_buffer_size).map(|size| hal::BufferCopy { src_offset: 0, dst_offset: buffer_offset, size, }); + let inner_buffer = staging_buffer.raw.lock(); let barriers = iter::once(hal::BufferBarrier { - buffer: &staging_buffer.raw, + buffer: inner_buffer.as_ref().unwrap(), usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, }) - .chain(transition.map(|pending| pending.into_hal(dst))); - let encoder = device.pending_writes.activate(); + .chain(transition.map(|pending| pending.into_hal(&dst, &snatch_guard))); + let encoder = pending_writes.activate(); unsafe { encoder.transition_buffers(barriers); - encoder.copy_buffer_to_buffer(&staging_buffer.raw, dst_raw, region.into_iter()); + encoder.copy_buffer_to_buffer( + inner_buffer.as_ref().unwrap(), + dst_raw, + region.into_iter(), + ); } - - device.pending_writes.dst_buffers.insert(buffer_id); + let dst = hub.buffers.get(buffer_id).unwrap(); + pending_writes.dst_buffers.insert(buffer_id, dst.clone()); // Ensure the overwritten bytes are marked as initialized so // they don't need to be nulled prior to mapping or binding. { - drop(buffer_guard); - let mut buffer_guard = hub.buffers.write(device_token).0; - - let dst = buffer_guard.get_mut(buffer_id).unwrap(); dst.initialization_status + .write() .drain(buffer_offset..(buffer_offset + src_buffer_size)); } @@ -598,24 +636,26 @@ impl Global { pub fn queue_write_texture( &self, - queue_id: id::QueueId, + queue_id: QueueId, destination: &ImageCopyTexture, data: &[u8], data_layout: &wgt::ImageDataLayout, size: &wgt::Extent3d, ) -> Result<(), QueueWriteError> { profiling::scope!("Queue::write_texture"); + api_log!("Queue::write_texture {:?} {size:?}", destination.texture); let hub = A::hub(self); - let mut token = Token::root(); - let (mut device_guard, mut token) = hub.devices.write(&mut token); - let device = device_guard - .get_mut(queue_id) - .map_err(|_| DeviceError::Invalid)?; + + let queue = hub + .queues + .get(queue_id) + .map_err(|_| DeviceError::InvalidQueueId)?; + + let device = queue.device.as_ref().unwrap(); #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - let mut trace = trace.lock(); + if let Some(ref mut trace) = *device.trace.lock() { let data_path = trace.make_binary("bin", data); trace.add(Action::WriteTexture { to: *destination, @@ -630,11 +670,15 @@ impl Global { return Ok(()); } - let (mut texture_guard, _) = hub.textures.write(&mut token); // For clear we need write access to the texture. TODO: Can we acquire write lock later? - let dst = texture_guard - .get_mut(destination.texture) + let dst = hub + .textures + .get(destination.texture) .map_err(|_| TransferError::InvalidTexture(destination.texture))?; + if dst.device.as_info().id() != queue_id { + return Err(DeviceError::WrongDevice.into()); + } + if !dst.desc.usage.contains(wgt::TextureUsages::COPY_DST) { return Err( TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(), @@ -646,7 +690,7 @@ impl Global { let (hal_copy_size, array_layer_count) = validate_texture_copy_range(destination, &dst.desc, CopySide::Destination, size)?; - let (selector, dst_base) = extract_texture_selector(destination, size, dst)?; + let (selector, dst_base) = extract_texture_selector(destination, size, &dst)?; if !dst_base.aspect.is_one() { return Err(TransferError::CopyAspectNotOne.into()); @@ -686,13 +730,13 @@ impl Global { // doesn't really matter because we need this only if we copy // more than one layer, and then we validate for this being not // None - size.height, + height_blocks, ); let block_size = dst .desc .format - .block_size(Some(destination.aspect)) + .block_copy_size(Some(destination.aspect)) .unwrap(); let bytes_per_row_alignment = get_lowest_common_denom(device.alignments.buffer_copy_pitch.get() as u32, block_size); @@ -703,8 +747,9 @@ impl Global { (size.depth_or_array_layers - 1) * block_rows_per_image + height_blocks; let stage_size = stage_bytes_per_row as u64 * block_rows_in_copy as u64; - let mut trackers = device.trackers.lock(); - let encoder = device.pending_writes.activate(); + let mut pending_writes = device.pending_writes.lock(); + let pending_writes = pending_writes.as_mut().unwrap(); + let encoder = pending_writes.activate(); // If the copy does not fully cover the layers, we need to initialize to // zero *first* as we don't keep track of partial texture layer inits. @@ -717,18 +762,19 @@ impl Global { } else { destination.origin.z..destination.origin.z + size.depth_or_array_layers }; - if dst.initialization_status.mips[destination.mip_level as usize] + let mut dst_initialization_status = dst.initialization_status.write(); + if dst_initialization_status.mips[destination.mip_level as usize] .check(init_layer_range.clone()) .is_some() { if has_copy_partial_init_tracker_coverage(size, destination.mip_level, &dst.desc) { - for layer_range in dst.initialization_status.mips[destination.mip_level as usize] + for layer_range in dst_initialization_status.mips[destination.mip_level as usize] .drain(init_layer_range) .collect::>>() { + let mut trackers = device.trackers.lock(); crate::command::clear_texture( - &*texture_guard, - id::Valid(destination.texture), + &dst, TextureInitRange { mip_range: destination.mip_level..(destination.mip_level + 1), layer_range, @@ -736,36 +782,28 @@ impl Global { encoder, &mut trackers.textures, &device.alignments, - &device.zero_buffer, + device.zero_buffer.as_ref().unwrap(), ) .map_err(QueueWriteError::from)?; } } else { - dst.initialization_status.mips[destination.mip_level as usize] + dst_initialization_status.mips[destination.mip_level as usize] .drain(init_layer_range); } } + let snatch_guard = device.snatchable_lock.read(); + // Re-get `dst` immutably here, so that the mutable borrow of the - // `texture_guard.get_mut` above ends in time for the `clear_texture` + // `texture_guard.get` above ends in time for the `clear_texture` // call above. Since we've held `texture_guard` the whole time, we know // the texture hasn't gone away in the mean time, so we can unwrap. - let dst = texture_guard.get(destination.texture).unwrap(); - let transition = trackers - .textures - .set_single( - dst, - destination.texture, - selector, - hal::TextureUses::COPY_DST, - ) - .ok_or(TransferError::InvalidTexture(destination.texture))?; - - dst.life_guard.use_at(device.active_submission_index + 1); + let dst = hub.textures.get(destination.texture).unwrap(); + dst.info + .use_at(device.active_submission_index.load(Ordering::Relaxed) + 1); let dst_raw = dst - .inner - .as_raw() + .raw(&snatch_guard) .ok_or(TransferError::InvalidTexture(destination.texture))?; let bytes_per_row = data_layout @@ -776,7 +814,10 @@ impl Global { // freed, even if an error occurs. All paths from here must call // `device.pending_writes.consume`. let (staging_buffer, staging_buffer_ptr) = - prepare_staging_buffer(&mut device.raw, stage_size)?; + prepare_staging_buffer(device, stage_size, device.instance_flags)?; + + let stage_fid = hub.staging_buffers.request(); + let staging_buffer = stage_fid.init(staging_buffer); if stage_bytes_per_row == bytes_per_row { profiling::scope!("copy aligned"); @@ -811,8 +852,8 @@ impl Global { } } - if let Err(e) = unsafe { staging_buffer.flush(&device.raw) } { - device.pending_writes.consume(staging_buffer); + if let Err(e) = unsafe { staging_buffer.flush(device.raw()) } { + pending_writes.consume(staging_buffer); return Err(e.into()); } @@ -831,30 +872,38 @@ impl Global { size: hal_copy_size, } }); - let barrier = hal::BufferBarrier { - buffer: &staging_buffer.raw, - usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, - }; - unsafe { - encoder.transition_textures(transition.map(|pending| pending.into_hal(dst))); - encoder.transition_buffers(iter::once(barrier)); - encoder.copy_buffer_to_texture(&staging_buffer.raw, dst_raw, regions); + { + let inner_buffer = staging_buffer.raw.lock(); + let barrier = hal::BufferBarrier { + buffer: inner_buffer.as_ref().unwrap(), + usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, + }; + + let mut trackers = device.trackers.lock(); + let transition = trackers + .textures + .set_single(&dst, selector, hal::TextureUses::COPY_DST) + .ok_or(TransferError::InvalidTexture(destination.texture))?; + unsafe { + encoder.transition_textures(transition.map(|pending| pending.into_hal(dst_raw))); + encoder.transition_buffers(iter::once(barrier)); + encoder.copy_buffer_to_texture(inner_buffer.as_ref().unwrap(), dst_raw, regions); + } } - device.pending_writes.consume(staging_buffer); - device - .pending_writes + pending_writes.consume(staging_buffer); + pending_writes .dst_textures - .insert(destination.texture); + .insert(destination.texture, dst.clone()); Ok(()) } - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] pub fn queue_copy_external_image_to_texture( &self, - queue_id: id::QueueId, + queue_id: QueueId, source: &wgt::ImageCopyExternalImage, destination: crate::command::ImageCopyTextureTagged, size: wgt::Extent3d, @@ -862,11 +911,13 @@ impl Global { profiling::scope!("Queue::copy_external_image_to_texture"); let hub = A::hub(self); - let mut token = Token::root(); - let (mut device_guard, mut token) = hub.devices.write(&mut token); - let device = device_guard - .get_mut(queue_id) - .map_err(|_| DeviceError::Invalid)?; + + let queue = hub + .queues + .get(queue_id) + .map_err(|_| DeviceError::InvalidQueueId)?; + + let device = queue.device.as_ref().unwrap(); if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 { log::trace!("Ignoring write_texture of size 0"); @@ -892,8 +943,7 @@ impl Global { let src_width = source.source.width(); let src_height = source.source.height(); - let (mut texture_guard, _) = hub.textures.write(&mut token); // For clear we need write access to the texture. TODO: Can we acquire write lock later? - let dst = texture_guard.get_mut(destination.texture).unwrap(); + let dst = hub.textures.get(destination.texture).unwrap(); if !conv::is_valid_external_image_copy_dst_texture_format(dst.desc.format) { return Err( @@ -965,10 +1015,10 @@ impl Global { )?; let (selector, dst_base) = - extract_texture_selector(&destination.to_untagged(), &size, dst)?; + extract_texture_selector(&destination.to_untagged(), &size, &dst)?; - let mut trackers = device.trackers.lock(); - let encoder = device.pending_writes.activate(); + let mut pending_writes = device.pending_writes.lock(); + let encoder = pending_writes.as_mut().unwrap().activate(); // If the copy does not fully cover the layers, we need to initialize to // zero *first* as we don't keep track of partial texture layer inits. @@ -981,18 +1031,19 @@ impl Global { } else { destination.origin.z..destination.origin.z + size.depth_or_array_layers }; - if dst.initialization_status.mips[destination.mip_level as usize] + let mut dst_initialization_status = dst.initialization_status.write(); + if dst_initialization_status.mips[destination.mip_level as usize] .check(init_layer_range.clone()) .is_some() { if has_copy_partial_init_tracker_coverage(&size, destination.mip_level, &dst.desc) { - for layer_range in dst.initialization_status.mips[destination.mip_level as usize] + for layer_range in dst_initialization_status.mips[destination.mip_level as usize] .drain(init_layer_range) .collect::>>() { + let mut trackers = device.trackers.lock(); crate::command::clear_texture( - &*texture_guard, - id::Valid(destination.texture), + &dst, TextureInitRange { mip_range: destination.mip_level..(destination.mip_level + 1), layer_range, @@ -1000,33 +1051,21 @@ impl Global { encoder, &mut trackers.textures, &device.alignments, - &device.zero_buffer, + device.zero_buffer.as_ref().unwrap(), ) .map_err(QueueWriteError::from)?; } } else { - dst.initialization_status.mips[destination.mip_level as usize] + dst_initialization_status.mips[destination.mip_level as usize] .drain(init_layer_range); } } + dst.info + .use_at(device.active_submission_index.load(Ordering::Relaxed) + 1); - let dst = texture_guard.get(destination.texture).unwrap(); - - let transitions = trackers - .textures - .set_single( - dst, - destination.texture, - selector, - hal::TextureUses::COPY_DST, - ) - .ok_or(TransferError::InvalidTexture(destination.texture))?; - - dst.life_guard.use_at(device.active_submission_index + 1); - + let snatch_guard = device.snatchable_lock.read(); let dst_raw = dst - .inner - .as_raw() + .raw(&snatch_guard) .ok_or(TransferError::InvalidTexture(destination.texture))?; let regions = hal::TextureCopy { @@ -1041,7 +1080,12 @@ impl Global { }; unsafe { - encoder.transition_textures(transitions.map(|pending| pending.into_hal(dst))); + let mut trackers = device.trackers.lock(); + let transitions = trackers + .textures + .set_single(&dst, selector, hal::TextureUses::COPY_DST) + .ok_or(TransferError::InvalidTexture(destination.texture))?; + encoder.transition_textures(transitions.map(|pending| pending.into_hal(dst_raw))); encoder.copy_external_image_to_texture( source, dst_raw, @@ -1055,215 +1099,284 @@ impl Global { pub fn queue_submit( &self, - queue_id: id::QueueId, + queue_id: QueueId, command_buffer_ids: &[id::CommandBufferId], ) -> Result { profiling::scope!("Queue::submit"); + api_log!("Queue::submit {queue_id:?}"); let (submit_index, callbacks) = { let hub = A::hub(self); - let mut token = Token::root(); - - let (mut device_guard, mut token) = hub.devices.write(&mut token); - let device = device_guard - .get_mut(queue_id) - .map_err(|_| DeviceError::Invalid)?; - device.temp_suspected.clear(); - device.active_submission_index += 1; - let submit_index = device.active_submission_index; + + let queue = hub + .queues + .get(queue_id) + .map_err(|_| DeviceError::InvalidQueueId)?; + + let device = queue.device.as_ref().unwrap(); + + let mut fence = device.fence.write(); + let fence = fence.as_mut().unwrap(); + let submit_index = device + .active_submission_index + .fetch_add(1, Ordering::Relaxed) + + 1; let mut active_executions = Vec::new(); let mut used_surface_textures = track::TextureUsageScope::new(); + let snatch_guard = device.snatchable_lock.read(); + { - let (mut command_buffer_guard, mut token) = hub.command_buffers.write(&mut token); + let mut command_buffer_guard = hub.command_buffers.write(); if !command_buffer_ids.is_empty() { profiling::scope!("prepare"); - let (render_bundle_guard, mut token) = hub.render_bundles.read(&mut token); - let (_, mut token) = hub.pipeline_layouts.read(&mut token); - let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token); - let (compute_pipe_guard, mut token) = hub.compute_pipelines.read(&mut token); - let (render_pipe_guard, mut token) = hub.render_pipelines.read(&mut token); - let (mut buffer_guard, mut token) = hub.buffers.write(&mut token); - let (mut texture_guard, mut token) = hub.textures.write(&mut token); - let (texture_view_guard, mut token) = hub.texture_views.read(&mut token); - let (sampler_guard, mut token) = hub.samplers.read(&mut token); - let (query_set_guard, mut token) = hub.query_sets.read(&mut token); - let (mut blas_guard, mut token) = hub.blas_s.write(&mut token); - let (mut tlas_guard, _) = hub.tlas_s.write(&mut token); - - //Note: locking the trackers has to be done after the storages - let mut trackers = device.trackers.lock(); - //TODO: if multiple command buffers are submitted, we can re-use the last // native command buffer of the previous chain instead of always creating // a temporary one, since the chains are not finished. + let mut temp_suspected = device.temp_suspected.lock(); + { + let mut suspected = temp_suspected.replace(ResourceMaps::new()).unwrap(); + suspected.clear(); + } // finish all the command buffers first for &cmb_id in command_buffer_ids { // we reset the used surface textures every time we use // it, so make sure to set_size on it. - used_surface_textures.set_size(texture_guard.len()); + used_surface_textures.set_size(hub.textures.read().len()); #[allow(unused_mut)] - let mut cmdbuf = match hub - .command_buffers - .unregister_locked(cmb_id, &mut *command_buffer_guard) - { - Some(cmdbuf) => cmdbuf, - None => continue, + let mut cmdbuf = match command_buffer_guard.replace_with_error(cmb_id) { + Ok(cmdbuf) => cmdbuf, + Err(_) => continue, }; + + if cmdbuf.device.as_info().id() != queue_id { + return Err(DeviceError::WrongDevice.into()); + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(Action::Submit( + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(Action::Submit( submit_index, - cmdbuf.commands.take().unwrap(), + cmdbuf + .data + .lock() + .as_mut() + .unwrap() + .commands + .take() + .unwrap(), )); } if !cmdbuf.is_finished() { - device.destroy_command_buffer(cmdbuf); + if let Some(cmdbuf) = Arc::into_inner(cmdbuf) { + device.destroy_command_buffer(cmdbuf); + } else { + panic!( + "Command buffer cannot be destroyed because is still in use" + ); + } continue; } // optimize the tracked states // cmdbuf.trackers.optimize(); - - // update submission IDs - for id in cmdbuf.trackers.buffers.used() { - let buffer = &mut buffer_guard[id]; - let raw_buf = match buffer.raw { - Some(ref raw) => raw, - None => { - return Err(QueueSubmitError::DestroyedBuffer(id.0)); - } - }; - if !buffer.life_guard.use_at(submit_index) { - if let BufferMapState::Active { .. } = buffer.map_state { - log::warn!("Dropped buffer has a pending mapping."); - unsafe { device.raw.unmap_buffer(raw_buf) } - .map_err(DeviceError::from)?; - } - device.temp_suspected.buffers.push(id); - } else { - match buffer.map_state { - BufferMapState::Idle => (), - _ => return Err(QueueSubmitError::BufferStillMapped(id.0)), + { + let cmd_buf_data = cmdbuf.data.lock(); + let cmd_buf_trackers = &cmd_buf_data.as_ref().unwrap().trackers; + + // update submission IDs + for buffer in cmd_buf_trackers.buffers.used_resources() { + let id = buffer.info.id(); + let raw_buf = match buffer.raw.get(&snatch_guard) { + Some(raw) => raw, + None => { + return Err(QueueSubmitError::DestroyedBuffer(id)); + } + }; + buffer.info.use_at(submit_index); + if buffer.is_unique() { + if let BufferMapState::Active { .. } = *buffer.map_state.lock() + { + log::warn!("Dropped buffer has a pending mapping."); + unsafe { device.raw().unmap_buffer(raw_buf) } + .map_err(DeviceError::from)?; + } + temp_suspected + .as_mut() + .unwrap() + .buffers + .insert(id, buffer.clone()); + } else { + match *buffer.map_state.lock() { + BufferMapState::Idle => (), + _ => return Err(QueueSubmitError::BufferStillMapped(id)), + } } } - } - for id in cmdbuf.trackers.textures.used() { - let texture = &mut texture_guard[id]; - let should_extend = match texture.inner { - TextureInner::Native { raw: None } => { - return Err(QueueSubmitError::DestroyedTexture(id.0)); + for texture in cmd_buf_trackers.textures.used_resources() { + let id = texture.info.id(); + let should_extend = match texture.inner.get(&snatch_guard) { + None => { + return Err(QueueSubmitError::DestroyedTexture(id)); + } + Some(TextureInner::Native { .. }) => false, + Some(TextureInner::Surface { ref has_work, .. }) => { + has_work.store(true, Ordering::Relaxed); + true + } + }; + texture.info.use_at(submit_index); + if texture.is_unique() { + temp_suspected + .as_mut() + .unwrap() + .textures + .insert(id, texture.clone()); } - TextureInner::Native { raw: Some(_) } => false, - TextureInner::Surface { - ref mut has_work, .. - } => { - *has_work = true; - true + if should_extend { + unsafe { + used_surface_textures + .merge_single(&texture, None, hal::TextureUses::PRESENT) + .unwrap(); + }; } - }; - if !texture.life_guard.use_at(submit_index) { - device.temp_suspected.textures.push(id); } - if should_extend { - unsafe { - let ref_count = cmdbuf.trackers.textures.get_ref_count(id); - used_surface_textures - .merge_single( - &*texture_guard, - id, - None, - ref_count, - hal::TextureUses::PRESENT, - ) - .unwrap(); - }; - } - } - for id in cmdbuf.trackers.views.used() { - if !texture_view_guard[id].life_guard.use_at(submit_index) { - device.temp_suspected.texture_views.push(id); - } - } - for id in cmdbuf.trackers.bind_groups.used() { - let bg = &bind_group_guard[id]; - if !bg.life_guard.use_at(submit_index) { - device.temp_suspected.bind_groups.push(id); - } - // We need to update the submission indices for the contained - // state-less (!) resources as well, so that they don't get - // deleted too early if the parent bind group goes out of scope. - for sub_id in bg.used.views.used() { - texture_view_guard[sub_id].life_guard.use_at(submit_index); - } - for sub_id in bg.used.samplers.used() { - sampler_guard[sub_id].life_guard.use_at(submit_index); - } - } - // assert!(cmdbuf.trackers.samplers.is_empty()); - for id in cmdbuf.trackers.compute_pipelines.used() { - if !compute_pipe_guard[id].life_guard.use_at(submit_index) { - device.temp_suspected.compute_pipelines.push(id); + for texture_view in cmd_buf_trackers.views.used_resources() { + texture_view.info.use_at(submit_index); + if texture_view.is_unique() { + temp_suspected + .as_mut() + .unwrap() + .texture_views + .insert(texture_view.as_info().id(), texture_view.clone()); + } } - } - for id in cmdbuf.trackers.render_pipelines.used() { - if !render_pipe_guard[id].life_guard.use_at(submit_index) { - device.temp_suspected.render_pipelines.push(id); + { + for bg in cmd_buf_trackers.bind_groups.used_resources() { + bg.info.use_at(submit_index); + // We need to update the submission indices for the contained + // state-less (!) resources as well, so that they don't get + // deleted too early if the parent bind group goes out of scope. + for view in bg.used.views.used_resources() { + view.info.use_at(submit_index); + } + for sampler in bg.used.samplers.used_resources() { + sampler.info.use_at(submit_index); + } + if bg.is_unique() { + temp_suspected + .as_mut() + .unwrap() + .bind_groups + .insert(bg.as_info().id(), bg.clone()); + } + } } - } - for id in cmdbuf.trackers.query_sets.used() { - if !query_set_guard[id].life_guard.use_at(submit_index) { - device.temp_suspected.query_sets.push(id); + // assert!(cmd_buf_trackers.samplers.is_empty()); + for compute_pipeline in + cmd_buf_trackers.compute_pipelines.used_resources() + { + compute_pipeline.info.use_at(submit_index); + if compute_pipeline.is_unique() { + temp_suspected.as_mut().unwrap().compute_pipelines.insert( + compute_pipeline.as_info().id(), + compute_pipeline.clone(), + ); + } } - } - for id in cmdbuf.trackers.bundles.used() { - let bundle = &render_bundle_guard[id]; - if !bundle.life_guard.use_at(submit_index) { - device.temp_suspected.render_bundles.push(id); + for render_pipeline in + cmd_buf_trackers.render_pipelines.used_resources() + { + render_pipeline.info.use_at(submit_index); + if render_pipeline.is_unique() { + temp_suspected.as_mut().unwrap().render_pipelines.insert( + render_pipeline.as_info().id(), + render_pipeline.clone(), + ); + } } - // We need to update the submission indices for the contained - // state-less (!) resources as well, excluding the bind groups. - // They don't get deleted too early if the bundle goes out of scope. - for sub_id in bundle.used.render_pipelines.used() { - render_pipe_guard[sub_id].life_guard.use_at(submit_index); + for query_set in cmd_buf_trackers.query_sets.used_resources() { + query_set.info.use_at(submit_index); + if query_set.is_unique() { + temp_suspected + .as_mut() + .unwrap() + .query_sets + .insert(query_set.as_info().id(), query_set.clone()); + } } - for sub_id in bundle.used.query_sets.used() { - query_set_guard[sub_id].life_guard.use_at(submit_index); + for bundle in cmd_buf_trackers.bundles.used_resources() { + bundle.info.use_at(submit_index); + // We need to update the submission indices for the contained + // state-less (!) resources as well, excluding the bind groups. + // They don't get deleted too early if the bundle goes out of scope. + for render_pipeline in + bundle.used.render_pipelines.read().used_resources() + { + render_pipeline.info.use_at(submit_index); + } + for query_set in bundle.used.query_sets.read().used_resources() { + query_set.info.use_at(submit_index); + } + if bundle.is_unique() { + temp_suspected + .as_mut() + .unwrap() + .render_bundles + .insert(bundle.as_info().id(), bundle.clone()); + } } - } - for id in cmdbuf.trackers.blas_s.used() { - if !blas_guard[id].life_guard.use_at(submit_index) { - device.temp_suspected.blas_s.push(id); + for blas in cmd_buf_trackers.blas_s.used_resources() { + blas.info.use_at(submit_index); + if blas.is_unique() { + temp_suspected + .as_mut() + .unwrap() + .blas_s + .insert(blas.as_info().id(), blas.clone()); + } } - } - for id in cmdbuf.trackers.tlas_s.used() { - if !tlas_guard[id].life_guard.use_at(submit_index) { - device.temp_suspected.tlas_s.push(id); + for tlas in cmd_buf_trackers.tlas_s.used_resources() { + tlas.info.use_at(submit_index); + if tlas.is_unique() { + temp_suspected + .as_mut() + .unwrap() + .tlas_s + .insert(tlas.as_info().id(), tlas.clone()); + } } } - let mut baked = cmdbuf.into_baked(); + let mut baked = cmdbuf.from_arc_into_baked(); // execute resource transitions unsafe { baked .encoder - .begin_encoding(Some("(wgpu internal) Transit")) + .begin_encoding(hal_label( + Some("(wgpu internal) Transit"), + device.instance_flags, + )) .map_err(DeviceError::from)? }; log::trace!("Stitching command buffer {:?} before submission", cmb_id); + + //Note: locking the trackers has to be done after the storages + let mut trackers = device.trackers.lock(); baked - .initialize_buffer_memory(&mut *trackers, &mut *buffer_guard) + .initialize_buffer_memory(&mut *trackers) .map_err(|err| QueueSubmitError::DestroyedBuffer(err.0))?; baked - .initialize_texture_memory(&mut *trackers, &mut *texture_guard, device) + .initialize_texture_memory(&mut *trackers, device) .map_err(|err| QueueSubmitError::DestroyedTexture(err.0))?; - - baked.validate_blas_actions(&mut *blas_guard)?; - baked.validate_tlas_actions(&*blas_guard, &mut *tlas_guard)?; + let mut blas_guard = hub.blas_s.write(); + baked.validate_blas_actions(&mut blas_guard)?; + let mut tlas_guard = hub.tlas_s.write(); + baked.validate_tlas_actions(&blas_guard, &mut tlas_guard)?; //Note: stateless trackers are not merged: // device already knows these resources exist. @@ -1271,8 +1384,7 @@ impl Global { &mut baked.encoder, &mut *trackers, &baked.trackers, - &*buffer_guard, - &*texture_guard, + &snatch_guard, ); let transit = unsafe { baked.encoder.end_encoding().unwrap() }; @@ -1285,16 +1397,21 @@ impl Global { unsafe { baked .encoder - .begin_encoding(Some("(wgpu internal) Present")) + .begin_encoding(hal_label( + Some("(wgpu internal) Present"), + device.instance_flags, + )) .map_err(DeviceError::from)? }; trackers .textures - .set_from_usage_scope(&*texture_guard, &used_surface_textures); - let texture_barriers = trackers.textures.drain().map(|pending| { - let tex = unsafe { texture_guard.get_unchecked(pending.id) }; - pending.into_hal(tex) - }); + .set_from_usage_scope(&used_surface_textures); + let (transitions, textures) = + trackers.textures.drain_transitions(&snatch_guard); + let texture_barriers = transitions + .into_iter() + .enumerate() + .map(|(i, p)| p.into_hal(textures[i].unwrap().raw().unwrap())); let present = unsafe { baked.encoder.transition_textures(texture_barriers); baked.encoder.end_encoding().unwrap() @@ -1312,102 +1429,80 @@ impl Global { log::trace!("Device after submission {}", submit_index); } + } - let super::Device { - ref mut pending_writes, - ref mut queue, - ref mut fence, - .. - } = *device; + let mut pending_writes = device.pending_writes.lock(); + let pending_writes = pending_writes.as_mut().unwrap(); - { - // TODO: These blocks have a few organizational issues, and - // should be refactored. - // - // 1) It's similar to the code we have per-command-buffer - // (at the begin and end) Maybe we can merge some? - // - // 2) It's doing the extra locking unconditionally. Maybe we - // can only do so if any surfaces are being written to? - let (_, mut token) = hub.buffers.read(&mut token); // skip token - let (mut texture_guard, _) = hub.textures.write(&mut token); - - used_surface_textures.set_size(texture_guard.len()); - - for &id in pending_writes.dst_textures.iter() { - let texture = texture_guard.get_mut(id).unwrap(); - match texture.inner { - TextureInner::Native { raw: None } => { - return Err(QueueSubmitError::DestroyedTexture(id)); - } - TextureInner::Native { raw: Some(_) } => {} - TextureInner::Surface { - ref mut has_work, .. - } => { - *has_work = true; - let ref_count = texture.life_guard.add_ref(); - unsafe { - used_surface_textures - .merge_single( - &*texture_guard, - id::Valid(id), - None, - &ref_count, - hal::TextureUses::PRESENT, - ) - .unwrap() - }; - } + { + used_surface_textures.set_size(hub.textures.read().len()); + for (&id, texture) in pending_writes.dst_textures.iter() { + match texture.inner.get(&snatch_guard) { + None => { + return Err(QueueSubmitError::DestroyedTexture(id)); + } + Some(TextureInner::Native { .. }) => {} + Some(TextureInner::Surface { ref has_work, .. }) => { + has_work.store(true, Ordering::Relaxed); + unsafe { + used_surface_textures + .merge_single(texture, None, hal::TextureUses::PRESENT) + .unwrap() + }; } } + } - if !used_surface_textures.is_empty() { - let mut trackers = device.trackers.lock(); - - trackers - .textures - .set_from_usage_scope(&*texture_guard, &used_surface_textures); - let texture_barriers = trackers.textures.drain().map(|pending| { - let tex = unsafe { texture_guard.get_unchecked(pending.id) }; - pending.into_hal(tex) - }); + if !used_surface_textures.is_empty() { + let mut trackers = device.trackers.lock(); - unsafe { - pending_writes - .command_encoder - .transition_textures(texture_barriers); - }; - } + trackers + .textures + .set_from_usage_scope(&used_surface_textures); + let (transitions, textures) = + trackers.textures.drain_transitions(&snatch_guard); + let texture_barriers = transitions + .into_iter() + .enumerate() + .map(|(i, p)| p.into_hal(textures[i].unwrap().raw().unwrap())); + unsafe { + pending_writes + .command_encoder + .transition_textures(texture_barriers); + }; } + } - let refs = pending_writes - .pre_submit() - .into_iter() - .chain( - active_executions - .iter() - .flat_map(|pool_execution| pool_execution.cmd_buffers.iter()), - ) - .collect::>(); - unsafe { - queue - .submit(&refs, Some((fence, submit_index))) - .map_err(DeviceError::from)?; - } + let refs = pending_writes + .pre_submit() + .into_iter() + .chain( + active_executions + .iter() + .flat_map(|pool_execution| pool_execution.cmd_buffers.iter()), + ) + .collect::>(); + unsafe { + queue + .raw + .as_ref() + .unwrap() + .submit(&refs, Some((fence, submit_index))) + .map_err(DeviceError::from)?; } profiling::scope!("cleanup"); - if let Some(pending_execution) = device.pending_writes.post_submit( - &device.command_allocator, - &device.raw, - &device.queue, + if let Some(pending_execution) = pending_writes.post_submit( + device.command_allocator.lock().as_mut().unwrap(), + device.raw(), + queue.raw.as_ref().unwrap(), ) { active_executions.push(pending_execution); } // this will register the new submission to the life time tracker - let mut pending_write_resources = mem::take(&mut device.pending_writes.temp_resources); - device.lock_life(&mut token).track_submission( + let mut pending_write_resources = mem::take(&mut pending_writes.temp_resources); + device.lock_life().track_submission( submit_index, pending_write_resources.drain(..), active_executions, @@ -1415,7 +1510,7 @@ impl Global { // This will schedule destruction of all resources that are no longer needed // by the user but used in the command stream, among other things. - let (closures, _) = match device.maintain(hub, wgt::Maintain::Poll, &mut token) { + let (closures, _) = match device.maintain(fence, wgt::Maintain::Poll) { Ok(closures) => closures, Err(WaitIdleError::Device(err)) => return Err(QueueSubmitError::Queue(err)), Err(WaitIdleError::StuckGpu) => return Err(QueueSubmitError::StuckGpu), @@ -1424,9 +1519,8 @@ impl Global { // pending_write_resources has been drained, so it's empty, but we // want to retain its heap allocation. - device.pending_writes.temp_resources = pending_write_resources; - device.temp_suspected.clear(); - device.lock_life(&mut token).post_submit(); + pending_writes.temp_resources = pending_write_resources; + device.lock_life().post_submit(); (submit_index, closures) }; @@ -1442,28 +1536,31 @@ impl Global { pub fn queue_get_timestamp_period( &self, - queue_id: id::QueueId, + queue_id: QueueId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, _) = hub.devices.read(&mut token); - match device_guard.get(queue_id) { - Ok(device) => Ok(unsafe { device.queue.get_timestamp_period() }), + match hub.queues.get(queue_id) { + Ok(queue) => Ok(unsafe { queue.raw.as_ref().unwrap().get_timestamp_period() }), Err(_) => Err(InvalidQueue), } } pub fn queue_on_submitted_work_done( &self, - queue_id: id::QueueId, + queue_id: QueueId, closure: SubmittedWorkDoneClosure, ) -> Result<(), InvalidQueue> { + api_log!("Queue::on_submitted_work_done {queue_id:?}"); + //TODO: flush pending writes let hub = A::hub(self); - let mut token = Token::root(); - let (device_guard, mut token) = hub.devices.read(&mut token); - match device_guard.get(queue_id) { - Ok(device) => device.lock_life(&mut token).add_work_done_closure(closure), + match hub.queues.get(queue_id) { + Ok(queue) => queue + .device + .as_ref() + .unwrap() + .lock_life() + .add_work_done_closure(closure), Err(_) => return Err(InvalidQueue), } Ok(()) diff --git a/wgpu-core/src/device/ray_tracing.rs b/wgpu-core/src/device/ray_tracing.rs index 1fa30b14ee..f329847f3c 100644 --- a/wgpu-core/src/device/ray_tracing.rs +++ b/wgpu-core/src/device/ray_tracing.rs @@ -4,20 +4,20 @@ use crate::{ device::{queue::TempResource, Device, DeviceError}, global::Global, hal_api::HalApi, - hub::Token, id::{self, BlasId, TlasId}, identity::{GlobalIdentityHandlerFactory, Input}, ray_tracing::{get_raw_tlas_instance_size, CreateBlasError, CreateTlasError}, - resource, - storage::InvalidId, - LabelHelpers, LifeGuard, Stored, + resource, LabelHelpers, }; +use parking_lot::{Mutex, RwLock}; +use std::sync::Arc; +use crate::resource::{ResourceInfo, StagingBuffer}; use hal::{AccelerationStructureTriangleIndices, Device as _}; impl Device { fn create_blas( - &self, + self: &Arc, self_id: id::DeviceId, blas_desc: &resource::BlasDescriptor, sizes: wgt::BlasGeometrySizeDescriptors, @@ -25,7 +25,7 @@ impl Device { debug_assert_eq!(self_id.backend(), A::VARIANT); let size_info = match &sizes { - &wgt::BlasGeometrySizeDescriptors::Triangles { ref desc } => { + wgt::BlasGeometrySizeDescriptors::Triangles { desc } => { let mut entries = Vec::>::with_capacity(desc.len()); for x in desc { @@ -52,7 +52,7 @@ impl Device { }); } unsafe { - self.raw.get_acceleration_structure_build_sizes( + self.raw().get_acceleration_structure_build_sizes( &hal::GetAccelerationStructureBuildSizesDescriptor { entries: &hal::AccelerationStructureEntries::Triangles(entries), flags: blas_desc.flags, @@ -63,7 +63,7 @@ impl Device { }; let raw = unsafe { - self.raw + self.raw() .create_acceleration_structure(&hal::AccelerationStructureDescriptor { label: blas_desc.label.borrow_option(), size: size_info.acceleration_structure_size, @@ -72,33 +72,35 @@ impl Device { } .map_err(DeviceError::from)?; - let handle = unsafe { self.raw.get_acceleration_structure_device_address(&raw) }; + let handle = unsafe { self.raw().get_acceleration_structure_device_address(&raw) }; Ok(resource::Blas { raw: Some(raw), - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, - life_guard: LifeGuard::new(blas_desc.label.borrow_or_default()), + device: self.clone(), + info: ResourceInfo::new( + blas_desc + .label + .to_hal(self.instance_flags) + .unwrap_or(""), + ), size_info, sizes, flags: blas_desc.flags, update_mode: blas_desc.update_mode, handle, - built_index: None, + built_index: RwLock::new(None), }) } fn create_tlas( - &self, + self: &Arc, self_id: id::DeviceId, desc: &resource::TlasDescriptor, ) -> Result, CreateTlasError> { debug_assert_eq!(self_id.backend(), A::VARIANT); let size_info = unsafe { - self.raw.get_acceleration_structure_build_sizes( + self.raw().get_acceleration_structure_build_sizes( &hal::GetAccelerationStructureBuildSizesDescriptor { entries: &hal::AccelerationStructureEntries::Instances( hal::AccelerationStructureInstances { @@ -113,7 +115,7 @@ impl Device { }; let raw = unsafe { - self.raw + self.raw() .create_acceleration_structure(&hal::AccelerationStructureDescriptor { label: desc.label.borrow_option(), size: size_info.acceleration_structure_size, @@ -125,7 +127,7 @@ impl Device { let instance_buffer_size = get_raw_tlas_instance_size::() * std::cmp::max(desc.max_instances, 1) as usize; let instance_buffer = unsafe { - self.raw.create_buffer(&hal::BufferDescriptor { + self.raw().create_buffer(&hal::BufferDescriptor { label: Some("(wgpu-core) instances_buffer"), size: instance_buffer_size as u64, usage: hal::BufferUses::COPY_DST @@ -137,17 +139,18 @@ impl Device { Ok(resource::Tlas { raw: Some(raw), - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), + device: self.clone(), + info: ResourceInfo::new( + desc.label + .to_hal(self.instance_flags) + .unwrap_or(""), + ), size_info, flags: desc.flags, update_mode: desc.update_mode, - built_index: None, - dependencies: Vec::new(), - instance_buffer: Some(instance_buffer), + built_index: RwLock::new(None), + dependencies: RwLock::new(Vec::new()), + instance_buffer: RwLock::new(Some(instance_buffer)), max_instance_count: desc.max_instances, }) } @@ -164,18 +167,21 @@ impl Global { profiling::scope!("Device::create_blas"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.blas_s.prepare(id_in); + let fid = hub.blas_s.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); + let device_guard = hub.devices.read(); let error = loop { let device = match device_guard.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.is_valid() { + break DeviceError::Lost.into(); + } + #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::CreateBlas { + if let Some(trace) = device.trace.lock().as_mut() { + trace.add(trace::Action::CreateBlas { id: fid.id(), desc: desc.clone(), sizes: sizes.clone(), @@ -188,17 +194,15 @@ impl Global { }; let handle = blas.handle; - let ref_count = blas.life_guard.add_ref(); - - let id = fid.assign(blas, &mut token); + let (id, resource) = fid.assign(blas); log::info!("Created blas {:?} with {:?}", id, desc); - device.trackers.lock().blas_s.insert_single(id, ref_count); + device.trackers.lock().blas_s.insert_single(id, resource); - return (id.0, Some(handle), None); + return (id, Some(handle), None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let id = fid.assign_error(desc.label.borrow_or_default()); (id, None, Some(error)) } @@ -211,18 +215,17 @@ impl Global { profiling::scope!("Device::create_tlas"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.tlas_s.prepare(id_in); + let fid = hub.tlas_s.prepare::(id_in); - let (device_guard, mut token) = hub.devices.read(&mut token); + let device_guard = hub.devices.read(); let error = loop { let device = match device_guard.get(device_id) { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::CreateTlas { + if let Some(trace) = device.trace.lock().as_mut() { + trace.add(trace::Action::CreateTlas { id: fid.id(), desc: desc.clone(), }); @@ -232,17 +235,16 @@ impl Global { Ok(tlas) => tlas, Err(e) => break e, }; - let ref_count = tlas.life_guard.add_ref(); - let id = fid.assign(tlas, &mut token); - log::info!("Created blas {:?} with {:?}", id, desc); + let id = fid.assign(tlas); + log::info!("Created tlas {:?} with {:?}", id.0, desc); - device.trackers.lock().tlas_s.insert_single(id, ref_count); + device.trackers.lock().tlas_s.insert_single(id.0, id.1); return (id.0, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -250,33 +252,28 @@ impl Global { profiling::scope!("Blas::destroy"); let hub = A::hub(self); - let mut token = Token::root(); - let (mut device_guard, mut token) = hub.devices.write(&mut token); + let device_guard = hub.devices.write(); log::info!("Blas {:?} is destroyed", blas_id); - let (mut blas_guard, _) = hub.blas_s.write(&mut token); + let blas_guard = hub.blas_s.write(); let blas = blas_guard - .get_mut(blas_id) + .get(blas_id) .map_err(|_| resource::DestroyError::Invalid)?; - let device = &mut device_guard[blas.device_id.value]; + let device = device_guard.get(blas.device.info.id()).unwrap(); #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::FreeBlas(blas_id)); + if let Some(trace) = device.trace.lock().as_mut() { + trace.add(trace::Action::FreeBlas(blas_id)); } - let raw = blas - .raw - .take() - .ok_or(resource::DestroyError::AlreadyDestroyed)?; - let temp = TempResource::AccelerationStructure(raw); + let temp = TempResource::Blas(blas.clone()); { - let last_submit_index = blas.life_guard.life_count(); + let last_submit_index = blas.info.submission_index(); drop(blas_guard); device - .lock_life(&mut token) + .lock_life() .schedule_resource_destruction(temp, last_submit_index); } @@ -288,36 +285,21 @@ impl Global { log::debug!("blas {:?} is dropped", blas_id); let hub = A::hub(self); - let mut token = Token::root(); - - let (last_submit_index, device_id) = { - let (mut blas_guard, _) = hub.blas_s.write(&mut token); - match blas_guard.get_mut(blas_id) { - Ok(blas) => { - let last_submit_index = blas.life_guard.life_count(); - (last_submit_index, blas.device_id.value) - } - Err(InvalidId) => { - hub.blas_s.unregister_locked(blas_id, &mut *blas_guard); - return; - } - } - }; - let (device_guard, mut token) = hub.devices.read(&mut token); - let device = &device_guard[device_id]; - { - let mut life_lock = device.lock_life(&mut token); - life_lock + if let Some(blas) = hub.blas_s.unregister(blas_id) { + let last_submit_index = blas.info.submission_index(); + + blas.device + .lock_life() .suspected_resources .blas_s - .push(id::Valid(blas_id)); - } + .insert(blas_id, blas.clone()); - if wait { - match device.wait_for_submit(last_submit_index, &mut token) { - Ok(()) => (), - Err(e) => log::error!("Failed to wait for blas {:?}: {:?}", blas_id, e), + if wait { + match blas.device.wait_for_submit(last_submit_index) { + Ok(()) => (), + Err(e) => log::error!("Failed to wait for blas {:?}: {:?}", blas_id, e), + } } } } @@ -326,36 +308,47 @@ impl Global { profiling::scope!("Tlas::destroy"); let hub = A::hub(self); - let mut token = Token::root(); - - let (mut device_guard, mut token) = hub.devices.write(&mut token); log::info!("Tlas {:?} is destroyed", tlas_id); - let (mut tlas_guard, _) = hub.tlas_s.write(&mut token); + let tlas_guard = hub.tlas_s.write(); let tlas = tlas_guard - .get_mut(tlas_id) + .get(tlas_id) .map_err(|_| resource::DestroyError::Invalid)?; - let device = &mut device_guard[tlas.device_id.value]; + let device = &mut tlas.device.clone(); #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(trace::Action::FreeTlas(tlas_id)); + if let Some(trace) = device.trace.lock().as_mut() { + trace.add(trace::Action::FreeTlas(tlas_id)); } - let raw = tlas - .raw - .take() - .ok_or(resource::DestroyError::AlreadyDestroyed)?; - - let temp = TempResource::AccelerationStructure(raw); - - let raw_instance_buffer = tlas.instance_buffer.take(); - let temp_instance_buffer = raw_instance_buffer.map(|e| TempResource::Buffer(e)); + let temp = TempResource::Tlas(tlas.clone()); + + let raw_instance_buffer = tlas.instance_buffer.write().take(); + let temp_instance_buffer = match raw_instance_buffer { + None => None, + Some(e) => { + let size = get_raw_tlas_instance_size::() as u64 + * std::cmp::max(tlas.max_instance_count, 1) as u64; + let mapping = unsafe { + device + .raw() + .map_buffer(&e, 0..size) + .map_err(|_| resource::DestroyError::Invalid)? + }; + Some(TempResource::StagingBuffer(Arc::new(StagingBuffer { + raw: Mutex::new(Some(e)), + device: device.clone(), + size, + info: ResourceInfo::new("Raytracing scratch buffer"), + is_coherent: mapping.is_coherent, + }))) + } + }; { - let last_submit_index = tlas.life_guard.life_count(); + let last_submit_index = tlas.info.submission_index(); drop(tlas_guard); - let guard = &mut device.lock_life(&mut token); + let guard = &mut device.lock_life(); guard.schedule_resource_destruction(temp, last_submit_index); if let Some(temp_instance_buffer) = temp_instance_buffer { @@ -371,36 +364,21 @@ impl Global { log::debug!("tlas {:?} is dropped", tlas_id); let hub = A::hub(self); - let mut token = Token::root(); - - let (last_submit_index, device_id) = { - let (mut tlas_guard, _) = hub.tlas_s.write(&mut token); - match tlas_guard.get_mut(tlas_id) { - Ok(tlas) => { - let last_submit_index = tlas.life_guard.life_count(); - (last_submit_index, tlas.device_id.value) - } - Err(InvalidId) => { - hub.tlas_s.unregister_locked(tlas_id, &mut *tlas_guard); - return; - } - } - }; - let (device_guard, mut token) = hub.devices.read(&mut token); - let device = &device_guard[device_id]; - { - let mut life_lock = device.lock_life(&mut token); - life_lock + if let Some(tlas) = hub.tlas_s.unregister(tlas_id) { + let last_submit_index = tlas.info.submission_index(); + + tlas.device + .lock_life() .suspected_resources .tlas_s - .push(id::Valid(tlas_id)); - } + .insert(tlas_id, tlas.clone()); - if wait { - match device.wait_for_submit(last_submit_index, &mut token) { - Ok(()) => (), - Err(e) => log::error!("Failed to wait for tlas {:?}: {:?}", tlas_id, e), + if wait { + match tlas.device.wait_for_submit(last_submit_index) { + Ok(()) => (), + Err(e) => log::error!("Failed to wait for blas {:?}: {:?}", tlas_id, e), + } } } } diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 0fd157ba74..2895b01aaf 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -1,109 +1,178 @@ #[cfg(feature = "trace")] use crate::device::trace; use crate::{ - binding_model::{self, get_bind_group_layout, try_get_bind_group_layout}, + binding_model::{self, BindGroupLayout, BindGroupLayoutEntryError}, command, conv, - device::life::WaitIdleError, + device::life::{LifetimeTracker, WaitIdleError}, + device::queue::PendingWrites, device::{ - AttachmentData, CommandAllocator, MissingDownlevelFlags, MissingFeatures, - RenderPassContext, CLEANUP_WAIT_MS, + bgl, AttachmentData, CommandAllocator, DeviceLostInvocation, MissingDownlevelFlags, + MissingFeatures, RenderPassContext, CLEANUP_WAIT_MS, }, hal_api::HalApi, - hub::{Hub, Token}, - id, - identity::GlobalIdentityHandlerFactory, + hal_label, + hub::Hub, + id::{self, DeviceId, QueueId}, init_tracker::{ BufferInitTracker, BufferInitTrackerAction, MemoryInitKind, TextureInitRange, TextureInitTracker, TextureInitTrackerAction, }, instance::Adapter, pipeline, - resource::{self, Buffer, TextureViewNotRenderableReason}, + pool::ResourcePool, + registry::Registry, + resource::ResourceInfo, + resource::{ + self, Buffer, QuerySet, Resource, ResourceType, Sampler, Texture, TextureView, + TextureViewNotRenderableReason, + }, + resource_log, + snatch::{SnatchGuard, SnatchLock, Snatchable}, storage::Storage, track::{BindGroupStates, TextureSelector, Tracker}, validation::{self, check_buffer_usage, check_texture_usage}, - FastHashMap, LabelHelpers as _, LifeGuard, MultiRefCount, RefCount, Stored, SubmissionIndex, + FastHashMap, LabelHelpers as _, SubmissionIndex, }; use arrayvec::ArrayVec; use hal::{CommandEncoder as _, Device as _}; -use parking_lot::{Mutex, MutexGuard}; +use parking_lot::{Mutex, MutexGuard, RwLock}; + use smallvec::SmallVec; use thiserror::Error; -use wgt::{TextureFormat, TextureSampleType, TextureViewDimension}; - -use std::{borrow::Cow, iter, num::NonZeroU32, sync::atomic::AtomicU64}; +use wgt::{DeviceLostReason, TextureFormat, TextureSampleType, TextureViewDimension}; + +use std::{ + borrow::Cow, + iter, + num::NonZeroU32, + sync::{ + atomic::{AtomicBool, AtomicU64, Ordering}, + Arc, + }, +}; use super::{ - life, queue, DeviceDescriptor, DeviceError, ImplicitPipelineContext, UserClosures, EP_FAILURE, - IMPLICIT_FAILURE, ZERO_BUFFER_SIZE, + life::{self, ResourceMaps}, + queue::{self}, + DeviceDescriptor, DeviceError, ImplicitPipelineContext, UserClosures, ENTRYPOINT_FAILURE_ERROR, + IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL, ZERO_BUFFER_SIZE, }; /// Structure describing a logical device. Some members are internally mutable, /// stored behind mutexes. /// /// TODO: establish clear order of locking for these: -/// `mem_allocator`, `desc_allocator`, `life_tracker`, `trackers`, -/// `render_passes`, `pending_writes`, `trace`. +/// `life_tracker`, `trackers`, `render_passes`, `pending_writes`, `trace`. /// /// Currently, the rules are: /// 1. `life_tracker` is locked after `hub.devices`, enforced by the type system /// 1. `self.trackers` is locked last (unenforced) /// 1. `self.trace` is locked last (unenforced) +/// +/// Right now avoid locking twice same resource or registry in a call execution +/// and minimize the locking to the minimum scope possibile +/// Unless otherwise specified, no lock may be acquired while holding another lock. +/// This means that you must inspect function calls made while a lock is held +/// to see what locks the callee may try to acquire. +/// +/// As far as this point: +/// device_maintain_ids locks Device::lifetime_tracker, and calls... +/// triage_suspected locks Device::trackers, and calls... +/// Registry::unregister locks Registry::storage +/// +/// Important: +/// When locking pending_writes please check that trackers is not locked +/// trackers should be locked only when needed for the shortest time possible pub struct Device { - pub(crate) raw: A::Device, - pub(crate) adapter_id: Stored, - pub(crate) queue: A::Queue, - pub(crate) zero_buffer: A::Buffer, - //pub(crate) cmd_allocator: command::CommandAllocator, - //mem_allocator: Mutex>, - //desc_allocator: Mutex>, + raw: Option, + pub(crate) adapter: Arc>, + pub(crate) queue_id: RwLock>, + queue_to_drop: RwLock>, + pub(crate) zero_buffer: Option, + pub(crate) info: ResourceInfo, + + pub(crate) command_allocator: Mutex>>, //Note: The submission index here corresponds to the last submission that is done. - pub(crate) life_guard: LifeGuard, - - /// A clone of `life_guard.ref_count`. - /// - /// Holding a separate clone of the `RefCount` here lets us tell whether the - /// device is referenced by other resources, even if `life_guard.ref_count` - /// was set to `None` by a call to `device_drop`. - pub(super) ref_count: RefCount, - - pub(super) command_allocator: Mutex>, - pub(crate) active_submission_index: SubmissionIndex, - pub(super) fence: A::Fence, + pub(crate) active_submission_index: AtomicU64, //SubmissionIndex, + pub(crate) fence: RwLock>, + pub(crate) snatchable_lock: SnatchLock, + + /// Is this device valid? Valid is closely associated with "lose the device", + /// which can be triggered by various methods, including at the end of device + /// destroy, and by any GPU errors that cause us to no longer trust the state + /// of the device. Ideally we would like to fold valid into the storage of + /// the device itself (for example as an Error enum), but unfortunately we + /// need to continue to be able to retrieve the device in poll_devices to + /// determine if it can be dropped. If our internal accesses of devices were + /// done through ref-counted references and external accesses checked for + /// Error enums, we wouldn't need this. For now, we need it. All the call + /// sites where we check it are areas that should be revisited if we start + /// using ref-counted references for internal access. + pub(crate) valid: AtomicBool, /// All live resources allocated with this [`Device`]. /// /// Has to be locked temporarily only (locked last) + /// and never before pending_writes pub(crate) trackers: Mutex>, // Life tracker should be locked right after the device and before anything else. - life_tracker: Mutex>, + life_tracker: Mutex>, /// Temporary storage for resource management functions. Cleared at the end /// of every call (unless an error occurs). - pub(super) temp_suspected: life::SuspectedResources, + pub(crate) temp_suspected: Mutex>>, + /// Pool of bind group layouts, allowing deduplication. + pub(crate) bgl_pool: ResourcePool>, pub(crate) alignments: hal::Alignments, pub(crate) limits: wgt::Limits, pub(crate) features: wgt::Features, pub(crate) downlevel: wgt::DownlevelCapabilities, - // TODO: move this behind another mutex. This would allow several methods to - // switch to borrow Device immutably, such as `write_buffer`, `write_texture`, - // and `buffer_unmap`. - pub(crate) pending_writes: queue::PendingWrites, + pub(crate) instance_flags: wgt::InstanceFlags, + pub(crate) pending_writes: Mutex>>, pub(crate) last_acceleration_structure_build_command_index: AtomicU64, #[cfg(feature = "trace")] - pub(crate) trace: Option>, + pub(crate) trace: Mutex>, +} + +impl std::fmt::Debug for Device { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Device") + .field("adapter", &self.adapter.info.label()) + .field("limits", &self.limits) + .field("features", &self.features) + .field("downlevel", &self.downlevel) + .finish() + } +} + +impl Drop for Device { + fn drop(&mut self) { + resource_log!("Destroy raw Device {:?}", self.info.label()); + let raw = self.raw.take().unwrap(); + let pending_writes = self.pending_writes.lock().take().unwrap(); + pending_writes.dispose(&raw); + self.command_allocator.lock().take().unwrap().dispose(&raw); + unsafe { + raw.destroy_buffer(self.zero_buffer.take().unwrap()); + raw.destroy_fence(self.fence.write().take().unwrap()); + let queue = self.queue_to_drop.write().take().unwrap(); + raw.exit(queue); + } + } } #[derive(Clone, Debug, Error)] -#[non_exhaustive] pub enum CreateDeviceError { - #[error("Not enough memory left")] + #[error("Not enough memory left to create device")] OutOfMemory, #[error("Failed to create internal buffer for initializing textures")] FailedToCreateZeroBuffer(#[from] DeviceError), } impl Device { + pub(crate) fn raw(&self) -> &A::Device { + self.raw.as_ref().unwrap() + } pub(crate) fn require_features(&self, feature: wgt::Features) -> Result<(), MissingFeatures> { if self.features.contains(feature) { Ok(()) @@ -126,33 +195,33 @@ impl Device { impl Device { pub(crate) fn new( - open: hal::OpenDevice, - adapter_id: Stored, - alignments: hal::Alignments, - downlevel: wgt::DownlevelCapabilities, + raw_device: A::Device, + raw_queue: &A::Queue, + adapter: &Arc>, desc: &DeviceDescriptor, trace_path: Option<&std::path::Path>, + instance_flags: wgt::InstanceFlags, ) -> Result { #[cfg(not(feature = "trace"))] if let Some(_) = trace_path { log::error!("Feature 'trace' is not enabled"); } let fence = - unsafe { open.device.create_fence() }.map_err(|_| CreateDeviceError::OutOfMemory)?; + unsafe { raw_device.create_fence() }.map_err(|_| CreateDeviceError::OutOfMemory)?; let mut com_alloc = CommandAllocator { free_encoders: Vec::new(), }; let pending_encoder = com_alloc - .acquire_encoder(&open.device, &open.queue) + .acquire_encoder(&raw_device, raw_queue) .map_err(|_| CreateDeviceError::OutOfMemory)?; let mut pending_writes = queue::PendingWrites::::new(pending_encoder); // Create zeroed buffer used for texture clears. let zero_buffer = unsafe { - open.device + raw_device .create_buffer(&hal::BufferDescriptor { - label: Some("(wgpu internal) zero init buffer"), + label: hal_label(Some("(wgpu internal) zero init buffer"), instance_flags), size: ZERO_BUFFER_SIZE, usage: hal::BufferUses::COPY_SRC | hal::BufferUses::COPY_DST, memory_flags: hal::MemoryFlags::empty(), @@ -178,49 +247,58 @@ impl Device { })); } - let life_guard = LifeGuard::new(""); - let ref_count = life_guard.add_ref(); + let alignments = adapter.raw.capabilities.alignments.clone(); + let downlevel = adapter.raw.capabilities.downlevel.clone(); + Ok(Self { - raw: open.device, - adapter_id, - queue: open.queue, - zero_buffer, - life_guard, - ref_count, - command_allocator: Mutex::new(com_alloc), - active_submission_index: 0, - fence, + raw: Some(raw_device), + adapter: adapter.clone(), + queue_id: RwLock::new(None), + queue_to_drop: RwLock::new(None), + zero_buffer: Some(zero_buffer), + info: ResourceInfo::new(""), + command_allocator: Mutex::new(Some(com_alloc)), + active_submission_index: AtomicU64::new(0), + fence: RwLock::new(Some(fence)), + snatchable_lock: unsafe { SnatchLock::new() }, + valid: AtomicBool::new(true), trackers: Mutex::new(Tracker::new()), life_tracker: Mutex::new(life::LifetimeTracker::new()), - temp_suspected: life::SuspectedResources::default(), + temp_suspected: Mutex::new(Some(life::ResourceMaps::new())), + bgl_pool: ResourcePool::new(), #[cfg(feature = "trace")] - trace: trace_path.and_then(|path| match trace::Trace::new(path) { + trace: Mutex::new(trace_path.and_then(|path| match trace::Trace::new(path) { Ok(mut trace) => { trace.add(trace::Action::Init { desc: desc.clone(), backend: A::VARIANT, }); - Some(Mutex::new(trace)) + Some(trace) } Err(e) => { - log::error!("Unable to start a trace in '{:?}': {:?}", path, e); + log::error!("Unable to start a trace in '{path:?}': {e}"); None } - }), + })), alignments, - limits: desc.limits.clone(), - features: desc.features, + limits: desc.required_limits.clone(), + features: desc.required_features, downlevel, - pending_writes, + instance_flags, + pending_writes: Mutex::new(Some(pending_writes)), last_acceleration_structure_build_command_index: AtomicU64::new(0), }) } - pub(super) fn lock_life<'this, 'token: 'this>( - &'this self, - //TODO: fix this - the token has to be borrowed for the lock - _token: &mut Token<'token, Self>, - ) -> MutexGuard<'this, life::LifetimeTracker> { + pub fn is_valid(&self) -> bool { + self.valid.load(Ordering::Acquire) + } + + pub(crate) fn release_queue(&self, queue: A::Queue) { + self.queue_to_drop.write().replace(queue); + } + + pub(crate) fn lock_life<'a>(&'a self) -> MutexGuard<'a, LifetimeTracker> { self.life_tracker.lock() } @@ -237,33 +315,12 @@ impl Device { /// submissions still in flight. (We have to take the locks needed to /// produce this information for other reasons, so we might as well just /// return it to our callers.) - pub(super) fn maintain<'this, 'token: 'this, G: GlobalIdentityHandlerFactory>( + pub(crate) fn maintain<'this>( &'this self, - hub: &Hub, + fence: &A::Fence, maintain: wgt::Maintain, - token: &mut Token<'token, Self>, ) -> Result<(UserClosures, bool), WaitIdleError> { profiling::scope!("Device::maintain"); - let mut life_tracker = self.lock_life(token); - - // Normally, `temp_suspected` exists only to save heap - // allocations: it's cleared at the start of the function - // call, and cleared by the end. But `Global::queue_submit` is - // fallible; if it exits early, it may leave some resources in - // `temp_suspected`. - life_tracker - .suspected_resources - .extend(&self.temp_suspected); - - life_tracker.triage_suspected( - hub, - &self.trackers, - #[cfg(feature = "trace")] - self.trace.as_ref(), - token, - ); - life_tracker.triage_mapped(hub, token); - let last_done_index = if maintain.is_wait() { let index_to_wait_for = match maintain { wgt::Maintain::WaitForSubmissionIndex(submission_index) => { @@ -271,121 +328,171 @@ impl Device { // as we already checked this from inside the poll call. submission_index.index } - _ => self.active_submission_index, + _ => self.active_submission_index.load(Ordering::Relaxed), }; unsafe { self.raw - .wait(&self.fence, index_to_wait_for, CLEANUP_WAIT_MS) + .as_ref() + .unwrap() + .wait(fence, index_to_wait_for, CLEANUP_WAIT_MS) .map_err(DeviceError::from)? }; index_to_wait_for } else { unsafe { self.raw - .get_fence_value(&self.fence) + .as_ref() + .unwrap() + .get_fence_value(fence) .map_err(DeviceError::from)? } }; - let submission_closures = - life_tracker.triage_submissions(last_done_index, &self.command_allocator); - let mapping_closures = life_tracker.handle_mapping(hub, &self.raw, &self.trackers, token); - life_tracker.cleanup(&self.raw); + let mut life_tracker = self.lock_life(); + let submission_closures = life_tracker.triage_submissions( + last_done_index, + self.command_allocator.lock().as_mut().unwrap(), + ); + + { + // Normally, `temp_suspected` exists only to save heap + // allocations: it's cleared at the start of the function + // call, and cleared by the end. But `Global::queue_submit` is + // fallible; if it exits early, it may leave some resources in + // `temp_suspected`. + let temp_suspected = self + .temp_suspected + .lock() + .replace(ResourceMaps::new()) + .unwrap(); + + life_tracker.suspected_resources.extend(temp_suspected); + + life_tracker.triage_suspected(&self.trackers); + life_tracker.triage_mapped(); + } + + let mapping_closures = life_tracker.handle_mapping(self.raw(), &self.trackers); + + // Detect if we have been destroyed and now need to lose the device. + // If we are invalid (set at start of destroy) and our queue is empty, + // and we have a DeviceLostClosure, return the closure to be called by + // our caller. This will complete the steps for both destroy and for + // "lose the device". + let mut device_lost_invocations = SmallVec::new(); + if !self.is_valid() && life_tracker.queue_empty() { + // We can release gpu resources associated with this device. + life_tracker.release_gpu_resources(); + + // If we have a DeviceLostClosure, build an invocation with the + // reason DeviceLostReason::Destroyed and no message. + if life_tracker.device_lost_closure.is_some() { + device_lost_invocations.push(DeviceLostInvocation { + closure: life_tracker.device_lost_closure.take().unwrap(), + reason: DeviceLostReason::Destroyed, + message: String::new(), + }); + } + } let closures = UserClosures { mappings: mapping_closures, submissions: submission_closures, + device_lost_invocations, }; Ok((closures, life_tracker.queue_empty())) } - pub(super) fn untrack<'this, 'token: 'this, G: GlobalIdentityHandlerFactory>( - &'this mut self, - hub: &Hub, - trackers: &Tracker, - token: &mut Token<'token, Self>, - ) { - self.temp_suspected.clear(); + pub(crate) fn untrack(&self, trackers: &Tracker) { + let mut temp_suspected = self + .temp_suspected + .lock() + .replace(ResourceMaps::new()) + .unwrap(); + temp_suspected.clear(); // As the tracker is cleared/dropped, we need to consider all the resources // that it references for destruction in the next GC pass. { - let (bind_group_guard, mut token) = hub.bind_groups.read(token); - let (compute_pipe_guard, mut token) = hub.compute_pipelines.read(&mut token); - let (render_pipe_guard, mut token) = hub.render_pipelines.read(&mut token); - let (query_set_guard, mut token) = hub.query_sets.read(&mut token); - let (buffer_guard, mut token) = hub.buffers.read(&mut token); - let (texture_guard, mut token) = hub.textures.read(&mut token); - let (texture_view_guard, mut token) = hub.texture_views.read(&mut token); - let (sampler_guard, mut token) = hub.samplers.read(&mut token); - let (blas_guard, mut token) = hub.blas_s.read(&mut token); - let (tlas_guard, _) = hub.tlas_s.read(&mut token); - - for id in trackers.buffers.used() { - if buffer_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.buffers.push(id); + for resource in trackers.buffers.used_resources() { + if resource.is_unique() { + temp_suspected + .buffers + .insert(resource.as_info().id(), resource.clone()); } } - for id in trackers.textures.used() { - if texture_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.textures.push(id); + for resource in trackers.textures.used_resources() { + if resource.is_unique() { + temp_suspected + .textures + .insert(resource.as_info().id(), resource.clone()); } } - for id in trackers.views.used() { - if texture_view_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.texture_views.push(id); + for resource in trackers.views.used_resources() { + if resource.is_unique() { + temp_suspected + .texture_views + .insert(resource.as_info().id(), resource.clone()); } } - for id in trackers.bind_groups.used() { - if bind_group_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.bind_groups.push(id); + for resource in trackers.bind_groups.used_resources() { + if resource.is_unique() { + temp_suspected + .bind_groups + .insert(resource.as_info().id(), resource.clone()); } } - for id in trackers.samplers.used() { - if sampler_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.samplers.push(id); + for resource in trackers.samplers.used_resources() { + if resource.is_unique() { + temp_suspected + .samplers + .insert(resource.as_info().id(), resource.clone()); } } - for id in trackers.compute_pipelines.used() { - if compute_pipe_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.compute_pipelines.push(id); + for resource in trackers.compute_pipelines.used_resources() { + if resource.is_unique() { + temp_suspected + .compute_pipelines + .insert(resource.as_info().id(), resource.clone()); } } - for id in trackers.render_pipelines.used() { - if render_pipe_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.render_pipelines.push(id); + for resource in trackers.render_pipelines.used_resources() { + if resource.is_unique() { + temp_suspected + .render_pipelines + .insert(resource.as_info().id(), resource.clone()); } } - for id in trackers.query_sets.used() { - if query_set_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.query_sets.push(id); + for resource in trackers.query_sets.used_resources() { + if resource.is_unique() { + temp_suspected + .query_sets + .insert(resource.as_info().id(), resource.clone()); } } - for id in trackers.blas_s.used() { - if blas_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.blas_s.push(id); + for resource in trackers.blas_s.used_resources() { + if resource.is_unique() { + temp_suspected + .blas_s + .insert(resource.as_info().id(), resource.clone()); } } - for id in trackers.tlas_s.used() { - if tlas_guard[id].life_guard.ref_count.is_none() { - self.temp_suspected.tlas_s.push(id); + for resource in trackers.tlas_s.used_resources() { + if resource.is_unique() { + temp_suspected + .tlas_s + .insert(resource.as_info().id(), resource.clone()); } } } - - self.lock_life(token) - .suspected_resources - .extend(&self.temp_suspected); - - self.temp_suspected.clear(); + self.lock_life().suspected_resources.extend(temp_suspected); } - pub(super) fn create_buffer( - &self, - self_id: id::DeviceId, + pub(crate) fn create_buffer( + self: &Arc, desc: &resource::BufferDescriptor, transient: bool, ) -> Result, resource::CreateBufferError> { - debug_assert_eq!(self_id.backend(), A::VARIANT); + debug_assert_eq!(self.as_info().id().backend(), A::VARIANT); if desc.size > self.limits.max_buffer_size { return Err(resource::CreateBufferError::MaxBufferSize { @@ -459,92 +566,78 @@ impl Device { memory_flags.set(hal::MemoryFlags::TRANSIENT, transient); let hal_desc = hal::BufferDescriptor { - label: desc.label.borrow_option(), + label: desc.label.to_hal(self.instance_flags), size: aligned_size, usage, memory_flags, }; - let buffer = unsafe { self.raw.create_buffer(&hal_desc) }.map_err(DeviceError::from)?; + let buffer = unsafe { self.raw().create_buffer(&hal_desc) }.map_err(DeviceError::from)?; Ok(Buffer { - raw: Some(buffer), - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, + raw: Snatchable::new(buffer), + device: self.clone(), usage: desc.usage, size: desc.size, - initialization_status: BufferInitTracker::new(desc.size), - sync_mapped_writes: None, - map_state: resource::BufferMapState::Idle, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), + initialization_status: RwLock::new(BufferInitTracker::new(aligned_size)), + sync_mapped_writes: Mutex::new(None), + map_state: Mutex::new(resource::BufferMapState::Idle), + info: ResourceInfo::new(desc.label.borrow_or_default()), }) } - pub(super) fn create_texture_from_hal( - &self, + pub(crate) fn create_texture_from_hal( + self: &Arc, hal_texture: A::Texture, hal_usage: hal::TextureUses, - self_id: id::DeviceId, desc: &resource::TextureDescriptor, format_features: wgt::TextureFormatFeatures, clear_mode: resource::TextureClearMode, - ) -> resource::Texture { - debug_assert_eq!(self_id.backend(), A::VARIANT); + ) -> Texture { + debug_assert_eq!(self.as_info().id().backend(), A::VARIANT); - resource::Texture { - inner: resource::TextureInner::Native { - raw: Some(hal_texture), - }, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, + Texture { + inner: Snatchable::new(resource::TextureInner::Native { raw: hal_texture }), + device: self.clone(), desc: desc.map_label(|_| ()), hal_usage, format_features, - initialization_status: TextureInitTracker::new( + initialization_status: RwLock::new(TextureInitTracker::new( desc.mip_level_count, desc.array_layer_count(), - ), + )), full_range: TextureSelector { mips: 0..desc.mip_level_count, layers: 0..desc.array_layer_count(), }, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), - clear_mode, + info: ResourceInfo::new(desc.label.borrow_or_default()), + clear_mode: RwLock::new(clear_mode), } } pub fn create_buffer_from_hal( - &self, + self: &Arc, hal_buffer: A::Buffer, - self_id: id::DeviceId, desc: &resource::BufferDescriptor, ) -> Buffer { - debug_assert_eq!(self_id.backend(), A::VARIANT); + debug_assert_eq!(self.as_info().id().backend(), A::VARIANT); Buffer { - raw: Some(hal_buffer), - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, + raw: Snatchable::new(hal_buffer), + device: self.clone(), usage: desc.usage, size: desc.size, - initialization_status: BufferInitTracker::new(0), - sync_mapped_writes: None, - map_state: resource::BufferMapState::Idle, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), + initialization_status: RwLock::new(BufferInitTracker::new(0)), + sync_mapped_writes: Mutex::new(None), + map_state: Mutex::new(resource::BufferMapState::Idle), + info: ResourceInfo::new(desc.label.borrow_or_default()), } } - pub(super) fn create_texture( - &self, - self_id: id::DeviceId, + pub(crate) fn create_texture( + self: &Arc, adapter: &Adapter, desc: &resource::TextureDescriptor, - ) -> Result, resource::CreateTextureError> { + ) -> Result, resource::CreateTextureError> { use resource::{CreateTextureError, TextureDimensionError}; if desc.usage.is_empty() || desc.usage.contains_invalid_bits() { @@ -607,6 +700,30 @@ impl Device { } } + { + let (width_multiple, height_multiple) = desc.format.size_multiple_requirement(); + + if desc.size.width % width_multiple != 0 { + return Err(CreateTextureError::InvalidDimension( + TextureDimensionError::WidthNotMultipleOf { + width: desc.size.width, + multiple: width_multiple, + format: desc.format, + }, + )); + } + + if desc.size.height % height_multiple != 0 { + return Err(CreateTextureError::InvalidDimension( + TextureDimensionError::HeightNotMultipleOf { + height: desc.size.height, + multiple: height_multiple, + format: desc.format, + }, + )); + } + } + let format_features = self .describe_format_features(adapter, desc.format) .map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?; @@ -651,6 +768,14 @@ impl Device { return Err(CreateTextureError::InvalidSampleCount( desc.sample_count, desc.format, + desc.format + .guaranteed_format_features(self.features) + .flags + .supported_sample_counts(), + adapter + .get_texture_format_features(desc.format) + .flags + .supported_sample_counts(), )); }; } @@ -693,29 +818,10 @@ impl Device { self.require_downlevel_flags(wgt::DownlevelFlags::VIEW_FORMATS)?; } - // Enforce having COPY_DST/DEPTH_STENCIL_WRITE/COLOR_TARGET otherwise we - // wouldn't be able to initialize the texture. - let hal_usage = conv::map_texture_usage(desc.usage, desc.format.into()) - | if desc.format.is_depth_stencil_format() { - hal::TextureUses::DEPTH_STENCIL_WRITE - } else if desc.usage.contains(wgt::TextureUsages::COPY_DST) { - hal::TextureUses::COPY_DST // (set already) - } else { - // Use COPY_DST only if we can't use COLOR_TARGET - if format_features - .allowed_usages - .contains(wgt::TextureUsages::RENDER_ATTACHMENT) - && desc.dimension == wgt::TextureDimension::D2 - // Render targets dimension must be 2d - { - hal::TextureUses::COLOR_TARGET - } else { - hal::TextureUses::COPY_DST - } - }; + let hal_usage = conv::map_texture_usage_for_texture(desc, &format_features); let hal_desc = hal::TextureDescriptor { - label: desc.label.borrow_option(), + label: desc.label.to_hal(self.instance_flags), size: desc.size, mip_level_count: desc.mip_level_count, sample_count: desc.sample_count, @@ -728,6 +834,8 @@ impl Device { let raw_texture = unsafe { self.raw + .as_ref() + .unwrap() .create_texture(&hal_desc) .map_err(DeviceError::from)? }; @@ -746,26 +854,45 @@ impl Device { wgt::TextureDimension::D3 => unreachable!(), }; + let clear_label = hal_label( + Some("(wgpu internal) clear texture view"), + self.instance_flags, + ); + let mut clear_views = SmallVec::new(); for mip_level in 0..desc.mip_level_count { for array_layer in 0..desc.size.depth_or_array_layers { - let desc = hal::TextureViewDescriptor { - label: Some("(wgpu internal) clear texture view"), - format: desc.format, - dimension, - usage, - range: wgt::ImageSubresourceRange { - aspect: wgt::TextureAspect::All, - base_mip_level: mip_level, - mip_level_count: Some(1), - base_array_layer: array_layer, - array_layer_count: Some(1), - }, - }; - clear_views.push( - unsafe { self.raw.create_texture_view(&raw_texture, &desc) } - .map_err(DeviceError::from)?, - ); + macro_rules! push_clear_view { + ($format:expr, $aspect:expr) => { + let desc = hal::TextureViewDescriptor { + label: clear_label, + format: $format, + dimension, + usage, + range: wgt::ImageSubresourceRange { + aspect: $aspect, + base_mip_level: mip_level, + mip_level_count: Some(1), + base_array_layer: array_layer, + array_layer_count: Some(1), + }, + }; + clear_views.push(Some( + unsafe { self.raw().create_texture_view(&raw_texture, &desc) } + .map_err(DeviceError::from)?, + )); + }; + } + + if let Some(planes) = desc.format.planes() { + for plane in 0..planes { + let aspect = wgt::TextureAspect::from_plane(plane).unwrap(); + let format = desc.format.aspect_specific_format(aspect).unwrap(); + push_clear_view!(format, aspect); + } + } else { + push_clear_view!(desc.format, wgt::TextureAspect::All); + } } } resource::TextureClearMode::RenderPass { @@ -776,32 +903,25 @@ impl Device { resource::TextureClearMode::BufferCopy }; - let mut texture = self.create_texture_from_hal( - raw_texture, - hal_usage, - self_id, - desc, - format_features, - clear_mode, - ); + let mut texture = + self.create_texture_from_hal(raw_texture, hal_usage, desc, format_features, clear_mode); texture.hal_usage = hal_usage; Ok(texture) } - pub(super) fn create_texture_view( - &self, - texture: &resource::Texture, - texture_id: id::TextureId, + pub(crate) fn create_texture_view( + self: &Arc, + texture: &Arc>, desc: &resource::TextureViewDescriptor, - ) -> Result, resource::CreateTextureViewError> { + ) -> Result, resource::CreateTextureViewError> { + let snatch_guard = texture.device.snatchable_lock.read(); + let texture_raw = texture - .inner - .as_raw() + .raw(&snatch_guard) .ok_or(resource::CreateTextureViewError::InvalidTexture)?; // resolve TextureViewDescriptor defaults // https://gpuweb.github.io/gpuweb/#abstract-opdef-resolving-gputextureviewdescriptor-defaults - let resolved_format = desc.format.unwrap_or_else(|| { texture .desc @@ -1032,7 +1152,7 @@ impl Device { log::debug!( "Create view for texture {:?} filters usages to {:?}", - texture_id, + texture.as_info().id(), usage ); @@ -1052,7 +1172,7 @@ impl Device { }; let hal_desc = hal::TextureViewDescriptor { - label: desc.label.borrow_option(), + label: desc.label.to_hal(self.instance_flags), format, dimension: resolved_dimension, usage, @@ -1061,6 +1181,8 @@ impl Device { let raw = unsafe { self.raw + .as_ref() + .unwrap() .create_texture_view(texture_raw, &hal_desc) .map_err(|_| resource::CreateTextureViewError::OutOfMemory)? }; @@ -1070,14 +1192,12 @@ impl Device { layers: desc.range.base_array_layer..array_layer_end, }; - Ok(resource::TextureView { - raw, - parent_id: Stored { - value: id::Valid(texture_id), - ref_count: texture.life_guard.add_ref(), - }, - device_id: texture.device_id.clone(), + Ok(TextureView { + raw: Some(raw), + parent: RwLock::new(Some(texture.clone())), + device: self.clone(), desc: resource::HalTextureViewDescriptor { + texture_format: texture.desc.format, format: resolved_format, dimension: resolved_dimension, range: resolved_range, @@ -1086,15 +1206,14 @@ impl Device { render_extent, samples: texture.desc.sample_count, selector, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), + info: ResourceInfo::new(desc.label.borrow_or_default()), }) } - pub(super) fn create_sampler( - &self, - self_id: id::DeviceId, + pub(crate) fn create_sampler( + self: &Arc, desc: &resource::SamplerDescriptor, - ) -> Result, resource::CreateSamplerError> { + ) -> Result, resource::CreateSamplerError> { if desc .address_modes .iter() @@ -1170,7 +1289,7 @@ impl Device { //TODO: check for wgt::DownlevelFlags::COMPARISON_SAMPLERS let hal_desc = hal::SamplerDescriptor { - label: desc.label.borrow_option(), + label: desc.label.to_hal(self.instance_flags), address_modes: desc.address_modes, mag_filter: desc.mag_filter, min_filter: desc.min_filter, @@ -1183,25 +1302,23 @@ impl Device { let raw = unsafe { self.raw + .as_ref() + .unwrap() .create_sampler(&hal_desc) .map_err(DeviceError::from)? }; - Ok(resource::Sampler { - raw, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), + Ok(Sampler { + raw: Some(raw), + device: self.clone(), + info: ResourceInfo::new(desc.label.borrow_or_default()), comparison: desc.compare.is_some(), filtering: desc.min_filter == wgt::FilterMode::Linear || desc.mag_filter == wgt::FilterMode::Linear, }) } - pub(super) fn create_shader_module<'a>( - &self, - self_id: id::DeviceId, + pub(crate) fn create_shader_module<'a>( + self: &Arc, desc: &pipeline::ShaderModuleDescriptor<'a>, source: pipeline::ShaderModuleSource<'a>, ) -> Result, pipeline::CreateShaderModuleError> { @@ -1294,6 +1411,30 @@ impl Device { Caps::RAY_QUERY, self.features.contains(wgt::Features::RAY_QUERY), ); + caps.set( + Caps::DUAL_SOURCE_BLENDING, + self.features.contains(wgt::Features::DUAL_SOURCE_BLENDING), + ); + caps.set( + Caps::CUBE_ARRAY_TEXTURES, + self.downlevel + .flags + .contains(wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES), + ); + + let debug_source = + if self.instance_flags.contains(wgt::InstanceFlags::DEBUG) && !source.is_empty() { + Some(hal::DebugSource { + file_name: Cow::Owned( + desc.label + .as_ref() + .map_or("shader".to_string(), |l| l.to_string()), + ), + source_code: Cow::Owned(source.clone()), + }) + } else { + None + }; let info = naga::valid::Validator::new(naga::valid::ValidationFlags::all(), caps) .validate(&module) @@ -1304,14 +1445,24 @@ impl Device { inner: Box::new(inner), }) })?; - let interface = validation::Interface::new(&module, &info, self.limits.clone()); - let hal_shader = hal::ShaderInput::Naga(hal::NagaShader { module, info }); + let interface = + validation::Interface::new(&module, &info, self.limits.clone(), self.features); + let hal_shader = hal::ShaderInput::Naga(hal::NagaShader { + module, + info, + debug_source, + }); let hal_desc = hal::ShaderModuleDescriptor { - label: desc.label.borrow_option(), + label: desc.label.to_hal(self.instance_flags), runtime_checks: desc.shader_bound_checks.runtime_checks(), }; - let raw = match unsafe { self.raw.create_shader_module(&hal_desc, hal_shader) } { + let raw = match unsafe { + self.raw + .as_ref() + .unwrap() + .create_shader_module(&hal_desc, hal_shader) + } { Ok(raw) => raw, Err(error) => { return Err(match error { @@ -1327,31 +1478,32 @@ impl Device { }; Ok(pipeline::ShaderModule { - raw, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, + raw: Some(raw), + device: self.clone(), interface: Some(interface), - #[cfg(debug_assertions)] + info: ResourceInfo::new(desc.label.borrow_or_default()), label: desc.label.borrow_or_default().to_string(), }) } #[allow(unused_unsafe)] - pub(super) unsafe fn create_shader_module_spirv<'a>( - &self, - self_id: id::DeviceId, + pub(crate) unsafe fn create_shader_module_spirv<'a>( + self: &Arc, desc: &pipeline::ShaderModuleDescriptor<'a>, source: &'a [u32], ) -> Result, pipeline::CreateShaderModuleError> { self.require_features(wgt::Features::SPIRV_SHADER_PASSTHROUGH)?; let hal_desc = hal::ShaderModuleDescriptor { - label: desc.label.borrow_option(), + label: desc.label.to_hal(self.instance_flags), runtime_checks: desc.shader_bound_checks.runtime_checks(), }; let hal_shader = hal::ShaderInput::SpirV(source); - let raw = match unsafe { self.raw.create_shader_module(&hal_desc, hal_shader) } { + let raw = match unsafe { + self.raw + .as_ref() + .unwrap() + .create_shader_module(&hal_desc, hal_shader) + } { Ok(raw) => raw, Err(error) => { return Err(match error { @@ -1367,62 +1519,29 @@ impl Device { }; Ok(pipeline::ShaderModule { - raw, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, + raw: Some(raw), + device: self.clone(), interface: None, - #[cfg(debug_assertions)] + info: ResourceInfo::new(desc.label.borrow_or_default()), label: desc.label.borrow_or_default().to_string(), }) } - pub(super) fn deduplicate_bind_group_layout( - self_id: id::DeviceId, - entry_map: &binding_model::BindEntryMap, - guard: &Storage, id::BindGroupLayoutId>, - ) -> Option { - guard - .iter(self_id.backend()) - .find(|&(_, bgl)| { - bgl.device_id.value.0 == self_id - && bgl.compatible_layout.is_none() - && bgl.entries == *entry_map - }) - .map(|(id, value)| { - value.multi_ref_count.inc(); - id - }) - } - - fn get_introspection_bind_group_layouts<'a>( - pipeline_layout: &binding_model::PipelineLayout, - bgl_guard: &'a Storage, id::BindGroupLayoutId>, - ) -> ArrayVec<&'a binding_model::BindEntryMap, { hal::MAX_BIND_GROUPS }> { - pipeline_layout - .bind_group_layout_ids - .iter() - .map(|&id| &bgl_guard[id].entries) - .collect() - } - /// Generate information about late-validated buffer bindings for pipelines. //TODO: should this be combined with `get_introspection_bind_group_layouts` in some way? - fn make_late_sized_buffer_groups<'a>( + pub(crate) fn make_late_sized_buffer_groups( shader_binding_sizes: &FastHashMap, layout: &binding_model::PipelineLayout, - bgl_guard: &'a Storage, id::BindGroupLayoutId>, ) -> ArrayVec { // Given the shader-required binding sizes and the pipeline layout, // return the filtered list of them in the layout order, // removing those with given `min_binding_size`. layout - .bind_group_layout_ids + .bind_group_layouts .iter() .enumerate() - .map(|(group_index, &bgl_id)| pipeline::LateSizedBufferGroup { - shader_sizes: bgl_guard[bgl_id] + .map(|(group_index, bgl)| pipeline::LateSizedBufferGroup { + shader_sizes: bgl .entries .values() .filter_map(|entry| match entry.ty { @@ -1445,12 +1564,12 @@ impl Device { .collect() } - pub(super) fn create_bind_group_layout( - &self, - self_id: id::DeviceId, - label: Option<&str>, - entry_map: binding_model::BindEntryMap, - ) -> Result, binding_model::CreateBindGroupLayoutError> { + pub(crate) fn create_bind_group_layout( + self: &Arc, + label: &crate::Label, + entry_map: bgl::EntryMap, + origin: bgl::Origin, + ) -> Result, binding_model::CreateBindGroupLayoutError> { #[derive(PartialEq)] enum WritableStorage { Yes, @@ -1503,7 +1622,8 @@ impl Device { } => { return Err(binding_model::CreateBindGroupLayoutError::Entry { binding: entry.binding, - error: binding_model::BindGroupLayoutEntryError::SampleTypeFloatFilterableBindingMultisampled, + error: + BindGroupLayoutEntryError::SampleTypeFloatFilterableBindingMultisampled, }); } Bt::Texture { .. } => ( @@ -1519,7 +1639,7 @@ impl Device { wgt::TextureViewDimension::Cube | wgt::TextureViewDimension::CubeArray => { return Err(binding_model::CreateBindGroupLayoutError::Entry { binding: entry.binding, - error: binding_model::BindGroupLayoutEntryError::StorageTextureCube, + error: BindGroupLayoutEntryError::StorageTextureCube, }) } _ => (), @@ -1533,7 +1653,7 @@ impl Device { { return Err(binding_model::CreateBindGroupLayoutError::Entry { binding: entry.binding, - error: binding_model::BindGroupLayoutEntryError::StorageTextureReadWrite, + error: BindGroupLayoutEntryError::StorageTextureReadWrite, }); } _ => (), @@ -1564,7 +1684,7 @@ impl Device { // Validate the count parameter if entry.count.is_some() { required_features |= array_feature - .ok_or(binding_model::BindGroupLayoutEntryError::ArrayUnsupported) + .ok_or(BindGroupLayoutEntryError::ArrayUnsupported) .map_err(|error| binding_model::CreateBindGroupLayoutError::Entry { binding: entry.binding, error, @@ -1596,13 +1716,13 @@ impl Device { } self.require_features(required_features) - .map_err(binding_model::BindGroupLayoutEntryError::MissingFeatures) + .map_err(BindGroupLayoutEntryError::MissingFeatures) .map_err(|error| binding_model::CreateBindGroupLayoutError::Entry { binding: entry.binding, error, })?; self.require_downlevel_flags(required_downlevel_flags) - .map_err(binding_model::BindGroupLayoutEntryError::MissingDownlevelFlags) + .map_err(BindGroupLayoutEntryError::MissingDownlevelFlags) .map_err(|error| binding_model::CreateBindGroupLayoutError::Entry { binding: entry.binding, error, @@ -1611,8 +1731,8 @@ impl Device { let bgl_flags = conv::bind_group_layout_flags(self.features); - let mut hal_bindings = entry_map.values().cloned().collect::>(); - hal_bindings.sort_by_key(|b| b.binding); + let hal_bindings = entry_map.values().copied().collect::>(); + let label = label.to_hal(self.instance_flags); let hal_desc = hal::BindGroupLayoutDescriptor { label, flags: bgl_flags, @@ -1620,6 +1740,8 @@ impl Device { }; let raw = unsafe { self.raw + .as_ref() + .unwrap() .create_bind_group_layout(&hal_desc) .map_err(DeviceError::from)? }; @@ -1634,35 +1756,28 @@ impl Device { .validate(&self.limits) .map_err(binding_model::CreateBindGroupLayoutError::TooManyBindings)?; - Ok(binding_model::BindGroupLayout { - raw, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, - multi_ref_count: MultiRefCount::new(), - dynamic_count: entry_map - .values() - .filter(|b| b.ty.has_dynamic_offset()) - .count(), - count_validator, + Ok(BindGroupLayout { + raw: Some(raw), + device: self.clone(), entries: entry_map, - #[cfg(debug_assertions)] - label: label.unwrap_or("").to_string(), - compatible_layout: None, + origin, + binding_count_validator: count_validator, + info: ResourceInfo::new(label.unwrap_or("")), + label: label.unwrap_or_default().to_string(), }) } - fn create_buffer_binding<'a>( + pub(crate) fn create_buffer_binding<'a>( bb: &binding_model::BufferBinding, binding: u32, decl: &wgt::BindGroupLayoutEntry, - used_buffer_ranges: &mut Vec, + used_buffer_ranges: &mut Vec>, dynamic_binding_info: &mut Vec, late_buffer_binding_sizes: &mut FastHashMap, used: &mut BindGroupStates, storage: &'a Storage, id::BufferId>, limits: &wgt::Limits, + snatch_guard: &'a SnatchGuard<'a>, ) -> Result, binding_model::CreateBindGroupError> { use crate::binding_model::CreateBindGroupError as Error; @@ -1711,10 +1826,11 @@ impl Device { .buffers .add_single(storage, bb.buffer_id, internal_use) .ok_or(Error::InvalidBuffer(bb.buffer_id))?; + check_buffer_usage(buffer.usage, pub_usage)?; let raw_buffer = buffer .raw - .as_ref() + .get(snatch_guard) .ok_or(Error::InvalidBuffer(bb.buffer_id))?; let (bind_size, bind_end) = match bb.size { @@ -1729,7 +1845,16 @@ impl Device { } (size.get(), end) } - None => (buffer.size - bb.offset, buffer.size), + None => { + if buffer.size < bb.offset { + return Err(Error::BindingRangeTooLarge { + buffer: bb.buffer_id, + range: bb.offset..bb.offset, + size: buffer.size, + }); + } + (buffer.size - bb.offset, buffer.size) + } }; if bind_size > range_limit as u64 { @@ -1767,8 +1892,8 @@ impl Device { } assert_eq!(bb.offset % wgt::COPY_BUFFER_ALIGNMENT, 0); - used_buffer_ranges.extend(buffer.initialization_status.create_action( - bb.buffer_id, + used_buffer_ranges.extend(buffer.initialization_status.read().create_action( + buffer, bb.offset..bb.offset + bind_size, MemoryInitKind::NeedsInitializedMemory, )); @@ -1780,32 +1905,36 @@ impl Device { }) } - fn create_texture_binding( - view: &resource::TextureView, - texture_guard: &Storage, id::TextureId>, + pub(crate) fn create_texture_binding( + view: &TextureView, internal_use: hal::TextureUses, pub_usage: wgt::TextureUsages, used: &mut BindGroupStates, - used_texture_ranges: &mut Vec, + used_texture_ranges: &mut Vec>, ) -> Result<(), binding_model::CreateBindGroupError> { + let texture = view.parent.read(); + let texture_id = texture.as_ref().unwrap().as_info().id(); // Careful here: the texture may no longer have its own ref count, // if it was deleted by the user. let texture = used .textures .add_single( - texture_guard, - view.parent_id.value.0, - view.parent_id.ref_count.clone(), + texture.as_ref().unwrap(), Some(view.selector.clone()), internal_use, ) .ok_or(binding_model::CreateBindGroupError::InvalidTexture( - view.parent_id.value.0, + texture_id, ))?; + + if texture.device.as_info().id() != view.device.as_info().id() { + return Err(DeviceError::WrongDevice.into()); + } + check_texture_usage(texture.desc.usage, pub_usage)?; used_texture_ranges.push(TextureInitTrackerAction { - id: view.parent_id.value.0, + texture: texture.clone(), range: TextureInitRange { mip_range: view.desc.range.mip_range(texture.desc.mip_level_count), layer_range: view @@ -1819,14 +1948,13 @@ impl Device { Ok(()) } - pub(super) fn create_bind_group( - &self, - self_id: id::DeviceId, - layout: &binding_model::BindGroupLayout, - layout_id: id::Valid, + // This function expects the provided bind group layout to be resolved + // (not passing a duplicate) beforehand. + pub(crate) fn create_bind_group( + self: &Arc, + layout: &Arc>, desc: &binding_model::BindGroupDescriptor, - hub: &Hub, - token: &mut Token>, + hub: &Hub, ) -> Result, binding_model::CreateBindGroupError> { use crate::binding_model::{BindingResource as Br, CreateBindGroupError as Error}; { @@ -1849,11 +1977,10 @@ impl Device { // fill out the descriptors let mut used = BindGroupStates::new(); - let (buffer_guard, mut token) = hub.buffers.read(token); - let (texture_guard, mut token) = hub.textures.read(&mut token); //skip token - let (texture_view_guard, mut token) = hub.texture_views.read(&mut token); - let (sampler_guard, mut token) = hub.samplers.read(&mut token); - let (tlas_guard, _) = hub.tlas_s.read(&mut token); + let buffer_guard = hub.buffers.read(); + let texture_view_guard = hub.texture_views.read(); + let sampler_guard = hub.samplers.read(); + let tlas_guard = hub.tlas_s.read(); let mut used_buffer_ranges = Vec::new(); let mut used_texture_ranges = Vec::new(); @@ -1862,12 +1989,13 @@ impl Device { let mut hal_samplers = Vec::new(); let mut hal_textures = Vec::new(); let mut hal_tlas_s = Vec::new(); + let snatch_guard = self.snatchable_lock.read(); for entry in desc.entries.iter() { let binding = entry.binding; // Find the corresponding declaration in the layout let decl = layout .entries - .get(&binding) + .get(binding) .ok_or(Error::MissingBindingDeclaration(binding))?; let (res_index, count) = match entry.resource { Br::Buffer(ref bb) => { @@ -1881,6 +2009,7 @@ impl Device { &mut used, &*buffer_guard, &self.limits, + &snatch_guard, )?; let res_index = hal_buffers.len(); @@ -1903,6 +2032,7 @@ impl Device { &mut used, &*buffer_guard, &self.limits, + &snatch_guard, )?; hal_buffers.push(bb); } @@ -1916,6 +2046,10 @@ impl Device { .add_single(&*sampler_guard, id) .ok_or(Error::InvalidSampler(id))?; + if sampler.device.as_info().id() != self.as_info().id() { + return Err(DeviceError::WrongDevice.into()); + } + // Allowed sampler values for filtering and comparison let (allowed_filtering, allowed_comparison) = match ty { wgt::SamplerBindingType::Filtering => (None, false), @@ -1942,7 +2076,7 @@ impl Device { } let res_index = hal_samplers.len(); - hal_samplers.push(&sampler.raw); + hal_samplers.push(sampler.raw()); (res_index, 1) } _ => { @@ -1964,7 +2098,10 @@ impl Device { .samplers .add_single(&*sampler_guard, id) .ok_or(Error::InvalidSampler(id))?; - hal_samplers.push(&sampler.raw); + if sampler.device.as_info().id() != self.as_info().id() { + return Err(DeviceError::WrongDevice.into()); + } + hal_samplers.push(sampler.raw()); } (res_index, num_bindings) @@ -1974,7 +2111,7 @@ impl Device { .views .add_single(&*texture_view_guard, id) .ok_or(Error::InvalidTextureView(id))?; - let (pub_usage, internal_use) = Self::texture_use_parameters( + let (pub_usage, internal_use) = self.texture_use_parameters( binding, decl, view, @@ -1982,7 +2119,6 @@ impl Device { )?; Self::create_texture_binding( view, - &texture_guard, internal_use, pub_usage, &mut used, @@ -1990,7 +2126,7 @@ impl Device { )?; let res_index = hal_textures.len(); hal_textures.push(hal::TextureBinding { - view: &view.raw, + view: view.raw(), usage: internal_use, }); (res_index, 1) @@ -2006,18 +2142,17 @@ impl Device { .add_single(&*texture_view_guard, id) .ok_or(Error::InvalidTextureView(id))?; let (pub_usage, internal_use) = - Self::texture_use_parameters(binding, decl, view, + self.texture_use_parameters(binding, decl, view, "SampledTextureArray, ReadonlyStorageTextureArray or WriteonlyStorageTextureArray")?; Self::create_texture_binding( view, - &texture_guard, internal_use, pub_usage, &mut used, &mut used_texture_ranges, )?; hal_textures.push(hal::TextureBinding { - view: &view.raw, + view: view.raw(), usage: internal_use, }); } @@ -2053,10 +2188,9 @@ impl Device { return Err(Error::DuplicateBinding(a.binding)); } } - let hal_desc = hal::BindGroupDescriptor { - label: desc.label.borrow_option(), - layout: &layout.raw, + label: desc.label.to_hal(self.instance_flags), + layout: layout.raw(), entries: &hal_entries, buffers: &hal_buffers, samplers: &hal_samplers, @@ -2065,21 +2199,17 @@ impl Device { }; let raw = unsafe { self.raw + .as_ref() + .unwrap() .create_bind_group(&hal_desc) .map_err(DeviceError::from)? }; - // manually add a dependency on BGL - layout.multi_ref_count.inc(); - Ok(binding_model::BindGroup { - raw, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, - layout_id, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), + raw: Some(raw), + device: self.clone(), + layout: layout.clone(), + info: ResourceInfo::new(desc.label.borrow_or_default()), used, used_buffer_ranges, used_texture_ranges, @@ -2087,13 +2217,13 @@ impl Device { // collect in the order of BGL iteration late_buffer_binding_sizes: layout .entries - .keys() - .flat_map(|binding| late_buffer_binding_sizes.get(binding).cloned()) + .indices() + .flat_map(|binding| late_buffer_binding_sizes.get(&binding).cloned()) .collect(), }) } - fn check_array_binding( + pub(crate) fn check_array_binding( features: wgt::Features, count: Option, num_bindings: usize, @@ -2126,10 +2256,11 @@ impl Device { Ok(()) } - fn texture_use_parameters( + pub(crate) fn texture_use_parameters( + self: &Arc, binding: u32, decl: &wgt::BindGroupLayoutEntry, - view: &crate::resource::TextureView, + view: &TextureView, expected: &'static str, ) -> Result<(wgt::TextureUsages, hal::TextureUses), binding_model::CreateBindGroupError> { use crate::binding_model::CreateBindGroupError as Error; @@ -2157,7 +2288,7 @@ impl Device { let compat_sample_type = view .desc .format - .sample_type(Some(view.desc.range.aspect)) + .sample_type(Some(view.desc.range.aspect), Some(self.features)) .unwrap(); match (sample_type, compat_sample_type) { (Tst::Uint, Tst::Uint) | @@ -2256,11 +2387,10 @@ impl Device { } } - pub(super) fn create_pipeline_layout( - &self, - self_id: id::DeviceId, + pub(crate) fn create_pipeline_layout( + self: &Arc, desc: &binding_model::PipelineLayoutDescriptor, - bgl_guard: &Storage, id::BindGroupLayoutId>, + bgl_registry: &Registry>, ) -> Result, binding_model::CreatePipelineLayoutError> { use crate::binding_model::CreatePipelineLayoutError as Error; @@ -2313,66 +2443,69 @@ impl Device { let mut count_validator = binding_model::BindingTypeMaxCountValidator::default(); - // validate total resource counts + // Collect references to the BGLs + let mut bind_group_layouts = ArrayVec::new(); for &id in desc.bind_group_layouts.iter() { - let bind_group_layout = bgl_guard - .get(id) - .map_err(|_| Error::InvalidBindGroupLayout(id))?; - count_validator.merge(&bind_group_layout.count_validator); + let Ok(bgl) = bgl_registry.get(id) else { + return Err(Error::InvalidBindGroupLayout(id)); + }; + + bind_group_layouts.push(bgl); } + + // Validate total resource counts and check for a matching device + for bgl in &bind_group_layouts { + if bgl.device.as_info().id() != self.as_info().id() { + return Err(DeviceError::WrongDevice.into()); + } + + count_validator.merge(&bgl.binding_count_validator); + } + count_validator .validate(&self.limits) .map_err(Error::TooManyBindings)?; - let bgl_vec = desc - .bind_group_layouts + let raw_bind_group_layouts = bind_group_layouts .iter() - .map(|&id| &try_get_bind_group_layout(bgl_guard, id).unwrap().raw) - .collect::>(); + .map(|bgl| bgl.raw()) + .collect::>(); + let hal_desc = hal::PipelineLayoutDescriptor { - label: desc.label.borrow_option(), - flags: hal::PipelineLayoutFlags::BASE_VERTEX_INSTANCE, - bind_group_layouts: &bgl_vec, + label: desc.label.to_hal(self.instance_flags), + flags: hal::PipelineLayoutFlags::FIRST_VERTEX_INSTANCE, + bind_group_layouts: &raw_bind_group_layouts, push_constant_ranges: desc.push_constant_ranges.as_ref(), }; let raw = unsafe { self.raw + .as_ref() + .unwrap() .create_pipeline_layout(&hal_desc) .map_err(DeviceError::from)? }; + drop(raw_bind_group_layouts); + Ok(binding_model::PipelineLayout { - raw, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), - bind_group_layout_ids: desc - .bind_group_layouts - .iter() - .map(|&id| { - // manually add a dependency to BGL - let (id, layout) = get_bind_group_layout(bgl_guard, id::Valid(id)); - layout.multi_ref_count.inc(); - id - }) - .collect(), + raw: Some(raw), + device: self.clone(), + info: ResourceInfo::new(desc.label.borrow_or_default()), + bind_group_layouts, push_constant_ranges: desc.push_constant_ranges.iter().cloned().collect(), }) } //TODO: refactor this. It's the only method of `Device` that registers new objects // (the pipeline layout). - fn derive_pipeline_layout( - &self, - self_id: id::DeviceId, + pub(crate) fn derive_pipeline_layout( + self: &Arc, implicit_context: Option, - mut derived_group_layouts: ArrayVec, - bgl_guard: &mut Storage, id::BindGroupLayoutId>, - pipeline_layout_guard: &mut Storage, id::PipelineLayoutId>, - ) -> Result { + mut derived_group_layouts: ArrayVec, + bgl_registry: &Registry>, + pipeline_layout_registry: &Registry>, + ) -> Result>, pipeline::ImplicitLayoutError> { while derived_group_layouts .last() .map_or(false, |map| map.is_empty()) @@ -2391,15 +2524,8 @@ impl Device { } for (bgl_id, map) in ids.group_ids.iter_mut().zip(derived_group_layouts) { - match Device::deduplicate_bind_group_layout(self_id, &map, bgl_guard) { - Some(dedup_id) => { - *bgl_id = dedup_id; - } - None => { - let bgl = self.create_bind_group_layout(self_id, None, map)?; - bgl_guard.force_replace(*bgl_id, bgl); - } - }; + let bgl = self.create_bind_group_layout(&None, map, bgl::Origin::Derived)?; + bgl_registry.force_replace(*bgl_id, bgl); } let layout_desc = binding_model::PipelineLayoutDescriptor { @@ -2407,157 +2533,155 @@ impl Device { bind_group_layouts: Cow::Borrowed(&ids.group_ids[..group_count]), push_constant_ranges: Cow::Borrowed(&[]), //TODO? }; - let layout = self.create_pipeline_layout(self_id, &layout_desc, bgl_guard)?; - pipeline_layout_guard.force_replace(ids.root_id, layout); - Ok(ids.root_id) + let layout = self.create_pipeline_layout(&layout_desc, bgl_registry)?; + pipeline_layout_registry.force_replace(ids.root_id, layout); + Ok(pipeline_layout_registry.get(ids.root_id).unwrap()) } - pub(super) fn create_compute_pipeline( - &self, - self_id: id::DeviceId, + pub(crate) fn create_compute_pipeline( + self: &Arc, desc: &pipeline::ComputePipelineDescriptor, implicit_context: Option, - hub: &Hub, - token: &mut Token, + hub: &Hub, ) -> Result, pipeline::CreateComputePipelineError> { - //TODO: only lock mutable if the layout is derived - let (mut pipeline_layout_guard, mut token) = hub.pipeline_layouts.write(token); - let (mut bgl_guard, mut token) = hub.bind_group_layouts.write(&mut token); - // This has to be done first, or otherwise the IDs may be pointing to entries // that are not even in the storage. if let Some(ref ids) = implicit_context { - pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_FAILURE); + let mut pipeline_layout_guard = hub.pipeline_layouts.write(); + pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL); + let mut bgl_guard = hub.bind_group_layouts.write(); for &bgl_id in ids.group_ids.iter() { - bgl_guard.insert_error(bgl_id, IMPLICIT_FAILURE); + bgl_guard.insert_error(bgl_id, IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL); } } self.require_downlevel_flags(wgt::DownlevelFlags::COMPUTE_SHADERS)?; - let mut derived_group_layouts = - ArrayVec::::new(); - let mut shader_binding_sizes = FastHashMap::default(); - - let io = validation::StageIo::default(); - let (shader_module_guard, _) = hub.shader_modules.read(&mut token); - - let shader_module = shader_module_guard + let shader_module = hub + .shader_modules .get(desc.stage.module) .map_err(|_| validation::StageError::InvalidModule)?; - { - let flag = wgt::ShaderStages::COMPUTE; - let provided_layouts = match desc.layout { - Some(pipeline_layout_id) => Some(Device::get_introspection_bind_group_layouts( - pipeline_layout_guard - .get(pipeline_layout_id) - .map_err(|_| pipeline::CreateComputePipelineError::InvalidLayout)?, - &*bgl_guard, - )), - None => { - for _ in 0..self.limits.max_bind_groups { - derived_group_layouts.push(binding_model::BindEntryMap::default()); - } - None + if shader_module.device.as_info().id() != self.as_info().id() { + return Err(DeviceError::WrongDevice.into()); + } + + // Get the pipeline layout from the desc if it is provided. + let pipeline_layout = match desc.layout { + Some(pipeline_layout_id) => { + let pipeline_layout = hub + .pipeline_layouts + .get(pipeline_layout_id) + .map_err(|_| pipeline::CreateComputePipelineError::InvalidLayout)?; + + if pipeline_layout.device.as_info().id() != self.as_info().id() { + return Err(DeviceError::WrongDevice.into()); } - }; + + Some(pipeline_layout) + } + None => None, + }; + + let mut binding_layout_source = match pipeline_layout { + Some(ref pipeline_layout) => { + validation::BindingLayoutSource::Provided(pipeline_layout.get_binding_maps()) + } + None => validation::BindingLayoutSource::new_derived(&self.limits), + }; + let mut shader_binding_sizes = FastHashMap::default(); + let io = validation::StageIo::default(); + + { + let stage = wgt::ShaderStages::COMPUTE; + if let Some(ref interface) = shader_module.interface { let _ = interface.check_stage( - provided_layouts.as_ref().map(|p| p.as_slice()), - &mut derived_group_layouts, + &mut binding_layout_source, &mut shader_binding_sizes, &desc.stage.entry_point, - flag, + stage, io, None, )?; } } - let pipeline_layout_id = match desc.layout { - Some(id) => id, - None => self.derive_pipeline_layout( - self_id, + let pipeline_layout = match binding_layout_source { + validation::BindingLayoutSource::Provided(_) => { + drop(binding_layout_source); + pipeline_layout.unwrap() + } + validation::BindingLayoutSource::Derived(entries) => self.derive_pipeline_layout( implicit_context, - derived_group_layouts, - &mut *bgl_guard, - &mut *pipeline_layout_guard, + entries, + &hub.bind_group_layouts, + &hub.pipeline_layouts, )?, }; - let layout = pipeline_layout_guard - .get(pipeline_layout_id) - .map_err(|_| pipeline::CreateComputePipelineError::InvalidLayout)?; let late_sized_buffer_groups = - Device::make_late_sized_buffer_groups(&shader_binding_sizes, layout, &*bgl_guard); + Device::make_late_sized_buffer_groups(&shader_binding_sizes, &pipeline_layout); let pipeline_desc = hal::ComputePipelineDescriptor { - label: desc.label.borrow_option(), - layout: &layout.raw, + label: desc.label.to_hal(self.instance_flags), + layout: pipeline_layout.raw(), stage: hal::ProgrammableStage { entry_point: desc.stage.entry_point.as_ref(), - module: &shader_module.raw, + module: shader_module.raw(), }, }; - let raw = - unsafe { self.raw.create_compute_pipeline(&pipeline_desc) }.map_err( - |err| match err { - hal::PipelineError::Device(error) => { - pipeline::CreateComputePipelineError::Device(error.into()) - } - hal::PipelineError::Linkage(_stages, msg) => { - pipeline::CreateComputePipelineError::Internal(msg) - } - hal::PipelineError::EntryPoint(_stage) => { - pipeline::CreateComputePipelineError::Internal(EP_FAILURE.to_string()) - } - }, - )?; + let raw = unsafe { + self.raw + .as_ref() + .unwrap() + .create_compute_pipeline(&pipeline_desc) + } + .map_err(|err| match err { + hal::PipelineError::Device(error) => { + pipeline::CreateComputePipelineError::Device(error.into()) + } + hal::PipelineError::Linkage(_stages, msg) => { + pipeline::CreateComputePipelineError::Internal(msg) + } + hal::PipelineError::EntryPoint(_stage) => { + pipeline::CreateComputePipelineError::Internal(ENTRYPOINT_FAILURE_ERROR.to_string()) + } + })?; let pipeline = pipeline::ComputePipeline { - raw, - layout_id: Stored { - value: id::Valid(pipeline_layout_id), - ref_count: layout.life_guard.add_ref(), - }, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, + raw: Some(raw), + layout: pipeline_layout, + device: self.clone(), + _shader_module: shader_module, late_sized_buffer_groups, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), + info: ResourceInfo::new(desc.label.borrow_or_default()), }; Ok(pipeline) } - pub(super) fn create_render_pipeline( - &self, - self_id: id::DeviceId, + pub(crate) fn create_render_pipeline( + self: &Arc, adapter: &Adapter, desc: &pipeline::RenderPipelineDescriptor, implicit_context: Option, - hub: &Hub, - token: &mut Token, + hub: &Hub, ) -> Result, pipeline::CreateRenderPipelineError> { use wgt::TextureFormatFeatureFlags as Tfff; - //TODO: only lock mutable if the layout is derived - let (mut pipeline_layout_guard, mut token) = hub.pipeline_layouts.write(token); - let (mut bgl_guard, mut token) = hub.bind_group_layouts.write(&mut token); - // This has to be done first, or otherwise the IDs may be pointing to entries // that are not even in the storage. if let Some(ref ids) = implicit_context { - pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_FAILURE); + //TODO: only lock mutable if the layout is derived + let mut pipeline_layout_guard = hub.pipeline_layouts.write(); + let mut bgl_guard = hub.bind_group_layouts.write(); + pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL); for &bgl_id in ids.group_ids.iter() { - bgl_guard.insert_error(bgl_id, IMPLICIT_FAILURE); + bgl_guard.insert_error(bgl_id, IMPLICIT_BIND_GROUP_LAYOUT_ERROR_LABEL); } } - let mut derived_group_layouts = - ArrayVec::::new(); let mut shader_binding_sizes = FastHashMap::default(); let num_attachments = desc.fragment.as_ref().map(|f| f.targets.len()).unwrap_or(0); @@ -2584,7 +2708,7 @@ impl Device { .iter() .any(|ct| ct.write_mask != first.write_mask || ct.blend != first.blend) } { - log::info!("Color targets: {:?}", color_targets); + log::debug!("Color targets: {:?}", color_targets); self.require_downlevel_flags(wgt::DownlevelFlags::INDEPENDENT_BLEND)?; } @@ -2594,6 +2718,8 @@ impl Device { let mut vertex_steps = Vec::with_capacity(desc.vertex.buffers.len()); let mut vertex_buffers = Vec::with_capacity(desc.vertex.buffers.len()); let mut total_attributes = 0; + let mut shader_expects_dual_source_blending = false; + let mut pipeline_expects_dual_source_blending = false; for (i, vb_state) in desc.vertex.buffers.iter().enumerate() { vertex_steps.push(pipeline::VertexStep { stride: vb_state.array_stride, @@ -2732,9 +2858,38 @@ impl Device { .flags .sample_count_supported(desc.multisample.count) { - break Some(pipeline::ColorStateError::FormatNotMultisampled(cs.format)); + break Some(pipeline::ColorStateError::InvalidSampleCount( + desc.multisample.count, + cs.format, + cs.format + .guaranteed_format_features(self.features) + .flags + .supported_sample_counts(), + adapter + .get_texture_format_features(cs.format) + .flags + .supported_sample_counts(), + )); + } + if let Some(blend_mode) = cs.blend { + for factor in [ + blend_mode.color.src_factor, + blend_mode.color.dst_factor, + blend_mode.alpha.src_factor, + blend_mode.alpha.dst_factor, + ] { + if factor.ref_second_blend_source() { + self.require_features(wgt::Features::DUAL_SOURCE_BLENDING)?; + if i == 0 { + pipeline_expects_dual_source_blending = true; + break; + } else { + return Err(crate::pipeline::CreateRenderPipelineError + ::BlendFactorOnUnsupportedTarget { factor, target: i as u32 }); + } + } + } } - break None; }; if let Some(e) = error { @@ -2769,8 +2924,17 @@ impl Device { .flags .sample_count_supported(desc.multisample.count) { - break Some(pipeline::DepthStencilStateError::FormatNotMultisampled( + break Some(pipeline::DepthStencilStateError::InvalidSampleCount( + desc.multisample.count, ds.format, + ds.format + .guaranteed_format_features(self.features) + .flags + .supported_sample_counts(), + adapter + .get_texture_format_features(ds.format) + .flags + .supported_sample_counts(), )); } @@ -2785,11 +2949,29 @@ impl Device { } } - if desc.layout.is_none() { - for _ in 0..self.limits.max_bind_groups { - derived_group_layouts.push(binding_model::BindEntryMap::default()); + // Get the pipeline layout from the desc if it is provided. + let pipeline_layout = match desc.layout { + Some(pipeline_layout_id) => { + let pipeline_layout = hub + .pipeline_layouts + .get(pipeline_layout_id) + .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?; + + if pipeline_layout.device.as_info().id() != self.as_info().id() { + return Err(DeviceError::WrongDevice.into()); + } + + Some(pipeline_layout) } - } + None => None, + }; + + let mut binding_layout_source = match pipeline_layout { + Some(ref pipeline_layout) => { + validation::BindingLayoutSource::Provided(pipeline_layout.get_binding_maps()) + } + None => validation::BindingLayoutSource::new_derived(&self.limits), + }; let samples = { let sc = desc.multisample.count; @@ -2799,110 +2981,106 @@ impl Device { sc }; - let (shader_module_guard, _) = hub.shader_modules.read(&mut token); - + let vertex_shader_module; let vertex_stage = { - let stage = &desc.vertex.stage; - let flag = wgt::ShaderStages::VERTEX; + let stage_desc = &desc.vertex.stage; + let stage = wgt::ShaderStages::VERTEX; - let shader_module = shader_module_guard.get(stage.module).map_err(|_| { + vertex_shader_module = hub.shader_modules.get(stage_desc.module).map_err(|_| { pipeline::CreateRenderPipelineError::Stage { - stage: flag, + stage, error: validation::StageError::InvalidModule, } })?; + if vertex_shader_module.device.as_info().id() != self.as_info().id() { + return Err(DeviceError::WrongDevice.into()); + } - let provided_layouts = match desc.layout { - Some(pipeline_layout_id) => { - let pipeline_layout = pipeline_layout_guard - .get(pipeline_layout_id) - .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?; - Some(Device::get_introspection_bind_group_layouts( - pipeline_layout, - &*bgl_guard, - )) - } - None => None, - }; - - if let Some(ref interface) = shader_module.interface { + if let Some(ref interface) = vertex_shader_module.interface { io = interface .check_stage( - provided_layouts.as_ref().map(|p| p.as_slice()), - &mut derived_group_layouts, + &mut binding_layout_source, &mut shader_binding_sizes, - &stage.entry_point, - flag, + &stage_desc.entry_point, + stage, io, desc.depth_stencil.as_ref().map(|d| d.depth_compare), ) - .map_err(|error| pipeline::CreateRenderPipelineError::Stage { - stage: flag, - error, - })?; - validated_stages |= flag; + .map_err(|error| pipeline::CreateRenderPipelineError::Stage { stage, error })?; + validated_stages |= stage; } hal::ProgrammableStage { - module: &shader_module.raw, - entry_point: stage.entry_point.as_ref(), + module: vertex_shader_module.raw(), + entry_point: stage_desc.entry_point.as_ref(), } }; + let mut fragment_shader_module = None; let fragment_stage = match desc.fragment { - Some(ref fragment) => { - let flag = wgt::ShaderStages::FRAGMENT; + Some(ref fragment_state) => { + let stage = wgt::ShaderStages::FRAGMENT; - let shader_module = - shader_module_guard - .get(fragment.stage.module) + let shader_module = fragment_shader_module.insert( + hub.shader_modules + .get(fragment_state.stage.module) .map_err(|_| pipeline::CreateRenderPipelineError::Stage { - stage: flag, + stage, error: validation::StageError::InvalidModule, - })?; - - let provided_layouts = match desc.layout { - Some(pipeline_layout_id) => Some(Device::get_introspection_bind_group_layouts( - pipeline_layout_guard - .get(pipeline_layout_id) - .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?, - &*bgl_guard, - )), - None => None, - }; + })?, + ); if validated_stages == wgt::ShaderStages::VERTEX { if let Some(ref interface) = shader_module.interface { io = interface .check_stage( - provided_layouts.as_ref().map(|p| p.as_slice()), - &mut derived_group_layouts, + &mut binding_layout_source, &mut shader_binding_sizes, - &fragment.stage.entry_point, - flag, + &fragment_state.stage.entry_point, + stage, io, desc.depth_stencil.as_ref().map(|d| d.depth_compare), ) .map_err(|error| pipeline::CreateRenderPipelineError::Stage { - stage: flag, + stage, error, })?; - validated_stages |= flag; + validated_stages |= stage; } } + if let Some(ref interface) = shader_module.interface { + shader_expects_dual_source_blending = interface + .fragment_uses_dual_source_blending(&fragment_state.stage.entry_point) + .map_err(|error| pipeline::CreateRenderPipelineError::Stage { + stage, + error, + })?; + } + Some(hal::ProgrammableStage { - module: &shader_module.raw, - entry_point: fragment.stage.entry_point.as_ref(), + module: shader_module.raw(), + entry_point: fragment_state.stage.entry_point.as_ref(), }) } None => None, }; + if !pipeline_expects_dual_source_blending && shader_expects_dual_source_blending { + return Err( + pipeline::CreateRenderPipelineError::ShaderExpectsPipelineToUseDualSourceBlending, + ); + } + if pipeline_expects_dual_source_blending && !shader_expects_dual_source_blending { + return Err( + pipeline::CreateRenderPipelineError::PipelineExpectsShaderToUseDualSourceBlending, + ); + } + if validated_stages.contains(wgt::ShaderStages::FRAGMENT) { for (i, output) in io.iter() { match color_targets.get(*i as usize) { - Some(&Some(ref state)) => { + Some(Some(state)) => { validation::check_texture_format(state.format, &output.ty).map_err( |pipeline| { pipeline::CreateRenderPipelineError::ColorState( @@ -2916,7 +3094,7 @@ impl Device { )?; } _ => { - log::info!( + log::warn!( "The fragment stage {:?} output @location({}) values are ignored", fragment_stage .as_ref() @@ -2935,19 +3113,18 @@ impl Device { return Err(pipeline::ImplicitLayoutError::ReflectionError(last_stage).into()); } - let pipeline_layout_id = match desc.layout { - Some(id) => id, - None => self.derive_pipeline_layout( - self_id, + let pipeline_layout = match binding_layout_source { + validation::BindingLayoutSource::Provided(_) => { + drop(binding_layout_source); + pipeline_layout.unwrap() + } + validation::BindingLayoutSource::Derived(entries) => self.derive_pipeline_layout( implicit_context, - derived_group_layouts, - &mut *bgl_guard, - &mut *pipeline_layout_guard, + entries, + &hub.bind_group_layouts, + &hub.pipeline_layouts, )?, }; - let layout = pipeline_layout_guard - .get(pipeline_layout_id) - .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?; // Multiview is only supported if the feature is enabled if desc.multiview.is_some() { @@ -2971,11 +3148,11 @@ impl Device { } let late_sized_buffer_groups = - Device::make_late_sized_buffer_groups(&shader_binding_sizes, layout, &*bgl_guard); + Device::make_late_sized_buffer_groups(&shader_binding_sizes, &pipeline_layout); let pipeline_desc = hal::RenderPipelineDescriptor { - label: desc.label.borrow_option(), - layout: &layout.raw, + label: desc.label.to_hal(self.instance_flags), + layout: pipeline_layout.raw(), vertex_buffers: &vertex_buffers, vertex_stage, primitive: desc.primitive, @@ -2985,23 +3162,26 @@ impl Device { color_targets, multiview: desc.multiview, }; - let raw = - unsafe { self.raw.create_render_pipeline(&pipeline_desc) }.map_err( - |err| match err { - hal::PipelineError::Device(error) => { - pipeline::CreateRenderPipelineError::Device(error.into()) - } - hal::PipelineError::Linkage(stage, msg) => { - pipeline::CreateRenderPipelineError::Internal { stage, error: msg } - } - hal::PipelineError::EntryPoint(stage) => { - pipeline::CreateRenderPipelineError::Internal { - stage: hal::auxil::map_naga_stage(stage), - error: EP_FAILURE.to_string(), - } - } - }, - )?; + let raw = unsafe { + self.raw + .as_ref() + .unwrap() + .create_render_pipeline(&pipeline_desc) + } + .map_err(|err| match err { + hal::PipelineError::Device(error) => { + pipeline::CreateRenderPipelineError::Device(error.into()) + } + hal::PipelineError::Linkage(stage, msg) => { + pipeline::CreateRenderPipelineError::Internal { stage, error: msg } + } + hal::PipelineError::EntryPoint(stage) => { + pipeline::CreateRenderPipelineError::Internal { + stage: hal::auxil::map_naga_stage(stage), + error: ENTRYPOINT_FAILURE_ERROR.to_string(), + } + } + })?; let pass_context = RenderPassContext { attachments: AttachmentData { @@ -3036,27 +3216,47 @@ impl Device { } } + let shader_modules = { + let mut shader_modules = ArrayVec::new(); + shader_modules.push(vertex_shader_module); + shader_modules.extend(fragment_shader_module); + shader_modules + }; + let pipeline = pipeline::RenderPipeline { - raw, - layout_id: Stored { - value: id::Valid(pipeline_layout_id), - ref_count: layout.life_guard.add_ref(), - }, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, + raw: Some(raw), + layout: pipeline_layout, + device: self.clone(), pass_context, + _shader_modules: shader_modules, flags, strip_index_format: desc.primitive.strip_index_format, vertex_steps, late_sized_buffer_groups, - life_guard: LifeGuard::new(desc.label.borrow_or_default()), + info: ResourceInfo::new(desc.label.borrow_or_default()), }; Ok(pipeline) } - pub(super) fn describe_format_features( + pub(crate) fn get_texture_format_features( + &self, + adapter: &Adapter, + format: TextureFormat, + ) -> wgt::TextureFormatFeatures { + // Variant of adapter.get_texture_format_features that takes device features into account + use wgt::TextureFormatFeatureFlags as tfsc; + let mut format_features = adapter.get_texture_format_features(format); + if (format == TextureFormat::R32Float + || format == TextureFormat::Rg32Float + || format == TextureFormat::Rgba32Float) + && !self.features.contains(wgt::Features::FLOAT32_FILTERABLE) + { + format_features.flags.set(tfsc::FILTERABLE, false); + } + format_features + } + + pub(crate) fn describe_format_features( &self, adapter: &Adapter, format: TextureFormat, @@ -3068,35 +3268,45 @@ impl Device { .contains(wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES); // If we're running downlevel, we need to manually ask the backend what // we can use as we can't trust WebGPU. - let downlevel = !self.downlevel.is_webgpu_compliant(); + let downlevel = !self + .downlevel + .flags + .contains(wgt::DownlevelFlags::WEBGPU_TEXTURE_FORMAT_SUPPORT); if using_device_features || downlevel { - Ok(adapter.get_texture_format_features(format)) + Ok(self.get_texture_format_features(adapter, format)) } else { Ok(format.guaranteed_format_features(self.features)) } } - pub(super) fn wait_for_submit( + pub(crate) fn wait_for_submit( &self, submission_index: SubmissionIndex, - token: &mut Token, ) -> Result<(), WaitIdleError> { + let guard = self.fence.read(); + let fence = guard.as_ref().unwrap(); let last_done_index = unsafe { self.raw - .get_fence_value(&self.fence) + .as_ref() + .unwrap() + .get_fence_value(fence) .map_err(DeviceError::from)? }; if last_done_index < submission_index { log::info!("Waiting for submission {:?}", submission_index); unsafe { self.raw - .wait(&self.fence, submission_index, !0) + .as_ref() + .unwrap() + .wait(fence, submission_index, !0) .map_err(DeviceError::from)? }; - let closures = self - .lock_life(token) - .triage_submissions(submission_index, &self.command_allocator); + drop(guard); + let closures = self.lock_life().triage_submissions( + submission_index, + self.command_allocator.lock().as_mut().unwrap(), + ); assert!( closures.is_empty(), "wait_for_submit is not expected to work with closures" @@ -3105,11 +3315,10 @@ impl Device { Ok(()) } - pub(super) fn create_query_set( - &self, - self_id: id::DeviceId, + pub(crate) fn create_query_set( + self: &Arc, desc: &resource::QuerySetDescriptor, - ) -> Result, resource::CreateQuerySetError> { + ) -> Result, resource::CreateQuerySetError> { use resource::CreateQuerySetError as Error; match desc.ty { @@ -3133,69 +3342,93 @@ impl Device { }); } - let hal_desc = desc.map_label(crate::LabelHelpers::borrow_option); - Ok(resource::QuerySet { - raw: unsafe { self.raw.create_query_set(&hal_desc).unwrap() }, - device_id: Stored { - value: id::Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, - life_guard: LifeGuard::new(""), + let hal_desc = desc.map_label(|label| label.to_hal(self.instance_flags)); + Ok(QuerySet { + raw: Some(unsafe { self.raw().create_query_set(&hal_desc).unwrap() }), + device: self.clone(), + info: ResourceInfo::new(""), desc: desc.map_label(|_| ()), }) } -} -impl Device { - pub(crate) fn destroy_buffer(&self, buffer: Buffer) { - if let Some(raw) = buffer.raw { - unsafe { - self.raw.destroy_buffer(raw); - } + pub(crate) fn lose(&self, message: &str) { + // Follow the steps at https://gpuweb.github.io/gpuweb/#lose-the-device. + + // Mark the device explicitly as invalid. This is checked in various + // places to prevent new work from being submitted. + self.valid.store(false, Ordering::Release); + + // 1) Resolve the GPUDevice device.lost promise. + let mut life_lock = self.lock_life(); + let closure = life_lock.device_lost_closure.take(); + if let Some(device_lost_closure) = closure { + // It's important to not hold the lock while calling the closure. + drop(life_lock); + device_lost_closure.call(DeviceLostReason::Unknown, message.to_string()); + life_lock = self.lock_life(); } + + // 2) Complete any outstanding mapAsync() steps. + // 3) Complete any outstanding onSubmittedWorkDone() steps. + + // These parts are passively accomplished by setting valid to false, + // since that will prevent any new work from being added to the queues. + // Future calls to poll_devices will continue to check the work queues + // until they are cleared, and then drop the device. + + // Eagerly release GPU resources. + life_lock.release_gpu_resources(); } +} - pub(crate) fn destroy_command_buffer(&self, cmd_buf: command::CommandBuffer) { - let mut baked = cmd_buf.into_baked(); +impl Device { + pub(crate) fn destroy_command_buffer(&self, mut cmd_buf: command::CommandBuffer) { + let mut baked = cmd_buf.extract_baked_commands(); unsafe { baked.encoder.reset_all(baked.list.into_iter()); } unsafe { - self.raw.destroy_command_encoder(baked.encoder); + self.raw + .as_ref() + .unwrap() + .destroy_command_encoder(baked.encoder); } } /// Wait for idle and remove resources that we can, before we die. - pub(crate) fn prepare_to_die(&mut self) { - self.pending_writes.deactivate(); - let mut life_tracker = self.life_tracker.lock(); - let current_index = self.active_submission_index; - if let Err(error) = unsafe { self.raw.wait(&self.fence, current_index, CLEANUP_WAIT_MS) } { - log::error!("failed to wait for the device: {:?}", error); - } - let _ = life_tracker.triage_submissions(current_index, &self.command_allocator); - life_tracker.cleanup(&self.raw); + pub(crate) fn prepare_to_die(&self) { + self.pending_writes.lock().as_mut().unwrap().deactivate(); + let current_index = self.active_submission_index.load(Ordering::Relaxed); + if let Err(error) = unsafe { + let fence = self.fence.read(); + let fence = fence.as_ref().unwrap(); + self.raw + .as_ref() + .unwrap() + .wait(fence, current_index, CLEANUP_WAIT_MS) + } { + log::error!("failed to wait for the device: {error}"); + } + let mut life_tracker = self.lock_life(); + let _ = life_tracker.triage_submissions( + current_index, + self.command_allocator.lock().as_mut().unwrap(), + ); #[cfg(feature = "trace")] { - self.trace = None; - } - } - - pub(crate) fn dispose(self) { - self.pending_writes.dispose(&self.raw); - self.command_allocator.into_inner().dispose(&self.raw); - unsafe { - self.raw.destroy_buffer(self.zero_buffer); - self.raw.destroy_fence(self.fence); - self.raw.exit(self.queue); + *self.trace.lock() = None; } } } -impl crate::resource::Resource for Device { - const TYPE: &'static str = "Device"; +impl Resource for Device { + const TYPE: ResourceType = "Device"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } diff --git a/wgpu-core/src/device/trace.rs b/wgpu-core/src/device/trace.rs index 7f423f5234..42ee166167 100644 --- a/wgpu-core/src/device/trace.rs +++ b/wgpu-core/src/device/trace.rs @@ -167,7 +167,7 @@ pub enum Command { ClearBuffer { dst: id::BufferId, offset: wgt::BufferAddress, - size: Option, + size: Option, }, ClearTexture { dst: id::TextureId, diff --git a/wgpu-core/src/error.rs b/wgpu-core/src/error.rs index 62ecebe9b9..a6e3d31587 100644 --- a/wgpu-core/src/error.rs +++ b/wgpu-core/src/error.rs @@ -17,7 +17,7 @@ impl<'a> ErrorFormatter<'a> { writeln!(self.writer, " note: {note}").expect("Error formatting error"); } - pub fn label(&mut self, label_key: &str, label_value: &str) { + pub fn label(&mut self, label_key: &str, label_value: &String) { if !label_key.is_empty() && !label_value.is_empty() { self.note(&format!("{label_key} = `{label_value}`")); } @@ -162,21 +162,9 @@ pub fn format_pretty_any( #[derive(Debug)] pub struct ContextError { pub string: &'static str, - #[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - ))] + #[cfg(send_sync)] pub cause: Box, - #[cfg(not(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - )))] + #[cfg(not(send_sync))] pub cause: Box, pub label_key: &'static str, pub label: String, diff --git a/wgpu-core/src/global.rs b/wgpu-core/src/global.rs index e3ed2be761..87271917c2 100644 --- a/wgpu-core/src/global.rs +++ b/wgpu-core/src/global.rs @@ -1,32 +1,55 @@ +use std::{marker::PhantomData, sync::Arc}; + +use wgt::Backend; + use crate::{ hal_api::HalApi, hub::{HubReport, Hubs}, - id, + id::SurfaceId, identity::GlobalIdentityHandlerFactory, instance::{Instance, Surface}, - registry::Registry, - storage::{Element, StorageReport}, + registry::{Registry, RegistryReport}, + resource_log, + storage::Element, }; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct GlobalReport { - pub surfaces: StorageReport, - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + pub surfaces: RegistryReport, + #[cfg(vulkan)] pub vulkan: Option, - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] pub metal: Option, - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] pub dx12: Option, - #[cfg(all(feature = "dx11", windows))] - pub dx11: Option, - #[cfg(feature = "gles")] + #[cfg(gles)] pub gl: Option, } +impl GlobalReport { + pub fn surfaces(&self) -> &RegistryReport { + &self.surfaces + } + pub fn hub_report(&self, backend: Backend) -> &HubReport { + match backend { + #[cfg(vulkan)] + Backend::Vulkan => self.vulkan.as_ref().unwrap(), + #[cfg(metal)] + Backend::Metal => self.metal.as_ref().unwrap(), + #[cfg(dx12)] + Backend::Dx12 => self.dx12.as_ref().unwrap(), + #[cfg(gles)] + Backend::Gl => self.gl.as_ref().unwrap(), + _ => panic!("HubReport is not supported on this backend"), + } + } +} + pub struct Global { pub instance: Instance, - pub surfaces: Registry, - pub(crate) hubs: Hubs, + pub surfaces: Registry, + pub(crate) hubs: Hubs, + _phantom: PhantomData, } impl Global { @@ -34,8 +57,9 @@ impl Global { profiling::scope!("Global::new"); Self { instance: Instance::new(name, instance_desc), - surfaces: Registry::without_backend(&factory, "Surface"), + surfaces: Registry::without_backend(&factory), hubs: Hubs::new(&factory), + _phantom: PhantomData, } } @@ -50,8 +74,9 @@ impl Global { profiling::scope!("Global::new"); Self { instance: A::create_instance_from_hal(name, hal_instance), - surfaces: Registry::without_backend(&factory, "Surface"), + surfaces: Registry::without_backend(&factory), hubs: Hubs::new(&factory), + _phantom: PhantomData, } } @@ -69,46 +94,41 @@ impl Global { profiling::scope!("Global::new"); Self { instance, - surfaces: Registry::without_backend(&factory, "Surface"), + surfaces: Registry::without_backend(&factory), hubs: Hubs::new(&factory), + _phantom: PhantomData, } } pub fn clear_backend(&self, _dummy: ()) { - let mut surface_guard = self.surfaces.data.write(); let hub = A::hub(self); + let surfaces_locked = self.surfaces.read(); // this is used for tests, which keep the adapter - hub.clear(&mut surface_guard, false); + hub.clear(&surfaces_locked, false); } pub fn generate_report(&self) -> GlobalReport { GlobalReport { - surfaces: self.surfaces.data.read().generate_report(), - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + surfaces: self.surfaces.generate_report(), + #[cfg(vulkan)] vulkan: if self.instance.vulkan.is_some() { Some(self.hubs.vulkan.generate_report()) } else { None }, - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] metal: if self.instance.metal.is_some() { Some(self.hubs.metal.generate_report()) } else { None }, - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] dx12: if self.instance.dx12.is_some() { Some(self.hubs.dx12.generate_report()) } else { None }, - #[cfg(all(feature = "dx11", windows))] - dx11: if self.instance.dx11.is_some() { - Some(self.hubs.dx11.generate_report()) - } else { - None - }, - #[cfg(feature = "gles")] + #[cfg(gles)] gl: if self.instance.gl.is_some() { Some(self.hubs.gl.generate_report()) } else { @@ -121,50 +141,41 @@ impl Global { impl Drop for Global { fn drop(&mut self) { profiling::scope!("Global::drop"); - log::info!("Dropping Global"); - let mut surface_guard = self.surfaces.data.write(); + resource_log!("Global::drop"); + let mut surfaces_locked = self.surfaces.write(); // destroy hubs before the instance gets dropped - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - { - self.hubs.vulkan.clear(&mut surface_guard, true); - } - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(vulkan)] { - self.hubs.metal.clear(&mut surface_guard, true); + self.hubs.vulkan.clear(&surfaces_locked, true); } - #[cfg(all(feature = "dx12", windows))] + #[cfg(metal)] { - self.hubs.dx12.clear(&mut surface_guard, true); + self.hubs.metal.clear(&surfaces_locked, true); } - #[cfg(all(feature = "dx11", windows))] + #[cfg(dx12)] { - self.hubs.dx11.clear(&mut surface_guard, true); + self.hubs.dx12.clear(&surfaces_locked, true); } - #[cfg(feature = "gles")] + #[cfg(gles)] { - self.hubs.gl.clear(&mut surface_guard, true); + self.hubs.gl.clear(&surfaces_locked, true); } // destroy surfaces - for element in surface_guard.map.drain(..) { - if let Element::Occupied(surface, _) = element { - self.instance.destroy_surface(surface); + for element in surfaces_locked.map.drain(..) { + if let Element::Occupied(arc_surface, _) = element { + if let Some(surface) = Arc::into_inner(arc_surface) { + self.instance.destroy_surface(surface); + } else { + panic!("Surface cannot be destroyed because is still in use"); + } } } } } -#[cfg(all( - test, - any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - ) -))] +#[cfg(send_sync)] fn _test_send_sync(global: &Global) { fn test_internal(_: T) {} test_internal(global) diff --git a/wgpu-core/src/hal_api.rs b/wgpu-core/src/hal_api.rs index 00180d8ac5..d1dee98ed1 100644 --- a/wgpu-core/src/hal_api.rs +++ b/wgpu-core/src/hal_api.rs @@ -1,4 +1,4 @@ -use wgt::Backend; +use wgt::{Backend, WasmNotSendSync}; use crate::{ global::Global, @@ -7,13 +7,12 @@ use crate::{ instance::{HalSurface, Instance, Surface}, }; -pub trait HalApi: hal::Api { +pub trait HalApi: hal::Api + 'static + WasmNotSendSync { const VARIANT: Backend; fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance; fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance>; - fn hub(global: &Global) -> &Hub; + fn hub(global: &Global) -> &Hub; fn get_surface(surface: &Surface) -> Option<&HalSurface>; - fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface>; } impl HalApi for hal::api::Empty { @@ -24,18 +23,15 @@ impl HalApi for hal::api::Empty { fn instance_as_hal(_: &Instance) -> Option<&Self::Instance> { unimplemented!("called empty api") } - fn hub(_: &Global) -> &Hub { + fn hub(_: &Global) -> &Hub { unimplemented!("called empty api") } fn get_surface(_: &Surface) -> Option<&HalSurface> { unimplemented!("called empty api") } - fn get_surface_mut(_: &mut Surface) -> Option<&mut HalSurface> { - unimplemented!("called empty api") - } } -#[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] +#[cfg(vulkan)] impl HalApi for hal::api::Vulkan { const VARIANT: Backend = Backend::Vulkan; fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { @@ -48,18 +44,15 @@ impl HalApi for hal::api::Vulkan { fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { instance.vulkan.as_ref() } - fn hub(global: &Global) -> &Hub { + fn hub(global: &Global) -> &Hub { &global.hubs.vulkan } fn get_surface(surface: &Surface) -> Option<&HalSurface> { - surface.vulkan.as_ref() - } - fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface> { - surface.vulkan.as_mut() + surface.raw.downcast_ref() } } -#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] +#[cfg(metal)] impl HalApi for hal::api::Metal { const VARIANT: Backend = Backend::Metal; fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { @@ -72,18 +65,15 @@ impl HalApi for hal::api::Metal { fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { instance.metal.as_ref() } - fn hub(global: &Global) -> &Hub { + fn hub(global: &Global) -> &Hub { &global.hubs.metal } fn get_surface(surface: &Surface) -> Option<&HalSurface> { - surface.metal.as_ref() - } - fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface> { - surface.metal.as_mut() + surface.raw.downcast_ref() } } -#[cfg(all(feature = "dx12", windows))] +#[cfg(dx12)] impl HalApi for hal::api::Dx12 { const VARIANT: Backend = Backend::Dx12; fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { @@ -96,42 +86,15 @@ impl HalApi for hal::api::Dx12 { fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { instance.dx12.as_ref() } - fn hub(global: &Global) -> &Hub { + fn hub(global: &Global) -> &Hub { &global.hubs.dx12 } fn get_surface(surface: &Surface) -> Option<&HalSurface> { - surface.dx12.as_ref() - } - fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface> { - surface.dx12.as_mut() - } -} - -#[cfg(all(feature = "dx11", windows))] -impl HalApi for hal::api::Dx11 { - const VARIANT: Backend = Backend::Dx11; - fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { - Instance { - name: name.to_owned(), - dx11: Some(hal_instance), - ..Default::default() - } - } - fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { - instance.dx11.as_ref() - } - fn hub(global: &Global) -> &Hub { - &global.hubs.dx11 - } - fn get_surface(surface: &Surface) -> Option<&HalSurface> { - surface.dx11.as_ref() - } - fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface> { - surface.dx11.as_mut() + surface.raw.downcast_ref() } } -#[cfg(feature = "gles")] +#[cfg(gles)] impl HalApi for hal::api::Gles { const VARIANT: Backend = Backend::Gl; fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { @@ -145,13 +108,10 @@ impl HalApi for hal::api::Gles { fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { instance.gl.as_ref() } - fn hub(global: &Global) -> &Hub { + fn hub(global: &Global) -> &Hub { &global.hubs.gl } fn get_surface(surface: &Surface) -> Option<&HalSurface> { - surface.gl.as_ref() - } - fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface> { - surface.gl.as_mut() + surface.raw.downcast_ref() } } diff --git a/wgpu-core/src/hash_utils.rs b/wgpu-core/src/hash_utils.rs new file mode 100644 index 0000000000..f44aad2f1a --- /dev/null +++ b/wgpu-core/src/hash_utils.rs @@ -0,0 +1,86 @@ +//! Module for hashing utilities. +//! +//! Named hash_utils to prevent clashing with the std::hash module. + +/// HashMap using a fast, non-cryptographic hash algorithm. +pub type FastHashMap = + std::collections::HashMap>; +/// HashSet using a fast, non-cryptographic hash algorithm. +pub type FastHashSet = + std::collections::HashSet>; + +/// IndexMap using a fast, non-cryptographic hash algorithm. +pub type FastIndexMap = + indexmap::IndexMap>; + +/// HashMap that uses pre-hashed keys and an identity hasher. +/// +/// This is useful when you only need the key to lookup the value, and don't need to store the key, +/// particularly when the key is large. +pub type PreHashedMap = + std::collections::HashMap, V, std::hash::BuildHasherDefault>; + +/// A pre-hashed key using FxHash which allows the hashing operation to be disconnected +/// from the storage in the map. +pub struct PreHashedKey(u64, std::marker::PhantomData K>); + +impl std::fmt::Debug for PreHashedKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PreHashedKey").field(&self.0).finish() + } +} + +impl Copy for PreHashedKey {} + +impl Clone for PreHashedKey { + fn clone(&self) -> Self { + *self + } +} + +impl PartialEq for PreHashedKey { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for PreHashedKey {} + +impl std::hash::Hash for PreHashedKey { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl PreHashedKey { + pub fn from_key(key: &K) -> Self { + use std::hash::Hasher; + + let mut hasher = rustc_hash::FxHasher::default(); + key.hash(&mut hasher); + Self(hasher.finish(), std::marker::PhantomData) + } +} + +/// A hasher which does nothing. Useful for when you want to use a map with pre-hashed keys. +/// +/// When hashing with this hasher, you must provide exactly 8 bytes. Multiple calls to `write` +/// will overwrite the previous value. +#[derive(Default)] +pub struct IdentityHasher { + hash: u64, +} + +impl std::hash::Hasher for IdentityHasher { + fn write(&mut self, bytes: &[u8]) { + self.hash = u64::from_ne_bytes( + bytes + .try_into() + .expect("identity hasher must be given exactly 8 bytes"), + ); + } + + fn finish(&self) -> u64 { + self.hash + } +} diff --git a/wgpu-core/src/hub.rs b/wgpu-core/src/hub.rs index 682943ef4f..a953c329e2 100644 --- a/wgpu-core/src/hub.rs +++ b/wgpu-core/src/hub.rs @@ -7,7 +7,7 @@ resources of type `R`. For example, [`id::DeviceId`] is an alias for of course `Debug`. Each `Id` contains not only an index for the resource it denotes but -also a [`Backend`] indicating which `wgpu` backend it belongs to. You +also a Backend indicating which `wgpu` backend it belongs to. You can use the [`gfx_select`] macro to dynamically dispatch on an id's backend to a function specialized at compile time for a specific backend. See that macro's documentation for details. @@ -57,54 +57,15 @@ itself to choose ids always pass `()`. In either case, the id ultimately assigned is returned as the first element of the tuple. Producing true identifiers from `id_in` values is the job of an -[`IdentityHandler`] implementation, which has an associated type -[`Input`] saying what type of `id_in` values it accepts, and a -[`process`] method that turns such values into true identifiers of -type `I`. There are two kinds of `IdentityHandler`s: - -- Users that want `wgpu_core` to assign ids generally use - [`IdentityManager`] ([wrapped in a mutex]). Its `Input` type is - `()`, and it tracks assigned ids and generation numbers as - necessary. (This is what `wgpu` does.) - -- Users that want to assign ids themselves use an `IdentityHandler` - whose `Input` type is `I` itself, and whose `process` method simply - passes the `id_in` argument through unchanged. For example, the - `player` crate uses an `IdentityPassThrough` type whose `process` - method simply adjusts the id's backend (since recordings can be - replayed on a different backend than the one they were created on) - but passes the rest of the id's content through unchanged. - -Because an `IdentityHandler` can only create ids for a single -resource type `I`, constructing a [`Global`] entails constructing a -separate `IdentityHandler` for each resource type `I` that the -`Global` will manage: an `IdentityHandler`, an -`IdentityHandler`, and so on. - -The [`Global::new`] function could simply take a large collection of -`IdentityHandler` implementations as arguments, but that would be -ungainly. Instead, `Global::new` expects a `factory` argument that +[`crate::identity::IdentityManager`], but only if the `IdentityHandlerFactory` +create it and then generated by it, otherwise ids will be received from outside. + +`Global::new` expects a `factory` argument that implements the [`GlobalIdentityHandlerFactory`] trait, which extends -[`IdentityHandlerFactory`] for each resource id type `I`. This +[`crate::identity::IdentityHandlerFactory`] for each resource id type `I`. This trait, in turn, has a `spawn` method that constructs an -`IdentityHandler` for the `Global` to use. - -What this means is that the types of resource creation functions' -`id_in` arguments depend on the `Global`'s `G` type parameter. A -`Global`'s `IdentityHandler` implementation is: - -```ignore ->::Filter -``` - -where `Filter` is an associated type of the `IdentityHandlerFactory` trait. -Thus, its `id_in` type is: - -```ignore -<>::Filter as IdentityHandler>::Input -``` - -The [`Input`] type is an alias for this construction. +`crate::identity::IdentityManager` for the `Global` to use, +if ids should be generated by wgpu or will return None otherwise. ## Id allocation and streaming @@ -140,259 +101,48 @@ creation fails, the id supplied for that resource is marked to indicate as much, allowing subsequent operations using that id to be properly flagged as errors as well. -[`Backend`]: wgt::Backend -[`Global`]: crate::global::Global -[`Global::new`]: crate::global::Global::new [`gfx_select`]: crate::gfx_select -[`IdentityHandler`]: crate::identity::IdentityHandler -[`Input`]: crate::identity::IdentityHandler::Input -[`process`]: crate::identity::IdentityHandler::process +[`Input`]: crate::identity::IdentityHandlerFactory::Input +[`process`]: crate::identity::IdentityManager::process [`Id`]: crate::id::Id -[wrapped in a mutex]: ../identity/trait.IdentityHandler.html#impl-IdentityHandler%3CI%3E-for-Mutex%3CIdentityManager%3E +[wrapped in a mutex]: trait.IdentityHandler.html#impl-IdentityHandler%3CI%3E-for-Mutex%3CIdentityManager%3E [WebGPU]: https://www.w3.org/TR/webgpu/ -[`IdentityManager`]: crate::identity::IdentityManager -[`Input`]: crate::identity::Input -[`IdentityHandlerFactory`]: crate::identity::IdentityHandlerFactory + */ use crate::{ binding_model::{BindGroup, BindGroupLayout, PipelineLayout}, command::{CommandBuffer, RenderBundle}, - device::Device, + device::{queue::Queue, Device}, hal_api::HalApi, id, identity::GlobalIdentityHandlerFactory, - instance::{Adapter, HalSurface, Instance, Surface}, + instance::{Adapter, HalSurface, Surface}, pipeline::{ComputePipeline, RenderPipeline, ShaderModule}, - registry::Registry, - resource::{ - Blas, Buffer, QuerySet, Sampler, StagingBuffer, Texture, TextureClearMode, TextureView, - Tlas, - }, - storage::{Element, Storage, StorageReport}, + registry::{Registry, RegistryReport}, + resource::{Blas, Buffer, QuerySet, Sampler, StagingBuffer, Texture, TextureView, Tlas}, + storage::{Element, Storage}, }; +use std::fmt::Debug; -#[cfg(debug_assertions)] -use std::cell::Cell; -use std::{fmt::Debug, marker::PhantomData}; - -/// Type system for enforcing the lock order on [`Hub`] fields. -/// -/// If type `A` implements `Access`, that means we are allowed to -/// proceed with locking resource `B` after we lock `A`. -/// -/// The implementations of `Access` basically describe the edges in an -/// acyclic directed graph of lock transitions. As long as it doesn't have -/// cycles, any number of threads can acquire locks along paths through -/// the graph without deadlock. That is, if you look at each thread's -/// lock acquisitions as steps along a path in the graph, then because -/// there are no cycles in the graph, there must always be some thread -/// that is able to acquire its next lock, or that is about to release -/// a lock. (Assume that no thread just sits on its locks forever.) -/// -/// Locks must be acquired in the following order: -/// -/// - [`Adapter`] -/// - [`Device`] -/// - [`CommandBuffer`] -/// - [`RenderBundle`] -/// - [`PipelineLayout`] -/// - [`BindGroupLayout`] -/// - [`BindGroup`] -/// - [`ComputePipeline`] -/// - [`RenderPipeline`] -/// - [`ShaderModule`] -/// - [`Buffer`] -/// - [`StagingBuffer`] -/// - [`Texture`] -/// - [`TextureView`] -/// - [`Sampler`] -/// - [`QuerySet`] -/// -/// That is, you may only acquire a new lock on a `Hub` field if it -/// appears in the list after all the other fields you're already -/// holding locks for. When you are holding no locks, you can start -/// anywhere. -/// -/// It's fine to add more `Access` implementations as needed, as long -/// as you do not introduce a cycle. In other words, as long as there -/// is some ordering you can put the resource types in that respects -/// the extant `Access` implementations, that's fine. -/// -/// See the documentation for [`Hub`] for more details. -pub trait Access {} - -pub enum Root {} - -// These impls are arranged so that the target types (that is, the `T` -// in `Access`) appear in locking order. -// -// TODO: establish an order instead of declaring all the pairs. -impl Access for Root {} -impl Access for Root {} -impl Access for Instance {} -impl Access> for Root {} -impl Access> for Surface {} -impl Access> for Root {} -impl Access> for Surface {} -impl Access> for Adapter {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for Device {} -impl Access> for CommandBuffer {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for RenderBundle {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for PipelineLayout {} -impl Access> for QuerySet {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for BindGroupLayout {} -impl Access> for PipelineLayout {} -impl Access> for CommandBuffer {} -impl Access> for Device {} -impl Access> for BindGroup {} -impl Access> for Device {} -impl Access> for BindGroup {} -impl Access> for ComputePipeline {} -impl Access> for Device {} -impl Access> for BindGroupLayout {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for BindGroupLayout {} -impl Access> for BindGroup {} -impl Access> for CommandBuffer {} -impl Access> for ComputePipeline {} -impl Access> for RenderPipeline {} -impl Access> for QuerySet {} -impl Access> for Device {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for Buffer {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for Texture {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for TextureView {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for CommandBuffer {} -impl Access> for RenderPipeline {} -impl Access> for ComputePipeline {} -impl Access> for Sampler {} - -impl Access> for Root {} -impl Access> for Device {} -impl Access> for CommandBuffer {} -impl Access> for Buffer {} -impl Access> for Sampler {} -impl Access> for QuerySet {} -impl Access> for Texture {} -impl Access> for TextureView {} -impl Access> for Root {} -impl Access> for Device {} -impl Access> for CommandBuffer {} -impl Access> for Buffer {} -impl Access> for Blas {} -impl Access> for Sampler {} -impl Access> for QuerySet {} -impl Access> for Texture {} -impl Access> for TextureView {} - -#[cfg(debug_assertions)] -thread_local! { - /// Per-thread state checking `Token` creation in debug builds. - /// - /// This is the number of `Token` values alive on the current - /// thread. Since `Token` creation respects the [`Access`] graph, - /// there can never be more tokens alive than there are fields of - /// [`Hub`], so a `u8` is plenty. - static ACTIVE_TOKEN: Cell = Cell::new(0); -} - -/// A zero-size permission token to lock some fields of [`Hub`]. -/// -/// Access to a `Token` grants permission to lock any field of -/// [`Hub`] following the one of type [`Registry`], where -/// "following" is as defined by the [`Access`] implementations. -/// -/// Calling [`Token::root()`] returns a `Token`, which grants -/// permission to lock any field. Dynamic checks ensure that each -/// thread has at most one `Token` live at a time, in debug -/// builds. -/// -/// The locking methods on `Registry` take a `&'t mut -/// Token`, and return a fresh `Token<'t, T>` and a lock guard with -/// lifetime `'t`, so the caller cannot access their `Token` again -/// until they have dropped both the `Token` and the lock guard. -/// -/// Tokens are `!Send`, so one thread can't send its permissions to -/// another. -pub(crate) struct Token<'a, T: 'a> { - // The `*const` makes us `!Send` and `!Sync`. - level: PhantomData<&'a *const T>, -} - -impl<'a, T> Token<'a, T> { - /// Return a new token for a locked field. - /// - /// This should only be used by `Registry` locking methods. - pub(crate) fn new() -> Self { - #[cfg(debug_assertions)] - ACTIVE_TOKEN.with(|active| { - let old = active.get(); - assert_ne!(old, 0, "Root token was dropped"); - active.set(old + 1); - }); - Self { level: PhantomData } - } -} - -impl Token<'static, Root> { - /// Return a `Token`, granting permission to lock any [`Hub`] field. - /// - /// Debug builds check dynamically that each thread has at most - /// one root token at a time. - pub fn root() -> Self { - #[cfg(debug_assertions)] - ACTIVE_TOKEN.with(|active| { - assert_eq!(0, active.replace(1), "Root token is already active"); - }); - - Self { level: PhantomData } - } -} - -impl<'a, T> Drop for Token<'a, T> { - fn drop(&mut self) { - #[cfg(debug_assertions)] - ACTIVE_TOKEN.with(|active| { - let old = active.get(); - active.set(old - 1); - }); - } -} - -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct HubReport { - pub adapters: StorageReport, - pub devices: StorageReport, - pub pipeline_layouts: StorageReport, - pub shader_modules: StorageReport, - pub bind_group_layouts: StorageReport, - pub bind_groups: StorageReport, - pub command_buffers: StorageReport, - pub render_bundles: StorageReport, - pub render_pipelines: StorageReport, - pub compute_pipelines: StorageReport, - pub query_sets: StorageReport, - pub buffers: StorageReport, - pub textures: StorageReport, - pub texture_views: StorageReport, - pub samplers: StorageReport, + pub adapters: RegistryReport, + pub devices: RegistryReport, + pub queues: RegistryReport, + pub pipeline_layouts: RegistryReport, + pub shader_modules: RegistryReport, + pub bind_group_layouts: RegistryReport, + pub bind_groups: RegistryReport, + pub command_buffers: RegistryReport, + pub render_bundles: RegistryReport, + pub render_pipelines: RegistryReport, + pub compute_pipelines: RegistryReport, + pub query_sets: RegistryReport, + pub buffers: RegistryReport, + pub textures: RegistryReport, + pub texture_views: RegistryReport, + pub samplers: RegistryReport, } impl HubReport { @@ -402,7 +152,7 @@ impl HubReport { } #[allow(rustdoc::private_intra_doc_links)] -/// All the resources for a particular backend in a [`Global`]. +/// All the resources for a particular backend in a [`crate::global::Global`]. /// /// To obtain `global`'s `Hub` for some [`HalApi`] backend type `A`, /// call [`A::hub(global)`]. @@ -410,89 +160,50 @@ impl HubReport { /// ## Locking /// /// Each field in `Hub` is a [`Registry`] holding all the values of a -/// particular type of resource, all protected by a single [`RwLock`]. +/// particular type of resource, all protected by a single RwLock. /// So for example, to access any [`Buffer`], you must acquire a read -/// lock on the `Hub`s entire [`buffers`] registry. The lock guard +/// lock on the `Hub`s entire buffers registry. The lock guard /// gives you access to the `Registry`'s [`Storage`], which you can /// then index with the buffer's id. (Yes, this design causes /// contention; see [#2272].) /// /// But most `wgpu` operations require access to several different /// kinds of resource, so you often need to hold locks on several -/// different fields of your [`Hub`] simultaneously. To avoid -/// deadlock, there is an ordering imposed on the fields, and you may -/// only acquire new locks on fields that come *after* all those you -/// are already holding locks on, in this ordering. (The ordering is -/// described in the documentation for the [`Access`] trait.) -/// -/// We use Rust's type system to statically check that `wgpu_core` can -/// only ever acquire locks in the correct order: -/// -/// - A value of type [`Token`] represents proof that the owner -/// only holds locks on the `Hub` fields holding resources of type -/// `T` or earlier in the lock ordering. A special value of type -/// `Token`, obtained by calling [`Token::root`], represents -/// proof that no `Hub` field locks are held. -/// -/// - To lock the `Hub` field holding resources of type `T`, you must -/// call its [`read`] or [`write`] methods. These require you to -/// pass in a `&mut Token`, for some `A` that implements -/// [`Access`]. This implementation exists only if `T` follows `A` -/// in the field ordering, which statically ensures that you are -/// indeed allowed to lock this new `Hub` field. +/// different fields of your [`Hub`] simultaneously. /// -/// - The locking methods return both an [`RwLock`] guard that you can -/// use to access the field's resources, and a new `Token` value. -/// These both borrow from the lifetime of your `Token`, so since -/// you passed that by mutable reference, you cannot access it again -/// until you drop the new token and lock guard. +/// Inside the `Registry` there are `Arc` where `T` is a Resource +/// Lock of `Registry` happens only when accessing to get the specific resource /// -/// Because a thread only ever has access to the `Token` for the -/// last resource type `T` it holds a lock for, and the `Access` trait -/// implementations only permit acquiring locks for types `U` that -/// follow `T` in the lock ordering, it is statically impossible for a -/// program to violate the locking order. /// -/// This does assume that threads cannot call `Token` when they -/// already hold locks (dynamically enforced in debug builds) and that -/// threads cannot send their `Token`s to other threads (enforced by -/// making `Token` neither `Send` nor `Sync`). -/// -/// [`Global`]: crate::global::Global /// [`A::hub(global)`]: HalApi::hub -/// [`RwLock`]: parking_lot::RwLock -/// [`buffers`]: Hub::buffers -/// [`read`]: Registry::read -/// [`write`]: Registry::write -/// [`Token`]: Token -/// [`Access`]: Access -/// [#2272]: https://github.com/gfx-rs/wgpu/pull/2272 -pub struct Hub { - pub adapters: Registry, id::AdapterId, F>, - pub devices: Registry, id::DeviceId, F>, - pub pipeline_layouts: Registry, id::PipelineLayoutId, F>, - pub shader_modules: Registry, id::ShaderModuleId, F>, - pub bind_group_layouts: Registry, id::BindGroupLayoutId, F>, - pub bind_groups: Registry, id::BindGroupId, F>, - pub command_buffers: Registry, id::CommandBufferId, F>, - pub render_bundles: Registry, id::RenderBundleId, F>, - pub render_pipelines: Registry, id::RenderPipelineId, F>, - pub compute_pipelines: Registry, id::ComputePipelineId, F>, - pub query_sets: Registry, id::QuerySetId, F>, - pub buffers: Registry, id::BufferId, F>, - pub staging_buffers: Registry, id::StagingBufferId, F>, - pub textures: Registry, id::TextureId, F>, - pub texture_views: Registry, id::TextureViewId, F>, - pub samplers: Registry, id::SamplerId, F>, - pub blas_s: Registry, id::BlasId, F>, - pub tlas_s: Registry, id::TlasId, F>, +pub struct Hub { + pub adapters: Registry>, + pub devices: Registry>, + pub queues: Registry>, + pub pipeline_layouts: Registry>, + pub shader_modules: Registry>, + pub bind_group_layouts: Registry>, + pub bind_groups: Registry>, + pub command_buffers: Registry>, + pub render_bundles: Registry>, + pub render_pipelines: Registry>, + pub compute_pipelines: Registry>, + pub query_sets: Registry>, + pub buffers: Registry>, + pub staging_buffers: Registry>, + pub textures: Registry>, + pub texture_views: Registry>, + pub samplers: Registry>, + pub blas_s: Registry>, + pub tlas_s: Registry>, } -impl Hub { - fn new(factory: &F) -> Self { +impl Hub { + fn new(factory: &F) -> Self { Self { adapters: Registry::new(A::VARIANT, factory), devices: Registry::new(A::VARIANT, factory), + queues: Registry::new(A::VARIANT, factory), pipeline_layouts: Registry::new(A::VARIANT, factory), shader_modules: Registry::new(A::VARIANT, factory), bind_group_layouts: Registry::new(A::VARIANT, factory), @@ -517,264 +228,108 @@ impl Hub { // everything related to a logical device. pub(crate) fn clear( &self, - surface_guard: &mut Storage, + surface_guard: &Storage, with_adapters: bool, ) { - use crate::resource::TextureInner; - use hal::{Device as _, Surface as _}; + use hal::Surface; - let mut devices = self.devices.data.write(); - for element in devices.map.iter_mut() { - if let Element::Occupied(ref mut device, _) = *element { + let mut devices = self.devices.write(); + for element in devices.map.iter() { + if let Element::Occupied(ref device, _) = *element { device.prepare_to_die(); } } - // destroy command buffers first, since otherwise DX12 isn't happy - for element in self.command_buffers.data.write().map.drain(..) { - if let Element::Occupied(command_buffer, _) = element { - let device = &devices[command_buffer.device_id.value]; - device.destroy_command_buffer(command_buffer); - } - } - - for element in self.samplers.data.write().map.drain(..) { - if let Element::Occupied(sampler, _) = element { - unsafe { - devices[sampler.device_id.value] - .raw - .destroy_sampler(sampler.raw); - } - } - } - - for element in self.texture_views.data.write().map.drain(..) { - if let Element::Occupied(texture_view, _) = element { - let device = &devices[texture_view.device_id.value]; - unsafe { - device.raw.destroy_texture_view(texture_view.raw); - } - } - } - - for element in self.textures.data.write().map.drain(..) { - if let Element::Occupied(texture, _) = element { - let device = &devices[texture.device_id.value]; - if let TextureInner::Native { raw: Some(raw) } = texture.inner { - unsafe { - device.raw.destroy_texture(raw); - } - } - if let TextureClearMode::RenderPass { clear_views, .. } = texture.clear_mode { - for view in clear_views { + self.command_buffers.write().map.clear(); + self.samplers.write().map.clear(); + self.texture_views.write().map.clear(); + self.textures.write().map.clear(); + self.buffers.write().map.clear(); + self.bind_groups.write().map.clear(); + self.shader_modules.write().map.clear(); + self.bind_group_layouts.write().map.clear(); + self.pipeline_layouts.write().map.clear(); + self.compute_pipelines.write().map.clear(); + self.render_pipelines.write().map.clear(); + self.query_sets.write().map.clear(); + + for element in surface_guard.map.iter() { + if let Element::Occupied(ref surface, _epoch) = *element { + if let Some(ref mut present) = surface.presentation.lock().take() { + if let Some(device) = present.device.downcast_ref::() { + let suf = A::get_surface(surface); unsafe { - device.raw.destroy_texture_view(view); + suf.unwrap().raw.unconfigure(device.raw()); + //TODO: we could destroy the surface here } } } } } - for element in self.buffers.data.write().map.drain(..) { - if let Element::Occupied(buffer, _) = element { - //TODO: unmap if needed - devices[buffer.device_id.value].destroy_buffer(buffer); - } - } - for element in self.bind_groups.data.write().map.drain(..) { - if let Element::Occupied(bind_group, _) = element { - let device = &devices[bind_group.device_id.value]; - unsafe { - device.raw.destroy_bind_group(bind_group.raw); - } - } - } - - for element in self.shader_modules.data.write().map.drain(..) { - if let Element::Occupied(module, _) = element { - let device = &devices[module.device_id.value]; - unsafe { - device.raw.destroy_shader_module(module.raw); - } - } - } - for element in self.bind_group_layouts.data.write().map.drain(..) { - if let Element::Occupied(bgl, _) = element { - let device = &devices[bgl.device_id.value]; - unsafe { - device.raw.destroy_bind_group_layout(bgl.raw); - } - } - } - for element in self.pipeline_layouts.data.write().map.drain(..) { - if let Element::Occupied(pipeline_layout, _) = element { - let device = &devices[pipeline_layout.device_id.value]; - unsafe { - device.raw.destroy_pipeline_layout(pipeline_layout.raw); - } - } - } - for element in self.compute_pipelines.data.write().map.drain(..) { - if let Element::Occupied(pipeline, _) = element { - let device = &devices[pipeline.device_id.value]; - unsafe { - device.raw.destroy_compute_pipeline(pipeline.raw); - } - } - } - for element in self.render_pipelines.data.write().map.drain(..) { - if let Element::Occupied(pipeline, _) = element { - let device = &devices[pipeline.device_id.value]; - unsafe { - device.raw.destroy_render_pipeline(pipeline.raw); - } - } - } - - for element in self.blas_s.data.write().map.drain(..) { - if let Element::Occupied(blas, _) = element { - let device = &devices[blas.device_id.value]; - if let Some(raw) = blas.raw { - unsafe { - device.raw.destroy_acceleration_structure(raw); - } - } - } - } - for element in self.tlas_s.data.write().map.drain(..) { - if let Element::Occupied(tlas, _) = element { - let device = &devices[tlas.device_id.value]; - if let Some(raw) = tlas.raw { - unsafe { - device.raw.destroy_acceleration_structure(raw); - } - } - if let Some(raw) = tlas.instance_buffer { - unsafe { - device.raw.destroy_buffer(raw); - } - } - } - } - - for element in surface_guard.map.iter_mut() { - if let Element::Occupied(ref mut surface, _epoch) = *element { - if surface - .presentation - .as_ref() - .map_or(wgt::Backend::Empty, |p| p.backend()) - != A::VARIANT - { - continue; - } - if let Some(present) = surface.presentation.take() { - let device = &devices[present.device_id.value]; - let suf = A::get_surface_mut(surface); - unsafe { - suf.unwrap().raw.unconfigure(&device.raw); - //TODO: we could destroy the surface here - } - } - } - } - - for element in self.query_sets.data.write().map.drain(..) { - if let Element::Occupied(query_set, _) = element { - let device = &devices[query_set.device_id.value]; - unsafe { - device.raw.destroy_query_set(query_set.raw); - } - } - } - - for element in devices.map.drain(..) { - if let Element::Occupied(device, _) = element { - device.dispose(); - } - } + self.queues.write().map.clear(); + devices.map.clear(); if with_adapters { drop(devices); - self.adapters.data.write().map.clear(); + self.adapters.write().map.clear(); } } - pub(crate) fn surface_unconfigure( - &self, - device_id: id::Valid, - surface: &mut HalSurface, - ) { - use hal::Surface as _; - - let devices = self.devices.data.read(); - let device = &devices[device_id]; + pub(crate) fn surface_unconfigure(&self, device: &Device, surface: &HalSurface) { unsafe { - surface.raw.unconfigure(&device.raw); + use hal::Surface; + surface.raw.unconfigure(device.raw()); } } pub fn generate_report(&self) -> HubReport { HubReport { - adapters: self.adapters.data.read().generate_report(), - devices: self.devices.data.read().generate_report(), - pipeline_layouts: self.pipeline_layouts.data.read().generate_report(), - shader_modules: self.shader_modules.data.read().generate_report(), - bind_group_layouts: self.bind_group_layouts.data.read().generate_report(), - bind_groups: self.bind_groups.data.read().generate_report(), - command_buffers: self.command_buffers.data.read().generate_report(), - render_bundles: self.render_bundles.data.read().generate_report(), - render_pipelines: self.render_pipelines.data.read().generate_report(), - compute_pipelines: self.compute_pipelines.data.read().generate_report(), - query_sets: self.query_sets.data.read().generate_report(), - buffers: self.buffers.data.read().generate_report(), - textures: self.textures.data.read().generate_report(), - texture_views: self.texture_views.data.read().generate_report(), - samplers: self.samplers.data.read().generate_report(), + adapters: self.adapters.generate_report(), + devices: self.devices.generate_report(), + queues: self.queues.generate_report(), + pipeline_layouts: self.pipeline_layouts.generate_report(), + shader_modules: self.shader_modules.generate_report(), + bind_group_layouts: self.bind_group_layouts.generate_report(), + bind_groups: self.bind_groups.generate_report(), + command_buffers: self.command_buffers.generate_report(), + render_bundles: self.render_bundles.generate_report(), + render_pipelines: self.render_pipelines.generate_report(), + compute_pipelines: self.compute_pipelines.generate_report(), + query_sets: self.query_sets.generate_report(), + buffers: self.buffers.generate_report(), + textures: self.textures.generate_report(), + texture_views: self.texture_views.generate_report(), + samplers: self.samplers.generate_report(), } } } -pub struct Hubs { - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - pub(crate) vulkan: Hub, - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] - pub(crate) metal: Hub, - #[cfg(all(feature = "dx12", windows))] - pub(crate) dx12: Hub, - #[cfg(all(feature = "dx11", windows))] - pub(crate) dx11: Hub, - #[cfg(feature = "gles")] - pub(crate) gl: Hub, - #[cfg(all( - not(all(feature = "vulkan", not(target_arch = "wasm32"))), - not(all(feature = "metal", any(target_os = "macos", target_os = "ios"))), - not(all(feature = "dx12", windows)), - not(all(feature = "dx11", windows)), - not(feature = "gles"), - ))] - pub(crate) empty: Hub, +pub struct Hubs { + #[cfg(vulkan)] + pub(crate) vulkan: Hub, + #[cfg(metal)] + pub(crate) metal: Hub, + #[cfg(dx12)] + pub(crate) dx12: Hub, + #[cfg(gles)] + pub(crate) gl: Hub, + #[cfg(all(not(vulkan), not(metal), not(dx12), not(gles)))] + pub(crate) empty: Hub, } -impl Hubs { - pub(crate) fn new(factory: &F) -> Self { +impl Hubs { + pub(crate) fn new(factory: &F) -> Self { Self { - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + #[cfg(vulkan)] vulkan: Hub::new(factory), - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] metal: Hub::new(factory), - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] dx12: Hub::new(factory), - #[cfg(all(feature = "dx11", windows))] - dx11: Hub::new(factory), - #[cfg(feature = "gles")] + #[cfg(gles)] gl: Hub::new(factory), - #[cfg(all( - not(all(feature = "vulkan", not(target_arch = "wasm32"))), - not(all(feature = "metal", any(target_os = "macos", target_os = "ios"))), - not(all(feature = "dx12", windows)), - not(all(feature = "dx11", windows)), - not(feature = "gles"), - ))] + #[cfg(all(not(vulkan), not(metal), not(dx12), not(gles)))] empty: Hub::new(factory), } } diff --git a/wgpu-core/src/id.rs b/wgpu-core/src/id.rs index e003afc3b8..fb024ed22c 100644 --- a/wgpu-core/src/id.rs +++ b/wgpu-core/src/id.rs @@ -1,18 +1,15 @@ use crate::{Epoch, Index}; -use std::{cmp::Ordering, fmt, marker::PhantomData}; -use wgt::Backend; +use std::{ + any::Any, + cmp::Ordering, + fmt::{self, Debug}, + hash::Hash, + marker::PhantomData, +}; +use wgt::{Backend, WasmNotSendSync}; -#[cfg(feature = "id32")] -type IdType = u32; -#[cfg(not(feature = "id32"))] type IdType = u64; -#[cfg(feature = "id32")] -type NonZeroId = std::num::NonZeroU32; -#[cfg(not(feature = "id32"))] type NonZeroId = std::num::NonZeroU64; -#[cfg(feature = "id32")] -type ZippedIndex = u16; -#[cfg(not(feature = "id32"))] type ZippedIndex = Index; const INDEX_BITS: usize = std::mem::size_of::() * 8; @@ -66,7 +63,7 @@ type Dummy = hal::api::Empty; all(feature = "serde", not(feature = "replay")), derive(serde::Deserialize) )] -pub struct Id(NonZeroId, PhantomData); +pub struct Id(NonZeroId, PhantomData); // This type represents Id in a more readable (and editable) way. #[allow(dead_code)] @@ -77,14 +74,20 @@ enum SerialId { Id(Index, Epoch, Backend), } #[cfg(feature = "trace")] -impl From> for SerialId { +impl From> for SerialId +where + T: 'static + WasmNotSendSync, +{ fn from(id: Id) -> Self { let (index, epoch, backend) = id.unzip(); Self::Id(index, epoch, backend) } } #[cfg(feature = "replay")] -impl From for Id { +impl From for Id +where + T: 'static + WasmNotSendSync, +{ fn from(id: SerialId) -> Self { match id { SerialId::Id(index, epoch, backend) => TypedId::zip(index, epoch, backend), @@ -92,7 +95,10 @@ impl From for Id { } } -impl Id { +impl Id +where + T: 'static + WasmNotSendSync, +{ /// # Safety /// /// The raw id must be valid for the type. @@ -101,8 +107,13 @@ impl Id { } #[allow(dead_code)] - pub(crate) fn dummy(index: u32) -> Valid { - Valid(Id::zip(index, 1, Backend::Empty)) + pub(crate) fn dummy(index: u32) -> Self { + Id::zip(index, 1, Backend::Empty) + } + + #[allow(dead_code)] + pub(crate) fn is_valid(&self) -> bool { + self.backend() != Backend::Empty } pub fn backend(self) -> Backend { @@ -111,74 +122,96 @@ impl Id { 1 => Backend::Vulkan, 2 => Backend::Metal, 3 => Backend::Dx12, - 4 => Backend::Dx11, - 5 => Backend::Gl, + 4 => Backend::Gl, _ => unreachable!(), } } } -impl Copy for Id {} +impl Copy for Id where T: 'static + WasmNotSendSync {} -impl Clone for Id { +impl Clone for Id +where + T: 'static + WasmNotSendSync, +{ fn clone(&self) -> Self { - Self(self.0, PhantomData) + *self } } -impl fmt::Debug for Id { +impl Debug for Id +where + T: 'static + WasmNotSendSync, +{ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - self.unzip().fmt(formatter) + let (index, epoch, backend) = self.unzip(); + let backend = match backend { + Backend::Empty => "_", + Backend::Vulkan => "vk", + Backend::Metal => "mtl", + Backend::Dx12 => "d3d12", + Backend::Gl => "gl", + Backend::BrowserWebGpu => "webgpu", + }; + write!(formatter, "Id({index},{epoch},{backend})")?; + Ok(()) } } -impl std::hash::Hash for Id { +impl Hash for Id +where + T: 'static + WasmNotSendSync, +{ fn hash(&self, state: &mut H) { self.0.hash(state); } } -impl PartialEq for Id { +impl PartialEq for Id +where + T: 'static + WasmNotSendSync, +{ fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } -impl Eq for Id {} +impl Eq for Id where T: 'static + WasmNotSendSync {} -impl PartialOrd for Id { +impl PartialOrd for Id +where + T: 'static + WasmNotSendSync, +{ fn partial_cmp(&self, other: &Self) -> Option { self.0.partial_cmp(&other.0) } } -impl Ord for Id { +impl Ord for Id +where + T: 'static + WasmNotSendSync, +{ fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } } -/// An internal ID that has been checked to point to -/// a valid object in the storages. -#[repr(transparent)] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -#[cfg_attr(feature = "trace", derive(serde::Serialize))] -#[cfg_attr(feature = "replay", derive(serde::Deserialize))] -pub(crate) struct Valid(pub I); - /// Trait carrying methods for direct `Id` access. /// /// Most `wgpu-core` clients should not use this trait. Unusual clients that /// need to construct `Id` values directly, or access their components, like the /// WGPU recording player, may use this trait to do so. -pub trait TypedId: Copy { +pub trait TypedId: Copy + Debug + Any + 'static + WasmNotSendSync + Eq + Hash { fn zip(index: Index, epoch: Epoch, backend: Backend) -> Self; fn unzip(self) -> (Index, Epoch, Backend); fn into_raw(self) -> NonZeroId; } #[allow(trivial_numeric_casts)] -impl TypedId for Id { +impl TypedId for Id +where + T: 'static + WasmNotSendSync, +{ fn zip(index: Index, epoch: Epoch, backend: Backend) -> Self { assert_eq!(0, epoch >> EPOCH_BITS); assert_eq!(0, (index as IdType) >> INDEX_BITS); @@ -240,7 +273,6 @@ fn test_id_backend() { Backend::Vulkan, Backend::Metal, Backend::Dx12, - Backend::Dx11, Backend::Gl, ] { let id: Id<()> = Id::zip(1, 0, b); @@ -260,7 +292,6 @@ fn test_id() { Backend::Vulkan, Backend::Metal, Backend::Dx12, - Backend::Dx11, Backend::Gl, ]; for &i in &indexes { diff --git a/wgpu-core/src/identity.rs b/wgpu-core/src/identity.rs index d92c3ba066..ad7ca95b1b 100644 --- a/wgpu-core/src/identity.rs +++ b/wgpu-core/src/identity.rs @@ -1,8 +1,11 @@ use parking_lot::Mutex; use wgt::Backend; -use crate::{id, Epoch, Index}; -use std::fmt::Debug; +use crate::{ + id::{self}, + Epoch, FastHashMap, Index, +}; +use std::{fmt::Debug, marker::PhantomData, sync::Arc}; /// A simple structure to allocate [`Id`] identifiers. /// @@ -32,98 +35,99 @@ use std::fmt::Debug; /// [`alloc`]: IdentityManager::alloc /// [`free`]: IdentityManager::free #[derive(Debug, Default)] -pub struct IdentityManager { - /// Available index values. If empty, then `epochs.len()` is the next index - /// to allocate. - free: Vec, - - /// The next or currently-live epoch value associated with each `Id` index. - /// - /// If there is a live id with index `i`, then `epochs[i]` is its epoch; any - /// id with the same index but an older epoch is dead. - /// - /// If index `i` is currently unused, `epochs[i]` is the epoch to use in its - /// next `Id`. - epochs: Vec, +pub(super) struct IdentityValues { + free: Vec<(Index, Epoch)>, + //sorted by Index + used: FastHashMap>, + count: usize, } -impl IdentityManager { +impl IdentityValues { /// Allocate a fresh, never-before-seen id with the given `backend`. /// /// The backend is incorporated into the id, so that ids allocated with /// different `backend` values are always distinct. pub fn alloc(&mut self, backend: Backend) -> I { + self.count += 1; match self.free.pop() { - Some(index) => I::zip(index, self.epochs[index as usize], backend), + Some((index, epoch)) => I::zip(index, epoch + 1, backend), None => { let epoch = 1; - let id = I::zip(self.epochs.len() as Index, epoch, backend); - self.epochs.push(epoch); - id + let used = self.used.entry(epoch).or_insert_with(Default::default); + let index = if let Some(i) = used.iter().max_by_key(|v| *v) { + i + 1 + } else { + 0 + }; + used.push(index); + I::zip(index, epoch, backend) } } } - /// Free `id`. It will never be returned from `alloc` again. - pub fn free(&mut self, id: I) { + pub fn mark_as_used(&mut self, id: I) -> I { + self.count += 1; let (index, epoch, _backend) = id.unzip(); - let pe = &mut self.epochs[index as usize]; - assert_eq!(*pe, epoch); - // If the epoch reaches EOL, the index doesn't go - // into the free list, will never be reused again. - if epoch < id::EPOCH_MASK { - *pe = epoch + 1; - self.free.push(index); - } + let used = self.used.entry(epoch).or_insert_with(Default::default); + used.push(index); + id } -} -/// A type that can build true ids from proto-ids, and free true ids. -/// -/// For some implementations, the true id is based on the proto-id. -/// The caller is responsible for providing well-allocated proto-ids. -/// -/// For other implementations, the proto-id carries no information -/// (it's `()`, say), and this `IdentityHandler` type takes care of -/// allocating a fresh true id. -/// -/// See the module-level documentation for details. -pub trait IdentityHandler: Debug { - /// The type of proto-id consumed by this filter, to produce a true id. - type Input: Clone + Debug; + /// Free `id`. It will never be returned from `alloc` again. + pub fn release(&mut self, id: I) { + let (index, epoch, _backend) = id.unzip(); + self.free.push((index, epoch)); + self.count -= 1; + } - /// Given a proto-id value `id`, return a true id for `backend`. - fn process(&self, id: Self::Input, backend: Backend) -> I; + pub fn count(&self) -> usize { + self.count + } +} - /// Free the true id `id`. - fn free(&self, id: I); +#[derive(Debug)] +pub struct IdentityManager { + pub(super) values: Mutex, + _phantom: PhantomData, } -impl IdentityHandler for Mutex { - type Input = (); - fn process(&self, _id: Self::Input, backend: Backend) -> I { - self.lock().alloc(backend) +impl IdentityManager { + pub fn process(&self, backend: Backend) -> I { + self.values.lock().alloc(backend) + } + pub fn mark_as_used(&self, id: I) -> I { + self.values.lock().mark_as_used(id) } - fn free(&self, id: I) { - self.lock().free(id) + pub fn free(&self, id: I) { + self.values.lock().release(id) + } +} + +impl IdentityManager { + pub fn new() -> Self { + Self { + values: Mutex::new(IdentityValues::default()), + _phantom: PhantomData, + } } } -/// A type that can produce [`IdentityHandler`] filters for ids of type `I`. +/// A type that can produce [`IdentityManager`] filters for ids of type `I`. /// /// See the module-level documentation for details. -pub trait IdentityHandlerFactory { - /// The type of filter this factory constructs. - /// - /// "Filter" and "handler" seem to both mean the same thing here: - /// something that can produce true ids from proto-ids. - type Filter: IdentityHandler; - - /// Create an [`IdentityHandler`] implementation that can +pub trait IdentityHandlerFactory { + type Input: Copy; + /// Create an [`IdentityManager`] implementation that can /// transform proto-ids into ids of type `I`. + /// It can return None if ids are passed from outside + /// and are not generated by wgpu /// - /// [`IdentityHandler`]: IdentityHandler - fn spawn(&self) -> Self::Filter; + /// [`IdentityManager`]: IdentityManager + fn spawn(&self) -> Arc> { + Arc::new(IdentityManager::new()) + } + fn autogenerate_ids() -> bool; + fn input_to_id(id_in: Self::Input) -> I; } /// A global identity handler factory based on [`IdentityManager`]. @@ -134,14 +138,18 @@ pub trait IdentityHandlerFactory { #[derive(Debug)] pub struct IdentityManagerFactory; -impl IdentityHandlerFactory for IdentityManagerFactory { - type Filter = Mutex; - fn spawn(&self) -> Self::Filter { - Mutex::new(IdentityManager::default()) +impl IdentityHandlerFactory for IdentityManagerFactory { + type Input = (); + fn autogenerate_ids() -> bool { + true + } + + fn input_to_id(_id_in: Self::Input) -> I { + unreachable!("It should not be called") } } -/// A factory that can build [`IdentityHandler`]s for all resource +/// A factory that can build [`IdentityManager`]s for all resource /// types. pub trait GlobalIdentityHandlerFactory: IdentityHandlerFactory @@ -164,27 +172,23 @@ pub trait GlobalIdentityHandlerFactory: + IdentityHandlerFactory + IdentityHandlerFactory { - fn ids_are_generated_in_wgpu() -> bool; } -impl GlobalIdentityHandlerFactory for IdentityManagerFactory { - fn ids_are_generated_in_wgpu() -> bool { - true - } -} +impl GlobalIdentityHandlerFactory for IdentityManagerFactory {} -pub type Input = <>::Filter as IdentityHandler>::Input; +pub type Input = >::Input; #[test] fn test_epoch_end_of_life() { use id::TypedId as _; - let mut man = IdentityManager::default(); - man.epochs.push(id::EPOCH_MASK); - man.free.push(0); - let id1 = man.alloc::(Backend::Empty); - assert_eq!(id1.unzip().0, 0); + let man = IdentityManager::::new(); + let forced_id = man.mark_as_used(id::BufferId::zip(0, 1, Backend::Empty)); + assert_eq!(forced_id.unzip().0, 0); + let id1 = man.process(Backend::Empty); + assert_eq!(id1.unzip().0, 1); man.free(id1); - let id2 = man.alloc::(Backend::Empty); - // confirm that the index 0 is no longer re-used + let id2 = man.process(Backend::Empty); + // confirm that the epoch 1 is no longer re-used assert_eq!(id2.unzip().0, 1); + assert_eq!(id2.unzip().1, 2); } diff --git a/wgpu-core/src/init_tracker/buffer.rs b/wgpu-core/src/init_tracker/buffer.rs index ea9b9f6a8d..2c0fa8d372 100644 --- a/wgpu-core/src/init_tracker/buffer.rs +++ b/wgpu-core/src/init_tracker/buffer.rs @@ -1,10 +1,10 @@ use super::{InitTracker, MemoryInitKind}; -use crate::id::BufferId; -use std::ops::Range; +use crate::{hal_api::HalApi, resource::Buffer}; +use std::{ops::Range, sync::Arc}; #[derive(Debug, Clone)] -pub(crate) struct BufferInitTrackerAction { - pub id: BufferId, +pub(crate) struct BufferInitTrackerAction { + pub buffer: Arc>, pub range: Range, pub kind: MemoryInitKind, } @@ -14,22 +14,26 @@ pub(crate) type BufferInitTracker = InitTracker; impl BufferInitTracker { /// Checks if an action has/requires any effect on the initialization status /// and shrinks its range if possible. - pub(crate) fn check_action( + pub(crate) fn check_action( &self, - action: &BufferInitTrackerAction, - ) -> Option { - self.create_action(action.id, action.range.clone(), action.kind) + action: &BufferInitTrackerAction, + ) -> Option> { + self.create_action(&action.buffer, action.range.clone(), action.kind) } /// Creates an action if it would have any effect on the initialization /// status and shrinks the range if possible. - pub(crate) fn create_action( + pub(crate) fn create_action( &self, - id: BufferId, + buffer: &Arc>, query_range: Range, kind: MemoryInitKind, - ) -> Option { + ) -> Option> { self.check(query_range) - .map(|range| BufferInitTrackerAction { id, range, kind }) + .map(|range| BufferInitTrackerAction { + buffer: buffer.clone(), + range, + kind, + }) } } diff --git a/wgpu-core/src/init_tracker/mod.rs b/wgpu-core/src/init_tracker/mod.rs index c7e78bc993..908ddef638 100644 --- a/wgpu-core/src/init_tracker/mod.rs +++ b/wgpu-core/src/init_tracker/mod.rs @@ -60,7 +60,8 @@ type UninitializedRangeVec = SmallVec<[Range; 1]>; /// Tracks initialization status of a linear range from 0..size #[derive(Debug, Clone)] pub(crate) struct InitTracker { - // Ordered, non overlapping list of all uninitialized ranges. + /// Non-overlapping list of all uninitialized ranges, sorted by + /// range end. uninitialized_ranges: UninitializedRangeVec, } @@ -154,11 +155,14 @@ where } } - // Checks if there's any uninitialized ranges within a query. - // - // If there are any, the range returned a the subrange of the query_range - // that contains all these uninitialized regions. Returned range may be - // larger than necessary (tradeoff for making this function O(log n)) + /// Checks for uninitialized ranges within a given query range. + /// + /// If `query_range` includes any uninitialized portions of this init + /// tracker's resource, return the smallest subrange of `query_range` that + /// covers all uninitialized regions. + /// + /// The returned range may be larger than necessary, to keep this function + /// O(log n). pub(crate) fn check(&self, query_range: Range) -> Option> { let index = self .uninitialized_ranges diff --git a/wgpu-core/src/init_tracker/texture.rs b/wgpu-core/src/init_tracker/texture.rs index 17368e1014..a859b5f784 100644 --- a/wgpu-core/src/init_tracker/texture.rs +++ b/wgpu-core/src/init_tracker/texture.rs @@ -1,7 +1,7 @@ use super::{InitTracker, MemoryInitKind}; -use crate::{id::TextureId, track::TextureSelector}; +use crate::{hal_api::HalApi, resource::Texture, track::TextureSelector}; use arrayvec::ArrayVec; -use std::ops::Range; +use std::{ops::Range, sync::Arc}; #[derive(Debug, Clone)] pub(crate) struct TextureInitRange { @@ -35,8 +35,8 @@ impl From for TextureInitRange { } #[derive(Debug, Clone)] -pub(crate) struct TextureInitTrackerAction { - pub(crate) id: TextureId, +pub(crate) struct TextureInitTrackerAction { + pub(crate) texture: Arc>, pub(crate) range: TextureInitRange, pub(crate) kind: MemoryInitKind, } @@ -57,10 +57,10 @@ impl TextureInitTracker { } } - pub(crate) fn check_action( + pub(crate) fn check_action( &self, - action: &TextureInitTrackerAction, - ) -> Option { + action: &TextureInitTrackerAction, + ) -> Option> { let mut mip_range_start = std::usize::MAX; let mut mip_range_end = std::usize::MIN; let mut layer_range_start = std::u32::MAX; @@ -85,7 +85,7 @@ impl TextureInitTracker { if mip_range_start < mip_range_end && layer_range_start < layer_range_end { Some(TextureInitTrackerAction { - id: action.id, + texture: action.texture.clone(), range: TextureInitRange { mip_range: mip_range_start as u32..mip_range_end as u32, layer_range: layer_range_start..layer_range_end, diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index ae1a395d85..158a305772 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -1,25 +1,30 @@ +use std::sync::Arc; + use crate::{ - device::{Device, DeviceDescriptor}, + any_surface::AnySurface, + api_log, + device::{queue::Queue, resource::Device, DeviceDescriptor}, global::Global, hal_api::HalApi, - hub::Token, - id::{AdapterId, DeviceId, SurfaceId, Valid}, + id::{AdapterId, DeviceId, QueueId, SurfaceId}, identity::{GlobalIdentityHandlerFactory, Input}, present::Presentation, - LabelHelpers, LifeGuard, Stored, DOWNLEVEL_WARNING_MESSAGE, + resource::{Resource, ResourceInfo, ResourceType}, + resource_log, LabelHelpers, DOWNLEVEL_WARNING_MESSAGE, }; +use parking_lot::Mutex; use wgt::{Backend, Backends, PowerPreference}; -use hal::{Adapter as _, Instance as _}; +use hal::{Adapter as _, Instance as _, OpenDevice}; use thiserror::Error; pub type RequestAdapterOptions = wgt::RequestAdapterOptions; type HalInstance = ::Instance; //TODO: remove this -pub struct HalSurface { - pub raw: A::Surface, - //pub acquired_texture: Option, +#[derive(Clone)] +pub struct HalSurface { + pub raw: Arc, } #[derive(Clone, Debug, Error)] @@ -57,102 +62,110 @@ fn downlevel_default_limits_less_than_default_limits() { pub struct Instance { #[allow(dead_code)] pub name: String, - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + #[cfg(vulkan)] pub vulkan: Option>, - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] pub metal: Option>, - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] pub dx12: Option>, - #[cfg(all(feature = "dx11", windows))] - pub dx11: Option>, - #[cfg(feature = "gles")] + #[cfg(gles)] pub gl: Option>, + pub flags: wgt::InstanceFlags, } impl Instance { pub fn new(name: &str, instance_desc: wgt::InstanceDescriptor) -> Self { fn init(_: A, instance_desc: &wgt::InstanceDescriptor) -> Option { if instance_desc.backends.contains(A::VARIANT.into()) { - let mut flags = hal::InstanceFlags::empty(); - if cfg!(debug_assertions) { - flags |= hal::InstanceFlags::VALIDATION; - flags |= hal::InstanceFlags::DEBUG; - } let hal_desc = hal::InstanceDescriptor { name: "wgpu", - flags, + flags: instance_desc.flags, dx12_shader_compiler: instance_desc.dx12_shader_compiler.clone(), gles_minor_version: instance_desc.gles_minor_version, }; - unsafe { hal::Instance::init(&hal_desc).ok() } + match unsafe { hal::Instance::init(&hal_desc) } { + Ok(instance) => { + log::debug!("Instance::new: created {:?} backend", A::VARIANT); + Some(instance) + } + Err(err) => { + log::debug!( + "Instance::new: failed to create {:?} backend: {:?}", + A::VARIANT, + err + ); + None + } + } } else { + log::trace!("Instance::new: backend {:?} not requested", A::VARIANT); None } } Self { name: name.to_string(), - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + #[cfg(vulkan)] vulkan: init(hal::api::Vulkan, &instance_desc), - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] metal: init(hal::api::Metal, &instance_desc), - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] dx12: init(hal::api::Dx12, &instance_desc), - #[cfg(all(feature = "dx11", windows))] - dx11: init(hal::api::Dx11, &instance_desc), - #[cfg(feature = "gles")] + #[cfg(gles)] gl: init(hal::api::Gles, &instance_desc), + flags: instance_desc.flags, } } pub(crate) fn destroy_surface(&self, surface: Surface) { - fn destroy( - _: A, - instance: &Option, - surface: Option>, - ) { + fn destroy(_: A, instance: &Option, surface: AnySurface) { unsafe { - if let Some(suf) = surface { - instance.as_ref().unwrap().destroy_surface(suf.raw); + if let Some(surface) = surface.take::() { + if let Some(suf) = Arc::into_inner(surface) { + if let Some(raw) = Arc::into_inner(suf.raw) { + instance.as_ref().unwrap().destroy_surface(raw); + } else { + panic!("Surface cannot be destroyed because is still in use"); + } + } else { + panic!("Surface cannot be destroyed because is still in use"); + } } } } - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - destroy(hal::api::Vulkan, &self.vulkan, surface.vulkan); - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] - destroy(hal::api::Metal, &self.metal, surface.metal); - #[cfg(all(feature = "dx12", windows))] - destroy(hal::api::Dx12, &self.dx12, surface.dx12); - #[cfg(all(feature = "dx11", windows))] - destroy(hal::api::Dx11, &self.dx11, surface.dx11); - #[cfg(feature = "gles")] - destroy(hal::api::Gles, &self.gl, surface.gl); + match surface.raw.backend() { + #[cfg(vulkan)] + Backend::Vulkan => destroy(hal::api::Vulkan, &self.vulkan, surface.raw), + #[cfg(metal)] + Backend::Metal => destroy(hal::api::Metal, &self.metal, surface.raw), + #[cfg(dx12)] + Backend::Dx12 => destroy(hal::api::Dx12, &self.dx12, surface.raw), + #[cfg(gles)] + Backend::Gl => destroy(hal::api::Gles, &self.gl, surface.raw), + _ => unreachable!(), + } } } pub struct Surface { - pub(crate) presentation: Option, - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - pub vulkan: Option>, - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] - pub metal: Option>, - #[cfg(all(feature = "dx12", windows))] - pub dx12: Option>, - #[cfg(all(feature = "dx11", windows))] - pub dx11: Option>, - #[cfg(feature = "gles")] - pub gl: Option>, + pub(crate) presentation: Mutex>, + pub(crate) info: ResourceInfo, + pub(crate) raw: AnySurface, } -impl crate::resource::Resource for Surface { - const TYPE: &'static str = "Surface"; +impl Resource for Surface { + const TYPE: ResourceType = "Surface"; - fn life_guard(&self) -> &LifeGuard { - unreachable!() + fn as_info(&self) -> &ResourceInfo { + &self.info } - fn label(&self) -> &str { - "" + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info + } + + fn label(&self) -> String { + String::from("") } } @@ -175,9 +188,9 @@ impl Surface { } } -pub struct Adapter { +pub struct Adapter { pub(crate) raw: hal::ExposedAdapter, - life_guard: LifeGuard, + pub(crate) info: ResourceInfo, } impl Adapter { @@ -196,7 +209,7 @@ impl Adapter { Self { raw, - life_guard: LifeGuard::new(""), + info: ResourceInfo::new(""), } } @@ -281,38 +294,43 @@ impl Adapter { } } - fn create_device_from_hal( - &self, - self_id: AdapterId, - open: hal::OpenDevice, + fn create_device_and_queue_from_hal( + self: &Arc, + hal_device: OpenDevice, desc: &DeviceDescriptor, + instance_flags: wgt::InstanceFlags, trace_path: Option<&std::path::Path>, - ) -> Result, RequestDeviceError> { - let caps = &self.raw.capabilities; - Device::new( - open, - Stored { - value: Valid(self_id), - ref_count: self.life_guard.add_ref(), - }, - caps.alignments.clone(), - caps.downlevel.clone(), + ) -> Result<(Device, Queue), RequestDeviceError> { + api_log!("Adapter::create_device"); + + if let Ok(device) = Device::new( + hal_device.device, + &hal_device.queue, + self, desc, trace_path, - ) - .or(Err(RequestDeviceError::OutOfMemory)) + instance_flags, + ) { + let queue = Queue { + device: None, + raw: Some(hal_device.queue), + info: ResourceInfo::new(""), + }; + return Ok((device, queue)); + } + Err(RequestDeviceError::OutOfMemory) } - fn create_device( - &self, - self_id: AdapterId, + fn create_device_and_queue( + self: &Arc, desc: &DeviceDescriptor, + instance_flags: wgt::InstanceFlags, trace_path: Option<&std::path::Path>, - ) -> Result, RequestDeviceError> { + ) -> Result<(Device, Queue), RequestDeviceError> { // Verify all features were exposed by the adapter - if !self.raw.features.contains(desc.features) { + if !self.raw.features.contains(desc.required_features) { return Err(RequestDeviceError::UnsupportedFeature( - desc.features - self.raw.features, + desc.required_features - self.raw.features, )); } @@ -326,12 +344,12 @@ impl Adapter { missing_flags, DOWNLEVEL_WARNING_MESSAGE ); - log::info!("{:#?}", caps.downlevel); + log::warn!("{:#?}", caps.downlevel); } // Verify feature preconditions if desc - .features + .required_features .contains(wgt::Features::MAPPABLE_PRIMARY_BUFFERS) && self.raw.info.device_type == wgt::DeviceType::DiscreteGpu { @@ -345,27 +363,34 @@ impl Adapter { //TODO } - if let Some(failed) = check_limits(&desc.limits, &caps.limits).pop() { + if let Some(failed) = check_limits(&desc.required_limits, &caps.limits).pop() { return Err(RequestDeviceError::LimitsExceeded(failed)); } - let open = unsafe { self.raw.adapter.open(desc.features, &desc.limits) }.map_err( - |err| match err { - hal::DeviceError::Lost => RequestDeviceError::DeviceLost, - hal::DeviceError::OutOfMemory => RequestDeviceError::OutOfMemory, - hal::DeviceError::ResourceCreationFailed => RequestDeviceError::Internal, - }, - )?; + let open = unsafe { + self.raw + .adapter + .open(desc.required_features, &desc.required_limits) + } + .map_err(|err| match err { + hal::DeviceError::Lost => RequestDeviceError::DeviceLost, + hal::DeviceError::OutOfMemory => RequestDeviceError::OutOfMemory, + hal::DeviceError::ResourceCreationFailed => RequestDeviceError::Internal, + })?; - self.create_device_from_hal(self_id, open, desc, trace_path) + self.create_device_and_queue_from_hal(open, desc, instance_flags, trace_path) } } -impl crate::resource::Resource for Adapter { - const TYPE: &'static str = "Adapter"; +impl Resource for Adapter { + const TYPE: ResourceType = "Adapter"; - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } @@ -403,7 +428,7 @@ pub enum RequestDeviceError { LimitsExceeded(#[from] FailedLimit), #[error("Device has no queue supporting graphics")] NoGraphicsQueue, - #[error("Not enough memory left")] + #[error("Not enough memory left to request device")] OutOfMemory, #[error("Unsupported features were requested: {0:?}")] UnsupportedFeature(wgt::Features), @@ -414,10 +439,10 @@ pub enum AdapterInputs<'a, I> { Mask(Backends, fn(Backend) -> I), } -impl AdapterInputs<'_, I> { +impl AdapterInputs<'_, I> { fn find(&self, b: Backend) -> Option { match *self { - Self::IdSet(ids, ref fun) => ids.iter().find(|id| fun(id) == b).cloned(), + Self::IdSet(ids, ref fun) => ids.iter().find(|id| fun(id) == b).copied(), Self::Mask(bits, ref fun) => { if bits.contains(b.into()) { Some(fun(b)) @@ -443,57 +468,72 @@ pub enum RequestAdapterError { } impl Global { + /// # Safety + /// + /// - `display_handle` must be a valid object to create a surface upon. + /// - `window_handle` must remain valid as long as the returned + /// [`SurfaceId`] is being used. #[cfg(feature = "raw-window-handle")] - pub fn instance_create_surface( + pub unsafe fn instance_create_surface( &self, display_handle: raw_window_handle::RawDisplayHandle, window_handle: raw_window_handle::RawWindowHandle, id_in: Input, - ) -> SurfaceId { + ) -> Result { profiling::scope!("Instance::create_surface"); - fn init( + fn init( inst: &Option, display_handle: raw_window_handle::RawDisplayHandle, window_handle: raw_window_handle::RawWindowHandle, - ) -> Option> { - inst.as_ref().and_then(|inst| unsafe { + ) -> Option> { + inst.as_ref().map(|inst| unsafe { match inst.create_surface(display_handle, window_handle) { - Ok(raw) => Some(HalSurface { - raw, - //acquired_texture: None, - }), - Err(e) => { - log::warn!("Error: {:?}", e); - None - } + Ok(raw) => Ok(AnySurface::new(HalSurface:: { raw: Arc::new(raw) })), + Err(e) => Err(e), } }) } + let mut hal_surface: Option> = None; + + #[cfg(vulkan)] + if hal_surface.is_none() { + hal_surface = + init::(&self.instance.vulkan, display_handle, window_handle); + } + #[cfg(metal)] + if hal_surface.is_none() { + hal_surface = + init::(&self.instance.metal, display_handle, window_handle); + } + #[cfg(dx12)] + if hal_surface.is_none() { + hal_surface = + init::(&self.instance.dx12, display_handle, window_handle); + } + #[cfg(gles)] + if hal_surface.is_none() { + hal_surface = init::(&self.instance.gl, display_handle, window_handle); + } + + // This is only None if there's no instance at all. + let hal_surface = hal_surface.unwrap()?; + let surface = Surface { - presentation: None, - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - vulkan: init::(&self.instance.vulkan, display_handle, window_handle), - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] - metal: init::(&self.instance.metal, display_handle, window_handle), - #[cfg(all(feature = "dx12", windows))] - dx12: init::(&self.instance.dx12, display_handle, window_handle), - #[cfg(all(feature = "dx11", windows))] - dx11: init::(&self.instance.dx11, display_handle, window_handle), - #[cfg(feature = "gles")] - gl: init::(&self.instance.gl, display_handle, window_handle), + presentation: Mutex::new(None), + info: ResourceInfo::new(""), + raw: hal_surface, }; - let mut token = Token::root(); - let id = self.surfaces.prepare(id_in).assign(surface, &mut token); - id.0 + let (id, _) = self.surfaces.prepare::(id_in).assign(surface); + Ok(id) } /// # Safety /// /// `layer` must be a valid pointer. - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] pub unsafe fn instance_create_surface_metal( &self, layer: *mut std::ffi::c_void, @@ -502,176 +542,161 @@ impl Global { profiling::scope!("Instance::create_surface_metal"); let surface = Surface { - presentation: None, - metal: self.instance.metal.as_ref().map(|inst| HalSurface { - raw: { - // we don't want to link to metal-rs for this - #[allow(clippy::transmute_ptr_to_ref)] - inst.create_surface_from_layer(unsafe { std::mem::transmute(layer) }) - }, - //acquired_texture: None, - }), - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - vulkan: None, - #[cfg(feature = "gles")] - gl: None, - }; - - let mut token = Token::root(); - let id = self.surfaces.prepare(id_in).assign(surface, &mut token); - id.0 - } - - #[cfg(all( - target_arch = "wasm32", - not(target_os = "emscripten"), - feature = "gles" - ))] - pub fn create_surface_webgl_canvas( - &self, - canvas: web_sys::HtmlCanvasElement, - id_in: Input, - ) -> Result { - profiling::scope!("Instance::create_surface_webgl_canvas"); - - let surface = Surface { - presentation: None, - gl: self - .instance - .gl - .as_ref() - .map(|inst| { - Ok(HalSurface { - raw: inst.create_surface_from_canvas(canvas)?, + presentation: Mutex::new(None), + info: ResourceInfo::new(""), + raw: { + let hal_surface: HalSurface = self + .instance + .metal + .as_ref() + .map(|inst| HalSurface { + raw: Arc::new( + // we don't want to link to metal-rs for this + #[allow(clippy::transmute_ptr_to_ref)] + inst.create_surface_from_layer(unsafe { std::mem::transmute(layer) }), + ), //acquired_texture: None, }) - }) - .transpose()?, + .unwrap(); + AnySurface::new(hal_surface) + }, }; - let mut token = Token::root(); - let id = self.surfaces.prepare(id_in).assign(surface, &mut token); - Ok(id.0) + let (id, _) = self.surfaces.prepare::(id_in).assign(surface); + id } - #[cfg(all( - target_arch = "wasm32", - not(target_os = "emscripten"), - feature = "gles" - ))] - pub fn create_surface_webgl_offscreen_canvas( + #[cfg(dx12)] + /// # Safety + /// + /// The visual must be valid and able to be used to make a swapchain with. + pub unsafe fn instance_create_surface_from_visual( &self, - canvas: web_sys::OffscreenCanvas, + visual: *mut std::ffi::c_void, id_in: Input, - ) -> Result { - profiling::scope!("Instance::create_surface_webgl_offscreen_canvas"); + ) -> SurfaceId { + profiling::scope!("Instance::instance_create_surface_from_visual"); let surface = Surface { - presentation: None, - gl: self - .instance - .gl - .as_ref() - .map(|inst| { - Ok(HalSurface { - raw: inst.create_surface_from_offscreen_canvas(canvas)?, + presentation: Mutex::new(None), + info: ResourceInfo::new(""), + raw: { + let hal_surface: HalSurface = self + .instance + .dx12 + .as_ref() + .map(|inst| HalSurface { + raw: Arc::new(unsafe { inst.create_surface_from_visual(visual as _) }), }) - }) - .transpose()?, + .unwrap(); + AnySurface::new(hal_surface) + }, }; - let mut token = Token::root(); - let id = self.surfaces.prepare(id_in).assign(surface, &mut token); - Ok(id.0) + let (id, _) = self.surfaces.prepare::(id_in).assign(surface); + id } - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] /// # Safety /// - /// The visual must be valid and able to be used to make a swapchain with. - pub unsafe fn instance_create_surface_from_visual( + /// The surface_handle must be valid and able to be used to make a swapchain with. + pub unsafe fn instance_create_surface_from_surface_handle( &self, - visual: *mut std::ffi::c_void, + surface_handle: *mut std::ffi::c_void, id_in: Input, ) -> SurfaceId { - profiling::scope!("Instance::instance_create_surface_from_visual"); + profiling::scope!("Instance::instance_create_surface_from_surface_handle"); let surface = Surface { - presentation: None, - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - vulkan: None, - dx12: self.instance.dx12.as_ref().map(|inst| HalSurface { - raw: unsafe { inst.create_surface_from_visual(visual as _) }, - }), - dx11: None, - #[cfg(feature = "gles")] - gl: None, + presentation: Mutex::new(None), + info: ResourceInfo::new(""), + raw: { + let hal_surface: HalSurface = self + .instance + .dx12 + .as_ref() + .map(|inst| HalSurface { + raw: Arc::new(unsafe { + inst.create_surface_from_surface_handle(surface_handle) + }), + }) + .unwrap(); + AnySurface::new(hal_surface) + }, }; - let mut token = Token::root(); - let id = self.surfaces.prepare(id_in).assign(surface, &mut token); - id.0 + let (id, _) = self.surfaces.prepare::(id_in).assign(surface); + id } - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] /// # Safety /// - /// The surface_handle must be valid and able to be used to make a swapchain with. - pub unsafe fn instance_create_surface_from_surface_handle( + /// The swap_chain_panel must be valid and able to be used to make a swapchain with. + pub unsafe fn instance_create_surface_from_swap_chain_panel( &self, - surface_handle: *mut std::ffi::c_void, + swap_chain_panel: *mut std::ffi::c_void, id_in: Input, ) -> SurfaceId { - profiling::scope!("Instance::instance_create_surface_from_surface_handle"); + profiling::scope!("Instance::instance_create_surface_from_swap_chain_panel"); let surface = Surface { - presentation: None, - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - vulkan: None, - dx12: self.instance.dx12.as_ref().map(|inst| HalSurface { - raw: unsafe { inst.create_surface_from_surface_handle(surface_handle) }, - }), - dx11: None, - #[cfg(feature = "gles")] - gl: None, + presentation: Mutex::new(None), + info: ResourceInfo::new(""), + raw: { + let hal_surface: HalSurface = self + .instance + .dx12 + .as_ref() + .map(|inst| HalSurface { + raw: Arc::new(unsafe { + inst.create_surface_from_swap_chain_panel(swap_chain_panel as _) + }), + }) + .unwrap(); + AnySurface::new(hal_surface) + }, }; - let mut token = Token::root(); - let id = self.surfaces.prepare(id_in).assign(surface, &mut token); - id.0 + let (id, _) = self.surfaces.prepare::(id_in).assign(surface); + id } pub fn surface_drop(&self, id: SurfaceId) { profiling::scope!("Surface::drop"); - let mut token = Token::root(); - let (surface, _) = self.surfaces.unregister(id, &mut token); - let mut surface = surface.unwrap(); + + api_log!("Surface::drop {id:?}"); fn unconfigure( global: &Global, - surface: &mut HalSurface, + surface: &AnySurface, present: &Presentation, ) { let hub = HalApi::hub(global); - hub.surface_unconfigure(present.device_id.value, surface); + if let Some(hal_surface) = surface.downcast_ref::() { + if let Some(device) = present.device.downcast_ref::() { + hub.surface_unconfigure(device, hal_surface); + } + } } - if let Some(present) = surface.presentation.take() { - match present.backend() { - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - Backend::Vulkan => unconfigure(self, surface.vulkan.as_mut().unwrap(), &present), - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] - Backend::Metal => unconfigure(self, surface.metal.as_mut().unwrap(), &present), - #[cfg(all(feature = "dx12", windows))] - Backend::Dx12 => unconfigure(self, surface.dx12.as_mut().unwrap(), &present), - #[cfg(all(feature = "dx11", windows))] - Backend::Dx11 => unconfigure(self, surface.dx11.as_mut().unwrap(), &present), - #[cfg(feature = "gles")] - Backend::Gl => unconfigure(self, surface.gl.as_mut().unwrap(), &present), - _ => unreachable!(), + let surface = self.surfaces.unregister(id); + if let Some(surface) = Arc::into_inner(surface.unwrap()) { + if let Some(present) = surface.presentation.lock().take() { + #[cfg(vulkan)] + unconfigure::<_, hal::api::Vulkan>(self, &surface.raw, &present); + #[cfg(metal)] + unconfigure::<_, hal::api::Metal>(self, &surface.raw, &present); + #[cfg(dx12)] + unconfigure::<_, hal::api::Dx12>(self, &surface.raw, &present); + #[cfg(gles)] + unconfigure::<_, hal::api::Gles>(self, &surface.raw, &present); } - } - self.instance.destroy_surface(surface); + self.instance.destroy_surface(surface); + } else { + panic!("Surface cannot be destroyed because is still in use"); + } } fn enumerate( @@ -692,44 +717,39 @@ impl Global { profiling::scope!("enumerating", &*format!("{:?}", A::VARIANT)); let hub = HalApi::hub(self); - let mut token = Token::root(); let hal_adapters = unsafe { inst.enumerate_adapters() }; for raw in hal_adapters { let adapter = Adapter::new(raw); log::info!("Adapter {:?} {:?}", A::VARIANT, adapter.raw.info); - let id = hub - .adapters - .prepare(id_backend.clone()) - .assign(adapter, &mut token); - list.push(id.0); + let (id, _) = hub.adapters.prepare::(id_backend).assign(adapter); + list.push(id); } } pub fn enumerate_adapters(&self, inputs: AdapterInputs>) -> Vec { profiling::scope!("Instance::enumerate_adapters"); + api_log!("Instance::enumerate_adapters"); let mut adapters = Vec::new(); - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + #[cfg(vulkan)] self.enumerate( hal::api::Vulkan, &self.instance.vulkan, &inputs, &mut adapters, ); - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] self.enumerate( hal::api::Metal, &self.instance.metal, &inputs, &mut adapters, ); - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] self.enumerate(hal::api::Dx12, &self.instance.dx12, &inputs, &mut adapters); - #[cfg(all(feature = "dx11", windows))] - self.enumerate(hal::api::Dx11, &self.instance.dx11, &inputs, &mut adapters); - #[cfg(feature = "gles")] + #[cfg(gles)] self.enumerate(hal::api::Gles, &self.instance.gl, &inputs, &mut adapters); adapters @@ -747,14 +767,13 @@ impl Global { None } None => { - let mut token = Token::root(); let adapter = Adapter::new(list.swap_remove(*selected)); log::info!("Adapter {:?} {:?}", A::VARIANT, adapter.raw.info); - let id = HalApi::hub(self) + let (id, _) = HalApi::hub(self) .adapters - .prepare(new_id.unwrap()) - .assign(adapter, &mut token); - Some(id.0) + .prepare::(new_id.unwrap()) + .assign(adapter); + Some(id) } } } @@ -764,9 +783,10 @@ impl Global { desc: &RequestAdapterOptions, inputs: AdapterInputs>, ) -> Result { - profiling::scope!("Instance::pick_adapter"); + profiling::scope!("Instance::request_adapter"); + api_log!("Instance::request_adapter"); - fn gather( + fn gather( _: A, instance: Option<&A::Instance>, inputs: &AdapterInputs, @@ -800,19 +820,18 @@ impl Global { } } - let mut token = Token::root(); - let (surface_guard, _) = self.surfaces.read(&mut token); let compatible_surface = desc .compatible_surface .map(|id| { - surface_guard + self.surfaces .get(id) .map_err(|_| RequestAdapterError::InvalidSurface(id)) }) .transpose()?; + let compatible_surface = compatible_surface.as_ref().map(|surface| surface.as_ref()); let mut device_types = Vec::new(); - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + #[cfg(vulkan)] let (id_vulkan, adapters_vk) = gather( hal::api::Vulkan, self.instance.vulkan.as_ref(), @@ -821,7 +840,7 @@ impl Global { desc.force_fallback_adapter, &mut device_types, ); - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] let (id_metal, adapters_metal) = gather( hal::api::Metal, self.instance.metal.as_ref(), @@ -830,7 +849,7 @@ impl Global { desc.force_fallback_adapter, &mut device_types, ); - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] let (id_dx12, adapters_dx12) = gather( hal::api::Dx12, self.instance.dx12.as_ref(), @@ -839,16 +858,7 @@ impl Global { desc.force_fallback_adapter, &mut device_types, ); - #[cfg(all(feature = "dx11", windows))] - let (id_dx11, adapters_dx11) = gather( - hal::api::Dx11, - self.instance.dx11.as_ref(), - &inputs, - compatible_surface, - desc.force_fallback_adapter, - &mut device_types, - ); - #[cfg(feature = "gles")] + #[cfg(gles)] let (id_gl, adapters_gl) = gather( hal::api::Gles, self.instance.gl.as_ref(), @@ -858,9 +868,6 @@ impl Global { &mut device_types, ); - // need to free the token to be used by `select` - drop(surface_guard); - drop(token); if device_types.is_empty() { return Err(RequestAdapterError::NotFound); } @@ -912,23 +919,19 @@ impl Global { }; let mut selected = preferred_gpu.unwrap_or(0); - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + #[cfg(vulkan)] if let Some(id) = self.select(&mut selected, id_vulkan, adapters_vk) { return Ok(id); } - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] if let Some(id) = self.select(&mut selected, id_metal, adapters_metal) { return Ok(id); } - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] if let Some(id) = self.select(&mut selected, id_dx12, adapters_dx12) { return Ok(id); } - #[cfg(all(feature = "dx11", windows))] - if let Some(id) = self.select(&mut selected, id_dx11, adapters_dx11) { - return Ok(id); - } - #[cfg(feature = "gles")] + #[cfg(gles)] if let Some(id) = self.select(&mut selected, id_gl, adapters_gl) { return Ok(id); } @@ -948,22 +951,22 @@ impl Global { ) -> AdapterId { profiling::scope!("Instance::create_adapter_from_hal"); - let mut token = Token::root(); - let fid = A::hub(self).adapters.prepare(input); - - match A::VARIANT { - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - Backend::Vulkan => fid.assign(Adapter::new(hal_adapter), &mut token).0, - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] - Backend::Metal => fid.assign(Adapter::new(hal_adapter), &mut token).0, - #[cfg(all(feature = "dx12", windows))] - Backend::Dx12 => fid.assign(Adapter::new(hal_adapter), &mut token).0, - #[cfg(all(feature = "dx11", windows))] - Backend::Dx11 => fid.assign(Adapter::new(hal_adapter), &mut token).0, - #[cfg(feature = "gles")] - Backend::Gl => fid.assign(Adapter::new(hal_adapter), &mut token).0, - _ => unreachable!(), - } + let fid = A::hub(self).adapters.prepare::(input); + + let (id, _adapter): (crate::id::Id>, Arc>) = + match A::VARIANT { + #[cfg(vulkan)] + Backend::Vulkan => fid.assign(Adapter::new(hal_adapter)), + #[cfg(metal)] + Backend::Metal => fid.assign(Adapter::new(hal_adapter)), + #[cfg(dx12)] + Backend::Dx12 => fid.assign(Adapter::new(hal_adapter)), + #[cfg(gles)] + Backend::Gl => fid.assign(Adapter::new(hal_adapter)), + _ => unreachable!(), + }; + resource_log!("Created Adapter {:?}", id); + id } pub fn adapter_get_info( @@ -971,9 +974,8 @@ impl Global { adapter_id: AdapterId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (adapter_guard, _) = hub.adapters.read(&mut token); - adapter_guard + + hub.adapters .get(adapter_id) .map(|adapter| adapter.raw.info.clone()) .map_err(|_| InvalidAdapter) @@ -985,9 +987,8 @@ impl Global { format: wgt::TextureFormat, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (adapter_guard, _) = hub.adapters.read(&mut token); - adapter_guard + + hub.adapters .get(adapter_id) .map(|adapter| adapter.get_texture_format_features(format)) .map_err(|_| InvalidAdapter) @@ -998,9 +999,8 @@ impl Global { adapter_id: AdapterId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (adapter_guard, _) = hub.adapters.read(&mut token); - adapter_guard + + hub.adapters .get(adapter_id) .map(|adapter| adapter.raw.features) .map_err(|_| InvalidAdapter) @@ -1011,9 +1011,8 @@ impl Global { adapter_id: AdapterId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (adapter_guard, _) = hub.adapters.read(&mut token); - adapter_guard + + hub.adapters .get(adapter_id) .map(|adapter| adapter.raw.capabilities.limits.clone()) .map_err(|_| InvalidAdapter) @@ -1024,9 +1023,8 @@ impl Global { adapter_id: AdapterId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (adapter_guard, _) = hub.adapters.read(&mut token); - adapter_guard + + hub.adapters .get(adapter_id) .map(|adapter| adapter.raw.capabilities.downlevel.clone()) .map_err(|_| InvalidAdapter) @@ -1037,27 +1035,26 @@ impl Global { adapter_id: AdapterId, ) -> Result { let hub = A::hub(self); - let mut token = Token::root(); - let (adapter_guard, _) = hub.adapters.read(&mut token); - let adapter = adapter_guard.get(adapter_id).map_err(|_| InvalidAdapter)?; + + let adapter = hub.adapters.get(adapter_id).map_err(|_| InvalidAdapter)?; Ok(unsafe { adapter.raw.adapter.get_presentation_timestamp() }) } pub fn adapter_drop(&self, adapter_id: AdapterId) { profiling::scope!("Adapter::drop"); + api_log!("Adapter::drop {adapter_id:?}"); let hub = A::hub(self); - let mut token = Token::root(); - let (mut adapter_guard, _) = hub.adapters.write(&mut token); + let mut adapters_locked = hub.adapters.write(); - let free = match adapter_guard.get_mut(adapter_id) { - Ok(adapter) => adapter.life_guard.ref_count.take().unwrap().load() == 1, + let free = match adapters_locked.get(adapter_id) { + Ok(adapter) => Arc::strong_count(adapter) == 1, Err(_) => true, }; if free { hub.adapters - .unregister_locked(adapter_id, &mut *adapter_guard); + .unregister_locked(adapter_id, &mut *adapters_locked); } } } @@ -1068,30 +1065,43 @@ impl Global { adapter_id: AdapterId, desc: &DeviceDescriptor, trace_path: Option<&std::path::Path>, - id_in: Input, - ) -> (DeviceId, Option) { + device_id_in: Input, + queue_id_in: Input, + ) -> (DeviceId, QueueId, Option) { profiling::scope!("Adapter::request_device"); + api_log!("Adapter::request_device"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.devices.prepare(id_in); + let device_fid = hub.devices.prepare::(device_id_in); + let queue_fid = hub.queues.prepare::(queue_id_in); let error = loop { - let (adapter_guard, mut token) = hub.adapters.read(&mut token); - let adapter = match adapter_guard.get(adapter_id) { + let adapter = match hub.adapters.get(adapter_id) { Ok(adapter) => adapter, Err(_) => break RequestDeviceError::InvalidAdapter, }; - let device = match adapter.create_device(adapter_id, desc, trace_path) { - Ok(device) => device, - Err(e) => break e, - }; - let id = fid.assign(device, &mut token); - return (id.0, None); + let (device, mut queue) = + match adapter.create_device_and_queue(desc, self.instance.flags, trace_path) { + Ok((device, queue)) => (device, queue), + Err(e) => break e, + }; + let (device_id, _) = device_fid.assign(device); + resource_log!("Created Device {:?}", device_id); + + let device = hub.devices.get(device_id).unwrap(); + queue.device = Some(device.clone()); + + let (queue_id, _) = queue_fid.assign(queue); + resource_log!("Created Queue {:?}", queue_id); + + device.queue_id.write().replace(queue_id); + + return (device_id, queue_id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); - (id, Some(error)) + let device_id = device_fid.assign_error(desc.label.borrow_or_default()); + let queue_id = queue_fid.assign_error(desc.label.borrow_or_default()); + (device_id, queue_id, Some(error)) } /// # Safety @@ -1101,34 +1111,49 @@ impl Global { pub unsafe fn create_device_from_hal( &self, adapter_id: AdapterId, - hal_device: hal::OpenDevice, + hal_device: OpenDevice, desc: &DeviceDescriptor, trace_path: Option<&std::path::Path>, - id_in: Input, - ) -> (DeviceId, Option) { - profiling::scope!("Adapter::create_device_from_hal"); + device_id_in: Input, + queue_id_in: Input, + ) -> (DeviceId, QueueId, Option) { + profiling::scope!("Global::create_device_from_hal"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.devices.prepare(id_in); + let devices_fid = hub.devices.prepare::(device_id_in); + let queues_fid = hub.queues.prepare::(queue_id_in); let error = loop { - let (adapter_guard, mut token) = hub.adapters.read(&mut token); - let adapter = match adapter_guard.get(adapter_id) { + let adapter = match hub.adapters.get(adapter_id) { Ok(adapter) => adapter, Err(_) => break RequestDeviceError::InvalidAdapter, }; - let device = - match adapter.create_device_from_hal(adapter_id, hal_device, desc, trace_path) { - Ok(device) => device, - Err(e) => break e, - }; - let id = fid.assign(device, &mut token); - return (id.0, None); + let (device, mut queue) = match adapter.create_device_and_queue_from_hal( + hal_device, + desc, + self.instance.flags, + trace_path, + ) { + Ok(device) => device, + Err(e) => break e, + }; + let (device_id, _) = devices_fid.assign(device); + resource_log!("Created Device {:?}", device_id); + + let device = hub.devices.get(device_id).unwrap(); + queue.device = Some(device.clone()); + + let (queue_id, _) = queues_fid.assign(queue); + resource_log!("Created Queue {:?}", queue_id); + + device.queue_id.write().replace(queue_id); + + return (device_id, queue_id, None); }; - let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); - (id, Some(error)) + let device_id = devices_fid.assign_error(desc.label.borrow_or_default()); + let queue_id = queues_fid.assign_error(desc.label.borrow_or_default()); + (device_id, queue_id, Some(error)) } } @@ -1141,7 +1166,6 @@ impl Global { /// Names: /// - vulkan = "vulkan" or "vk" /// - dx12 = "dx12" or "d3d12" -/// - dx11 = "dx11" or "d3d11" /// - metal = "metal" or "mtl" /// - gles = "opengl" or "gles" or "gl" /// - webgpu = "webgpu" @@ -1151,7 +1175,6 @@ pub fn parse_backends_from_comma_list(string: &str) -> Backends { backends |= match backend.trim() { "vulkan" | "vk" => Backends::VULKAN, "dx12" | "d3d12" => Backends::DX12, - "dx11" | "d3d11" => Backends::DX11, "metal" | "mtl" => Backends::METAL, "opengl" | "gles" | "gl" => Backends::GL, "webgpu" => Backends::BROWSER_WEBGPU, diff --git a/wgpu-core/src/lib.rs b/wgpu-core/src/lib.rs index ae475930bf..fc93f9a7f6 100644 --- a/wgpu-core/src/lib.rs +++ b/wgpu-core/src/lib.rs @@ -1,7 +1,38 @@ -/*! This library safely implements WebGPU on native platforms. - * It is designed for integration into browsers, as well as wrapping - * into other language-specific user-friendly libraries. - */ +//! This library safely implements WebGPU on native platforms. +//! It is designed for integration into browsers, as well as wrapping +//! into other language-specific user-friendly libraries. +//! +//! ## Feature flags +// NOTE: feature docs. below should be kept in sync. with `Cargo.toml`! +//! +//! - **`api_log_info`** --- Log all API entry points at info instead of trace level. +//! - **`resource_log_info`** --- Log resource lifecycle management at info instead of trace level. +//! - **`link`** _(enabled by default)_ --- Use static linking for libraries. Disale to manually +//! link. Enabled by default. +//! - **`renderdoc`** --- Support the Renderdoc graphics debugger: +//! [https://renderdoc.org/](https://renderdoc.org/) +//! - **`strict_asserts`** --- Apply run-time checks, even in release builds. These are in addition +//! to the validation carried out at public APIs in all builds. +//! - **`trace`** --- Enable API tracing. +//! - **`replay`** --- Enable API replaying +//! - **`serial-pass`** --- Enable serializable compute/render passes, and bundle encoders. +//! - **`wgsl`** --- Enable `ShaderModuleSource::Wgsl` +//! - **`fragile-send-sync-non-atomic-wasm`** --- Implement `Send` and `Sync` on Wasm, but only if +//! atomics are not enabled. +//! +//! WebGL/WebGPU objects can not be shared between threads. However, it can be useful to +//! artificially mark them as `Send` and `Sync` anyways to make it easier to write cross-platform +//! code. This is technically _very_ unsafe in a multithreaded environment, but on a wasm binary +//! compiled without atomics we know we are definitely not in a multithreaded environment. +//! +//! ### Backends, passed through to wgpu-hal +//! +//! - **`metal`** --- Enable the `metal` backend. +//! - **`vulkan`** --- Enable the `vulkan` backend. +//! - **`gles`** --- Enable the `GLES` backend. +//! +//! This is used for all of GLES, OpenGL, and WebGL. +//! - **`dx12`** --- Enable the `dx12` backend. // When we have no backends, we end up with a lot of dead or otherwise unreachable code. #![cfg_attr( @@ -9,7 +40,6 @@ not(all(feature = "vulkan", not(target_arch = "wasm32"))), not(all(feature = "metal", any(target_os = "macos", target_os = "ios"))), not(all(feature = "dx12", windows)), - not(all(feature = "dx11", windows)), not(feature = "gles"), ), allow(unused, clippy::let_and_return) @@ -35,19 +65,18 @@ // For some reason `rustc` can warn about these in const generics even // though they are required. unused_braces, - // Clashes with clippy::pattern_type_mismatch - clippy::needless_borrowed_reference, + // It gets in the way a lot and does not prevent bugs in practice. + clippy::pattern_type_mismatch, )] #![warn( trivial_casts, trivial_numeric_casts, unsafe_op_in_unsafe_fn, unused_extern_crates, - unused_qualifications, - // We don't match on a reference, unless required. - clippy::pattern_type_mismatch, + unused_qualifications )] +pub mod any_surface; pub mod binding_model; pub mod command; mod conv; @@ -55,25 +84,32 @@ pub mod device; pub mod error; pub mod global; pub mod hal_api; +mod hash_utils; pub mod hub; pub mod id; pub mod identity; mod init_tracker; pub mod instance; pub mod pipeline; +mod pool; pub mod present; pub mod ray_tracing; pub mod registry; pub mod resource; +mod snatch; pub mod storage; mod track; -mod validation; +// This is public for users who pre-compile shaders while still wanting to +// preserve all run-time checks that `wgpu-core` does. +// See , after which this can be +// made private again. +pub mod validation; pub use hal::{api, MAX_BIND_GROUPS, MAX_COLOR_ATTACHMENTS, MAX_VERTEX_BUFFERS}; -use atomic::{AtomicUsize, Ordering}; +use std::{borrow::Cow, os::raw::c_char}; -use std::{borrow::Cow, os::raw::c_char, ptr, sync::atomic}; +pub(crate) use hash_utils::*; /// The index of a queue submission. /// @@ -88,164 +124,31 @@ pub type Label<'a> = Option>; trait LabelHelpers<'a> { fn borrow_option(&'a self) -> Option<&'a str>; + fn to_hal(&'a self, flags: wgt::InstanceFlags) -> Option<&'a str>; fn borrow_or_default(&'a self) -> &'a str; } impl<'a> LabelHelpers<'a> for Label<'a> { fn borrow_option(&'a self) -> Option<&'a str> { self.as_ref().map(|cow| cow.as_ref()) } - fn borrow_or_default(&'a self) -> &'a str { - self.borrow_option().unwrap_or_default() - } -} - -/// Reference count object that is 1:1 with each reference. -/// -/// All the clones of a given `RefCount` point to the same -/// heap-allocated atomic reference count. When the count drops to -/// zero, only the count is freed. No other automatic cleanup takes -/// place; this is just a reference count, not a smart pointer. -/// -/// `RefCount` values are created only by [`LifeGuard::new`] and by -/// `Clone`, so every `RefCount` is implicitly tied to some -/// [`LifeGuard`]. -#[derive(Debug)] -struct RefCount(ptr::NonNull); - -unsafe impl Send for RefCount {} -unsafe impl Sync for RefCount {} - -impl RefCount { - const MAX: usize = 1 << 24; - - /// Construct a new `RefCount`, with an initial count of 1. - fn new() -> RefCount { - let bx = Box::new(AtomicUsize::new(1)); - Self(unsafe { ptr::NonNull::new_unchecked(Box::into_raw(bx)) }) - } - - fn load(&self) -> usize { - unsafe { self.0.as_ref() }.load(Ordering::Acquire) - } -} - -impl Clone for RefCount { - fn clone(&self) -> Self { - let old_size = unsafe { self.0.as_ref() }.fetch_add(1, Ordering::AcqRel); - assert!(old_size < Self::MAX); - Self(self.0) - } -} - -impl Drop for RefCount { - fn drop(&mut self) { - unsafe { - if self.0.as_ref().fetch_sub(1, Ordering::AcqRel) == 1 { - drop(Box::from_raw(self.0.as_ptr())); - } + fn to_hal(&'a self, flags: wgt::InstanceFlags) -> Option<&'a str> { + if flags.contains(wgt::InstanceFlags::DISCARD_HAL_LABELS) { + return None; } - } -} - -/// Reference count object that tracks multiple references. -/// Unlike `RefCount`, it's manually inc()/dec() called. -#[derive(Debug)] -struct MultiRefCount(AtomicUsize); -impl MultiRefCount { - fn new() -> Self { - Self(AtomicUsize::new(1)) - } - - fn inc(&self) { - self.0.fetch_add(1, Ordering::AcqRel); + self.as_ref().map(|cow| cow.as_ref()) } - - fn dec_and_check_empty(&self) -> bool { - self.0.fetch_sub(1, Ordering::AcqRel) == 1 + fn borrow_or_default(&'a self) -> &'a str { + self.borrow_option().unwrap_or_default() } } -/// Information needed to decide when it's safe to free some wgpu-core -/// resource. -/// -/// Each type representing a `wgpu-core` resource, like [`Device`], -/// [`Buffer`], etc., contains a `LifeGuard` which indicates whether -/// it is safe to free. -/// -/// A resource may need to be retained for any of several reasons: -/// -/// - The user may hold a reference to it (via a `wgpu::Buffer`, say). -/// -/// - Other resources may depend on it (a texture view's backing -/// texture, for example). -/// -/// - It may be used by commands sent to the GPU that have not yet -/// finished execution. -/// -/// [`Device`]: device::Device -/// [`Buffer`]: resource::Buffer -#[derive(Debug)] -pub struct LifeGuard { - /// `RefCount` for the user's reference to this resource. - /// - /// When the user first creates a `wgpu-core` resource, this `RefCount` is - /// created along with the resource's `LifeGuard`. When the user drops the - /// resource, we swap this out for `None`. Note that the resource may - /// still be held alive by other resources. - /// - /// Any `Stored` value holds a clone of this `RefCount` along with the id - /// of a `T` resource. - ref_count: Option, - - /// The index of the last queue submission in which the resource - /// was used. - /// - /// Each queue submission is fenced and assigned an index number - /// sequentially. Thus, when a queue submission completes, we know any - /// resources used in that submission and any lower-numbered submissions are - /// no longer in use by the GPU. - submission_index: AtomicUsize, - - /// The `label` from the descriptor used to create the resource. - #[cfg(debug_assertions)] - pub(crate) label: String, -} - -impl LifeGuard { - #[allow(unused_variables)] - fn new(label: &str) -> Self { - Self { - ref_count: Some(RefCount::new()), - submission_index: AtomicUsize::new(0), - #[cfg(debug_assertions)] - label: label.to_string(), - } - } - - fn add_ref(&self) -> RefCount { - self.ref_count.clone().unwrap() +pub fn hal_label(opt: Option<&str>, flags: wgt::InstanceFlags) -> Option<&str> { + if flags.contains(wgt::InstanceFlags::DISCARD_HAL_LABELS) { + return None; } - /// Record that this resource will be used by the queue submission with the - /// given index. - /// - /// Returns `true` if the resource is still held by the user. - fn use_at(&self, submit_index: SubmissionIndex) -> bool { - self.submission_index - .store(submit_index as _, Ordering::Release); - self.ref_count.is_some() - } - - fn life_count(&self) -> SubmissionIndex { - self.submission_index.load(Ordering::Acquire) as _ - } -} - -#[derive(Clone, Debug)] -struct Stored { - value: id::Valid, - ref_count: RefCount, + opt } const DOWNLEVEL_WARNING_MESSAGE: &str = "The underlying API or device in use does not \ @@ -352,8 +255,11 @@ macro_rules! define_backend_caller { define_backend_caller! { gfx_if_vulkan, gfx_if_vulkan_hidden, "vulkan" if all(feature = "vulkan", not(target_arch = "wasm32")) } define_backend_caller! { gfx_if_metal, gfx_if_metal_hidden, "metal" if all(feature = "metal", any(target_os = "macos", target_os = "ios")) } define_backend_caller! { gfx_if_dx12, gfx_if_dx12_hidden, "dx12" if all(feature = "dx12", windows) } -define_backend_caller! { gfx_if_dx11, gfx_if_dx11_hidden, "dx11" if all(feature = "dx11", windows) } define_backend_caller! { gfx_if_gles, gfx_if_gles_hidden, "gles" if feature = "gles" } +define_backend_caller! { gfx_if_empty, gfx_if_empty_hidden, "empty" if all( + not(any(feature = "metal", feature = "vulkan", feature = "gles")), + any(target_os = "macos", target_os = "ios"), +) } /// Dispatch on an [`Id`]'s backend to a backend-generic method. /// @@ -384,7 +290,7 @@ define_backend_caller! { gfx_if_gles, gfx_if_gles_hidden, "gles" if feature = "g /// /// ```ignore /// impl<...> Global<...> { -/// pub fn device_create_buffer(&self, ...) -> ... +/// pub fn device_create_buffer(&self, ...) -> ... /// { ... } /// } /// ``` @@ -392,7 +298,7 @@ define_backend_caller! { gfx_if_gles, gfx_if_gles_hidden, "gles" if feature = "g /// That `gfx_select!` call uses `device_id`'s backend to select the right /// backend type `A` for a call to `Global::device_create_buffer`. /// -/// However, there's nothing about this macro that is specific to `global::Global`. +/// However, there's nothing about this macro that is specific to `hub::Global`. /// For example, Firefox's embedding of `wgpu_core` defines its own types with /// methods that take `hal::Api` type parameters. Firefox uses `gfx_select!` to /// dynamically dispatch to the right specialization based on the resource's id. @@ -407,19 +313,32 @@ macro_rules! gfx_select { wgt::Backend::Vulkan => $crate::gfx_if_vulkan!($global.$method::<$crate::api::Vulkan>( $($param),* )), wgt::Backend::Metal => $crate::gfx_if_metal!($global.$method::<$crate::api::Metal>( $($param),* )), wgt::Backend::Dx12 => $crate::gfx_if_dx12!($global.$method::<$crate::api::Dx12>( $($param),* )), - wgt::Backend::Dx11 => $crate::gfx_if_dx11!($global.$method::<$crate::api::Dx11>( $($param),* )), wgt::Backend::Gl => $crate::gfx_if_gles!($global.$method::<$crate::api::Gles>( $($param),+ )), + wgt::Backend::Empty => $crate::gfx_if_empty!($global.$method::<$crate::api::Empty>( $($param),+ )), other => panic!("Unexpected backend {:?}", other), } }; } -/// Fast hash map used internally. -type FastHashMap = - std::collections::HashMap>; -/// Fast hash set used internally. -type FastHashSet = - std::collections::HashSet>; +#[cfg(feature = "api_log_info")] +macro_rules! api_log { + ($($arg:tt)+) => (log::info!($($arg)+)) +} +#[cfg(not(feature = "api_log_info"))] +macro_rules! api_log { + ($($arg:tt)+) => (log::trace!($($arg)+)) +} +pub(crate) use api_log; + +#[cfg(feature = "resource_log_info")] +macro_rules! resource_log { + ($($arg:tt)+) => (log::info!($($arg)+)) +} +#[cfg(not(feature = "resource_log_info"))] +macro_rules! resource_log { + ($($arg:tt)+) => (log::trace!($($arg)+)) +} +pub(crate) use resource_log; #[inline] pub(crate) fn get_lowest_common_denom(a: u32, b: u32) -> u32 { diff --git a/wgpu-core/src/pipeline.rs b/wgpu-core/src/pipeline.rs index da06b652ea..6e2998235d 100644 --- a/wgpu-core/src/pipeline.rs +++ b/wgpu-core/src/pipeline.rs @@ -1,13 +1,16 @@ +#[cfg(feature = "trace")] +use crate::device::trace; use crate::{ - binding_model::{CreateBindGroupLayoutError, CreatePipelineLayoutError}, + binding_model::{CreateBindGroupLayoutError, CreatePipelineLayoutError, PipelineLayout}, command::ColorAttachmentError, - device::{DeviceError, MissingDownlevelFlags, MissingFeatures, RenderPassContext}, - id::{DeviceId, PipelineLayoutId, ShaderModuleId}, - resource::Resource, - validation, Label, LifeGuard, Stored, + device::{Device, DeviceError, MissingDownlevelFlags, MissingFeatures, RenderPassContext}, + hal_api::HalApi, + id::{ComputePipelineId, PipelineLayoutId, RenderPipelineId, ShaderModuleId}, + resource::{Resource, ResourceInfo, ResourceType}, + resource_log, validation, Label, }; use arrayvec::ArrayVec; -use std::{borrow::Cow, error::Error, fmt, marker::PhantomData, num::NonZeroU32}; +use std::{borrow::Cow, error::Error, fmt, marker::PhantomData, num::NonZeroU32, sync::Arc}; use thiserror::Error; /// Information about buffer bindings, which @@ -40,26 +43,49 @@ pub struct ShaderModuleDescriptor<'a> { } #[derive(Debug)] -pub struct ShaderModule { - pub(crate) raw: A::ShaderModule, - pub(crate) device_id: Stored, +pub struct ShaderModule { + pub(crate) raw: Option, + pub(crate) device: Arc>, pub(crate) interface: Option, - #[cfg(debug_assertions)] + pub(crate) info: ResourceInfo, pub(crate) label: String, } -impl Resource for ShaderModule { - const TYPE: &'static str = "ShaderModule"; +impl Drop for ShaderModule { + fn drop(&mut self) { + if let Some(raw) = self.raw.take() { + resource_log!("Destroy raw ShaderModule {:?}", self.info.label()); + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyShaderModule(self.info.id())); + } + unsafe { + use hal::Device; + self.device.raw().destroy_shader_module(raw); + } + } + } +} + +impl Resource for ShaderModule { + const TYPE: ResourceType = "ShaderModule"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info + } - fn life_guard(&self) -> &LifeGuard { - unreachable!() + fn label(&self) -> String { + self.label.clone() } +} - fn label(&self) -> &str { - #[cfg(debug_assertions)] - return &self.label; - #[cfg(not(debug_assertions))] - return ""; +impl ShaderModule { + pub(crate) fn raw(&self) -> &A::ShaderModule { + self.raw.as_ref().unwrap() } } @@ -212,19 +238,48 @@ pub enum CreateComputePipelineError { } #[derive(Debug)] -pub struct ComputePipeline { - pub(crate) raw: A::ComputePipeline, - pub(crate) layout_id: Stored, - pub(crate) device_id: Stored, +pub struct ComputePipeline { + pub(crate) raw: Option, + pub(crate) layout: Arc>, + pub(crate) device: Arc>, + pub(crate) _shader_module: Arc>, pub(crate) late_sized_buffer_groups: ArrayVec, - pub(crate) life_guard: LifeGuard, + pub(crate) info: ResourceInfo, +} + +impl Drop for ComputePipeline { + fn drop(&mut self) { + if let Some(raw) = self.raw.take() { + resource_log!("Destroy raw ComputePipeline {:?}", self.info.label()); + + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyComputePipeline(self.info.id())); + } + + unsafe { + use hal::Device; + self.device.raw().destroy_compute_pipeline(raw); + } + } + } } -impl Resource for ComputePipeline { - const TYPE: &'static str = "ComputePipeline"; +impl Resource for ComputePipeline { + const TYPE: ResourceType = "ComputePipeline"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info + } +} - fn life_guard(&self) -> &LifeGuard { - &self.life_guard +impl ComputePipeline { + pub(crate) fn raw(&self) -> &A::ComputePipeline { + self.raw.as_ref().unwrap() } } @@ -299,8 +354,8 @@ pub enum ColorStateError { FormatNotBlendable(wgt::TextureFormat), #[error("Format {0:?} does not have a color aspect")] FormatNotColor(wgt::TextureFormat), - #[error("Format {0:?} can't be multisampled")] - FormatNotMultisampled(wgt::TextureFormat), + #[error("Sample count {0} is not supported by format {1:?} on this device. The WebGPU spec guarentees {2:?} samples are supported by this format. With the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature your device supports {3:?}.")] + InvalidSampleCount(u32, wgt::TextureFormat, Vec, Vec), #[error("Output format {pipeline} is incompatible with the shader {shader}")] IncompatibleFormat { pipeline: validation::NumericType, @@ -321,8 +376,8 @@ pub enum DepthStencilStateError { FormatNotDepth(wgt::TextureFormat), #[error("Format {0:?} does not have a stencil aspect, but stencil test/write is enabled")] FormatNotStencil(wgt::TextureFormat), - #[error("Format {0:?} can't be multisampled")] - FormatNotMultisampled(wgt::TextureFormat), + #[error("Sample count {0} is not supported by format {1:?} on this device. The WebGPU spec guarentees {2:?} samples are supported by this format. With the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature your device supports {3:?}.")] + InvalidSampleCount(u32, wgt::TextureFormat, Vec, Vec), } #[derive(Clone, Debug, Error)] @@ -384,6 +439,15 @@ pub enum CreateRenderPipelineError { }, #[error("In the provided shader, the type given for group {group} binding {binding} has a size of {size}. As the device does not support `DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED`, the type must have a size that is a multiple of 16 bytes.")] UnalignedShader { group: u32, binding: u32, size: u64 }, + #[error("Using the blend factor {factor:?} for render target {target} is not possible. Only the first render target may be used when dual-source blending.")] + BlendFactorOnUnsupportedTarget { + factor: wgt::BlendFactor, + target: u32, + }, + #[error("Pipeline expects the shader entry point to make use of dual-source blending.")] + PipelineExpectsShaderToUseDualSourceBlending, + #[error("Shader entry point expects the pipeline to make use of dual-source blending.")] + ShaderExpectsPipelineToUseDualSourceBlending, } bitflags::bitflags! { @@ -417,22 +481,52 @@ impl Default for VertexStep { } #[derive(Debug)] -pub struct RenderPipeline { - pub(crate) raw: A::RenderPipeline, - pub(crate) layout_id: Stored, - pub(crate) device_id: Stored, +pub struct RenderPipeline { + pub(crate) raw: Option, + pub(crate) device: Arc>, + pub(crate) layout: Arc>, + pub(crate) _shader_modules: + ArrayVec>, { hal::MAX_CONCURRENT_SHADER_STAGES }>, pub(crate) pass_context: RenderPassContext, pub(crate) flags: PipelineFlags, pub(crate) strip_index_format: Option, pub(crate) vertex_steps: Vec, pub(crate) late_sized_buffer_groups: ArrayVec, - pub(crate) life_guard: LifeGuard, + pub(crate) info: ResourceInfo, +} + +impl Drop for RenderPipeline { + fn drop(&mut self) { + if let Some(raw) = self.raw.take() { + resource_log!("Destroy raw RenderPipeline {:?}", self.info.label()); + + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyRenderPipeline(self.info.id())); + } + + unsafe { + use hal::Device; + self.device.raw().destroy_render_pipeline(raw); + } + } + } } -impl Resource for RenderPipeline { - const TYPE: &'static str = "RenderPipeline"; +impl Resource for RenderPipeline { + const TYPE: ResourceType = "RenderPipeline"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info + } +} - fn life_guard(&self) -> &LifeGuard { - &self.life_guard +impl RenderPipeline { + pub(crate) fn raw(&self) -> &A::RenderPipeline { + self.raw.as_ref().unwrap() } } diff --git a/wgpu-core/src/pool.rs b/wgpu-core/src/pool.rs new file mode 100644 index 0000000000..ddf45fbcb3 --- /dev/null +++ b/wgpu-core/src/pool.rs @@ -0,0 +1,312 @@ +use std::{ + collections::{hash_map::Entry, HashMap}, + hash::Hash, + sync::{Arc, Weak}, +}; + +use once_cell::sync::OnceCell; +use parking_lot::Mutex; + +use crate::{PreHashedKey, PreHashedMap}; + +type SlotInner = Weak; +type ResourcePoolSlot = Arc>>; + +pub struct ResourcePool { + // We use a pre-hashed map as we never actually need to read the keys. + // + // This additionally allows us to not need to hash more than once on get_or_init. + inner: Mutex>>, +} + +impl ResourcePool { + pub fn new() -> Self { + Self { + inner: Mutex::new(HashMap::default()), + } + } + + /// Get a resource from the pool with the given entry map, or create a new one if it doesn't exist using the given constructor. + /// + /// Behaves such that only one resource will be created for each unique entry map at any one time. + pub fn get_or_init(&self, key: K, constructor: F) -> Result, E> + where + F: FnOnce(K) -> Result, E>, + { + // Hash the key outside of the lock. + let hashed_key = PreHashedKey::from_key(&key); + + // We can't prove at compile time that these will only ever be consumed once, + // so we need to do the check at runtime. + let mut key = Some(key); + let mut constructor = Some(constructor); + + 'race: loop { + let mut map_guard = self.inner.lock(); + + let entry = match map_guard.entry(hashed_key) { + // An entry exists for this resource. + // + // We know that either: + // - The resource is still alive, and Weak::upgrade will succeed. + // - The resource is in the process of being dropped, and Weak::upgrade will fail. + // + // The entry will never be empty while the BGL is still alive. + Entry::Occupied(entry) => Arc::clone(entry.get()), + // No entry exists for this resource. + // + // We know that the resource is not alive, so we can create a new entry. + Entry::Vacant(entry) => Arc::clone(entry.insert(Arc::new(OnceCell::new()))), + }; + + drop(map_guard); + + // Some other thread may beat us to initializing the entry, but OnceCell guarentees that only one thread + // will actually initialize the entry. + // + // We pass the strong reference outside of the closure to keep it alive while we're the only one keeping a reference to it. + let mut strong = None; + let weak = entry.get_or_try_init(|| { + let strong_inner = constructor.take().unwrap()(key.take().unwrap())?; + let weak = Arc::downgrade(&strong_inner); + strong = Some(strong_inner); + Ok(weak) + })?; + + // If strong is Some, that means we just initialized the entry, so we can just return it. + if let Some(strong) = strong { + return Ok(strong); + } + + // The entry was already initialized by someone else, so we need to try to upgrade it. + if let Some(strong) = weak.upgrade() { + // We succeed, the resource is still alive, just return that. + return Ok(strong); + } + + // The resource is in the process of being dropped, because upgrade failed. The entry still exists in the map, but it points to nothing. + // + // We're in a race with the drop implementation of the resource, so lets just go around again. When we go around again: + // - If the entry exists, we might need to go around a few more times. + // - If the entry doesn't exist, we'll create a new one. + continue 'race; + } + } + + /// Remove the given entry map from the pool. + /// + /// Must *only* be called in the Drop impl of [`BindGroupLayout`]. + pub fn remove(&self, key: &K) { + let hashed_key = PreHashedKey::from_key(key); + + let mut map_guard = self.inner.lock(); + + // Weak::upgrade will be failing long before this code is called. All threads trying to access the resource will be spinning, + // waiting for the entry to be removed. It is safe to remove the entry from the map. + map_guard.remove(&hashed_key); + } +} + +#[cfg(test)] +mod tests { + use std::sync::{ + atomic::{AtomicU32, Ordering}, + Barrier, + }; + + use super::*; + + #[test] + fn deduplication() { + let pool = ResourcePool::::new(); + + let mut counter = 0_u32; + + let arc1 = pool + .get_or_init::<_, ()>(0, |key| { + counter += 1; + Ok(Arc::new(key)) + }) + .unwrap(); + + assert_eq!(*arc1, 0); + assert_eq!(counter, 1); + + let arc2 = pool + .get_or_init::<_, ()>(0, |key| { + counter += 1; + Ok(Arc::new(key)) + }) + .unwrap(); + + assert!(Arc::ptr_eq(&arc1, &arc2)); + assert_eq!(*arc2, 0); + assert_eq!(counter, 1); + + drop(arc1); + drop(arc2); + pool.remove(&0); + + let arc3 = pool + .get_or_init::<_, ()>(0, |key| { + counter += 1; + Ok(Arc::new(key)) + }) + .unwrap(); + + assert_eq!(*arc3, 0); + assert_eq!(counter, 2); + } + + // Test name has "2_threads" in the name so nextest reserves two threads for it. + #[test] + fn concurrent_creation_2_threads() { + struct Resources { + pool: ResourcePool, + counter: AtomicU32, + barrier: Barrier, + } + + let resources = Arc::new(Resources { + pool: ResourcePool::::new(), + counter: AtomicU32::new(0), + barrier: Barrier::new(2), + }); + + // Like all races, this is not inherently guaranteed to work, but in practice it should work fine. + // + // To validate the expected order of events, we've put print statements in the code, indicating when each thread is at a certain point. + // The output will look something like this if the test is working as expected: + // + // ``` + // 0: prewait + // 1: prewait + // 1: postwait + // 0: postwait + // 1: init + // 1: postget + // 0: postget + // ``` + fn thread_inner(idx: u8, resources: &Resources) -> Arc { + eprintln!("{idx}: prewait"); + + // Once this returns, both threads should hit get_or_init at about the same time, + // allowing us to actually test concurrent creation. + // + // Like all races, this is not inherently guaranteed to work, but in practice it should work fine. + resources.barrier.wait(); + + eprintln!("{idx}: postwait"); + + let ret = resources + .pool + .get_or_init::<_, ()>(0, |key| { + eprintln!("{idx}: init"); + + // Simulate long running constructor, ensuring that both threads will be in get_or_init. + std::thread::sleep(std::time::Duration::from_millis(250)); + + resources.counter.fetch_add(1, Ordering::SeqCst); + + Ok(Arc::new(key)) + }) + .unwrap(); + + eprintln!("{idx}: postget"); + + ret + } + + let thread1 = std::thread::spawn({ + let resource_clone = Arc::clone(&resources); + move || thread_inner(1, &resource_clone) + }); + + let arc0 = thread_inner(0, &resources); + + assert_eq!(resources.counter.load(Ordering::Acquire), 1); + + let arc1 = thread1.join().unwrap(); + + assert!(Arc::ptr_eq(&arc0, &arc1)); + } + + // Test name has "2_threads" in the name so nextest reserves two threads for it. + #[test] + fn create_while_drop_2_threads() { + struct Resources { + pool: ResourcePool, + barrier: Barrier, + } + + let resources = Arc::new(Resources { + pool: ResourcePool::::new(), + barrier: Barrier::new(2), + }); + + // Like all races, this is not inherently guaranteed to work, but in practice it should work fine. + // + // To validate the expected order of events, we've put print statements in the code, indicating when each thread is at a certain point. + // The output will look something like this if the test is working as expected: + // + // ``` + // 0: prewait + // 1: prewait + // 1: postwait + // 0: postwait + // 1: postsleep + // 1: removal + // 0: postget + // ``` + // + // The last two _may_ be flipped. + + let existing_entry = resources + .pool + .get_or_init::<_, ()>(0, |key| Ok(Arc::new(key))) + .unwrap(); + + // Drop the entry, but do _not_ remove it from the pool. + // This simulates the situation where the resource arc has been dropped, but the Drop implementation + // has not yet run, which calls remove. + drop(existing_entry); + + fn thread0_inner(resources: &Resources) { + eprintln!("0: prewait"); + resources.barrier.wait(); + + eprintln!("0: postwait"); + // We try to create a new entry, but the entry already exists. + // + // As Arc::upgrade is failing, we will just keep spinning until remove is called. + resources + .pool + .get_or_init::<_, ()>(0, |key| Ok(Arc::new(key))) + .unwrap(); + eprintln!("0: postget"); + } + + fn thread1_inner(resources: &Resources) { + eprintln!("1: prewait"); + resources.barrier.wait(); + + eprintln!("1: postwait"); + // We wait a little bit, making sure that thread0_inner has started spinning. + std::thread::sleep(std::time::Duration::from_millis(250)); + eprintln!("1: postsleep"); + + // We remove the entry from the pool, allowing thread0_inner to re-create. + resources.pool.remove(&0); + eprintln!("1: removal"); + } + + let thread1 = std::thread::spawn({ + let resource_clone = Arc::clone(&resources); + move || thread1_inner(&resource_clone) + }); + + thread0_inner(&resources); + + thread1.join().unwrap(); + } +} diff --git a/wgpu-core/src/present.rs b/wgpu-core/src/present.rs index 1303769d29..00dc049679 100644 --- a/wgpu-core/src/present.rs +++ b/wgpu-core/src/present.rs @@ -9,23 +9,30 @@ When this texture is presented, we remove it from the device tracker as well as extract it from the hub. !*/ -use std::borrow::Borrow; +use std::{ + borrow::Borrow, + sync::atomic::{AtomicBool, Ordering}, +}; #[cfg(feature = "trace")] use crate::device::trace::Action; use crate::{ conv, - device::{DeviceError, MissingDownlevelFlags}, + device::any_device::AnyDevice, + device::{DeviceError, MissingDownlevelFlags, WaitIdleError}, global::Global, hal_api::HalApi, - hub::Token, - id::{DeviceId, SurfaceId, TextureId, Valid}, + hal_label, + id::{SurfaceId, TextureId}, identity::{GlobalIdentityHandlerFactory, Input}, init_tracker::TextureInitTracker, - resource, track, LifeGuard, Stored, + resource::{self, ResourceInfo}, + snatch::Snatchable, + track, }; use hal::{Queue as _, Surface as _}; +use parking_lot::RwLock; use thiserror::Error; use wgt::SurfaceStatus as Status; @@ -34,17 +41,11 @@ pub const DESIRED_NUM_FRAMES: u32 = 3; #[derive(Debug)] pub(crate) struct Presentation { - pub(crate) device_id: Stored, + pub(crate) device: AnyDevice, pub(crate) config: wgt::SurfaceConfiguration>, #[allow(unused)] pub(crate) num_frames: u32, - pub(crate) acquired_texture: Option>, -} - -impl Presentation { - pub(crate) fn backend(&self) -> wgt::Backend { - crate::id::TypedId::unzip(self.device_id.value.0).2 - } + pub(crate) acquired_texture: Option, } #[derive(Clone, Debug, Error)] @@ -77,6 +78,12 @@ pub enum ConfigureSurfaceError { PreviousOutputExists, #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")] ZeroArea, + #[error("`Surface` width and height must be within the maximum supported texture size. Requested was ({width}, {height}), maximum extent is {max_texture_dimension_2d}.")] + TooLarge { + width: u32, + height: u32, + max_texture_dimension_2d: u32, + }, #[error("Surface does not support the adapter's queue family")] UnsupportedQueueFamily, #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")] @@ -96,6 +103,18 @@ pub enum ConfigureSurfaceError { }, #[error("Requested usage is not supported")] UnsupportedUsage, + #[error("Gpu got stuck :(")] + StuckGpu, +} + +impl From for ConfigureSurfaceError { + fn from(e: WaitIdleError) -> Self { + match e { + WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d), + WaitIdleError::WrongSubmissionIndex(..) => unreachable!(), + WaitIdleError::StuckGpu => ConfigureSurfaceError::StuckGpu, + } + } } #[repr(C)] @@ -114,26 +133,31 @@ impl Global { profiling::scope!("SwapChain::get_next_texture"); let hub = A::hub(self); - let mut token = Token::root(); - let fid = hub.textures.prepare(texture_id_in); - let (mut surface_guard, mut token) = self.surfaces.write(&mut token); - let surface = surface_guard - .get_mut(surface_id) + let fid = hub.textures.prepare::(texture_id_in); + + let surface = self + .surfaces + .get(surface_id) .map_err(|_| SurfaceError::Invalid)?; - let (device_guard, mut token) = hub.devices.read(&mut token); - let (device, config) = match surface.presentation { - Some(ref present) => { - let device = &device_guard[present.device_id.value]; - (device, present.config.clone()) + let (device, config) = if let Some(ref present) = *surface.presentation.lock() { + match present.device.downcast_clone::() { + Some(device) => { + if !device.is_valid() { + return Err(DeviceError::Lost.into()); + } + (device, present.config.clone()) + } + None => return Err(SurfaceError::NotConfigured), } - None => return Err(SurfaceError::NotConfigured), + } else { + return Err(SurfaceError::NotConfigured); }; #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(Action::GetSurfaceTexture { + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(Action::GetSurfaceTexture { id: fid.id(), parent_id: surface_id, }); @@ -141,7 +165,7 @@ impl Global { #[cfg(not(feature = "trace"))] let _ = device; - let suf = A::get_surface_mut(surface); + let suf = A::get_surface(surface.as_ref()); let (texture_id, status) = match unsafe { suf.unwrap() .raw @@ -150,92 +174,90 @@ impl Global { ))) } { Ok(Some(ast)) => { + let texture_desc = wgt::TextureDescriptor { + label: (), + size: wgt::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + sample_count: 1, + mip_level_count: 1, + format: config.format, + dimension: wgt::TextureDimension::D2, + usage: config.usage, + view_formats: config.view_formats, + }; + let hal_usage = conv::map_texture_usage(config.usage, config.format.into()); + let format_features = wgt::TextureFormatFeatures { + allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT, + flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4 + | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE, + }; let clear_view_desc = hal::TextureViewDescriptor { - label: Some("(wgpu internal) clear surface texture view"), + label: hal_label( + Some("(wgpu internal) clear surface texture view"), + self.instance.flags, + ), format: config.format, dimension: wgt::TextureViewDimension::D2, usage: hal::TextureUses::COLOR_TARGET, range: wgt::ImageSubresourceRange::default(), }; - let mut clear_views = smallvec::SmallVec::new(); - clear_views.push( - unsafe { - hal::Device::create_texture_view( - &device.raw, - ast.texture.borrow(), - &clear_view_desc, - ) - } - .map_err(DeviceError::from)?, - ); + let clear_view = unsafe { + hal::Device::create_texture_view( + device.raw(), + ast.texture.borrow(), + &clear_view_desc, + ) + } + .map_err(DeviceError::from)?; - let present = surface.presentation.as_mut().unwrap(); + let mut presentation = surface.presentation.lock(); + let present = presentation.as_mut().unwrap(); let texture = resource::Texture { - inner: resource::TextureInner::Surface { - raw: ast.texture, - parent_id: Valid(surface_id), - has_work: false, - }, - device_id: present.device_id.clone(), - desc: wgt::TextureDescriptor { - label: (), - size: wgt::Extent3d { - width: config.width, - height: config.height, - depth_or_array_layers: 1, - }, - sample_count: 1, - mip_level_count: 1, - format: config.format, - dimension: wgt::TextureDimension::D2, - usage: config.usage, - view_formats: config.view_formats, - }, - hal_usage: conv::map_texture_usage(config.usage, config.format.into()), - format_features: wgt::TextureFormatFeatures { - allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT, - flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4 - | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE, - }, - initialization_status: TextureInitTracker::new(1, 1), + inner: Snatchable::new(resource::TextureInner::Surface { + raw: Some(ast.texture), + parent_id: surface_id, + has_work: AtomicBool::new(false), + }), + device: device.clone(), + desc: texture_desc, + hal_usage, + format_features, + initialization_status: RwLock::new(TextureInitTracker::new(1, 1)), full_range: track::TextureSelector { layers: 0..1, mips: 0..1, }, - life_guard: LifeGuard::new(""), - clear_mode: resource::TextureClearMode::RenderPass { - clear_views, - is_color: true, - }, + info: ResourceInfo::new(""), + clear_mode: RwLock::new(resource::TextureClearMode::Surface { + clear_view: Some(clear_view), + }), }; - let ref_count = texture.life_guard.add_ref(); - let id = fid.assign(texture, &mut token); + let (id, resource) = fid.assign(texture); + log::debug!("Created CURRENT Surface Texture {:?}", id); { // register it in the device tracker as uninitialized let mut trackers = device.trackers.lock(); - trackers.textures.insert_single( - id.0, - ref_count.clone(), - hal::TextureUses::UNINITIALIZED, - ); + trackers + .textures + .insert_single(id, resource, hal::TextureUses::UNINITIALIZED); } if present.acquired_texture.is_some() { return Err(SurfaceError::AlreadyAcquired); } - present.acquired_texture = Some(Stored { - value: id, - ref_count, - }); + present.acquired_texture = Some(id); let status = if ast.suboptimal { Status::Suboptimal } else { Status::Good }; - (Some(id.0), status) + (Some(id), status) } Ok(None) => (None, Status::Timeout), Err(err) => ( @@ -264,24 +286,28 @@ impl Global { profiling::scope!("SwapChain::present"); let hub = A::hub(self); - let mut token = Token::root(); - let (mut surface_guard, mut token) = self.surfaces.write(&mut token); - let surface = surface_guard - .get_mut(surface_id) + let surface = self + .surfaces + .get(surface_id) .map_err(|_| SurfaceError::Invalid)?; - let (mut device_guard, mut token) = hub.devices.write(&mut token); - let present = match surface.presentation { - Some(ref mut present) => present, + let mut presentation = surface.presentation.lock(); + let present = match presentation.as_mut() { + Some(present) => present, None => return Err(SurfaceError::NotConfigured), }; - let device = &mut device_guard[present.device_id.value]; + let device = present.device.downcast_ref::().unwrap(); + if !device.is_valid() { + return Err(DeviceError::Lost.into()); + } + let queue_id = device.queue_id.read().unwrap(); + let queue = hub.queues.get(queue_id).unwrap(); #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(Action::Present(surface_id)); + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(Action::Present(surface_id)); } let result = { @@ -294,33 +320,41 @@ impl Global { // and now we are moving it away. log::debug!( "Removing swapchain texture {:?} from the device tracker", - texture_id.value + texture_id ); - device.trackers.lock().textures.remove(texture_id.value); + device.trackers.lock().textures.remove(texture_id); - let (texture, _) = hub.textures.unregister(texture_id.value.0, &mut token); + let texture = hub.textures.unregister(texture_id); if let Some(texture) = texture { - texture.clear_mode.destroy_clear_views(&device.raw); + let mut exclusive_snatch_guard = device.snatchable_lock.write(); + let suf = A::get_surface(&surface); + let mut inner = texture.inner_mut(&mut exclusive_snatch_guard); + let inner = inner.as_mut().unwrap(); - let suf = A::get_surface_mut(surface); - match texture.inner { + match *inner { resource::TextureInner::Surface { - raw, - parent_id, - has_work, + ref mut raw, + ref parent_id, + ref has_work, } => { - if surface_id != parent_id.0 { + if surface_id != *parent_id { log::error!("Presented frame is from a different surface"); Err(hal::SurfaceError::Lost) - } else if !has_work { + } else if !has_work.load(Ordering::Relaxed) { log::error!("No work has been submitted for this frame"); - unsafe { suf.unwrap().raw.discard_texture(raw) }; + unsafe { suf.unwrap().raw.discard_texture(raw.take().unwrap()) }; Err(hal::SurfaceError::Outdated) } else { - unsafe { device.queue.present(&mut suf.unwrap().raw, raw) } + unsafe { + queue + .raw + .as_ref() + .unwrap() + .present(&suf.unwrap().raw, raw.take().unwrap()) + } } } - resource::TextureInner::Native { .. } => unreachable!(), + _ => unreachable!(), } } else { Err(hal::SurfaceError::Outdated) //TODO? @@ -350,24 +384,25 @@ impl Global { profiling::scope!("SwapChain::discard"); let hub = A::hub(self); - let mut token = Token::root(); - let (mut surface_guard, mut token) = self.surfaces.write(&mut token); - let surface = surface_guard - .get_mut(surface_id) + let surface = self + .surfaces + .get(surface_id) .map_err(|_| SurfaceError::Invalid)?; - let (mut device_guard, mut token) = hub.devices.write(&mut token); - - let present = match surface.presentation { - Some(ref mut present) => present, + let mut presentation = surface.presentation.lock(); + let present = match presentation.as_mut() { + Some(present) => present, None => return Err(SurfaceError::NotConfigured), }; - let device = &mut device_guard[present.device_id.value]; + let device = present.device.downcast_ref::().unwrap(); + if !device.is_valid() { + return Err(DeviceError::Lost.into()); + } #[cfg(feature = "trace")] - if let Some(ref trace) = device.trace { - trace.lock().add(Action::DiscardSurfaceTexture(surface_id)); + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(Action::DiscardSurfaceTexture(surface_id)); } { @@ -380,28 +415,27 @@ impl Global { // and now we are moving it away. log::debug!( "Removing swapchain texture {:?} from the device tracker", - texture_id.value + texture_id ); - device.trackers.lock().textures.remove(texture_id.value); + device.trackers.lock().textures.remove(texture_id); - let (texture, _) = hub.textures.unregister(texture_id.value.0, &mut token); + let texture = hub.textures.unregister(texture_id); if let Some(texture) = texture { - texture.clear_mode.destroy_clear_views(&device.raw); - - let suf = A::get_surface_mut(surface); - match texture.inner { + let suf = A::get_surface(&surface); + let exclusive_snatch_guard = device.snatchable_lock.write(); + match texture.inner.snatch(exclusive_snatch_guard).unwrap() { resource::TextureInner::Surface { - raw, + mut raw, parent_id, has_work: _, } => { - if surface_id == parent_id.0 { - unsafe { suf.unwrap().raw.discard_texture(raw) }; + if surface_id == parent_id { + unsafe { suf.unwrap().raw.discard_texture(raw.take().unwrap()) }; } else { log::warn!("Surface texture is outdated"); } } - resource::TextureInner::Native { .. } => unreachable!(), + _ => unreachable!(), } } } diff --git a/wgpu-core/src/registry.rs b/wgpu-core/src/registry.rs index ef47f7d244..d4a69321ae 100644 --- a/wgpu-core/src/registry.rs +++ b/wgpu-core/src/registry.rs @@ -1,57 +1,71 @@ -use std::marker::PhantomData; +use std::sync::Arc; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use wgt::Backend; use crate::{ - hub::{Access, Token}, id, - identity::{IdentityHandler, IdentityHandlerFactory}, + identity::{IdentityHandlerFactory, IdentityManager}, resource::Resource, - storage::Storage, + storage::{Element, InvalidId, Storage}, }; +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct RegistryReport { + pub num_allocated: usize, + pub num_kept_from_user: usize, + pub num_released_from_user: usize, + pub num_error: usize, + pub element_size: usize, +} + +impl RegistryReport { + pub fn is_empty(&self) -> bool { + self.num_allocated + self.num_kept_from_user == 0 + } +} + +/// Registry is the primary holder of each resource type +/// Every resource is now arcanized so the last arc released +/// will in the end free the memory and release the inner raw resource +/// +/// Registry act as the main entry point to keep resource alive +/// when created and released from user land code +/// +/// A resource may still be alive when released from user land code +/// if it's used in active submission or anyway kept alive from +/// any other dependent resource +/// #[derive(Debug)] -pub struct Registry> { - identity: F::Filter, - pub(crate) data: RwLock>, +pub struct Registry> { + identity: Arc>, + storage: RwLock>, backend: Backend, } -impl> Registry { - pub(crate) fn new(backend: Backend, factory: &F) -> Self { +impl> Registry { + pub(crate) fn new>(backend: Backend, factory: &F) -> Self { Self { identity: factory.spawn(), - data: RwLock::new(Storage { - map: Vec::new(), - kind: T::TYPE, - _phantom: PhantomData, - }), + storage: RwLock::new(Storage::new()), backend, } } - pub(crate) fn without_backend(factory: &F, kind: &'static str) -> Self { - Self { - identity: factory.spawn(), - data: RwLock::new(Storage { - map: Vec::new(), - kind, - _phantom: PhantomData, - }), - backend: Backend::Empty, - } + pub(crate) fn without_backend>(factory: &F) -> Self { + Self::new(Backend::Empty, factory) } } #[must_use] -pub(crate) struct FutureId<'a, I: id::TypedId, T> { +pub(crate) struct FutureId<'a, I: id::TypedId, T: Resource> { id: I, + identity: Arc>, data: &'a RwLock>, } -impl FutureId<'_, I, T> { - #[cfg(feature = "trace")] +impl> FutureId<'_, I, T> { + #[allow(dead_code)] pub fn id(&self) -> I { self.id } @@ -60,134 +74,104 @@ impl FutureId<'_, I, T> { self.id } - pub fn assign<'a, A: Access>(self, value: T, _: &'a mut Token) -> id::Valid { - self.data.write().insert(self.id, value); - id::Valid(self.id) + pub fn init(&self, mut value: T) -> Arc { + value.as_info_mut().set_id(self.id, &self.identity); + Arc::new(value) + } + + /// Assign a new resource to this ID. + /// + /// Registers it with the registry, and fills out the resource info. + pub fn assign(self, value: T) -> (I, Arc) { + let mut data = self.data.write(); + let inited_val = self.init(value); + data.insert(self.id, inited_val.clone()); + (self.id, inited_val) } - pub fn assign_error<'a, A: Access>(self, label: &str, _: &'a mut Token) -> I { + /// Assign an existing resource to a new ID. + /// + /// Registers it with the registry. + /// + /// This _will_ leak the ID, and it will not be recycled again. + /// See https://github.com/gfx-rs/wgpu/issues/4912. + pub fn assign_existing(self, value: &Arc) -> I { + let mut data = self.data.write(); + debug_assert!(!data.contains(self.id)); + data.insert(self.id, value.clone()); + self.id + } + + pub fn assign_error(self, label: &str) -> I { self.data.write().insert_error(self.id, label); self.id } } -impl> Registry { - pub(crate) fn prepare( - &self, - id_in: >::Input, - ) -> FutureId { +impl> Registry { + pub(crate) fn prepare(&self, id_in: F::Input) -> FutureId + where + F: IdentityHandlerFactory, + { FutureId { - id: self.identity.process(id_in, self.backend), - data: &self.data, + id: if F::autogenerate_ids() { + self.identity.process(self.backend) + } else { + self.identity.mark_as_used(F::input_to_id(id_in)) + }, + identity: self.identity.clone(), + data: &self.storage, } } - - /// Acquire read access to this `Registry`'s contents. - /// - /// The caller must present a mutable reference to a `Token`, - /// for some type `A` that comes before this `Registry`'s resource - /// type `T` in the lock ordering. A `Token` grants - /// permission to lock any field; see [`Token::root`]. - /// - /// Once the read lock is acquired, return a new `Token`, along - /// with a read guard for this `Registry`'s [`Storage`], which can - /// be indexed by id to get at the actual resources. - /// - /// The borrow checker ensures that the caller cannot again access - /// its `Token` until it has dropped both the guard and the - /// `Token`. - /// - /// See the [`Hub`] type for more details on locking. - /// - /// [`Hub`]: crate::hub::Hub - pub(crate) fn read<'a, A: Access>( - &'a self, - _token: &'a mut Token, - ) -> (RwLockReadGuard<'a, Storage>, Token<'a, T>) { - (self.data.read(), Token::new()) + pub(crate) fn request(&self) -> FutureId { + FutureId { + id: self.identity.process(self.backend), + identity: self.identity.clone(), + data: &self.storage, + } } - - /// Acquire write access to this `Registry`'s contents. - /// - /// The caller must present a mutable reference to a `Token`, - /// for some type `A` that comes before this `Registry`'s resource - /// type `T` in the lock ordering. A `Token` grants - /// permission to lock any field; see [`Token::root`]. - /// - /// Once the lock is acquired, return a new `Token`, along with - /// a write guard for this `Registry`'s [`Storage`], which can be - /// indexed by id to get at the actual resources. - /// - /// The borrow checker ensures that the caller cannot again access - /// its `Token` until it has dropped both the guard and the - /// `Token`. - /// - /// See the [`Hub`] type for more details on locking. - /// - /// [`Hub`]: crate::hub::Hub - pub(crate) fn write<'a, A: Access>( - &'a self, - _token: &'a mut Token, - ) -> (RwLockWriteGuard<'a, Storage>, Token<'a, T>) { - (self.data.write(), Token::new()) + pub(crate) fn try_get(&self, id: I) -> Result>, InvalidId> { + self.read().try_get(id).map(|o| o.cloned()) } - - /// Unregister the resource at `id`. - /// - /// The caller must prove that it already holds a write lock for - /// this `Registry` by passing a mutable reference to this - /// `Registry`'s storage, obtained from the write guard returned - /// by a previous call to [`write`], as the `guard` parameter. - pub fn unregister_locked(&self, id: I, guard: &mut Storage) -> Option { - let value = guard.remove(id); - //Note: careful about the order here! - self.identity.free(id); - //Returning None is legal if it's an error ID - value + pub(crate) fn get(&self, id: I) -> Result, InvalidId> { + self.read().get_owned(id) } - - /// Unregister the resource at `id` and return its value, if any. - /// - /// The caller must present a mutable reference to a `Token`, - /// for some type `A` that comes before this `Registry`'s resource - /// type `T` in the lock ordering. - /// - /// This returns a `Token`, but it's almost useless, because it - /// doesn't return a lock guard to go with it: its only effect is - /// to make the token you passed to this function inaccessible. - /// However, the `Token` can be used to satisfy some functions' - /// bureacratic expectations that you will have one available. - /// - /// The borrow checker ensures that the caller cannot again access - /// its `Token` until it has dropped both the guard and the - /// `Token`. - /// - /// See the [`Hub`] type for more details on locking. - /// - /// [`Hub`]: crate::hub::Hub - pub(crate) fn unregister<'a, A: Access>( - &self, - id: I, - _token: &'a mut Token, - ) -> (Option, Token<'a, T>) { - let value = self.data.write().remove(id); - //Note: careful about the order here! - self.identity.free(id); + pub(crate) fn read<'a>(&'a self) -> RwLockReadGuard<'a, Storage> { + self.storage.read() + } + pub(crate) fn write<'a>(&'a self) -> RwLockWriteGuard<'a, Storage> { + self.storage.write() + } + pub fn unregister_locked(&self, id: I, storage: &mut Storage) -> Option> { + storage.remove(id) + } + pub fn force_replace(&self, id: I, mut value: T) { + let mut storage = self.storage.write(); + value.as_info_mut().set_id(id, &self.identity); + storage.force_replace(id, value) + } + pub fn force_replace_with_error(&self, id: I, label: &str) { + let mut storage = self.storage.write(); + storage.remove(id); + storage.insert_error(id, label); + } + pub(crate) fn unregister(&self, id: I) -> Option> { + let value = self.storage.write().remove(id); //Returning None is legal if it's an error ID - (value, Token::new()) + value } pub fn label_for_resource(&self, id: I) -> String { - let guard = self.data.read(); + let guard = self.storage.read(); - let type_name = guard.kind; + let type_name = guard.kind(); match guard.get(id) { Ok(res) => { let label = res.label(); if label.is_empty() { format!("<{}-{:?}>", type_name, id.unzip()) } else { - label.to_string() + label } } Err(_) => format!( @@ -197,4 +181,21 @@ impl> Registry< ), } } + + pub(crate) fn generate_report(&self) -> RegistryReport { + let storage = self.storage.read(); + let mut report = RegistryReport { + element_size: std::mem::size_of::(), + ..Default::default() + }; + report.num_allocated = self.identity.values.lock().count(); + for element in storage.map.iter() { + match *element { + Element::Occupied(..) => report.num_kept_from_user += 1, + Element::Vacant => report.num_released_from_user += 1, + Element::Error(..) => report.num_error += 1, + } + } + report + } } diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index b9361668af..024bdcd2c9 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -1,29 +1,155 @@ +#[cfg(feature = "trace")] +use crate::device::trace; use crate::{ - device::{DeviceError, HostMap, MissingDownlevelFlags, MissingFeatures}, + device::{ + queue, BufferMapPendingClosure, Device, DeviceError, HostMap, MissingDownlevelFlags, + MissingFeatures, + }, global::Global, hal_api::HalApi, - hub::Token, - id::{AdapterId, DeviceId, SurfaceId, TextureId, Valid}, - identity::GlobalIdentityHandlerFactory, + id::{ + AdapterId, BufferId, DeviceId, QuerySetId, SamplerId, StagingBufferId, SurfaceId, + TextureId, TextureViewId, TypedId, + }, + identity::{GlobalIdentityHandlerFactory, IdentityManager}, init_tracker::{BufferInitTracker, TextureInitTracker}, + resource, resource_log, + snatch::{ExclusiveSnatchGuard, SnatchGuard, Snatchable}, track::TextureSelector, validation::MissingBufferUsageError, - Label, LifeGuard, RefCount, Stored, + Label, SubmissionIndex, }; +use hal::CommandEncoder; +use parking_lot::{Mutex, RwLock}; use smallvec::SmallVec; use thiserror::Error; +use wgt::WasmNotSendSync; + +use std::{ + borrow::Borrow, + fmt::Debug, + iter, mem, + ops::Range, + ptr::NonNull, + sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, + }, +}; + +use crate::id::{BlasId, TlasId}; +use std::num::NonZeroU64; + +/// Information about the wgpu-core resource. +/// +/// Each type representing a `wgpu-core` resource, like [`Device`], +/// [`Buffer`], etc., contains a `ResourceInfo` which contains +/// its latest submission index and label. +/// +/// A resource may need to be retained for any of several reasons: +/// and any lifetime logic will be handled by `Arc` refcount +/// +/// - The user may hold a reference to it (via a `wgpu::Buffer`, say). +/// +/// - Other resources may depend on it (a texture view's backing +/// texture, for example). +/// +/// - It may be used by commands sent to the GPU that have not yet +/// finished execution. +/// +/// [`Device`]: crate::device::resource::Device +/// [`Buffer`]: crate::resource::Buffer +#[derive(Debug)] +pub struct ResourceInfo { + id: Option, + identity: Option>>, + /// The index of the last queue submission in which the resource + /// was used. + /// + /// Each queue submission is fenced and assigned an index number + /// sequentially. Thus, when a queue submission completes, we know any + /// resources used in that submission and any lower-numbered submissions are + /// no longer in use by the GPU. + submission_index: AtomicUsize, + + /// The `label` from the descriptor used to create the resource. + pub(crate) label: String, +} + +impl Drop for ResourceInfo { + fn drop(&mut self) { + if let Some(identity) = self.identity.as_ref() { + let id = self.id.as_ref().unwrap(); + identity.free(*id); + } + } +} + +impl ResourceInfo { + #[allow(unused_variables)] + pub(crate) fn new(label: &str) -> Self { + Self { + id: None, + identity: None, + submission_index: AtomicUsize::new(0), + label: label.to_string(), + } + } + + pub(crate) fn label(&self) -> &dyn Debug + where + Id: Debug, + { + if !self.label.is_empty() { + return &self.label; + } + + if let Some(id) = &self.id { + return id; + } + + &"" + } + + pub(crate) fn id(&self) -> Id { + self.id.unwrap() + } + + pub(crate) fn set_id(&mut self, id: Id, identity: &Arc>) { + self.id = Some(id); + self.identity = Some(identity.clone()); + } + + /// Record that this resource will be used by the queue submission with the + /// given index. + pub(crate) fn use_at(&self, submit_index: SubmissionIndex) { + self.submission_index + .store(submit_index as _, Ordering::Release); + } -use std::{borrow::Borrow, num::NonZeroU64, ops::Range, ptr::NonNull}; + pub(crate) fn submission_index(&self) -> SubmissionIndex { + self.submission_index.load(Ordering::Acquire) as _ + } +} -pub trait Resource { - const TYPE: &'static str; - fn life_guard(&self) -> &LifeGuard; - fn label(&self) -> &str { - #[cfg(debug_assertions)] - return &self.life_guard().label; - #[cfg(not(debug_assertions))] - return ""; +pub(crate) type ResourceType = &'static str; + +pub trait Resource: 'static + WasmNotSendSync { + const TYPE: ResourceType; + fn as_info(&self) -> &ResourceInfo; + fn as_info_mut(&mut self) -> &mut ResourceInfo; + fn label(&self) -> String { + self.as_info().label.clone() + } + fn ref_count(self: &Arc) -> usize { + Arc::strong_count(self) + } + fn is_unique(self: &Arc) -> bool { + self.ref_count() == 1 + } + fn is_equal(&self, other: &Self) -> bool { + self.as_info().id().unzip() == other.as_info().id().unzip() } } @@ -60,15 +186,16 @@ pub enum BufferMapAsyncStatus { InvalidUsageFlags, } -pub(crate) enum BufferMapState { +#[derive(Debug)] +pub(crate) enum BufferMapState { /// Mapped at creation. Init { ptr: NonNull, - stage_buffer: A::Buffer, + stage_buffer: Arc>, needs_flush: bool, }, /// Waiting for GPU to be done before mapping - Waiting(BufferPendingMapping), + Waiting(BufferPendingMapping), /// Mapped Active { ptr: NonNull, @@ -79,22 +206,10 @@ pub(crate) enum BufferMapState { Idle, } -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] -unsafe impl Send for BufferMapState {} -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] -unsafe impl Sync for BufferMapState {} +#[cfg(send_sync)] +unsafe impl Send for BufferMapState {} +#[cfg(send_sync)] +unsafe impl Sync for BufferMapState {} #[repr(C)] pub struct BufferMapCallbackC { @@ -102,36 +217,19 @@ pub struct BufferMapCallbackC { pub user_data: *mut u8, } -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] +#[cfg(send_sync)] unsafe impl Send for BufferMapCallbackC {} +#[derive(Debug)] pub struct BufferMapCallback { // We wrap this so creating the enum in the C variant can be unsafe, // allowing our call function to be safe. - inner: Option, + inner: BufferMapCallbackInner, } -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] +#[cfg(send_sync)] type BufferMapCallbackCallback = Box; -#[cfg(not(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -)))] +#[cfg(not(send_sync))] type BufferMapCallbackCallback = Box; enum BufferMapCallbackInner { @@ -139,10 +237,19 @@ enum BufferMapCallbackInner { C { inner: BufferMapCallbackC }, } +impl Debug for BufferMapCallbackInner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + BufferMapCallbackInner::Rust { callback: _ } => f.debug_struct("Rust").finish(), + BufferMapCallbackInner::C { inner: _ } => f.debug_struct("C").finish(), + } + } +} + impl BufferMapCallback { pub fn from_rust(callback: BufferMapCallbackCallback) -> Self { Self { - inner: Some(BufferMapCallbackInner::Rust { callback }), + inner: BufferMapCallbackInner::Rust { callback }, } } @@ -155,17 +262,17 @@ impl BufferMapCallback { /// invoked, which may happen at an unspecified time. pub unsafe fn from_c(inner: BufferMapCallbackC) -> Self { Self { - inner: Some(BufferMapCallbackInner::C { inner }), + inner: BufferMapCallbackInner::C { inner }, } } - pub(crate) fn call(mut self, result: BufferAccessResult) { - match self.inner.take() { - Some(BufferMapCallbackInner::Rust { callback }) => { + pub(crate) fn call(self, result: BufferAccessResult) { + match self.inner { + BufferMapCallbackInner::Rust { callback } => { callback(result); } // SAFETY: the contract of the call to from_c says that this unsafe is sound. - Some(BufferMapCallbackInner::C { inner }) => unsafe { + BufferMapCallbackInner::C { inner } => unsafe { let status = match result { Ok(()) => BufferMapAsyncStatus::Success, Err(BufferAccessError::Device(_)) => BufferMapAsyncStatus::ContextLost, @@ -194,24 +301,14 @@ impl BufferMapCallback { (inner.callback)(status, inner.user_data); }, - None => { - panic!("Map callback invoked twice"); - } - } - } -} - -impl Drop for BufferMapCallback { - fn drop(&mut self) { - if self.inner.is_some() { - panic!("Map callback was leaked"); } } } +#[derive(Debug)] pub struct BufferMapOperation { pub host: HostMap, - pub callback: BufferMapCallback, + pub callback: Option, } #[derive(Clone, Debug, Error)] @@ -263,24 +360,212 @@ pub enum BufferAccessError { } pub type BufferAccessResult = Result<(), BufferAccessError>; -pub(crate) struct BufferPendingMapping { + +#[derive(Debug)] +pub(crate) struct BufferPendingMapping { pub range: Range, pub op: BufferMapOperation, // hold the parent alive while the mapping is active - pub _parent_ref_count: RefCount, + pub _parent_buffer: Arc>, } pub type BufferDescriptor<'a> = wgt::BufferDescriptor>; -pub struct Buffer { - pub(crate) raw: Option, - pub(crate) device_id: Stored, +#[derive(Debug)] +pub struct Buffer { + pub(crate) raw: Snatchable, + pub(crate) device: Arc>, pub(crate) usage: wgt::BufferUsages, pub(crate) size: wgt::BufferAddress, - pub(crate) initialization_status: BufferInitTracker, - pub(crate) sync_mapped_writes: Option, - pub(crate) life_guard: LifeGuard, - pub(crate) map_state: BufferMapState, + pub(crate) initialization_status: RwLock, + pub(crate) sync_mapped_writes: Mutex>, + pub(crate) info: ResourceInfo, + pub(crate) map_state: Mutex>, +} + +impl Drop for Buffer { + fn drop(&mut self) { + if let Some(raw) = self.raw.take() { + resource_log!("Destroy raw Buffer (dropped) {:?}", self.info.label()); + + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyBuffer(self.info.id())); + } + + unsafe { + use hal::Device; + self.device.raw().destroy_buffer(raw); + } + } + } +} + +impl Buffer { + pub(crate) fn raw(&self, guard: &SnatchGuard) -> Option<&A::Buffer> { + self.raw.get(guard) + } + + pub(crate) fn is_destroyed(&self, guard: &SnatchGuard) -> bool { + self.raw.get(guard).is_none() + } + + // Note: This must not be called while holding a lock. + pub(crate) fn unmap(self: &Arc) -> Result<(), BufferAccessError> { + if let Some((mut operation, status)) = self.unmap_inner()? { + if let Some(callback) = operation.callback.take() { + callback.call(status); + } + } + + Ok(()) + } + + fn unmap_inner(self: &Arc) -> Result, BufferAccessError> { + use hal::Device; + + let device = &self.device; + let snatch_guard = device.snatchable_lock.read(); + let raw_buf = self + .raw(&snatch_guard) + .ok_or(BufferAccessError::Destroyed)?; + let buffer_id = self.info.id(); + log::debug!("Buffer {:?} map state -> Idle", buffer_id); + match mem::replace(&mut *self.map_state.lock(), resource::BufferMapState::Idle) { + resource::BufferMapState::Init { + ptr, + stage_buffer, + needs_flush, + } => { + #[cfg(feature = "trace")] + if let Some(ref mut trace) = *device.trace.lock() { + let data = trace.make_binary("bin", unsafe { + std::slice::from_raw_parts(ptr.as_ptr(), self.size as usize) + }); + trace.add(trace::Action::WriteBuffer { + id: buffer_id, + data, + range: 0..self.size, + queued: true, + }); + } + let _ = ptr; + if needs_flush { + unsafe { + device.raw().flush_mapped_ranges( + stage_buffer.raw(&snatch_guard).unwrap(), + iter::once(0..self.size), + ); + } + } + + self.info + .use_at(device.active_submission_index.load(Ordering::Relaxed) + 1); + let region = wgt::BufferSize::new(self.size).map(|size| hal::BufferCopy { + src_offset: 0, + dst_offset: 0, + size, + }); + let transition_src = hal::BufferBarrier { + buffer: stage_buffer.raw(&snatch_guard).unwrap(), + usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, + }; + let transition_dst = hal::BufferBarrier { + buffer: raw_buf, + usage: hal::BufferUses::empty()..hal::BufferUses::COPY_DST, + }; + let mut pending_writes = device.pending_writes.lock(); + let pending_writes = pending_writes.as_mut().unwrap(); + let encoder = pending_writes.activate(); + unsafe { + encoder.transition_buffers( + iter::once(transition_src).chain(iter::once(transition_dst)), + ); + if self.size > 0 { + encoder.copy_buffer_to_buffer( + stage_buffer.raw(&snatch_guard).unwrap(), + raw_buf, + region.into_iter(), + ); + } + } + pending_writes.consume_temp(queue::TempResource::Buffer(stage_buffer)); + pending_writes.dst_buffers.insert(buffer_id, self.clone()); + } + resource::BufferMapState::Idle => { + return Err(BufferAccessError::NotMapped); + } + resource::BufferMapState::Waiting(pending) => { + return Ok(Some((pending.op, Err(BufferAccessError::MapAborted)))); + } + resource::BufferMapState::Active { ptr, range, host } => { + if host == HostMap::Write { + #[cfg(feature = "trace")] + if let Some(ref mut trace) = *device.trace.lock() { + let size = range.end - range.start; + let data = trace.make_binary("bin", unsafe { + std::slice::from_raw_parts(ptr.as_ptr(), size as usize) + }); + trace.add(trace::Action::WriteBuffer { + id: buffer_id, + data, + range: range.clone(), + queued: false, + }); + } + let _ = (ptr, range); + } + unsafe { + device + .raw() + .unmap_buffer(raw_buf) + .map_err(DeviceError::from)? + }; + } + } + Ok(None) + } + + pub(crate) fn destroy(self: &Arc) -> Result<(), DestroyError> { + let device = &self.device; + let buffer_id = self.info.id(); + + #[cfg(feature = "trace")] + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::FreeBuffer(buffer_id)); + } + + let temp = { + let snatch_guard = device.snatchable_lock.write(); + let raw = match self.raw.snatch(snatch_guard) { + Some(raw) => raw, + None => { + return Err(resource::DestroyError::AlreadyDestroyed); + } + }; + + queue::TempResource::DestroyedBuffer(Arc::new(DestroyedBuffer { + raw: Some(raw), + device: Arc::clone(&self.device), + submission_index: self.info.submission_index(), + id: self.info.id.unwrap(), + label: self.info.label.clone(), + })) + }; + + let mut pending_writes = device.pending_writes.lock(); + let pending_writes = pending_writes.as_mut().unwrap(); + if pending_writes.dst_buffers.contains_key(&buffer_id) { + pending_writes.temp_resources.push(temp); + } else { + let last_submit_index = self.info.submission_index(); + device + .lock_life() + .schedule_resource_destruction(temp, last_submit_index); + } + + Ok(()) + } } #[derive(Clone, Debug, Error)] @@ -302,11 +587,53 @@ pub enum CreateBufferError { MissingDownlevelFlags(#[from] MissingDownlevelFlags), } -impl Resource for Buffer { - const TYPE: &'static str = "Buffer"; +impl Resource for Buffer { + const TYPE: ResourceType = "Buffer"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info + } +} + +/// A buffer that has been marked as destroyed and is staged for actual deletion soon. +#[derive(Debug)] +pub struct DestroyedBuffer { + raw: Option, + device: Arc>, + label: String, + pub(crate) id: BufferId, + pub(crate) submission_index: u64, +} + +impl DestroyedBuffer { + pub fn label(&self) -> &dyn Debug { + if !self.label.is_empty() { + return &self.label; + } + + &self.id + } +} + +impl Drop for DestroyedBuffer { + fn drop(&mut self) { + if let Some(raw) = self.raw.take() { + resource_log!("Destroy raw Buffer (destroyed) {:?}", self.label()); + + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyBuffer(self.id)); + } - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + unsafe { + use hal::Device; + self.device.raw().destroy_buffer(raw); + } + } } } @@ -329,109 +656,227 @@ impl Resource for Buffer { /// [`queue_write_staging_buffer`]: Global::queue_write_staging_buffer /// [`queue_write_texture`]: Global::queue_write_texture /// [`Device::pending_writes`]: crate::device::Device -pub struct StagingBuffer { - pub(crate) raw: A::Buffer, +#[derive(Debug)] +pub struct StagingBuffer { + pub(crate) raw: Mutex>, + pub(crate) device: Arc>, pub(crate) size: wgt::BufferAddress, pub(crate) is_coherent: bool, + pub(crate) info: ResourceInfo, } -impl Resource for StagingBuffer { - const TYPE: &'static str = "StagingBuffer"; +impl Drop for StagingBuffer { + fn drop(&mut self) { + if let Some(raw) = self.raw.lock().take() { + resource_log!("Destroy raw StagingBuffer {:?}", self.info.label()); + unsafe { + use hal::Device; + self.device.raw().destroy_buffer(raw); + } + } + } +} + +impl Resource for StagingBuffer { + const TYPE: ResourceType = "StagingBuffer"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } - fn life_guard(&self) -> &LifeGuard { - unreachable!() + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } - fn label(&self) -> &str { - "" + fn label(&self) -> String { + String::from("") } } pub type TextureDescriptor<'a> = wgt::TextureDescriptor, Vec>; #[derive(Debug)] -pub(crate) enum TextureInner { +pub(crate) enum TextureInner { Native { - raw: Option, + raw: A::Texture, }, Surface { - raw: A::SurfaceTexture, - parent_id: Valid, - has_work: bool, + raw: Option, + parent_id: SurfaceId, + has_work: AtomicBool, }, } -impl TextureInner { - pub fn as_raw(&self) -> Option<&A::Texture> { - match *self { - Self::Native { raw: Some(ref tex) } => Some(tex), - Self::Native { raw: None } => None, - Self::Surface { ref raw, .. } => Some(raw.borrow()), +impl TextureInner { + pub fn raw(&self) -> Option<&A::Texture> { + match self { + Self::Native { raw } => Some(raw), + Self::Surface { raw: Some(tex), .. } => Some(tex.borrow()), + _ => None, } } } #[derive(Debug)] -pub enum TextureClearMode { +pub enum TextureClearMode { BufferCopy, // View for clear via RenderPass for every subsurface (mip/layer/slice) RenderPass { - clear_views: SmallVec<[A::TextureView; 1]>, + clear_views: SmallVec<[Option; 1]>, is_color: bool, }, + Surface { + clear_view: Option, + }, // Texture can't be cleared, attempting to do so will cause panic. // (either because it is impossible for the type of texture or it is being destroyed) None, } -impl TextureClearMode { - pub(crate) fn destroy_clear_views(self, device: &A::Device) { - if let TextureClearMode::RenderPass { clear_views, .. } = self { - for clear_view in clear_views { - unsafe { - hal::Device::destroy_texture_view(device, clear_view); - } - } - } - } -} - #[derive(Debug)] -pub struct Texture { - pub(crate) inner: TextureInner, - pub(crate) device_id: Stored, +pub struct Texture { + pub(crate) inner: Snatchable>, + pub(crate) device: Arc>, pub(crate) desc: wgt::TextureDescriptor<(), Vec>, pub(crate) hal_usage: hal::TextureUses, pub(crate) format_features: wgt::TextureFormatFeatures, - pub(crate) initialization_status: TextureInitTracker, + pub(crate) initialization_status: RwLock, pub(crate) full_range: TextureSelector, - pub(crate) life_guard: LifeGuard, - pub(crate) clear_mode: TextureClearMode, + pub(crate) info: ResourceInfo, + pub(crate) clear_mode: RwLock>, } -impl Texture { - pub(crate) fn get_clear_view(&self, mip_level: u32, depth_or_layer: u32) -> &A::TextureView { - match self.clear_mode { +impl Drop for Texture { + fn drop(&mut self) { + resource_log!("Destroy raw Texture {:?}", self.info.label()); + use hal::Device; + let mut clear_mode = self.clear_mode.write(); + let clear_mode = &mut *clear_mode; + match *clear_mode { + TextureClearMode::Surface { + ref mut clear_view, .. + } => { + if let Some(view) = clear_view.take() { + unsafe { + self.device.raw().destroy_texture_view(view); + } + } + } + TextureClearMode::RenderPass { + ref mut clear_views, + .. + } => { + clear_views.iter_mut().for_each(|clear_view| { + if let Some(view) = clear_view.take() { + unsafe { + self.device.raw().destroy_texture_view(view); + } + } + }); + } + _ => {} + }; + + if let Some(TextureInner::Native { raw }) = self.inner.take() { + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyTexture(self.info.id())); + } + + unsafe { + self.device.raw().destroy_texture(raw); + } + } + } +} + +impl Texture { + pub(crate) fn raw<'a>(&'a self, snatch_guard: &'a SnatchGuard) -> Option<&'a A::Texture> { + self.inner.get(snatch_guard)?.raw() + } + + pub(crate) fn is_destroyed(&self, guard: &SnatchGuard) -> bool { + self.inner.get(guard).is_none() + } + + pub(crate) fn inner_mut<'a>( + &'a self, + guard: &mut ExclusiveSnatchGuard, + ) -> Option<&'a mut TextureInner> { + self.inner.get_mut(guard) + } + pub(crate) fn get_clear_view<'a>( + clear_mode: &'a TextureClearMode, + desc: &'a wgt::TextureDescriptor<(), Vec>, + mip_level: u32, + depth_or_layer: u32, + ) -> &'a A::TextureView { + match *clear_mode { TextureClearMode::BufferCopy => { panic!("Given texture is cleared with buffer copies, not render passes") } TextureClearMode::None => { panic!("Given texture can't be cleared") } + TextureClearMode::Surface { ref clear_view, .. } => clear_view.as_ref().unwrap(), TextureClearMode::RenderPass { ref clear_views, .. } => { - let index = if self.desc.dimension == wgt::TextureDimension::D3 { + let index = if desc.dimension == wgt::TextureDimension::D3 { (0..mip_level).fold(0, |acc, mip| { - acc + (self.desc.size.depth_or_array_layers >> mip).max(1) + acc + (desc.size.depth_or_array_layers >> mip).max(1) }) } else { - mip_level * self.desc.size.depth_or_array_layers + mip_level * desc.size.depth_or_array_layers } + depth_or_layer; - &clear_views[index as usize] + clear_views[index as usize].as_ref().unwrap() } } } + + pub(crate) fn destroy(self: &Arc) -> Result<(), DestroyError> { + let device = &self.device; + let texture_id = self.info.id(); + + #[cfg(feature = "trace")] + if let Some(ref mut trace) = *device.trace.lock() { + trace.add(trace::Action::FreeTexture(texture_id)); + } + + let temp = { + let snatch_guard = device.snatchable_lock.write(); + let raw = match self.inner.snatch(snatch_guard) { + Some(TextureInner::Native { raw }) => raw, + Some(TextureInner::Surface { .. }) => { + return Ok(()); + } + None => { + return Err(resource::DestroyError::AlreadyDestroyed); + } + }; + + queue::TempResource::DestroyedTexture(Arc::new(DestroyedTexture { + raw: Some(raw), + device: Arc::clone(&self.device), + submission_index: self.info.submission_index(), + id: self.info.id.unwrap(), + label: self.info.label.clone(), + })) + }; + + let mut pending_writes = device.pending_writes.lock(); + let pending_writes = pending_writes.as_mut().unwrap(); + if pending_writes.dst_textures.contains_key(&texture_id) { + pending_writes.temp_resources.push(temp); + } else { + let last_submit_index = self.info.submission_index(); + device + .lock_life() + .schedule_resource_destruction(temp, last_submit_index); + } + + Ok(()) + } } impl Global { @@ -446,10 +891,10 @@ impl Global { profiling::scope!("Texture::as_hal"); let hub = A::hub(self); - let mut token = Token::root(); - let (guard, _) = hub.textures.read(&mut token); - let texture = guard.try_get(id).ok().flatten(); - let hal_texture = texture.and_then(|tex| tex.inner.as_raw()); + let texture_opt = { hub.textures.try_get(id).ok().flatten() }; + let texture = texture_opt.as_ref().unwrap(); + let snatch_guard = texture.device.snatchable_lock.read(); + let hal_texture = texture.raw(&snatch_guard); hal_texture_callback(hal_texture); } @@ -465,11 +910,8 @@ impl Global { profiling::scope!("Adapter::as_hal"); let hub = A::hub(self); - let mut token = Token::root(); - - let (guard, _) = hub.adapters.read(&mut token); - let adapter = guard.try_get(id).ok().flatten(); - let hal_adapter = adapter.map(|adapter| &adapter.raw.adapter); + let adapter = hub.adapters.try_get(id).ok().flatten(); + let hal_adapter = adapter.as_ref().map(|adapter| &adapter.raw.adapter); hal_adapter_callback(hal_adapter) } @@ -485,34 +927,86 @@ impl Global { profiling::scope!("Device::as_hal"); let hub = A::hub(self); - let mut token = Token::root(); - let (guard, _) = hub.devices.read(&mut token); - let device = guard.try_get(id).ok().flatten(); - let hal_device = device.map(|device| &device.raw); + let device = hub.devices.try_get(id).ok().flatten(); + let hal_device = device.as_ref().map(|device| device.raw()); hal_device_callback(hal_device) } + /// # Safety + /// + /// - The raw fence handle must not be manually destroyed + pub unsafe fn device_fence_as_hal) -> R, R>( + &self, + id: DeviceId, + hal_fence_callback: F, + ) -> R { + profiling::scope!("Device::fence_as_hal"); + + let hub = A::hub(self); + let device = hub.devices.try_get(id).ok().flatten(); + let hal_fence = device.as_ref().map(|device| device.fence.read()); + + hal_fence_callback(hal_fence.as_deref().unwrap().as_ref()) + } + /// # Safety /// - The raw surface handle must not be manually destroyed - pub unsafe fn surface_as_hal_mut) -> R, R>( + pub unsafe fn surface_as_hal) -> R, R>( &self, id: SurfaceId, hal_surface_callback: F, ) -> R { - profiling::scope!("Surface::as_hal_mut"); + profiling::scope!("Surface::as_hal"); - let mut token = Token::root(); - let (mut guard, _) = self.surfaces.write(&mut token); - let surface = guard.get_mut(id).ok(); + let surface = self.surfaces.get(id).ok(); let hal_surface = surface - .and_then(|surface| A::get_surface_mut(surface)) - .map(|surface| &mut surface.raw); + .as_ref() + .and_then(|surface| A::get_surface(surface)) + .map(|surface| &*surface.raw); hal_surface_callback(hal_surface) } } +/// A texture that has been marked as destroyed and is staged for actual deletion soon. +#[derive(Debug)] +pub struct DestroyedTexture { + raw: Option, + device: Arc>, + label: String, + pub(crate) id: TextureId, + pub(crate) submission_index: u64, +} + +impl DestroyedTexture { + pub fn label(&self) -> &dyn Debug { + if !self.label.is_empty() { + return &self.label; + } + + &self.id + } +} + +impl Drop for DestroyedTexture { + fn drop(&mut self) { + if let Some(raw) = self.raw.take() { + resource_log!("Destroy raw Texture (destroyed) {:?}", self.label()); + + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyTexture(self.id)); + } + + unsafe { + use hal::Device; + self.device.raw().destroy_texture(raw); + } + } + } +} + #[derive(Clone, Copy, Debug)] pub enum TextureErrorDimension { X, @@ -545,6 +1039,20 @@ pub enum TextureDimensionError { block_height: u32, format: wgt::TextureFormat, }, + #[error( + "Width {width} is not a multiple of {format:?}'s width multiple requirement ({multiple})" + )] + WidthNotMultipleOf { + width: u32, + multiple: u32, + format: wgt::TextureFormat, + }, + #[error("Height {height} is not a multiple of {format:?}'s height multiple requirement ({multiple})")] + HeightNotMultipleOf { + height: u32, + multiple: u32, + format: wgt::TextureFormat, + }, #[error("Multisampled texture depth or array layers must be 1, got {0}")] MultisampledDepthOrArrayLayer(u32), } @@ -554,6 +1062,8 @@ pub enum TextureDimensionError { pub enum CreateTextureError { #[error(transparent)] Device(#[from] DeviceError), + #[error(transparent)] + CreateTextureView(#[from] CreateTextureViewError), #[error("Invalid usage flags {0:?}")] InvalidUsage(wgt::TextureUsages), #[error(transparent)] @@ -579,8 +1089,8 @@ pub enum CreateTextureError { InvalidMultisampledStorageBinding, #[error("Format {0:?} does not support multisampling")] InvalidMultisampledFormat(wgt::TextureFormat), - #[error("Sample count {0} is not supported by format {1:?} on this device. It may be supported by your adapter through the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature.")] - InvalidSampleCount(u32, wgt::TextureFormat), + #[error("Sample count {0} is not supported by format {1:?} on this device. The WebGPU spec guarentees {2:?} samples are supported by this format. With the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature your device supports {3:?}.")] + InvalidSampleCount(u32, wgt::TextureFormat, Vec, Vec), #[error("Multisampled textures must have RENDER_ATTACHMENT usage")] MultisampledNotRenderAttachment, #[error("Texture format {0:?} can't be used due to missing features")] @@ -589,15 +1099,19 @@ pub enum CreateTextureError { MissingDownlevelFlags(#[from] MissingDownlevelFlags), } -impl Resource for Texture { - const TYPE: &'static str = "Texture"; +impl Resource for Texture { + const TYPE: ResourceType = "Texture"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } -impl Borrow for Texture { +impl Borrow for Texture { fn borrow(&self) -> &TextureSelector { &self.full_range } @@ -629,6 +1143,7 @@ pub struct TextureViewDescriptor<'a> { #[derive(Debug)] pub(crate) struct HalTextureViewDescriptor { + pub texture_format: wgt::TextureFormat, pub format: wgt::TextureFormat, pub dimension: wgt::TextureViewDimension, pub range: wgt::ImageSubresourceRange, @@ -636,7 +1151,7 @@ pub(crate) struct HalTextureViewDescriptor { impl HalTextureViewDescriptor { pub fn aspects(&self) -> hal::FormatAspects { - hal::FormatAspects::new(self.format, self.range.aspect) + hal::FormatAspects::new(self.texture_format, self.range.aspect) } } @@ -657,12 +1172,11 @@ pub enum TextureViewNotRenderableReason { } #[derive(Debug)] -pub struct TextureView { - pub(crate) raw: A::TextureView, - // The parent's refcount is held alive, but the parent may still be deleted - // if it's a surface texture. TODO: make this cleaner. - pub(crate) parent_id: Stored, - pub(crate) device_id: Stored, +pub struct TextureView { + pub(crate) raw: Option, + // if it's a surface texture - it's none + pub(crate) parent: RwLock>>>, + pub(crate) device: Arc>, //TODO: store device_id for quick access? pub(crate) desc: HalTextureViewDescriptor, pub(crate) format_features: wgt::TextureFormatFeatures, @@ -670,7 +1184,31 @@ pub struct TextureView { pub(crate) render_extent: Result, pub(crate) samples: u32, pub(crate) selector: TextureSelector, - pub(crate) life_guard: LifeGuard, + pub(crate) info: ResourceInfo, +} + +impl Drop for TextureView { + fn drop(&mut self) { + if let Some(raw) = self.raw.take() { + resource_log!("Destroy raw TextureView {:?}", self.info.label()); + + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyTextureView(self.info.id())); + } + + unsafe { + use hal::Device; + self.device.raw().destroy_texture_view(raw); + } + } + } +} + +impl TextureView { + pub(crate) fn raw(&self) -> &A::TextureView { + self.raw.as_ref().unwrap() + } } #[derive(Clone, Debug, Error)] @@ -678,7 +1216,7 @@ pub struct TextureView { pub enum CreateTextureViewError { #[error("Parent texture is invalid or destroyed")] InvalidTexture, - #[error("Not enough memory left")] + #[error("Not enough memory left to create texture view")] OutOfMemory, #[error("Invalid texture view dimension `{view:?}` with texture of dimension `{texture:?}`")] InvalidTextureViewDimension { @@ -724,11 +1262,15 @@ pub enum CreateTextureViewError { #[non_exhaustive] pub enum TextureViewDestroyError {} -impl Resource for TextureView { - const TYPE: &'static str = "TextureView"; +impl Resource for TextureView { + const TYPE: ResourceType = "TextureView"; - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } @@ -763,16 +1305,39 @@ pub struct SamplerDescriptor<'a> { } #[derive(Debug)] -pub struct Sampler { - pub(crate) raw: A::Sampler, - pub(crate) device_id: Stored, - pub(crate) life_guard: LifeGuard, +pub struct Sampler { + pub(crate) raw: Option, + pub(crate) device: Arc>, + pub(crate) info: ResourceInfo, /// `true` if this is a comparison sampler pub(crate) comparison: bool, /// `true` if this is a filtering sampler pub(crate) filtering: bool, } +impl Drop for Sampler { + fn drop(&mut self) { + resource_log!("Destroy raw Sampler {:?}", self.info.label()); + if let Some(raw) = self.raw.take() { + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroySampler(self.info.id())); + } + + unsafe { + use hal::Device; + self.device.raw().destroy_sampler(raw); + } + } + } +} + +impl Sampler { + pub(crate) fn raw(&self) -> &A::Sampler { + self.raw.as_ref().unwrap() + } +} + #[derive(Copy, Clone)] pub enum SamplerFilterErrorType { MagFilter, @@ -780,7 +1345,7 @@ pub enum SamplerFilterErrorType { MipmapFilter, } -impl std::fmt::Debug for SamplerFilterErrorType { +impl Debug for SamplerFilterErrorType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { SamplerFilterErrorType::MagFilter => write!(f, "magFilter"), @@ -817,11 +1382,15 @@ pub enum CreateSamplerError { MissingFeatures(#[from] MissingFeatures), } -impl Resource for Sampler { - const TYPE: &'static str = "Sampler"; +impl Resource for Sampler { + const TYPE: ResourceType = "Sampler"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } @@ -841,18 +1410,45 @@ pub enum CreateQuerySetError { pub type QuerySetDescriptor<'a> = wgt::QuerySetDescriptor>; #[derive(Debug)] -pub struct QuerySet { - pub(crate) raw: A::QuerySet, - pub(crate) device_id: Stored, - pub(crate) life_guard: LifeGuard, +pub struct QuerySet { + pub(crate) raw: Option, + pub(crate) device: Arc>, + pub(crate) info: ResourceInfo, pub(crate) desc: wgt::QuerySetDescriptor<()>, } -impl Resource for QuerySet { - const TYPE: &'static str = "QuerySet"; +impl Drop for QuerySet { + fn drop(&mut self) { + resource_log!("Destroy raw QuerySet {:?}", self.info.label()); + if let Some(raw) = self.raw.take() { + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyQuerySet(self.info.id())); + } - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + unsafe { + use hal::Device; + self.device.raw().destroy_query_set(raw); + } + } + } +} + +impl Resource for QuerySet { + const TYPE: ResourceType = "QuerySet"; + + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info + } +} + +impl QuerySet { + pub(crate) fn raw(&self) -> &A::QuerySet { + self.raw.as_ref().unwrap() } } @@ -868,43 +1464,88 @@ pub enum DestroyError { pub type BlasDescriptor<'a> = wgt::CreateBlasDescriptor>; pub type TlasDescriptor<'a> = wgt::CreateTlasDescriptor>; -pub struct Blas { +#[derive(Debug)] +pub struct Blas { pub(crate) raw: Option, - pub(crate) device_id: Stored, - pub(crate) life_guard: LifeGuard, + pub(crate) device: Arc>, + pub(crate) info: ResourceInfo, pub(crate) size_info: hal::AccelerationStructureBuildSizes, pub(crate) sizes: wgt::BlasGeometrySizeDescriptors, pub(crate) flags: wgt::AccelerationStructureFlags, pub(crate) update_mode: wgt::AccelerationStructureUpdateMode, - pub(crate) built_index: Option, + pub(crate) built_index: RwLock>, pub(crate) handle: u64, } -impl Resource for Blas { +impl Drop for Blas { + fn drop(&mut self) { + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyBlas(self.info.id())); + } + unsafe { + if let Some(structure) = self.raw.take() { + resource_log!("Destroy raw Blas {:?}", self.info.label()); + use hal::Device; + self.device.raw().destroy_acceleration_structure(structure); + } + } + } +} + +impl Resource for Blas { const TYPE: &'static str = "Blas"; - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } -pub struct Tlas { +#[derive(Debug)] +pub struct Tlas { pub(crate) raw: Option, - pub(crate) device_id: Stored, - pub(crate) life_guard: LifeGuard, + pub(crate) device: Arc>, + pub(crate) info: ResourceInfo, pub(crate) size_info: hal::AccelerationStructureBuildSizes, pub(crate) max_instance_count: u32, pub(crate) flags: wgt::AccelerationStructureFlags, pub(crate) update_mode: wgt::AccelerationStructureUpdateMode, - pub(crate) built_index: Option, - pub(crate) dependencies: Vec, - pub(crate) instance_buffer: Option, + pub(crate) built_index: RwLock>, + pub(crate) dependencies: RwLock>, + pub(crate) instance_buffer: RwLock>, } -impl Resource for Tlas { +impl Drop for Tlas { + fn drop(&mut self) { + #[cfg(feature = "trace")] + if let Some(t) = self.device.trace.lock().as_mut() { + t.add(trace::Action::DestroyTlas(self.info.id())); + } + unsafe { + use hal::Device; + if let Some(structure) = self.raw.take() { + resource_log!("Destroy raw Tlas {:?}", self.info.label()); + self.device.raw().destroy_acceleration_structure(structure); + } + if let Some(buffer) = self.instance_buffer.write().take() { + self.device.raw().destroy_buffer(buffer) + } + } + } +} + +impl Resource for Tlas { const TYPE: &'static str = "Tlas"; - fn life_guard(&self) -> &LifeGuard { - &self.life_guard + fn as_info(&self) -> &ResourceInfo { + &self.info + } + + fn as_info_mut(&mut self) -> &mut ResourceInfo { + &mut self.info } } diff --git a/wgpu-core/src/snatch.rs b/wgpu-core/src/snatch.rs new file mode 100644 index 0000000000..54983120c8 --- /dev/null +++ b/wgpu-core/src/snatch.rs @@ -0,0 +1,91 @@ +#![allow(unused)] + +use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use std::cell::UnsafeCell; + +/// A guard that provides read access to snatchable data. +pub struct SnatchGuard<'a>(RwLockReadGuard<'a, ()>); +/// A guard that allows snatching the snatchable data. +pub struct ExclusiveSnatchGuard<'a>(RwLockWriteGuard<'a, ()>); + +/// A value that is mostly immutable but can be "snatched" if we need to destroy +/// it early. +/// +/// In order to safely access the underlying data, the device's global snatchable +/// lock must be taken. To guarentee it, methods take a read or write guard of that +/// special lock. +pub struct Snatchable { + value: UnsafeCell>, +} + +impl Snatchable { + pub fn new(val: T) -> Self { + Snatchable { + value: UnsafeCell::new(Some(val)), + } + } + + /// Get read access to the value. Requires a the snatchable lock's read guard. + pub fn get(&self, _guard: &SnatchGuard) -> Option<&T> { + unsafe { (*self.value.get()).as_ref() } + } + + /// Get write access to the value. Requires a the snatchable lock's write guard. + pub fn get_mut(&self, _guard: &mut ExclusiveSnatchGuard) -> Option<&mut T> { + unsafe { (*self.value.get()).as_mut() } + } + + /// Take the value. Requires a the snatchable lock's write guard. + pub fn snatch(&self, _guard: ExclusiveSnatchGuard) -> Option { + unsafe { (*self.value.get()).take() } + } + + /// Take the value without a guard. This can only be used with exclusive access + /// to self, so it does not require locking. + /// + /// Typically useful in a drop implementation. + pub fn take(&mut self) -> Option { + self.value.get_mut().take() + } +} + +// Can't safely print the contents of a snatchable object without holding +// the lock. +impl std::fmt::Debug for Snatchable { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "") + } +} + +unsafe impl Sync for Snatchable {} + +/// A Device-global lock for all snatchable data. +pub struct SnatchLock { + lock: RwLock<()>, +} + +impl SnatchLock { + /// The safety of `Snatchable::get` and `Snatchable::snatch` rely on their using of the + /// right SnatchLock (the one associated to the same device). This method is unsafe + /// to force force sers to think twice about creating a SnatchLock. The only place this + /// method sould be called is when creating the device. + pub unsafe fn new() -> Self { + SnatchLock { + lock: RwLock::new(()), + } + } + + /// Request read access to snatchable resources. + pub fn read(&self) -> SnatchGuard { + SnatchGuard(self.lock.read()) + } + + /// Request write access to snatchable resources. + /// + /// This should only be called when a resource needs to be snatched. This has + /// a high risk of causing lock contention if called concurrently with other + /// wgpu work. + pub fn write(&self) -> ExclusiveSnatchGuard { + ExclusiveSnatchGuard(self.lock.write()) + } +} diff --git a/wgpu-core/src/storage.rs b/wgpu-core/src/storage.rs index 07387b0194..3e7a8f44fc 100644 --- a/wgpu-core/src/storage.rs +++ b/wgpu-core/src/storage.rs @@ -1,8 +1,8 @@ -use std::{marker::PhantomData, mem, ops}; +use std::{marker::PhantomData, ops, sync::Arc}; use wgt::Backend; -use crate::{id, Epoch, Index}; +use crate::{id, resource::Resource, Epoch, Index}; /// An entry in a `Storage::map` table. #[derive(Debug)] @@ -12,7 +12,7 @@ pub(crate) enum Element { /// There is one live id with this index, allocated at the given /// epoch. - Occupied(T, Epoch), + Occupied(Arc, Epoch), /// Like `Occupied`, but an error occurred when creating the /// resource. @@ -21,20 +21,6 @@ pub(crate) enum Element { Error(Epoch, String), } -#[derive(Clone, Debug, Default)] -pub struct StorageReport { - pub num_occupied: usize, - pub num_vacant: usize, - pub num_error: usize, - pub element_size: usize, -} - -impl StorageReport { - pub fn is_empty(&self) -> bool { - self.num_occupied + self.num_vacant + self.num_error == 0 - } -} - #[derive(Clone, Debug)] pub(crate) struct InvalidId; @@ -44,26 +30,46 @@ pub(crate) struct InvalidId; /// values, so you should use an id allocator like `IdentityManager` /// that keeps the index values dense and close to zero. #[derive(Debug)] -pub struct Storage { +pub struct Storage +where + T: Resource, + I: id::TypedId, +{ pub(crate) map: Vec>, - pub(crate) kind: &'static str, - pub(crate) _phantom: PhantomData, + kind: &'static str, + _phantom: PhantomData, } -impl ops::Index> for Storage { - type Output = T; - fn index(&self, id: id::Valid) -> &T { - self.get(id.0).unwrap() +impl ops::Index for Storage +where + T: Resource, + I: id::TypedId, +{ + type Output = Arc; + fn index(&self, id: I) -> &Arc { + self.get(id).unwrap() } } - -impl ops::IndexMut> for Storage { - fn index_mut(&mut self, id: id::Valid) -> &mut T { - self.get_mut(id.0).unwrap() +impl Storage +where + T: Resource, + I: id::TypedId, +{ + pub(crate) fn new() -> Self { + Self { + map: Vec::new(), + kind: T::TYPE, + _phantom: PhantomData, + } } } -impl Storage { +impl Storage +where + T: Resource, + I: id::TypedId, +{ + #[allow(dead_code)] pub(crate) fn contains(&self, id: I) -> bool { let (index, epoch, _) = id.unzip(); match self.map.get(index as usize) { @@ -82,7 +88,7 @@ impl Storage { /// This function is primarily intended for the `as_hal` family of functions /// where you may need to fallibly get a object backed by an id that could /// be in a different hub. - pub(crate) fn try_get(&self, id: I) -> Result, InvalidId> { + pub(crate) fn try_get(&self, id: I) -> Result>, InvalidId> { let (index, epoch, _) = id.unzip(); let (result, storage_epoch) = match self.map.get(index as usize) { Some(&Element::Occupied(ref v, epoch)) => (Ok(Some(v)), epoch), @@ -92,89 +98,108 @@ impl Storage { }; assert_eq!( epoch, storage_epoch, - "{}[{}] is no longer alive", - self.kind, index + "{}[{:?}] is no longer alive", + self.kind, id ); result } /// Get a reference to an item behind a potentially invalid ID. /// Panics if there is an epoch mismatch, or the entry is empty. - pub(crate) fn get(&self, id: I) -> Result<&T, InvalidId> { + pub(crate) fn get(&self, id: I) -> Result<&Arc, InvalidId> { let (index, epoch, _) = id.unzip(); let (result, storage_epoch) = match self.map.get(index as usize) { Some(&Element::Occupied(ref v, epoch)) => (Ok(v), epoch), - Some(&Element::Vacant) => panic!("{}[{}] does not exist", self.kind, index), + Some(&Element::Vacant) => panic!("{}[{:?}] does not exist", self.kind, id), Some(&Element::Error(epoch, ..)) => (Err(InvalidId), epoch), None => return Err(InvalidId), }; assert_eq!( epoch, storage_epoch, - "{}[{}] is no longer alive", - self.kind, index + "{}[{:?}] is no longer alive", + self.kind, id ); result } - /// Get a mutable reference to an item behind a potentially invalid ID. + /// Get an owned reference to an item behind a potentially invalid ID. /// Panics if there is an epoch mismatch, or the entry is empty. - pub(crate) fn get_mut(&mut self, id: I) -> Result<&mut T, InvalidId> { - let (index, epoch, _) = id.unzip(); - let (result, storage_epoch) = match self.map.get_mut(index as usize) { - Some(&mut Element::Occupied(ref mut v, epoch)) => (Ok(v), epoch), - Some(&mut Element::Vacant) | None => panic!("{}[{}] does not exist", self.kind, index), - Some(&mut Element::Error(epoch, ..)) => (Err(InvalidId), epoch), - }; - assert_eq!( - epoch, storage_epoch, - "{}[{}] is no longer alive", - self.kind, index - ); - result - } - - pub(crate) unsafe fn get_unchecked(&self, id: u32) -> &T { - match self.map[id as usize] { - Element::Occupied(ref v, _) => v, - Element::Vacant => panic!("{}[{}] does not exist", self.kind, id), - Element::Error(_, _) => panic!(""), - } + pub(crate) fn get_owned(&self, id: I) -> Result, InvalidId> { + Ok(Arc::clone(self.get(id)?)) } pub(crate) fn label_for_invalid_id(&self, id: I) -> &str { let (index, _, _) = id.unzip(); match self.map.get(index as usize) { - Some(&Element::Error(_, ref label)) => label, + Some(Element::Error(_, label)) => label, _ => "", } } - fn insert_impl(&mut self, index: usize, element: Element) { + fn insert_impl(&mut self, index: usize, epoch: Epoch, element: Element) { if index >= self.map.len() { self.map.resize_with(index + 1, || Element::Vacant); } match std::mem::replace(&mut self.map[index], element) { Element::Vacant => {} - _ => panic!("Index {index:?} is already occupied"), + Element::Occupied(_, storage_epoch) => { + assert_ne!( + epoch, + storage_epoch, + "Index {index:?} of {} is already occupied", + T::TYPE + ); + } + Element::Error(storage_epoch, _) => { + assert_ne!( + epoch, + storage_epoch, + "Index {index:?} of {} is already occupied with Error", + T::TYPE + ); + } } } - pub(crate) fn insert(&mut self, id: I, value: T) { - let (index, epoch, _) = id.unzip(); - self.insert_impl(index as usize, Element::Occupied(value, epoch)) + pub(crate) fn insert(&mut self, id: I, value: Arc) { + log::trace!("User is inserting {}{:?}", T::TYPE, id); + let (index, epoch, _backend) = id.unzip(); + self.insert_impl(index as usize, epoch, Element::Occupied(value, epoch)) } pub(crate) fn insert_error(&mut self, id: I, label: &str) { + log::trace!("User is insering as error {}{:?}", T::TYPE, id); let (index, epoch, _) = id.unzip(); - self.insert_impl(index as usize, Element::Error(epoch, label.to_string())) + self.insert_impl( + index as usize, + epoch, + Element::Error(epoch, label.to_string()), + ) + } + + pub(crate) fn replace_with_error(&mut self, id: I) -> Result, InvalidId> { + let (index, epoch, _) = id.unzip(); + match std::mem::replace( + &mut self.map[index as usize], + Element::Error(epoch, String::new()), + ) { + Element::Vacant => panic!("Cannot access vacant resource"), + Element::Occupied(value, storage_epoch) => { + assert_eq!(epoch, storage_epoch); + Ok(value) + } + _ => Err(InvalidId), + } } pub(crate) fn force_replace(&mut self, id: I, value: T) { + log::trace!("User is replacing {}{:?}", T::TYPE, id); let (index, epoch, _) = id.unzip(); - self.map[index as usize] = Element::Occupied(value, epoch); + self.map[index as usize] = Element::Occupied(Arc::new(value), epoch); } - pub(crate) fn remove(&mut self, id: I) -> Option { + pub(crate) fn remove(&mut self, id: I) -> Option> { + log::trace!("User is removing {}{:?}", T::TYPE, id); let (index, epoch, _) = id.unzip(); match std::mem::replace(&mut self.map[index as usize], Element::Vacant) { Element::Occupied(value, storage_epoch) => { @@ -186,22 +211,7 @@ impl Storage { } } - // Prevents panic on out of range access, allows Vacant elements. - pub(crate) fn _try_remove(&mut self, id: I) -> Option { - let (index, epoch, _) = id.unzip(); - if index as usize >= self.map.len() { - None - } else if let Element::Occupied(value, storage_epoch) = - std::mem::replace(&mut self.map[index as usize], Element::Vacant) - { - assert_eq!(epoch, storage_epoch); - Some(value) - } else { - None - } - } - - pub(crate) fn iter(&self, backend: Backend) -> impl Iterator { + pub(crate) fn iter(&self, backend: Backend) -> impl Iterator)> { self.map .iter() .enumerate() @@ -213,22 +223,11 @@ impl Storage { }) } - pub(crate) fn len(&self) -> usize { - self.map.len() + pub(crate) fn kind(&self) -> &str { + self.kind } - pub(crate) fn generate_report(&self) -> StorageReport { - let mut report = StorageReport { - element_size: mem::size_of::(), - ..Default::default() - }; - for element in self.map.iter() { - match *element { - Element::Occupied(..) => report.num_occupied += 1, - Element::Vacant => report.num_vacant += 1, - Element::Error(..) => report.num_error += 1, - } - } - report + pub(crate) fn len(&self) -> usize { + self.map.len() } } diff --git a/wgpu-core/src/track/buffer.rs b/wgpu-core/src/track/buffer.rs index b5e61c5a22..2c2a6937f9 100644 --- a/wgpu-core/src/track/buffer.rs +++ b/wgpu-core/src/track/buffer.rs @@ -5,21 +5,22 @@ * one subresource, they have no selector. !*/ -use std::{borrow::Cow, marker::PhantomData, vec::Drain}; +use std::{borrow::Cow, marker::PhantomData, sync::Arc}; -use super::PendingTransition; +use super::{PendingTransition, ResourceTracker}; use crate::{ hal_api::HalApi, - id::{BufferId, TypedId, Valid}, - resource::Buffer, - storage, + id::{BufferId, TypedId}, + resource::{Buffer, Resource}, + snatch::SnatchGuard, + storage::Storage, track::{ invalid_resource_state, skip_barrier, ResourceMetadata, ResourceMetadataProvider, ResourceUses, UsageConflict, }, - LifeGuard, RefCount, }; -use hal::BufferUses; +use hal::{BufferBarrier, BufferUses}; +use parking_lot::Mutex; use wgt::{strict_assert, strict_assert_eq}; impl ResourceUses for BufferUses { @@ -42,15 +43,16 @@ impl ResourceUses for BufferUses { } /// Stores all the buffers that a bind group stores. +#[derive(Debug)] pub(crate) struct BufferBindGroupState { - buffers: Vec<(Valid, RefCount, BufferUses)>, + buffers: Mutex>, BufferUses)>>, _phantom: PhantomData, } impl BufferBindGroupState { pub fn new() -> Self { Self { - buffers: Vec::new(), + buffers: Mutex::new(Vec::new()), _phantom: PhantomData, } @@ -60,27 +62,44 @@ impl BufferBindGroupState { /// /// When this list of states is merged into a tracker, the memory /// accesses will be in a constant assending order. - pub(crate) fn optimize(&mut self) { - self.buffers - .sort_unstable_by_key(|&(id, _, _)| id.0.unzip().0); + #[allow(clippy::pattern_type_mismatch)] + pub(crate) fn optimize(&self) { + let mut buffers = self.buffers.lock(); + buffers.sort_unstable_by_key(|(b, _)| b.as_info().id().unzip().0); + } + + /// Returns a list of all buffers tracked. May contain duplicates. + #[allow(clippy::pattern_type_mismatch)] + pub fn used_ids(&self) -> impl Iterator + '_ { + let buffers = self.buffers.lock(); + buffers + .iter() + .map(|(ref b, _)| b.as_info().id()) + .collect::>() + .into_iter() } /// Returns a list of all buffers tracked. May contain duplicates. - pub fn used(&self) -> impl Iterator> + '_ { - self.buffers.iter().map(|&(id, _, _)| id) + pub fn drain_resources(&self) -> impl Iterator>> + '_ { + let mut buffers = self.buffers.lock(); + buffers + .drain(..) + .map(|(buffer, _u)| buffer) + .collect::>() + .into_iter() } /// Adds the given resource with the given state. pub fn add_single<'a>( - &mut self, - storage: &'a storage::Storage, BufferId>, + &self, + storage: &'a Storage, BufferId>, id: BufferId, state: BufferUses, - ) -> Option<&'a Buffer> { + ) -> Option<&'a Arc>> { let buffer = storage.get(id).ok()?; - self.buffers - .push((Valid(id), buffer.life_guard.add_ref(), state)); + let mut buffers = self.buffers.lock(); + buffers.push((buffer.clone(), state)); Some(buffer) } @@ -91,7 +110,7 @@ impl BufferBindGroupState { pub(crate) struct BufferUsageScope { state: Vec, - metadata: ResourceMetadata, + metadata: ResourceMetadata>, } impl BufferUsageScope { @@ -124,9 +143,25 @@ impl BufferUsageScope { } } - /// Returns a list of all buffers tracked. - pub fn used(&self) -> impl Iterator> + '_ { - self.metadata.owned_ids() + /// Drains all buffers tracked. + pub fn drain_resources(&mut self) -> impl Iterator>> + '_ { + let resources = self.metadata.drain_resources(); + self.state.clear(); + resources.into_iter() + } + + pub fn get(&self, id: BufferId) -> Option<&Arc>> { + let index = id.unzip().0 as usize; + if index > self.metadata.size() { + return None; + } + self.tracker_assert_in_bounds(index); + unsafe { + if self.metadata.contains_unchecked(index) { + return Some(self.metadata.get_resource_unchecked(index)); + } + } + None } /// Merge the list of buffer states in the given bind group into this usage scope. @@ -145,22 +180,20 @@ impl BufferUsageScope { &mut self, bind_group: &BufferBindGroupState, ) -> Result<(), UsageConflict> { - for &(id, ref ref_count, state) in &bind_group.buffers { - let (index32, epoch, _) = id.0.unzip(); - let index = index32 as usize; + let buffers = bind_group.buffers.lock(); + for &(ref resource, state) in &*buffers { + let index = resource.as_info().id().unzip().0 as usize; unsafe { insert_or_merge( - None, None, &mut self.state, &mut self.metadata, - index32, + index as _, index, BufferStateProvider::Direct { state }, ResourceMetadataProvider::Direct { - epoch, - ref_count: Cow::Borrowed(ref_count), + resource: Cow::Borrowed(resource), }, )? }; @@ -188,7 +221,6 @@ impl BufferUsageScope { unsafe { insert_or_merge( - None, None, &mut self.state, &mut self.metadata, @@ -216,16 +248,15 @@ impl BufferUsageScope { /// the vectors will be extended. A call to set_size is not needed. pub fn merge_single<'a>( &mut self, - storage: &'a storage::Storage, BufferId>, + storage: &'a Storage, BufferId>, id: BufferId, new_state: BufferUses, - ) -> Result<&'a Buffer, UsageConflict> { + ) -> Result<&'a Arc>, UsageConflict> { let buffer = storage .get(id) .map_err(|_| UsageConflict::BufferInvalid { id })?; - let (index32, epoch, _) = id.unzip(); - let index = index32 as usize; + let index = id.unzip().0 as usize; self.allow_index(index); @@ -233,14 +264,15 @@ impl BufferUsageScope { unsafe { insert_or_merge( - Some(&buffer.life_guard), None, &mut self.state, &mut self.metadata, - index32, + index as _, index, BufferStateProvider::Direct { state: new_state }, - ResourceMetadataProvider::Resource { epoch }, + ResourceMetadataProvider::Direct { + resource: Cow::Owned(buffer.clone()), + }, )?; } @@ -248,15 +280,72 @@ impl BufferUsageScope { } } +pub(crate) type SetSingleResult = + Option<(Arc>, Option>)>; + /// Stores all buffer state within a command buffer or device. pub(crate) struct BufferTracker { start: Vec, end: Vec, - metadata: ResourceMetadata, + metadata: ResourceMetadata>, temp: Vec>, } + +impl ResourceTracker> for BufferTracker { + /// Try to remove the buffer `id` from this tracker if it is otherwise unused. + /// + /// A buffer is 'otherwise unused' when the only references to it are: + /// + /// 1) the `Arc` that our caller, `LifetimeTracker::triage_suspected`, has just + /// drained from `LifetimeTracker::suspected_resources`, + /// + /// 2) its `Arc` in [`self.metadata`] (owned by [`Device::trackers`]), and + /// + /// 3) its `Arc` in the [`Hub::buffers`] registry. + /// + /// If the buffer is indeed unused, this function removes 2), and + /// `triage_suspected` will remove 3), leaving 1) as the sole + /// remaining reference. + /// + /// Returns true if the resource was removed or if not existing in metadata. + /// + /// [`Device::trackers`]: crate::device::Device + /// [`self.metadata`]: BufferTracker::metadata + /// [`Hub::buffers`]: crate::hub::Hub::buffers + fn remove_abandoned(&mut self, id: BufferId) -> bool { + let index = id.unzip().0 as usize; + + if index > self.metadata.size() { + return false; + } + + self.tracker_assert_in_bounds(index); + + unsafe { + if self.metadata.contains_unchecked(index) { + let existing_ref_count = self.metadata.get_ref_count_unchecked(index); + //RefCount 2 means that resource is hold just by DeviceTracker and this suspected resource itself + //so it's already been released from user and so it's not inside Registry\Storage + if existing_ref_count <= 2 { + self.metadata.remove(index); + log::trace!("Buffer {:?} is not tracked anymore", id,); + return true; + } else { + log::trace!( + "Buffer {:?} is still referenced from {}", + id, + existing_ref_count + ); + return false; + } + } + } + true + } +} + impl BufferTracker { pub fn new() -> Self { Self { @@ -294,13 +383,20 @@ impl BufferTracker { } /// Returns a list of all buffers tracked. - pub fn used(&self) -> impl Iterator> + '_ { - self.metadata.owned_ids() + pub fn used_resources(&self) -> impl Iterator>> + '_ { + self.metadata.owned_resources() } /// Drains all currently pending transitions. - pub fn drain(&mut self) -> Drain<'_, PendingTransition> { - self.temp.drain(..) + pub fn drain_transitions<'a, 'b: 'a>( + &'b mut self, + snatch_guard: &'a SnatchGuard<'a>, + ) -> impl Iterator> { + let buffer_barriers = self.temp.drain(..).map(|pending| { + let buf = unsafe { self.metadata.get_resource_unchecked(pending.id as _) }; + pending.into_hal(buf, snatch_guard) + }); + buffer_barriers } /// Inserts a single buffer and its state into the resource tracker. @@ -309,9 +405,8 @@ impl BufferTracker { /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. - pub fn insert_single(&mut self, id: Valid, ref_count: RefCount, state: BufferUses) { - let (index32, epoch, _) = id.0.unzip(); - let index = index32 as usize; + pub fn insert_single(&mut self, id: BufferId, resource: Arc>, state: BufferUses) { + let index = id.unzip().0 as usize; self.allow_index(index); @@ -325,7 +420,6 @@ impl BufferTracker { } insert( - None, Some(&mut self.start), &mut self.end, &mut self.metadata, @@ -333,8 +427,7 @@ impl BufferTracker { BufferStateProvider::Direct { state }, None, ResourceMetadataProvider::Direct { - epoch, - ref_count: Cow::Owned(ref_count), + resource: Cow::Owned(resource), }, ) } @@ -347,16 +440,8 @@ impl BufferTracker { /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. - pub fn set_single<'a>( - &mut self, - storage: &'a storage::Storage, BufferId>, - id: BufferId, - state: BufferUses, - ) -> Option<(&'a Buffer, Option>)> { - let value = storage.get(id).ok()?; - - let (index32, epoch, _) = id.unzip(); - let index = index32 as usize; + pub fn set_single(&mut self, buffer: &Arc>, state: BufferUses) -> SetSingleResult { + let index: usize = buffer.as_info().id().unzip().0 as usize; self.allow_index(index); @@ -364,29 +449,29 @@ impl BufferTracker { unsafe { insert_or_barrier_update( - Some(&value.life_guard), Some(&mut self.start), &mut self.end, &mut self.metadata, - index32, index, BufferStateProvider::Direct { state }, None, - ResourceMetadataProvider::Resource { epoch }, + ResourceMetadataProvider::Direct { + resource: Cow::Owned(buffer.clone()), + }, &mut self.temp, ) }; strict_assert!(self.temp.len() <= 1); - Some((value, self.temp.pop())) + Some((buffer.clone(), self.temp.pop())) } /// Sets the given state for all buffers in the given tracker. /// /// If a transition is needed to get the buffers into the needed state, /// those transitions are stored within the tracker. A subsequent - /// call to [`Self::drain`] is needed to get those transitions. + /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. @@ -401,11 +486,9 @@ impl BufferTracker { tracker.tracker_assert_in_bounds(index); unsafe { insert_or_barrier_update( - None, Some(&mut self.start), &mut self.end, &mut self.metadata, - index as u32, index, BufferStateProvider::Indirect { state: &tracker.start, @@ -426,7 +509,7 @@ impl BufferTracker { /// /// If a transition is needed to get the buffers into the needed state, /// those transitions are stored within the tracker. A subsequent - /// call to [`Self::drain`] is needed to get those transitions. + /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. @@ -441,11 +524,9 @@ impl BufferTracker { scope.tracker_assert_in_bounds(index); unsafe { insert_or_barrier_update( - None, Some(&mut self.start), &mut self.end, &mut self.metadata, - index as u32, index, BufferStateProvider::Indirect { state: &scope.state, @@ -466,7 +547,7 @@ impl BufferTracker { /// /// If a transition is needed to get the buffers into the needed state, /// those transitions are stored within the tracker. A subsequent - /// call to [`Self::drain`] is needed to get those transitions. + /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// This is a really funky method used by Compute Passes to generate /// barriers after a call to dispatch without needing to iterate @@ -481,7 +562,7 @@ impl BufferTracker { pub unsafe fn set_and_remove_from_usage_scope_sparse( &mut self, scope: &mut BufferUsageScope, - id_source: impl IntoIterator>, + id_source: impl IntoIterator, ) { let incoming_size = scope.state.len(); if incoming_size > self.start.len() { @@ -489,7 +570,7 @@ impl BufferTracker { } for id in id_source { - let (index32, _, _) = id.0.unzip(); + let (index32, _, _) = id.unzip(); let index = index32 as usize; scope.tracker_assert_in_bounds(index); @@ -499,11 +580,9 @@ impl BufferTracker { } unsafe { insert_or_barrier_update( - None, Some(&mut self.start), &mut self.end, &mut self.metadata, - index as u32, index, BufferStateProvider::Indirect { state: &scope.state, @@ -520,36 +599,19 @@ impl BufferTracker { } } - /// Removes the given resource from the tracker iff we have the last reference to the - /// resource and the epoch matches. - /// - /// Returns true if the resource was removed. - /// - /// If the ID is higher than the length of internal vectors, - /// false will be returned. - pub fn remove_abandoned(&mut self, id: Valid) -> bool { - let (index32, epoch, _) = id.0.unzip(); - let index = index32 as usize; - + #[allow(dead_code)] + pub fn get(&self, id: BufferId) -> Option<&Arc>> { + let index = id.unzip().0 as usize; if index > self.metadata.size() { - return false; + return None; } - self.tracker_assert_in_bounds(index); - unsafe { if self.metadata.contains_unchecked(index) { - let existing_epoch = self.metadata.get_epoch_unchecked(index); - let existing_ref_count = self.metadata.get_ref_count_unchecked(index); - - if existing_epoch == epoch && existing_ref_count.load() == 1 { - self.metadata.remove(index); - return true; - } + return Some(self.metadata.get_resource_unchecked(index)); } } - - false + None } } @@ -590,21 +652,19 @@ impl BufferStateProvider<'_> { /// to this function, either directly or via metadata or provider structs. #[inline(always)] unsafe fn insert_or_merge( - life_guard: Option<&LifeGuard>, start_states: Option<&mut [BufferUses]>, current_states: &mut [BufferUses], - resource_metadata: &mut ResourceMetadata, + resource_metadata: &mut ResourceMetadata>, index32: u32, index: usize, state_provider: BufferStateProvider<'_>, - metadata_provider: ResourceMetadataProvider<'_, A>, + metadata_provider: ResourceMetadataProvider<'_, A, BufferId, Buffer>, ) -> Result<(), UsageConflict> { let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; if !currently_owned { unsafe { insert( - life_guard, start_states, current_states, resource_metadata, @@ -647,15 +707,13 @@ unsafe fn insert_or_merge( /// to this function, either directly or via metadata or provider structs. #[inline(always)] unsafe fn insert_or_barrier_update( - life_guard: Option<&LifeGuard>, start_states: Option<&mut [BufferUses]>, current_states: &mut [BufferUses], - resource_metadata: &mut ResourceMetadata, - index32: u32, + resource_metadata: &mut ResourceMetadata>, index: usize, start_state_provider: BufferStateProvider<'_>, end_state_provider: Option>, - metadata_provider: ResourceMetadataProvider<'_, A>, + metadata_provider: ResourceMetadataProvider<'_, A, BufferId, Buffer>, barriers: &mut Vec>, ) { let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; @@ -663,7 +721,6 @@ unsafe fn insert_or_barrier_update( if !currently_owned { unsafe { insert( - life_guard, start_states, current_states, resource_metadata, @@ -677,29 +734,20 @@ unsafe fn insert_or_barrier_update( } let update_state_provider = end_state_provider.unwrap_or_else(|| start_state_provider.clone()); - unsafe { - barrier( - current_states, - index32, - index, - start_state_provider, - barriers, - ) - }; + unsafe { barrier(current_states, index, start_state_provider, barriers) }; unsafe { update(current_states, index, update_state_provider) }; } #[inline(always)] unsafe fn insert( - life_guard: Option<&LifeGuard>, start_states: Option<&mut [BufferUses]>, current_states: &mut [BufferUses], - resource_metadata: &mut ResourceMetadata, + resource_metadata: &mut ResourceMetadata>, index: usize, start_state_provider: BufferStateProvider<'_>, end_state_provider: Option>, - metadata_provider: ResourceMetadataProvider<'_, A>, + metadata_provider: ResourceMetadataProvider<'_, A, BufferId, Buffer>, ) { let new_start_state = unsafe { start_state_provider.get_state(index) }; let new_end_state = @@ -718,8 +766,8 @@ unsafe fn insert( } *current_states.get_unchecked_mut(index) = new_end_state; - let (epoch, ref_count) = metadata_provider.get_own(life_guard, index); - resource_metadata.insert(index, epoch, ref_count); + let resource = metadata_provider.get_own(index); + resource_metadata.insert(index, resource); } } @@ -729,7 +777,7 @@ unsafe fn merge( index32: u32, index: usize, state_provider: BufferStateProvider<'_>, - metadata_provider: ResourceMetadataProvider<'_, A>, + metadata_provider: ResourceMetadataProvider<'_, A, BufferId, Buffer>, ) -> Result<(), UsageConflict> { let current_state = unsafe { current_states.get_unchecked_mut(index) }; let new_state = unsafe { state_provider.get_state(index) }; @@ -758,7 +806,6 @@ unsafe fn merge( #[inline(always)] unsafe fn barrier( current_states: &mut [BufferUses], - index32: u32, index: usize, state_provider: BufferStateProvider<'_>, barriers: &mut Vec>, @@ -771,12 +818,12 @@ unsafe fn barrier( } barriers.push(PendingTransition { - id: index32, + id: index as _, selector: (), usage: current_state..new_state, }); - log::trace!("\tbuf {index32}: transition {current_state:?} -> {new_state:?}"); + log::trace!("\tbuf {index}: transition {current_state:?} -> {new_state:?}"); } #[inline(always)] diff --git a/wgpu-core/src/track/metadata.rs b/wgpu-core/src/track/metadata.rs index 8561d93bee..3464170ebf 100644 --- a/wgpu-core/src/track/metadata.rs +++ b/wgpu-core/src/track/metadata.rs @@ -1,15 +1,11 @@ //! The `ResourceMetadata` type. -use crate::{ - hal_api::HalApi, - id::{self, TypedId}, - Epoch, LifeGuard, RefCount, -}; +use crate::{hal_api::HalApi, id::TypedId, resource::Resource, Epoch}; use bit_vec::BitVec; -use std::{borrow::Cow, marker::PhantomData, mem}; +use std::{borrow::Cow, marker::PhantomData, mem, sync::Arc}; use wgt::strict_assert; -/// A set of resources, holding a [`RefCount`] and epoch for each member. +/// A set of resources, holding a `Arc` and epoch for each member. /// /// Testing for membership is fast, and iterating over members is /// reasonably fast in practice. Storage consumption is proportional @@ -17,27 +13,22 @@ use wgt::strict_assert; /// members, but a bit vector tracks occupancy, so iteration touches /// only occupied elements. #[derive(Debug)] -pub(super) struct ResourceMetadata { +pub(super) struct ResourceMetadata> { /// If the resource with index `i` is a member, `owned[i]` is `true`. owned: BitVec, - /// A vector parallel to `owned`, holding clones of members' `RefCount`s. - ref_counts: Vec>, - - /// A vector parallel to `owned`, holding the epoch of each members' id. - epochs: Vec, + /// A vector holding clones of members' `T`s. + resources: Vec>>, /// This tells Rust that this type should be covariant with `A`. - _phantom: PhantomData, + _phantom: PhantomData<(A, I)>, } -impl ResourceMetadata { +impl> ResourceMetadata { pub(super) fn new() -> Self { Self { owned: BitVec::default(), - ref_counts: Vec::new(), - epochs: Vec::new(), - + resources: Vec::new(), _phantom: PhantomData, } } @@ -48,9 +39,7 @@ impl ResourceMetadata { } pub(super) fn set_size(&mut self, size: usize) { - self.ref_counts.resize(size, None); - self.epochs.resize(size, u32::MAX); - + self.resources.resize(size, None); resize_bitvec(&mut self.owned, size); } @@ -61,11 +50,9 @@ impl ResourceMetadata { #[cfg_attr(not(feature = "strict_asserts"), allow(unused_variables))] pub(super) fn tracker_assert_in_bounds(&self, index: usize) { strict_assert!(index < self.owned.len()); - strict_assert!(index < self.ref_counts.len()); - strict_assert!(index < self.epochs.len()); - + strict_assert!(index < self.resources.len()); strict_assert!(if self.contains(index) { - self.ref_counts[index].is_some() + self.resources[index].is_some() } else { true }); @@ -104,52 +91,66 @@ impl ResourceMetadata { /// The given `index` must be in bounds for this `ResourceMetadata`'s /// existing tables. See `tracker_assert_in_bounds`. #[inline(always)] - pub(super) unsafe fn insert(&mut self, index: usize, epoch: Epoch, ref_count: RefCount) { + pub(super) unsafe fn insert(&mut self, index: usize, resource: Arc) { self.owned.set(index, true); unsafe { - *self.epochs.get_unchecked_mut(index) = epoch; - *self.ref_counts.get_unchecked_mut(index) = Some(ref_count); + *self.resources.get_unchecked_mut(index) = Some(resource); } } - /// Get the [`RefCount`] of the resource with the given index. + /// Get the resource with the given index. /// /// # Safety /// /// The given `index` must be in bounds for this `ResourceMetadata`'s /// existing tables. See `tracker_assert_in_bounds`. #[inline(always)] - pub(super) unsafe fn get_ref_count_unchecked(&self, index: usize) -> &RefCount { + pub(super) unsafe fn get_resource_unchecked(&self, index: usize) -> &Arc { unsafe { - self.ref_counts + self.resources .get_unchecked(index) .as_ref() .unwrap_unchecked() } } - /// Get the [`Epoch`] of the id of the resource with the given index. + /// Get the reference count of the resource with the given index. /// /// # Safety /// /// The given `index` must be in bounds for this `ResourceMetadata`'s /// existing tables. See `tracker_assert_in_bounds`. #[inline(always)] - pub(super) unsafe fn get_epoch_unchecked(&self, index: usize) -> Epoch { - unsafe { *self.epochs.get_unchecked(index) } + pub(super) unsafe fn get_ref_count_unchecked(&self, index: usize) -> usize { + unsafe { Arc::strong_count(self.get_resource_unchecked(index)) } } - /// Returns an iterator over the ids for all resources owned by `self`. - pub(super) fn owned_ids(&self) -> impl Iterator> + '_ { + /// Returns an iterator over the resources owned by `self`. + pub(super) fn owned_resources(&self) -> impl Iterator> + '_ { if !self.owned.is_empty() { self.tracker_assert_in_bounds(self.owned.len() - 1) }; iterate_bitvec_indices(&self.owned).map(move |index| { - let epoch = unsafe { *self.epochs.get_unchecked(index) }; - id::Valid(Id::zip(index as u32, epoch, A::VARIANT)) + let resource = unsafe { self.resources.get_unchecked(index) }; + resource.as_ref().unwrap().clone() }) } + /// Returns an iterator over the resources owned by `self`. + pub(super) fn drain_resources(&mut self) -> Vec> { + if !self.owned.is_empty() { + self.tracker_assert_in_bounds(self.owned.len() - 1) + }; + let mut resources = Vec::new(); + iterate_bitvec_indices(&self.owned).for_each(|index| { + let resource = unsafe { self.resources.get_unchecked(index) }; + resources.push(resource.as_ref().unwrap().clone()); + }); + self.owned.clear(); + self.resources.clear(); + resources + } + /// Returns an iterator over the indices of all resources owned by `self`. pub(super) fn owned_indices(&self) -> impl Iterator + '_ { if !self.owned.is_empty() { @@ -161,8 +162,7 @@ impl ResourceMetadata { /// Remove the resource with the given index from the set. pub(super) unsafe fn remove(&mut self, index: usize) { unsafe { - *self.ref_counts.get_unchecked_mut(index) = None; - *self.epochs.get_unchecked_mut(index) = u32::MAX; + *self.resources.get_unchecked_mut(index) = None; } self.owned.set(index, false); } @@ -172,44 +172,31 @@ impl ResourceMetadata { /// /// This is used to abstract over the various places /// trackers can get new resource metadata from. -pub(super) enum ResourceMetadataProvider<'a, A: HalApi> { +pub(super) enum ResourceMetadataProvider<'a, A: HalApi, I: TypedId, T: Resource> { /// Comes directly from explicit values. - Direct { - epoch: Epoch, - ref_count: Cow<'a, RefCount>, - }, + Direct { resource: Cow<'a, Arc> }, /// Comes from another metadata tracker. - Indirect { metadata: &'a ResourceMetadata }, - /// The epoch is given directly, but the life count comes from the resource itself. - Resource { epoch: Epoch }, + Indirect { + metadata: &'a ResourceMetadata, + }, } -impl ResourceMetadataProvider<'_, A> { +impl> ResourceMetadataProvider<'_, A, I, T> { /// Get the epoch and an owned refcount from this. /// /// # Safety /// /// - The index must be in bounds of the metadata tracker if this uses an indirect source. - /// - life_guard must be Some if this uses a Resource source. + /// - info must be Some if this uses a Resource source. #[inline(always)] - pub(super) unsafe fn get_own( - self, - life_guard: Option<&LifeGuard>, - index: usize, - ) -> (Epoch, RefCount) { + pub(super) unsafe fn get_own(self, index: usize) -> Arc { match self { - ResourceMetadataProvider::Direct { epoch, ref_count } => { - (epoch, ref_count.into_owned()) - } + ResourceMetadataProvider::Direct { resource } => resource.into_owned(), ResourceMetadataProvider::Indirect { metadata } => { metadata.tracker_assert_in_bounds(index); - (unsafe { *metadata.epochs.get_unchecked(index) }, { - let ref_count = unsafe { metadata.ref_counts.get_unchecked(index) }; - unsafe { ref_count.clone().unwrap_unchecked() } - }) - } - ResourceMetadataProvider::Resource { epoch } => { - strict_assert!(life_guard.is_some()); - (epoch, unsafe { life_guard.unwrap_unchecked() }.add_ref()) + { + let resource = unsafe { metadata.resources.get_unchecked(index) }; + unsafe { resource.clone().unwrap_unchecked() } + } } } } @@ -220,14 +207,7 @@ impl ResourceMetadataProvider<'_, A> { /// - The index must be in bounds of the metadata tracker if this uses an indirect source. #[inline(always)] pub(super) unsafe fn get_epoch(self, index: usize) -> Epoch { - match self { - ResourceMetadataProvider::Direct { epoch, .. } - | ResourceMetadataProvider::Resource { epoch, .. } => epoch, - ResourceMetadataProvider::Indirect { metadata } => { - metadata.tracker_assert_in_bounds(index); - unsafe { *metadata.epochs.get_unchecked(index) } - } - } + unsafe { self.get_own(index).as_info().id().unzip().1 } } } diff --git a/wgpu-core/src/track/mod.rs b/wgpu-core/src/track/mod.rs index 32bcaaa309..fcecffc044 100644 --- a/wgpu-core/src/track/mod.rs +++ b/wgpu-core/src/track/mod.rs @@ -105,9 +105,12 @@ use crate::{ binding_model, command, conv, hal_api::HalApi, id::{self, TypedId}, - pipeline, resource, storage, + pipeline, resource, + snatch::SnatchGuard, + storage::Storage, }; +use parking_lot::RwLock; use std::{fmt, ops}; use thiserror::Error; @@ -129,13 +132,16 @@ pub(crate) struct PendingTransition { pub usage: ops::Range, } +pub(crate) type PendingTransitionList = Vec>; + impl PendingTransition { /// Produce the hal barrier corresponding to the transition. - pub fn into_hal<'a, A: hal::Api>( + pub fn into_hal<'a, A: HalApi>( self, buf: &'a resource::Buffer, + snatch_guard: &'a SnatchGuard<'a>, ) -> hal::BufferBarrier<'a, A> { - let buffer = buf.raw.as_ref().expect("Buffer is destroyed"); + let buffer = buf.raw.get(snatch_guard).expect("Buffer is destroyed"); hal::BufferBarrier { buffer, usage: self.usage, @@ -145,12 +151,7 @@ impl PendingTransition { impl PendingTransition { /// Produce the hal barrier corresponding to the transition. - pub fn into_hal<'a, A: hal::Api>( - self, - tex: &'a resource::Texture, - ) -> hal::TextureBarrier<'a, A> { - let texture = tex.inner.as_raw().expect("Texture is destroyed"); - + pub fn into_hal<'a, A: HalApi>(self, texture: &'a A::Texture) -> hal::TextureBarrier<'a, A> { // These showing up in a barrier is always a bug strict_assert_ne!(self.usage.start, hal::TextureUses::UNKNOWN); strict_assert_ne!(self.usage.end, hal::TextureUses::UNKNOWN); @@ -315,12 +316,13 @@ impl fmt::Display for InvalidUse { /// /// All bind group states are sorted by their ID so that when adding to a tracker, /// they are added in the most efficient order possible (assending order). +#[derive(Debug)] pub(crate) struct BindGroupStates { pub buffers: BufferBindGroupState, pub textures: TextureBindGroupState, - pub views: StatelessBindGroupSate, id::TextureViewId>, - pub samplers: StatelessBindGroupSate, id::SamplerId>, - pub acceleration_structures: StatelessBindGroupSate, id::TlasId>, + pub views: StatelessBindGroupSate>, + pub samplers: StatelessBindGroupSate>, + pub acceleration_structures: StatelessBindGroupSate>, } impl BindGroupStates { @@ -350,37 +352,42 @@ impl BindGroupStates { /// This is a render bundle specific usage scope. It includes stateless resources /// that are not normally included in a usage scope, but are used by render bundles /// and need to be owned by the render bundles. +#[derive(Debug)] pub(crate) struct RenderBundleScope { - pub buffers: BufferUsageScope, - pub textures: TextureUsageScope, + pub buffers: RwLock>, + pub textures: RwLock>, // Don't need to track views and samplers, they are never used directly, only by bind groups. - pub bind_groups: StatelessTracker, id::BindGroupId>, - pub render_pipelines: StatelessTracker, id::RenderPipelineId>, - pub query_sets: StatelessTracker, id::QuerySetId>, + pub bind_groups: RwLock>>, + pub render_pipelines: + RwLock>>, + pub query_sets: RwLock>>, } impl RenderBundleScope { /// Create the render bundle scope and pull the maximum IDs from the hubs. pub fn new( - buffers: &storage::Storage, id::BufferId>, - textures: &storage::Storage, id::TextureId>, - bind_groups: &storage::Storage, id::BindGroupId>, - render_pipelines: &storage::Storage, id::RenderPipelineId>, - query_sets: &storage::Storage, id::QuerySetId>, + buffers: &Storage, id::BufferId>, + textures: &Storage, id::TextureId>, + bind_groups: &Storage, id::BindGroupId>, + render_pipelines: &Storage, id::RenderPipelineId>, + query_sets: &Storage, id::QuerySetId>, ) -> Self { - let mut value = Self { - buffers: BufferUsageScope::new(), - textures: TextureUsageScope::new(), - bind_groups: StatelessTracker::new(), - render_pipelines: StatelessTracker::new(), - query_sets: StatelessTracker::new(), + let value = Self { + buffers: RwLock::new(BufferUsageScope::new()), + textures: RwLock::new(TextureUsageScope::new()), + bind_groups: RwLock::new(StatelessTracker::new()), + render_pipelines: RwLock::new(StatelessTracker::new()), + query_sets: RwLock::new(StatelessTracker::new()), }; - value.buffers.set_size(buffers.len()); - value.textures.set_size(textures.len()); - value.bind_groups.set_size(bind_groups.len()); - value.render_pipelines.set_size(render_pipelines.len()); - value.query_sets.set_size(query_sets.len()); + value.buffers.write().set_size(buffers.len()); + value.textures.write().set_size(textures.len()); + value.bind_groups.write().set_size(bind_groups.len()); + value + .render_pipelines + .write() + .set_size(render_pipelines.len()); + value.query_sets.write().set_size(query_sets.len()); value } @@ -396,13 +403,13 @@ impl RenderBundleScope { /// length of the storage given at the call to `new`. pub unsafe fn merge_bind_group( &mut self, - textures: &storage::Storage, id::TextureId>, bind_group: &BindGroupStates, ) -> Result<(), UsageConflict> { - unsafe { self.buffers.merge_bind_group(&bind_group.buffers)? }; + unsafe { self.buffers.write().merge_bind_group(&bind_group.buffers)? }; unsafe { self.textures - .merge_bind_group(textures, &bind_group.textures)? + .write() + .merge_bind_group(&bind_group.textures)? }; Ok(()) @@ -420,8 +427,8 @@ pub(crate) struct UsageScope { impl UsageScope { /// Create the render bundle scope and pull the maximum IDs from the hubs. pub fn new( - buffers: &storage::Storage, id::BufferId>, - textures: &storage::Storage, id::TextureId>, + buffers: &Storage, id::BufferId>, + textures: &Storage, id::TextureId>, ) -> Self { let mut value = Self { buffers: BufferUsageScope::new(), @@ -445,13 +452,11 @@ impl UsageScope { /// length of the storage given at the call to `new`. pub unsafe fn merge_bind_group( &mut self, - textures: &storage::Storage, id::TextureId>, bind_group: &BindGroupStates, ) -> Result<(), UsageConflict> { unsafe { self.buffers.merge_bind_group(&bind_group.buffers)?; - self.textures - .merge_bind_group(textures, &bind_group.textures)?; + self.textures.merge_bind_group(&bind_group.textures)?; } Ok(()) @@ -468,30 +473,38 @@ impl UsageScope { /// length of the storage given at the call to `new`. pub unsafe fn merge_render_bundle( &mut self, - textures: &storage::Storage, id::TextureId>, render_bundle: &RenderBundleScope, ) -> Result<(), UsageConflict> { - self.buffers.merge_usage_scope(&render_bundle.buffers)?; + self.buffers + .merge_usage_scope(&*render_bundle.buffers.read())?; self.textures - .merge_usage_scope(textures, &render_bundle.textures)?; + .merge_usage_scope(&*render_bundle.textures.read())?; Ok(()) } } +pub(crate) trait ResourceTracker +where + Id: TypedId, + R: resource::Resource, +{ + fn remove_abandoned(&mut self, id: Id) -> bool; +} + /// A full double sided tracker used by CommandBuffers and the Device. pub(crate) struct Tracker { pub buffers: BufferTracker, pub textures: TextureTracker, - pub views: StatelessTracker, id::TextureViewId>, - pub samplers: StatelessTracker, id::SamplerId>, - pub bind_groups: StatelessTracker, id::BindGroupId>, - pub compute_pipelines: StatelessTracker, id::ComputePipelineId>, - pub render_pipelines: StatelessTracker, id::RenderPipelineId>, - pub bundles: StatelessTracker, id::RenderBundleId>, - pub query_sets: StatelessTracker, id::QuerySetId>, - pub blas_s: StatelessTracker, id::BlasId>, - pub tlas_s: StatelessTracker, id::TlasId>, + pub views: StatelessTracker>, + pub samplers: StatelessTracker>, + pub bind_groups: StatelessTracker>, + pub compute_pipelines: StatelessTracker>, + pub render_pipelines: StatelessTracker>, + pub bundles: StatelessTracker>, + pub query_sets: StatelessTracker>, + pub blas_s: StatelessTracker>, + pub tlas_s: StatelessTracker>, } impl Tracker { @@ -514,21 +527,17 @@ impl Tracker { /// Pull the maximum IDs from the hubs. pub fn set_size( &mut self, - buffers: Option<&storage::Storage, id::BufferId>>, - textures: Option<&storage::Storage, id::TextureId>>, - views: Option<&storage::Storage, id::TextureViewId>>, - samplers: Option<&storage::Storage, id::SamplerId>>, - bind_groups: Option<&storage::Storage, id::BindGroupId>>, - compute_pipelines: Option< - &storage::Storage, id::ComputePipelineId>, - >, - render_pipelines: Option< - &storage::Storage, id::RenderPipelineId>, - >, - bundles: Option<&storage::Storage, id::RenderBundleId>>, - query_sets: Option<&storage::Storage, id::QuerySetId>>, - blas_s: Option<&storage::Storage, id::BlasId>>, - tlas_s: Option<&storage::Storage, id::TlasId>>, + buffers: Option<&Storage, id::BufferId>>, + textures: Option<&Storage, id::TextureId>>, + views: Option<&Storage, id::TextureViewId>>, + samplers: Option<&Storage, id::SamplerId>>, + bind_groups: Option<&Storage, id::BindGroupId>>, + compute_pipelines: Option<&Storage, id::ComputePipelineId>>, + render_pipelines: Option<&Storage, id::RenderPipelineId>>, + bundles: Option<&Storage, id::RenderBundleId>>, + query_sets: Option<&Storage, id::QuerySetId>>, + blas_s: Option<&Storage, id::BlasId>>, + tlas_s: Option<&Storage, id::TlasId>>, ) { if let Some(buffers) = buffers { self.buffers.set_size(buffers.len()); @@ -589,22 +598,18 @@ impl Tracker { /// value given to `set_size` pub unsafe fn set_and_remove_from_usage_scope_sparse( &mut self, - textures: &storage::Storage, id::TextureId>, scope: &mut UsageScope, bind_group: &BindGroupStates, ) { unsafe { self.buffers.set_and_remove_from_usage_scope_sparse( &mut scope.buffers, - bind_group.buffers.used(), + bind_group.buffers.used_ids(), ) }; unsafe { - self.textures.set_and_remove_from_usage_scope_sparse( - textures, - &mut scope.textures, - &bind_group.textures, - ) + self.textures + .set_and_remove_from_usage_scope_sparse(&mut scope.textures, &bind_group.textures) }; } @@ -620,10 +625,11 @@ impl Tracker { render_bundle: &RenderBundleScope, ) -> Result<(), UsageConflict> { self.bind_groups - .add_from_tracker(&render_bundle.bind_groups); + .add_from_tracker(&*render_bundle.bind_groups.read()); self.render_pipelines - .add_from_tracker(&render_bundle.render_pipelines); - self.query_sets.add_from_tracker(&render_bundle.query_sets); + .add_from_tracker(&*render_bundle.render_pipelines.read()); + self.query_sets + .add_from_tracker(&*render_bundle.query_sets.read()); Ok(()) } diff --git a/wgpu-core/src/track/range.rs b/wgpu-core/src/track/range.rs index 12c527fb2a..3961220c2c 100644 --- a/wgpu-core/src/track/range.rs +++ b/wgpu-core/src/track/range.rs @@ -77,8 +77,8 @@ impl RangedStates { ) -> impl Iterator, &T)> + 'a { self.ranges .iter() - .filter(move |&&(ref inner, ..)| inner.end > range.start && inner.start < range.end) - .map(move |&(ref inner, ref v)| { + .filter(move |&(inner, ..)| inner.end > range.start && inner.start < range.end) + .map(move |(inner, v)| { let new_range = inner.start.max(range.start)..inner.end.min(range.end); (new_range, v) diff --git a/wgpu-core/src/track/stateless.rs b/wgpu-core/src/track/stateless.rs index bb4206b357..6795890cc6 100644 --- a/wgpu-core/src/track/stateless.rs +++ b/wgpu-core/src/track/stateless.rs @@ -4,29 +4,27 @@ * distinction between a usage scope and a full tracker. !*/ -use std::marker::PhantomData; +use std::{marker::PhantomData, sync::Arc}; + +use parking_lot::Mutex; use crate::{ - hal_api::HalApi, - id::{TypedId, Valid}, - resource, storage, + hal_api::HalApi, id::TypedId, resource::Resource, resource_log, storage::Storage, track::ResourceMetadata, - RefCount, }; -/// Stores all the resources that a bind group stores. -pub(crate) struct StatelessBindGroupSate { - resources: Vec<(Valid, RefCount)>, +use super::ResourceTracker; - _phantom: PhantomData, +/// Stores all the resources that a bind group stores. +#[derive(Debug)] +pub(crate) struct StatelessBindGroupSate> { + resources: Mutex)>>, } -impl StatelessBindGroupSate { +impl> StatelessBindGroupSate { pub fn new() -> Self { Self { - resources: Vec::new(), - - _phantom: PhantomData, + resources: Mutex::new(Vec::new()), } } @@ -34,43 +32,98 @@ impl StatelessBindGroupSate { /// /// When this list of states is merged into a tracker, the memory /// accesses will be in a constant assending order. - pub(crate) fn optimize(&mut self) { - self.resources - .sort_unstable_by_key(|&(id, _)| id.0.unzip().0); + pub(crate) fn optimize(&self) { + let mut resources = self.resources.lock(); + resources.sort_unstable_by_key(|&(id, _)| id.unzip().0); + } + + /// Returns a list of all resources tracked. May contain duplicates. + pub fn used_resources(&self) -> impl Iterator> + '_ { + let resources = self.resources.lock(); + resources + .iter() + .map(|(_, resource)| resource.clone()) + .collect::>() + .into_iter() } /// Returns a list of all resources tracked. May contain duplicates. - pub fn used(&self) -> impl Iterator> + '_ { - self.resources.iter().map(|&(id, _)| id) + pub fn drain_resources(&self) -> impl Iterator> + '_ { + let mut resources = self.resources.lock(); + resources + .drain(..) + .map(|(_, r)| r) + .collect::>() + .into_iter() } /// Adds the given resource. - pub fn add_single<'a>( - &mut self, - storage: &'a storage::Storage, - id: Id, - ) -> Option<&'a T> { + pub fn add_single<'a>(&self, storage: &'a Storage, id: Id) -> Option<&'a T> { let resource = storage.get(id).ok()?; - self.resources - .push((Valid(id), resource.life_guard().add_ref())); + let mut resources = self.resources.lock(); + resources.push((id, resource.clone())); Some(resource) } } /// Stores all resource state within a command buffer or device. -pub(crate) struct StatelessTracker { - metadata: ResourceMetadata, +#[derive(Debug)] +pub(crate) struct StatelessTracker> { + metadata: ResourceMetadata, + _phantom: PhantomData, +} - _phantom: PhantomData<(T, Id)>, +impl> ResourceTracker + for StatelessTracker +{ + /// Try to remove the given resource from the tracker iff we have the last reference to the + /// resource and the epoch matches. + /// + /// Returns true if the resource was removed or if not existing in metadata. + /// + /// If the ID is higher than the length of internal vectors, + /// false will be returned. + fn remove_abandoned(&mut self, id: Id) -> bool { + let index = id.unzip().0 as usize; + + if index >= self.metadata.size() { + return false; + } + + resource_log!("StatelessTracker::remove_abandoned {id:?}"); + + self.tracker_assert_in_bounds(index); + + unsafe { + if self.metadata.contains_unchecked(index) { + let existing_ref_count = self.metadata.get_ref_count_unchecked(index); + //RefCount 2 means that resource is hold just by DeviceTracker and this suspected resource itself + //so it's already been released from user and so it's not inside Registry\Storage + if existing_ref_count <= 2 { + self.metadata.remove(index); + log::trace!("{} {:?} is not tracked anymore", T::TYPE, id,); + return true; + } else { + log::trace!( + "{} {:?} is still referenced from {}", + T::TYPE, + id, + existing_ref_count + ); + return false; + } + } + } + true + } } -impl StatelessTracker { +impl> StatelessTracker { pub fn new() -> Self { Self { metadata: ResourceMetadata::new(), - _phantom: PhantomData, } } @@ -95,8 +148,14 @@ impl StatelessTracker { } /// Returns a list of all resources tracked. - pub fn used(&self) -> impl Iterator> + '_ { - self.metadata.owned_ids() + pub fn used_resources(&self) -> impl Iterator> + '_ { + self.metadata.owned_resources() + } + + /// Returns a list of all resources tracked. + pub fn drain_resources(&mut self) -> impl Iterator> + '_ { + let resources = self.metadata.drain_resources(); + resources.into_iter() } /// Inserts a single resource into the resource tracker. @@ -105,8 +164,8 @@ impl StatelessTracker { /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. - pub fn insert_single(&mut self, id: Valid, ref_count: RefCount) { - let (index32, epoch, _) = id.0.unzip(); + pub fn insert_single(&mut self, id: Id, resource: Arc) { + let (index32, _epoch, _) = id.unzip(); let index = index32 as usize; self.allow_index(index); @@ -114,7 +173,7 @@ impl StatelessTracker { self.tracker_assert_in_bounds(index); unsafe { - self.metadata.insert(index, epoch, ref_count); + self.metadata.insert(index, resource); } } @@ -122,14 +181,10 @@ impl StatelessTracker { /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. - pub fn add_single<'a>( - &mut self, - storage: &'a storage::Storage, - id: Id, - ) -> Option<&'a T> { - let item = storage.get(id).ok()?; - - let (index32, epoch, _) = id.unzip(); + pub fn add_single<'a>(&mut self, storage: &'a Storage, id: Id) -> Option<&'a Arc> { + let resource = storage.get(id).ok()?; + + let (index32, _epoch, _) = id.unzip(); let index = index32 as usize; self.allow_index(index); @@ -137,11 +192,10 @@ impl StatelessTracker { self.tracker_assert_in_bounds(index); unsafe { - self.metadata - .insert(index, epoch, item.life_guard().add_ref()); + self.metadata.insert(index, resource.clone()); } - Some(item) + Some(resource) } /// Adds the given resources from the given tracker. @@ -161,43 +215,24 @@ impl StatelessTracker { let previously_owned = self.metadata.contains_unchecked(index); if !previously_owned { - let epoch = other.metadata.get_epoch_unchecked(index); - let other_ref_count = other.metadata.get_ref_count_unchecked(index); - self.metadata.insert(index, epoch, other_ref_count.clone()); + let other_resource = other.metadata.get_resource_unchecked(index); + self.metadata.insert(index, other_resource.clone()); } } } } - /// Removes the given resource from the tracker iff we have the last reference to the - /// resource and the epoch matches. - /// - /// Returns true if the resource was removed. - /// - /// If the ID is higher than the length of internal vectors, - /// false will be returned. - pub fn remove_abandoned(&mut self, id: Valid) -> bool { - let (index32, epoch, _) = id.0.unzip(); - let index = index32 as usize; - + pub fn get(&self, id: Id) -> Option<&Arc> { + let index = id.unzip().0 as usize; if index > self.metadata.size() { - return false; + return None; } - self.tracker_assert_in_bounds(index); - unsafe { if self.metadata.contains_unchecked(index) { - let existing_epoch = self.metadata.get_epoch_unchecked(index); - let existing_ref_count = self.metadata.get_ref_count_unchecked(index); - - if existing_epoch == epoch && existing_ref_count.load() == 1 { - self.metadata.remove(index); - return true; - } + return Some(self.metadata.get_resource_unchecked(index)); } } - - false + None } } diff --git a/wgpu-core/src/track/texture.rs b/wgpu-core/src/track/texture.rs index 8b0926adf1..94574596a9 100644 --- a/wgpu-core/src/track/texture.rs +++ b/wgpu-core/src/track/texture.rs @@ -19,25 +19,26 @@ * will treat the contents as junk. !*/ -use super::{range::RangedStates, PendingTransition}; +use super::{range::RangedStates, PendingTransition, PendingTransitionList, ResourceTracker}; use crate::{ hal_api::HalApi, - id::{TextureId, TypedId, Valid}, - resource::Texture, - storage, + id::{TextureId, TypedId}, + resource::{Resource, Texture, TextureInner}, + snatch::SnatchGuard, track::{ invalid_resource_state, skip_barrier, ResourceMetadata, ResourceMetadataProvider, ResourceUses, UsageConflict, }, - LifeGuard, RefCount, }; use hal::TextureUses; use arrayvec::ArrayVec; use naga::FastHashMap; + +use parking_lot::Mutex; use wgt::{strict_assert, strict_assert_eq}; -use std::{borrow::Cow, iter, marker::PhantomData, ops::Range, vec::Drain}; +use std::{borrow::Cow, iter, marker::PhantomData, ops::Range, sync::Arc, vec::Drain}; /// Specifies a particular set of subresources in a texture. #[derive(Clone, Debug, PartialEq, Eq)] @@ -148,23 +149,22 @@ impl ComplexTextureState { } } +#[derive(Debug)] +struct TextureBindGroupStateData { + selector: Option, + texture: Arc>, + usage: TextureUses, +} + /// Stores all the textures that a bind group stores. +#[derive(Debug)] pub(crate) struct TextureBindGroupState { - textures: Vec<( - Valid, - Option, - RefCount, - TextureUses, - )>, - - _phantom: PhantomData, + textures: Mutex>>, } impl TextureBindGroupState { pub fn new() -> Self { Self { - textures: Vec::new(), - - _phantom: PhantomData, + textures: Mutex::new(Vec::new()), } } @@ -172,30 +172,35 @@ impl TextureBindGroupState { /// /// When this list of states is merged into a tracker, the memory /// accesses will be in a constant assending order. - pub(crate) fn optimize(&mut self) { - self.textures - .sort_unstable_by_key(|&(id, _, _, _)| id.0.unzip().0); + pub(crate) fn optimize(&self) { + let mut textures = self.textures.lock(); + textures.sort_unstable_by_key(|v| v.texture.as_info().id().unzip().0); } - /// Returns a list of all buffers tracked. May contain duplicates. - pub fn used(&self) -> impl Iterator> + '_ { - self.textures.iter().map(|&(id, _, _, _)| id) + /// Returns a list of all textures tracked. May contain duplicates. + pub fn drain_resources(&self) -> impl Iterator>> + '_ { + let mut textures = self.textures.lock(); + textures + .drain(..) + .map(|v| v.texture) + .collect::>() + .into_iter() } /// Adds the given resource with the given state. pub fn add_single<'a>( - &mut self, - storage: &'a storage::Storage, TextureId>, - id: TextureId, - ref_count: RefCount, + &self, + texture: &'a Arc>, selector: Option, state: TextureUses, - ) -> Option<&'a Texture> { - let value = storage.get(id).ok()?; - - self.textures.push((Valid(id), selector, ref_count, state)); - - Some(value) + ) -> Option<&'a Arc>> { + let mut textures = self.textures.lock(); + textures.push(TextureBindGroupStateData { + selector, + texture: texture.clone(), + usage: state, + }); + Some(texture) } } @@ -203,7 +208,7 @@ impl TextureBindGroupState { #[derive(Debug)] pub(crate) struct TextureStateSet { simple: Vec, - complex: FastHashMap, + complex: FastHashMap, } impl TextureStateSet { fn new() -> Self { @@ -213,6 +218,11 @@ impl TextureStateSet { } } + fn clear(&mut self) { + self.simple.clear(); + self.complex.clear(); + } + fn set_size(&mut self, size: usize) { self.simple.resize(size, TextureUses::UNINITIALIZED); } @@ -222,8 +232,7 @@ impl TextureStateSet { #[derive(Debug)] pub(crate) struct TextureUsageScope { set: TextureStateSet, - - metadata: ResourceMetadata, + metadata: ResourceMetadata>, } impl TextureUsageScope { @@ -243,7 +252,7 @@ impl TextureUsageScope { strict_assert!(if self.metadata.contains(index) && self.set.simple[index] == TextureUses::COMPLEX { - self.set.complex.contains_key(&(index as u32)) + self.set.complex.contains_key(&index) } else { true }); @@ -258,9 +267,11 @@ impl TextureUsageScope { self.metadata.set_size(size); } - /// Returns a list of all textures tracked. - pub fn used(&self) -> impl Iterator> + '_ { - self.metadata.owned_ids() + /// Drains all textures tracked. + pub(crate) fn drain_resources(&mut self) -> impl Iterator>> + '_ { + let resources = self.metadata.drain_resources(); + self.set.clear(); + resources.into_iter() } /// Returns true if the tracker owns no resources. @@ -277,29 +288,23 @@ impl TextureUsageScope { /// /// If the given tracker uses IDs higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. - pub fn merge_usage_scope( - &mut self, - storage: &storage::Storage, TextureId>, - scope: &Self, - ) -> Result<(), UsageConflict> { + pub fn merge_usage_scope(&mut self, scope: &Self) -> Result<(), UsageConflict> { let incoming_size = scope.set.simple.len(); if incoming_size > self.set.simple.len() { self.set_size(incoming_size); } for index in scope.metadata.owned_indices() { - let index32 = index as u32; - self.tracker_assert_in_bounds(index); scope.tracker_assert_in_bounds(index); - let texture_data = unsafe { texture_data_from_texture(storage, index32) }; + let texture_selector = + unsafe { &scope.metadata.get_resource_unchecked(index).full_range }; unsafe { insert_or_merge( - texture_data, + texture_selector, &mut self.set, &mut self.metadata, - index32, index, TextureStateProvider::TextureSet { set: &scope.set }, ResourceMetadataProvider::Indirect { @@ -326,11 +331,11 @@ impl TextureUsageScope { /// method is called. pub unsafe fn merge_bind_group( &mut self, - storage: &storage::Storage, TextureId>, bind_group: &TextureBindGroupState, ) -> Result<(), UsageConflict> { - for &(id, ref selector, ref ref_count, state) in &bind_group.textures { - unsafe { self.merge_single(storage, id, selector.clone(), ref_count, state)? }; + let textures = bind_group.textures.lock(); + for t in &*textures { + unsafe { self.merge_single(&t.texture, t.selector.clone(), t.usage)? }; } Ok(()) @@ -351,29 +356,24 @@ impl TextureUsageScope { /// method is called. pub unsafe fn merge_single( &mut self, - storage: &storage::Storage, TextureId>, - id: Valid, + texture: &Arc>, selector: Option, - ref_count: &RefCount, new_state: TextureUses, ) -> Result<(), UsageConflict> { - let (index32, epoch, _) = id.0.unzip(); - let index = index32 as usize; + let index = texture.as_info().id().unzip().0 as usize; self.tracker_assert_in_bounds(index); - let texture_data = unsafe { texture_data_from_texture(storage, index32) }; + let texture_selector = &texture.full_range; unsafe { insert_or_merge( - texture_data, + texture_selector, &mut self.set, &mut self.metadata, - index32, index, TextureStateProvider::from_option(selector, new_state), ResourceMetadataProvider::Direct { - epoch, - ref_count: Cow::Borrowed(ref_count), + resource: Cow::Borrowed(texture), }, )? }; @@ -387,12 +387,55 @@ pub(crate) struct TextureTracker { start_set: TextureStateSet, end_set: TextureStateSet, - metadata: ResourceMetadata, + metadata: ResourceMetadata>, temp: Vec>, _phantom: PhantomData, } + +impl ResourceTracker> for TextureTracker { + /// Try to remove the given resource from the tracker iff we have the last reference to the + /// resource and the epoch matches. + /// + /// Returns true if the resource was removed or if not existing in metadata. + /// + /// If the ID is higher than the length of internal vectors, + /// false will be returned. + fn remove_abandoned(&mut self, id: TextureId) -> bool { + let index = id.unzip().0 as usize; + + if index > self.metadata.size() { + return false; + } + + self.tracker_assert_in_bounds(index); + + unsafe { + if self.metadata.contains_unchecked(index) { + let existing_ref_count = self.metadata.get_ref_count_unchecked(index); + //RefCount 2 means that resource is hold just by DeviceTracker and this suspected resource itself + //so it's already been released from user and so it's not inside Registry\Storage + if existing_ref_count <= 2 { + self.start_set.complex.remove(&index); + self.end_set.complex.remove(&index); + self.metadata.remove(index); + log::trace!("Texture {:?} is not tracked anymore", id,); + return true; + } else { + log::trace!( + "Texture {:?} is still referenced from {}", + id, + existing_ref_count + ); + return false; + } + } + } + true + } +} + impl TextureTracker { pub fn new() -> Self { Self { @@ -416,14 +459,14 @@ impl TextureTracker { strict_assert!(if self.metadata.contains(index) && self.start_set.simple[index] == TextureUses::COMPLEX { - self.start_set.complex.contains_key(&(index as u32)) + self.start_set.complex.contains_key(&index) } else { true }); strict_assert!(if self.metadata.contains(index) && self.end_set.simple[index] == TextureUses::COMPLEX { - self.end_set.complex.contains_key(&(index as u32)) + self.end_set.complex.contains_key(&index) } else { true }); @@ -448,30 +491,26 @@ impl TextureTracker { } /// Returns a list of all textures tracked. - pub fn used(&self) -> impl Iterator> + '_ { - self.metadata.owned_ids() - } - - /// Drains all currently pending transitions. - pub fn drain(&mut self) -> Drain> { - self.temp.drain(..) + pub fn used_resources(&self) -> impl Iterator>> + '_ { + self.metadata.owned_resources() } - /// Get the refcount of the given resource. - /// - /// # Safety - /// - /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this - /// method is called. - /// - /// The resource must be tracked by this tracker. - pub unsafe fn get_ref_count(&self, id: Valid) -> &RefCount { - let (index32, _, _) = id.0.unzip(); - let index = index32 as usize; - - self.tracker_assert_in_bounds(index); - - unsafe { self.metadata.get_ref_count_unchecked(index) } + /// Drain all currently pending transitions. + pub fn drain_transitions<'a>( + &'a mut self, + snatch_guard: &'a SnatchGuard<'a>, + ) -> (PendingTransitionList, Vec>>) { + let mut textures = Vec::new(); + let transitions = self + .temp + .drain(..) + .map(|pending| { + let tex = unsafe { self.metadata.get_resource_unchecked(pending.id as _) }; + textures.push(tex.inner.get(snatch_guard)); + pending + }) + .collect(); + (transitions, textures) } /// Inserts a single texture and a state into the resource tracker. @@ -480,9 +519,8 @@ impl TextureTracker { /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. - pub fn insert_single(&mut self, id: TextureId, ref_count: RefCount, usage: TextureUses) { - let (index32, epoch, _) = id.unzip(); - let index = index32 as usize; + pub fn insert_single(&mut self, id: TextureId, resource: Arc>, usage: TextureUses) { + let index = id.unzip().0 as usize; self.allow_index(index); @@ -500,13 +538,11 @@ impl TextureTracker { Some(&mut self.start_set), &mut self.end_set, &mut self.metadata, - index32, index, TextureStateProvider::KnownSingle { state: usage }, None, ResourceMetadataProvider::Direct { - epoch, - ref_count: Cow::Owned(ref_count), + resource: Cow::Owned(resource), }, ) }; @@ -521,13 +557,11 @@ impl TextureTracker { /// the vectors will be extended. A call to set_size is not needed. pub fn set_single( &mut self, - texture: &Texture, - id: TextureId, + texture: &Arc>, selector: TextureSelector, new_state: TextureUses, ) -> Option>> { - let (index32, epoch, _) = id.unzip(); - let index = index32 as usize; + let index = texture.as_info().id().unzip().0 as usize; self.allow_index(index); @@ -535,18 +569,19 @@ impl TextureTracker { unsafe { insert_or_barrier_update( - (&texture.life_guard, &texture.full_range), + &texture.full_range, Some(&mut self.start_set), &mut self.end_set, &mut self.metadata, - index32, index, TextureStateProvider::Selector { selector, state: new_state, }, None, - ResourceMetadataProvider::Resource { epoch }, + ResourceMetadataProvider::Direct { + resource: Cow::Owned(texture.clone()), + }, &mut self.temp, ) } @@ -558,32 +593,26 @@ impl TextureTracker { /// /// If a transition is needed to get the texture into the needed state, /// those transitions are stored within the tracker. A subsequent - /// call to [`Self::drain`] is needed to get those transitions. + /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. - pub fn set_from_tracker( - &mut self, - storage: &storage::Storage, TextureId>, - tracker: &Self, - ) { + pub fn set_from_tracker(&mut self, tracker: &Self) { let incoming_size = tracker.start_set.simple.len(); if incoming_size > self.start_set.simple.len() { self.set_size(incoming_size); } for index in tracker.metadata.owned_indices() { - let index32 = index as u32; - self.tracker_assert_in_bounds(index); tracker.tracker_assert_in_bounds(index); unsafe { + let texture_selector = &tracker.metadata.get_resource_unchecked(index).full_range; insert_or_barrier_update( - texture_data_from_texture(storage, index32), + texture_selector, Some(&mut self.start_set), &mut self.end_set, &mut self.metadata, - index32, index, TextureStateProvider::TextureSet { set: &tracker.start_set, @@ -604,32 +633,26 @@ impl TextureTracker { /// /// If a transition is needed to get the textures into the needed state, /// those transitions are stored within the tracker. A subsequent - /// call to [`Self::drain`] is needed to get those transitions. + /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. - pub fn set_from_usage_scope( - &mut self, - storage: &storage::Storage, TextureId>, - scope: &TextureUsageScope, - ) { + pub fn set_from_usage_scope(&mut self, scope: &TextureUsageScope) { let incoming_size = scope.set.simple.len(); if incoming_size > self.start_set.simple.len() { self.set_size(incoming_size); } for index in scope.metadata.owned_indices() { - let index32 = index as u32; - self.tracker_assert_in_bounds(index); scope.tracker_assert_in_bounds(index); unsafe { + let texture_selector = &scope.metadata.get_resource_unchecked(index).full_range; insert_or_barrier_update( - texture_data_from_texture(storage, index32), + texture_selector, Some(&mut self.start_set), &mut self.end_set, &mut self.metadata, - index32, index, TextureStateProvider::TextureSet { set: &scope.set }, None, @@ -648,7 +671,7 @@ impl TextureTracker { /// /// If a transition is needed to get the textures into the needed state, /// those transitions are stored within the tracker. A subsequent - /// call to [`Self::drain`] is needed to get those transitions. + /// call to [`Self::drain_transitions`] is needed to get those transitions. /// /// This is a really funky method used by Compute Passes to generate /// barriers after a call to dispatch without needing to iterate @@ -662,7 +685,6 @@ impl TextureTracker { /// method is called. pub unsafe fn set_and_remove_from_usage_scope_sparse( &mut self, - storage: &storage::Storage, TextureId>, scope: &mut TextureUsageScope, bind_group_state: &TextureBindGroupState, ) { @@ -671,22 +693,21 @@ impl TextureTracker { self.set_size(incoming_size); } - for &(id, _, _, _) in bind_group_state.textures.iter() { - let (index32, _, _) = id.0.unzip(); - let index = index32 as usize; + let textures = bind_group_state.textures.lock(); + for t in textures.iter() { + let index = t.texture.as_info().id().unzip().0 as usize; scope.tracker_assert_in_bounds(index); if unsafe { !scope.metadata.contains_unchecked(index) } { continue; } - let texture_data = unsafe { texture_data_from_texture(storage, index32) }; + let texture_selector = &t.texture.full_range; unsafe { insert_or_barrier_update( - texture_data, + texture_selector, Some(&mut self.start_set), &mut self.end_set, &mut self.metadata, - index32, index, TextureStateProvider::TextureSet { set: &scope.set }, None, @@ -707,9 +728,8 @@ impl TextureTracker { /// /// If the ID is higher than the length of internal vectors, /// false will be returned. - pub fn remove(&mut self, id: Valid) -> bool { - let (index32, epoch, _) = id.0.unzip(); - let index = index32 as usize; + pub fn remove(&mut self, id: TextureId) -> bool { + let index = id.unzip().0 as usize; if index > self.metadata.size() { return false; @@ -719,56 +739,15 @@ impl TextureTracker { unsafe { if self.metadata.contains_unchecked(index) { - let existing_epoch = self.metadata.get_epoch_unchecked(index); - assert_eq!(existing_epoch, epoch); - - self.start_set.complex.remove(&index32); - self.end_set.complex.remove(&index32); - + self.start_set.complex.remove(&index); + self.end_set.complex.remove(&index); self.metadata.remove(index); - return true; } } false } - - /// Removes the given resource from the tracker iff we have the last reference to the - /// resource and the epoch matches. - /// - /// Returns true if the resource was removed. - /// - /// If the ID is higher than the length of internal vectors, - /// false will be returned. - pub fn remove_abandoned(&mut self, id: Valid) -> bool { - let (index32, epoch, _) = id.0.unzip(); - let index = index32 as usize; - - if index > self.metadata.size() { - return false; - } - - self.tracker_assert_in_bounds(index); - - unsafe { - if self.metadata.contains_unchecked(index) { - let existing_epoch = self.metadata.get_epoch_unchecked(index); - let existing_ref_count = self.metadata.get_ref_count_unchecked(index); - - if existing_epoch == epoch && existing_ref_count.load() == 1 { - self.start_set.complex.remove(&index32); - self.end_set.complex.remove(&index32); - - self.metadata.remove(index); - - return true; - } - } - } - - false - } } /// An iterator adapter that can store two different iterator types. @@ -828,7 +807,7 @@ impl<'a> TextureStateProvider<'a> { /// /// # Panics /// - /// Panics if texture_data is None and this uses a Selector source. + /// Panics if texture_selector is None and this uses a Selector source. /// /// # Safety /// @@ -836,8 +815,7 @@ impl<'a> TextureStateProvider<'a> { #[inline(always)] unsafe fn get_state( self, - texture_data: Option<(&LifeGuard, &TextureSelector)>, - index32: u32, + texture_selector: Option<&TextureSelector>, index: usize, ) -> SingleOrManyStates< TextureUses, @@ -850,7 +828,7 @@ impl<'a> TextureStateProvider<'a> { // and if it is we promote to a simple state. This allows upstream // code to specify selectors willy nilly, and all that are really // single states are promoted here. - if *texture_data.unwrap().1 == selector { + if *texture_selector.unwrap() == selector { SingleOrManyStates::Single(state) } else { SingleOrManyStates::Many(EitherIter::Left(iter::once((selector, state)))) @@ -860,7 +838,7 @@ impl<'a> TextureStateProvider<'a> { let new_state = *unsafe { set.simple.get_unchecked(index) }; if new_state == TextureUses::COMPLEX { - let new_complex = unsafe { set.complex.get(&index32).unwrap_unchecked() }; + let new_complex = unsafe { set.complex.get(&index).unwrap_unchecked() }; SingleOrManyStates::Many(EitherIter::Right( new_complex.to_selector_state_iter(), @@ -873,17 +851,6 @@ impl<'a> TextureStateProvider<'a> { } } -/// Helper function that gets what is needed from the texture storage -/// out of the texture storage. -#[inline(always)] -unsafe fn texture_data_from_texture( - storage: &storage::Storage, TextureId>, - index32: u32, -) -> (&LifeGuard, &TextureSelector) { - let texture = unsafe { storage.get_unchecked(index32) }; - (&texture.life_guard, &texture.full_range) -} - /// Does an insertion operation if the index isn't tracked /// in the current metadata, otherwise merges the given state /// with the current state. If the merging would cause @@ -895,24 +862,22 @@ unsafe fn texture_data_from_texture( /// to this function, either directly or via metadata or provider structs. #[inline(always)] unsafe fn insert_or_merge( - texture_data: (&LifeGuard, &TextureSelector), + texture_selector: &TextureSelector, current_state_set: &mut TextureStateSet, - resource_metadata: &mut ResourceMetadata, - index32: u32, + resource_metadata: &mut ResourceMetadata>, index: usize, state_provider: TextureStateProvider<'_>, - metadata_provider: ResourceMetadataProvider<'_, A>, + metadata_provider: ResourceMetadataProvider<'_, A, TextureId, Texture>, ) -> Result<(), UsageConflict> { let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; if !currently_owned { unsafe { insert( - Some(texture_data), + Some(texture_selector), None, current_state_set, resource_metadata, - index32, index, state_provider, None, @@ -924,9 +889,8 @@ unsafe fn insert_or_merge( unsafe { merge( - texture_data, + texture_selector, current_state_set, - index32, index, state_provider, metadata_provider, @@ -953,15 +917,14 @@ unsafe fn insert_or_merge( /// to this function, either directly or via metadata or provider structs. #[inline(always)] unsafe fn insert_or_barrier_update( - texture_data: (&LifeGuard, &TextureSelector), + texture_selector: &TextureSelector, start_state: Option<&mut TextureStateSet>, current_state_set: &mut TextureStateSet, - resource_metadata: &mut ResourceMetadata, - index32: u32, + resource_metadata: &mut ResourceMetadata>, index: usize, start_state_provider: TextureStateProvider<'_>, end_state_provider: Option>, - metadata_provider: ResourceMetadataProvider<'_, A>, + metadata_provider: ResourceMetadataProvider<'_, A, TextureId, Texture>, barriers: &mut Vec>, ) { let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; @@ -969,11 +932,10 @@ unsafe fn insert_or_barrier_update( if !currently_owned { unsafe { insert( - Some(texture_data), + Some(texture_selector), start_state, current_state_set, resource_metadata, - index32, index, start_state_provider, end_state_provider, @@ -986,9 +948,8 @@ unsafe fn insert_or_barrier_update( let update_state_provider = end_state_provider.unwrap_or_else(|| start_state_provider.clone()); unsafe { barrier( - texture_data, + texture_selector, current_state_set, - index32, index, start_state_provider, barriers, @@ -998,10 +959,9 @@ unsafe fn insert_or_barrier_update( let start_state_set = start_state.unwrap(); unsafe { update( - texture_data, + texture_selector, start_state_set, current_state_set, - index32, index, update_state_provider, ) @@ -1010,24 +970,23 @@ unsafe fn insert_or_barrier_update( #[inline(always)] unsafe fn insert( - texture_data: Option<(&LifeGuard, &TextureSelector)>, + texture_selector: Option<&TextureSelector>, start_state: Option<&mut TextureStateSet>, end_state: &mut TextureStateSet, - resource_metadata: &mut ResourceMetadata, - index32: u32, + resource_metadata: &mut ResourceMetadata>, index: usize, start_state_provider: TextureStateProvider<'_>, end_state_provider: Option>, - metadata_provider: ResourceMetadataProvider<'_, A>, + metadata_provider: ResourceMetadataProvider<'_, A, TextureId, Texture>, ) { - let start_layers = unsafe { start_state_provider.get_state(texture_data, index32, index) }; + let start_layers = unsafe { start_state_provider.get_state(texture_selector, index) }; match start_layers { SingleOrManyStates::Single(state) => { // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. strict_assert_eq!(invalid_resource_state(state), false); - log::trace!("\ttex {index32}: insert start {state:?}"); + log::trace!("\ttex {index}: insert start {state:?}"); if let Some(start_state) = start_state { unsafe { *start_state.simple.get_unchecked_mut(index) = state }; @@ -1039,100 +998,95 @@ unsafe fn insert( } } SingleOrManyStates::Many(state_iter) => { - let full_range = texture_data.unwrap().1.clone(); + let full_range = texture_selector.unwrap().clone(); let complex = unsafe { ComplexTextureState::from_selector_state_iter(full_range, state_iter) }; - log::trace!("\ttex {index32}: insert start {complex:?}"); + log::trace!("\ttex {index}: insert start {complex:?}"); if let Some(start_state) = start_state { unsafe { *start_state.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; - start_state.complex.insert(index32, complex.clone()); + start_state.complex.insert(index, complex.clone()); } // We only need to insert ourselves the end state if there is no end state provider. if end_state_provider.is_none() { unsafe { *end_state.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; - end_state.complex.insert(index32, complex); + end_state.complex.insert(index, complex); } } } if let Some(end_state_provider) = end_state_provider { - match unsafe { end_state_provider.get_state(texture_data, index32, index) } { + match unsafe { end_state_provider.get_state(texture_selector, index) } { SingleOrManyStates::Single(state) => { // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. strict_assert_eq!(invalid_resource_state(state), false); - log::trace!("\ttex {index32}: insert end {state:?}"); + log::trace!("\ttex {index}: insert end {state:?}"); // We only need to insert into the end, as there is guarenteed to be // a start state provider. unsafe { *end_state.simple.get_unchecked_mut(index) = state }; } SingleOrManyStates::Many(state_iter) => { - let full_range = texture_data.unwrap().1.clone(); + let full_range = texture_selector.unwrap().clone(); let complex = unsafe { ComplexTextureState::from_selector_state_iter(full_range, state_iter) }; - log::trace!("\ttex {index32}: insert end {complex:?}"); + log::trace!("\ttex {index}: insert end {complex:?}"); // We only need to insert into the end, as there is guarenteed to be // a start state provider. unsafe { *end_state.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; - end_state.complex.insert(index32, complex); + end_state.complex.insert(index, complex); } } } unsafe { - let (epoch, ref_count) = - metadata_provider.get_own(texture_data.map(|(life_guard, _)| life_guard), index); - resource_metadata.insert(index, epoch, ref_count); + let resource = metadata_provider.get_own(index); + resource_metadata.insert(index, resource); } } #[inline(always)] unsafe fn merge( - texture_data: (&LifeGuard, &TextureSelector), + texture_selector: &TextureSelector, current_state_set: &mut TextureStateSet, - index32: u32, index: usize, state_provider: TextureStateProvider<'_>, - metadata_provider: ResourceMetadataProvider<'_, A>, + metadata_provider: ResourceMetadataProvider<'_, A, TextureId, Texture>, ) -> Result<(), UsageConflict> { let current_simple = unsafe { current_state_set.simple.get_unchecked_mut(index) }; let current_state = if *current_simple == TextureUses::COMPLEX { SingleOrManyStates::Many(unsafe { - current_state_set - .complex - .get_mut(&index32) - .unwrap_unchecked() + current_state_set.complex.get_mut(&index).unwrap_unchecked() }) } else { SingleOrManyStates::Single(current_simple) }; - let new_state = unsafe { state_provider.get_state(Some(texture_data), index32, index) }; + let new_state = unsafe { state_provider.get_state(Some(texture_selector), index) }; match (current_state, new_state) { (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Single(new_simple)) => { let merged_state = *current_simple | new_simple; - log::trace!("\ttex {index32}: merge simple {current_simple:?} + {new_simple:?}"); + log::trace!("\ttex {index}: merge simple {current_simple:?} + {new_simple:?}"); if invalid_resource_state(merged_state) { return Err(UsageConflict::from_texture( TextureId::zip( - index32, + index as _, unsafe { metadata_provider.get_epoch(index) }, A::VARIANT, ), - texture_data.1.clone(), + texture_selector.clone(), *current_simple, new_simple, )); @@ -1146,22 +1100,20 @@ unsafe fn merge( // as there wasn't one before. let mut new_complex = unsafe { ComplexTextureState::from_selector_state_iter( - texture_data.1.clone(), - iter::once((texture_data.1.clone(), *current_simple)), + texture_selector.clone(), + iter::once((texture_selector.clone(), *current_simple)), ) }; for (selector, new_state) in new_many { let merged_state = *current_simple | new_state; - log::trace!( - "\ttex {index32}: merge {selector:?} {current_simple:?} + {new_state:?}" - ); + log::trace!("\ttex {index}: merge {selector:?} {current_simple:?} + {new_state:?}"); if invalid_resource_state(merged_state) { return Err(UsageConflict::from_texture( TextureId::zip( - index32, + index as _, unsafe { metadata_provider.get_epoch(index) }, A::VARIANT, ), @@ -1185,7 +1137,7 @@ unsafe fn merge( } *current_simple = TextureUses::COMPLEX; - current_state_set.complex.insert(index32, new_complex); + current_state_set.complex.insert(index, new_complex); } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_simple)) => { for (mip_id, mip) in current_complex.mips.iter_mut().enumerate() { @@ -1199,14 +1151,14 @@ unsafe fn merge( let merged_state = merged_state - TextureUses::UNKNOWN; log::trace!( - "\ttex {index32}: merge mip {mip_id} layers {layers:?} \ + "\ttex {index}: merge mip {mip_id} layers {layers:?} \ {current_layer_state:?} + {new_simple:?}" ); if invalid_resource_state(merged_state) { return Err(UsageConflict::from_texture( TextureId::zip( - index32, + index as _, unsafe { metadata_provider.get_epoch(index) }, A::VARIANT, ), @@ -1244,14 +1196,14 @@ unsafe fn merge( } log::trace!( - "\ttex {index32}: merge mip {mip_id} layers {layers:?} \ + "\ttex {index}: merge mip {mip_id} layers {layers:?} \ {current_layer_state:?} + {new_state:?}" ); if invalid_resource_state(merged_state) { return Err(UsageConflict::from_texture( TextureId::zip( - index32, + index as _, unsafe { metadata_provider.get_epoch(index) }, A::VARIANT, ), @@ -1276,9 +1228,8 @@ unsafe fn merge( #[inline(always)] unsafe fn barrier( - texture_data: (&LifeGuard, &TextureSelector), + texture_selector: &TextureSelector, current_state_set: &TextureStateSet, - index32: u32, index: usize, state_provider: TextureStateProvider<'_>, barriers: &mut Vec>, @@ -1286,13 +1237,13 @@ unsafe fn barrier( let current_simple = unsafe { *current_state_set.simple.get_unchecked(index) }; let current_state = if current_simple == TextureUses::COMPLEX { SingleOrManyStates::Many(unsafe { - current_state_set.complex.get(&index32).unwrap_unchecked() + current_state_set.complex.get(&index).unwrap_unchecked() }) } else { SingleOrManyStates::Single(current_simple) }; - let new_state = unsafe { state_provider.get_state(Some(texture_data), index32, index) }; + let new_state = unsafe { state_provider.get_state(Some(texture_selector), index) }; match (current_state, new_state) { (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Single(new_simple)) => { @@ -1300,11 +1251,11 @@ unsafe fn barrier( return; } - log::trace!("\ttex {index32}: transition simple {current_simple:?} -> {new_simple:?}"); + log::trace!("\ttex {index}: transition simple {current_simple:?} -> {new_simple:?}"); barriers.push(PendingTransition { - id: index32, - selector: texture_data.1.clone(), + id: index as _, + selector: texture_selector.clone(), usage: current_simple..new_simple, }); } @@ -1319,11 +1270,11 @@ unsafe fn barrier( } log::trace!( - "\ttex {index32}: transition {selector:?} {current_simple:?} -> {new_state:?}" + "\ttex {index}: transition {selector:?} {current_simple:?} -> {new_state:?}" ); barriers.push(PendingTransition { - id: index32, + id: index as _, selector, usage: current_simple..new_state, }); @@ -1343,12 +1294,12 @@ unsafe fn barrier( } log::trace!( - "\ttex {index32}: transition mip {mip_id} layers {layers:?} \ + "\ttex {index}: transition mip {mip_id} layers {layers:?} \ {current_layer_state:?} -> {new_simple:?}" ); barriers.push(PendingTransition { - id: index32, + id: index as _, selector: TextureSelector { mips: mip_id..mip_id + 1, layers: layers.clone(), @@ -1377,12 +1328,12 @@ unsafe fn barrier( } log::trace!( - "\ttex {index32}: transition mip {mip_id} layers {layers:?} \ + "\ttex {index}: transition mip {mip_id} layers {layers:?} \ {current_layer_state:?} -> {new_state:?}" ); barriers.push(PendingTransition { - id: index32, + id: index as _, selector: TextureSelector { mips: mip_id..mip_id + 1, layers, @@ -1399,10 +1350,9 @@ unsafe fn barrier( #[allow(clippy::needless_option_as_deref)] // we use this for reborrowing Option<&mut T> #[inline(always)] unsafe fn update( - texture_data: (&LifeGuard, &TextureSelector), + texture_selector: &TextureSelector, start_state_set: &mut TextureStateSet, current_state_set: &mut TextureStateSet, - index32: u32, index: usize, state_provider: TextureStateProvider<'_>, ) { @@ -1413,23 +1363,19 @@ unsafe fn update( // If the state is simple, the first insert to the tracker would cover it. let mut start_complex = None; if start_simple == TextureUses::COMPLEX { - start_complex = - Some(unsafe { start_state_set.complex.get_mut(&index32).unwrap_unchecked() }); + start_complex = Some(unsafe { start_state_set.complex.get_mut(&index).unwrap_unchecked() }); } let current_simple = unsafe { current_state_set.simple.get_unchecked_mut(index) }; let current_state = if *current_simple == TextureUses::COMPLEX { SingleOrManyStates::Many(unsafe { - current_state_set - .complex - .get_mut(&index32) - .unwrap_unchecked() + current_state_set.complex.get_mut(&index).unwrap_unchecked() }) } else { SingleOrManyStates::Single(current_simple) }; - let new_state = unsafe { state_provider.get_state(Some(texture_data), index32, index) }; + let new_state = unsafe { state_provider.get_state(Some(texture_selector), index) }; match (current_state, new_state) { (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Single(new_simple)) => { @@ -1441,8 +1387,8 @@ unsafe fn update( // as there wasn't one before. let mut new_complex = unsafe { ComplexTextureState::from_selector_state_iter( - texture_data.1.clone(), - iter::once((texture_data.1.clone(), *current_simple)), + texture_selector.clone(), + iter::once((texture_selector.clone(), *current_simple)), ) }; @@ -1464,7 +1410,7 @@ unsafe fn update( } *current_simple = TextureUses::COMPLEX; - current_state_set.complex.insert(index32, new_complex); + current_state_set.complex.insert(index, new_complex); } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_single)) => { for (mip_id, mip) in current_complex.mips.iter().enumerate() { @@ -1490,12 +1436,7 @@ unsafe fn update( } unsafe { *current_state_set.simple.get_unchecked_mut(index) = new_single }; - unsafe { - current_state_set - .complex - .remove(&index32) - .unwrap_unchecked() - }; + unsafe { current_state_set.complex.remove(&index).unwrap_unchecked() }; } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { for (selector, new_state) in new_many { diff --git a/wgpu-core/src/validation.rs b/wgpu-core/src/validation.rs index 5ae2a3a82c..239faa2c84 100644 --- a/wgpu-core/src/validation.rs +++ b/wgpu-core/src/validation.rs @@ -1,4 +1,5 @@ -use crate::{binding_model::BindEntryMap, FastHashMap, FastHashSet}; +use crate::{device::bgl, FastHashMap, FastHashSet}; +use arrayvec::ArrayVec; use std::{collections::hash_map::Entry, fmt}; use thiserror::Error; use wgt::{BindGroupLayoutEntry, BindingType}; @@ -58,13 +59,18 @@ impl NumericDimension { #[derive(Clone, Copy, Debug)] pub struct NumericType { dim: NumericDimension, - kind: naga::ScalarKind, - width: naga::Bytes, + scalar: naga::Scalar, } impl fmt::Display for NumericType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{}{}", self.kind, self.width * 8, self.dim) + write!( + f, + "{:?}{}{}", + self.scalar.kind, + self.scalar.width * 8, + self.dim + ) } } @@ -117,11 +123,13 @@ struct EntryPoint { spec_constants: Vec, sampling_pairs: FastHashSet<(naga::Handle, naga::Handle)>, workgroup_size: [u32; 3], + dual_source_blending: bool, } #[derive(Debug)] pub struct Interface { limits: wgt::Limits, + features: wgt::Features, resources: naga::Arena, entry_points: FastHashMap<(naga::ShaderStage, String), EntryPoint>, } @@ -246,7 +254,7 @@ pub enum StageError { TooManyVaryings { used: u32, limit: u32 }, #[error("Unable to find entry point '{0}'")] MissingEntryPoint(String), - #[error("Shader global {0:?} is not available in the layout pipeline layout")] + #[error("Shader global {0:?} is not available in the pipeline layout")] Binding(naga::ResourceBinding, #[source] BindingError), #[error("Unable to filter the texture ({texture:?}) by the sampler ({sampler:?})")] Filtering { @@ -294,7 +302,9 @@ fn map_storage_format_to_naga(format: wgt::TextureFormat) -> Option Sf::Rgba8Snorm, Tf::Rgba8Uint => Sf::Rgba8Uint, Tf::Rgba8Sint => Sf::Rgba8Sint, + Tf::Bgra8Unorm => Sf::Bgra8Unorm, + Tf::Rgb10a2Uint => Sf::Rgb10a2Uint, Tf::Rgb10a2Unorm => Sf::Rgb10a2Unorm, Tf::Rg11b10Float => Sf::Rg11b10Float, @@ -348,7 +358,9 @@ fn map_storage_format_from_naga(format: naga::StorageFormat) -> wgt::TextureForm Sf::Rgba8Snorm => Tf::Rgba8Snorm, Sf::Rgba8Uint => Tf::Rgba8Uint, Sf::Rgba8Sint => Tf::Rgba8Sint, + Sf::Bgra8Unorm => Tf::Bgra8Unorm, + Sf::Rgb10a2Uint => Tf::Rgb10a2Uint, Sf::Rgb10a2Unorm => Tf::Rgb10a2Unorm, Sf::Rg11b10Float => Tf::Rg11b10Float, @@ -551,7 +563,9 @@ impl Resource { } naga::ScalarKind::Sint => wgt::TextureSampleType::Sint, naga::ScalarKind::Uint => wgt::TextureSampleType::Uint, - naga::ScalarKind::Bool => unreachable!(), + naga::ScalarKind::AbstractInt + | naga::ScalarKind::AbstractFloat + | naga::ScalarKind::Bool => unreachable!(), }, view_dimension, multisampled: multi, @@ -589,77 +603,76 @@ impl Resource { impl NumericType { fn from_vertex_format(format: wgt::VertexFormat) -> Self { - use naga::{ScalarKind as Sk, VectorSize as Vs}; + use naga::{Scalar, VectorSize as Vs}; use wgt::VertexFormat as Vf; - let (dim, kind, width) = match format { - Vf::Uint32 => (NumericDimension::Scalar, Sk::Uint, 4), + let (dim, scalar) = match format { + Vf::Uint32 => (NumericDimension::Scalar, Scalar::U32), Vf::Uint8x2 | Vf::Uint16x2 | Vf::Uint32x2 => { - (NumericDimension::Vector(Vs::Bi), Sk::Uint, 4) + (NumericDimension::Vector(Vs::Bi), Scalar::U32) } - Vf::Uint32x3 => (NumericDimension::Vector(Vs::Tri), Sk::Uint, 4), + Vf::Uint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::U32), Vf::Uint8x4 | Vf::Uint16x4 | Vf::Uint32x4 => { - (NumericDimension::Vector(Vs::Quad), Sk::Uint, 4) + (NumericDimension::Vector(Vs::Quad), Scalar::U32) } - Vf::Sint32 => (NumericDimension::Scalar, Sk::Sint, 4), + Vf::Sint32 => (NumericDimension::Scalar, Scalar::I32), Vf::Sint8x2 | Vf::Sint16x2 | Vf::Sint32x2 => { - (NumericDimension::Vector(Vs::Bi), Sk::Sint, 4) + (NumericDimension::Vector(Vs::Bi), Scalar::I32) } - Vf::Sint32x3 => (NumericDimension::Vector(Vs::Tri), Sk::Sint, 4), + Vf::Sint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::I32), Vf::Sint8x4 | Vf::Sint16x4 | Vf::Sint32x4 => { - (NumericDimension::Vector(Vs::Quad), Sk::Sint, 4) + (NumericDimension::Vector(Vs::Quad), Scalar::I32) } - Vf::Float32 => (NumericDimension::Scalar, Sk::Float, 4), + Vf::Float32 => (NumericDimension::Scalar, Scalar::F32), Vf::Unorm8x2 | Vf::Snorm8x2 | Vf::Unorm16x2 | Vf::Snorm16x2 | Vf::Float16x2 - | Vf::Float32x2 => (NumericDimension::Vector(Vs::Bi), Sk::Float, 4), - Vf::Float32x3 => (NumericDimension::Vector(Vs::Tri), Sk::Float, 4), + | Vf::Float32x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F32), + Vf::Float32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F32), Vf::Unorm8x4 | Vf::Snorm8x4 | Vf::Unorm16x4 | Vf::Snorm16x4 | Vf::Float16x4 - | Vf::Float32x4 => (NumericDimension::Vector(Vs::Quad), Sk::Float, 4), - Vf::Float64 => (NumericDimension::Scalar, Sk::Float, 8), - Vf::Float64x2 => (NumericDimension::Vector(Vs::Bi), Sk::Float, 8), - Vf::Float64x3 => (NumericDimension::Vector(Vs::Tri), Sk::Float, 8), - Vf::Float64x4 => (NumericDimension::Vector(Vs::Quad), Sk::Float, 8), + | Vf::Float32x4 => (NumericDimension::Vector(Vs::Quad), Scalar::F32), + Vf::Float64 => (NumericDimension::Scalar, Scalar::F64), + Vf::Float64x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F64), + Vf::Float64x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F64), + Vf::Float64x4 => (NumericDimension::Vector(Vs::Quad), Scalar::F64), }; NumericType { dim, - kind, //Note: Shader always sees data as int, uint, or float. // It doesn't know if the original is normalized in a tighter form. - width, + scalar, } } fn from_texture_format(format: wgt::TextureFormat) -> Self { - use naga::{ScalarKind as Sk, VectorSize as Vs}; + use naga::{Scalar, VectorSize as Vs}; use wgt::TextureFormat as Tf; - let (dim, kind) = match format { + let (dim, scalar) = match format { Tf::R8Unorm | Tf::R8Snorm | Tf::R16Float | Tf::R32Float => { - (NumericDimension::Scalar, Sk::Float) + (NumericDimension::Scalar, Scalar::F32) } - Tf::R8Uint | Tf::R16Uint | Tf::R32Uint => (NumericDimension::Scalar, Sk::Uint), - Tf::R8Sint | Tf::R16Sint | Tf::R32Sint => (NumericDimension::Scalar, Sk::Sint), + Tf::R8Uint | Tf::R16Uint | Tf::R32Uint => (NumericDimension::Scalar, Scalar::U32), + Tf::R8Sint | Tf::R16Sint | Tf::R32Sint => (NumericDimension::Scalar, Scalar::I32), Tf::Rg8Unorm | Tf::Rg8Snorm | Tf::Rg16Float | Tf::Rg32Float => { - (NumericDimension::Vector(Vs::Bi), Sk::Float) + (NumericDimension::Vector(Vs::Bi), Scalar::F32) } Tf::Rg8Uint | Tf::Rg16Uint | Tf::Rg32Uint => { - (NumericDimension::Vector(Vs::Bi), Sk::Uint) + (NumericDimension::Vector(Vs::Bi), Scalar::U32) } Tf::Rg8Sint | Tf::Rg16Sint | Tf::Rg32Sint => { - (NumericDimension::Vector(Vs::Bi), Sk::Sint) + (NumericDimension::Vector(Vs::Bi), Scalar::I32) } - Tf::R16Snorm | Tf::R16Unorm => (NumericDimension::Scalar, Sk::Float), - Tf::Rg16Snorm | Tf::Rg16Unorm => (NumericDimension::Vector(Vs::Bi), Sk::Float), - Tf::Rgba16Snorm | Tf::Rgba16Unorm => (NumericDimension::Vector(Vs::Quad), Sk::Float), + Tf::R16Snorm | Tf::R16Unorm => (NumericDimension::Scalar, Scalar::F32), + Tf::Rg16Snorm | Tf::Rg16Unorm => (NumericDimension::Vector(Vs::Bi), Scalar::F32), + Tf::Rgba16Snorm | Tf::Rgba16Unorm => (NumericDimension::Vector(Vs::Quad), Scalar::F32), Tf::Rgba8Unorm | Tf::Rgba8UnormSrgb | Tf::Rgba8Snorm @@ -667,14 +680,14 @@ impl NumericType { | Tf::Bgra8UnormSrgb | Tf::Rgb10a2Unorm | Tf::Rgba16Float - | Tf::Rgba32Float => (NumericDimension::Vector(Vs::Quad), Sk::Float), - Tf::Rgba8Uint | Tf::Rgba16Uint | Tf::Rgba32Uint => { - (NumericDimension::Vector(Vs::Quad), Sk::Uint) + | Tf::Rgba32Float => (NumericDimension::Vector(Vs::Quad), Scalar::F32), + Tf::Rgba8Uint | Tf::Rgba16Uint | Tf::Rgba32Uint | Tf::Rgb10a2Uint => { + (NumericDimension::Vector(Vs::Quad), Scalar::U32) } Tf::Rgba8Sint | Tf::Rgba16Sint | Tf::Rgba32Sint => { - (NumericDimension::Vector(Vs::Quad), Sk::Sint) + (NumericDimension::Vector(Vs::Quad), Scalar::I32) } - Tf::Rg11b10Float => (NumericDimension::Vector(Vs::Tri), Sk::Float), + Tf::Rg11b10Float => (NumericDimension::Vector(Vs::Tri), Scalar::F32), Tf::Stencil8 | Tf::Depth16Unorm | Tf::Depth32Float @@ -683,7 +696,8 @@ impl NumericType { | Tf::Depth24PlusStencil8 => { panic!("Unexpected depth format") } - Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Sk::Float), + Tf::NV12 => panic!("Unexpected nv12 format"), + Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32), Tf::Bc1RgbaUnorm | Tf::Bc1RgbaUnormSrgb | Tf::Bc2RgbaUnorm @@ -695,36 +709,35 @@ impl NumericType { | Tf::Etc2Rgb8A1Unorm | Tf::Etc2Rgb8A1UnormSrgb | Tf::Etc2Rgba8Unorm - | Tf::Etc2Rgba8UnormSrgb => (NumericDimension::Vector(Vs::Quad), Sk::Float), + | Tf::Etc2Rgba8UnormSrgb => (NumericDimension::Vector(Vs::Quad), Scalar::F32), Tf::Bc4RUnorm | Tf::Bc4RSnorm | Tf::EacR11Unorm | Tf::EacR11Snorm => { - (NumericDimension::Scalar, Sk::Float) + (NumericDimension::Scalar, Scalar::F32) } Tf::Bc5RgUnorm | Tf::Bc5RgSnorm | Tf::EacRg11Unorm | Tf::EacRg11Snorm => { - (NumericDimension::Vector(Vs::Bi), Sk::Float) + (NumericDimension::Vector(Vs::Bi), Scalar::F32) } Tf::Bc6hRgbUfloat | Tf::Bc6hRgbFloat | Tf::Etc2Rgb8Unorm | Tf::Etc2Rgb8UnormSrgb => { - (NumericDimension::Vector(Vs::Tri), Sk::Float) + (NumericDimension::Vector(Vs::Tri), Scalar::F32) } Tf::Astc { block: _, channel: _, - } => (NumericDimension::Vector(Vs::Quad), Sk::Float), + } => (NumericDimension::Vector(Vs::Quad), Scalar::F32), }; NumericType { dim, - kind, //Note: Shader always sees data as int, uint, or float. // It doesn't know if the original is normalized in a tighter form. - width: 4, + scalar, } } fn is_subtype_of(&self, other: &NumericType) -> bool { - if self.width > other.width { + if self.scalar.width > other.scalar.width { return false; } - if self.kind != other.kind { + if self.scalar.kind != other.scalar.kind { return false; } match (self.dim, other.dim) { @@ -739,7 +752,7 @@ impl NumericType { } fn is_compatible_with(&self, other: &NumericType) -> bool { - if self.kind != other.kind { + if self.scalar.kind != other.scalar.kind { return false; } match (self.dim, other.dim) { @@ -765,6 +778,27 @@ pub fn check_texture_format( } } +pub enum BindingLayoutSource<'a> { + /// The binding layout is derived from the pipeline layout. + /// + /// This will be filled in by the shader binding validation, as it iterates the shader's interfaces. + Derived(ArrayVec), + /// The binding layout is provided by the user in BGLs. + /// + /// This will be validated against the shader's interfaces. + Provided(ArrayVec<&'a bgl::EntryMap, { hal::MAX_BIND_GROUPS }>), +} + +impl<'a> BindingLayoutSource<'a> { + pub fn new_derived(limits: &wgt::Limits) -> Self { + let mut array = ArrayVec::new(); + for _ in 0..limits.max_bind_groups { + array.push(Default::default()); + } + BindingLayoutSource::Derived(array) + } +} + pub type StageIo = FastHashMap; impl Interface { @@ -775,24 +809,21 @@ impl Interface { arena: &naga::UniqueArena, ) { let numeric_ty = match arena[ty].inner { - naga::TypeInner::Scalar { kind, width } => NumericType { + naga::TypeInner::Scalar(scalar) => NumericType { dim: NumericDimension::Scalar, - kind, - width, + scalar, }, - naga::TypeInner::Vector { size, kind, width } => NumericType { + naga::TypeInner::Vector { size, scalar } => NumericType { dim: NumericDimension::Vector(size), - kind, - width, + scalar, }, naga::TypeInner::Matrix { columns, rows, - width, + scalar, } => NumericType { dim: NumericDimension::Matrix(columns, rows), - kind: naga::ScalarKind::Float, - width, + scalar, }, naga::TypeInner::Struct { ref members, .. } => { for member in members { @@ -815,6 +846,7 @@ impl Interface { location, interpolation, sampling, + .. // second_blend_source }) => Varying::Local { location, iv: InterfaceVar { @@ -832,7 +864,12 @@ impl Interface { list.push(varying); } - pub fn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self { + pub fn new( + module: &naga::Module, + info: &naga::valid::ModuleInfo, + limits: wgt::Limits, + features: wgt::Features, + ) -> Self { let mut resources = naga::Arena::new(); let mut resource_mapping = FastHashMap::default(); for (var_handle, var) in module.global_variables.iter() { @@ -906,7 +943,7 @@ impl Interface { ep.sampling_pairs .insert((resource_mapping[&key.image], resource_mapping[&key.sampler])); } - + ep.dual_source_blending = info.dual_source_blending; ep.workgroup_size = entry_point.workgroup_size; entry_points.insert((entry_point.stage, entry_point.name.clone()), ep); @@ -914,6 +951,7 @@ impl Interface { Self { limits, + features, resources, entry_points, } @@ -921,8 +959,7 @@ impl Interface { pub fn check_stage( &self, - given_layouts: Option<&[&BindEntryMap]>, - derived_layouts: &mut [BindEntryMap], + layouts: &mut BindingLayoutSource<'_>, shader_binding_sizes: &mut FastHashMap, entry_point_name: &str, stage_bit: wgt::ShaderStages, @@ -946,45 +983,53 @@ impl Interface { // check resources visibility for &handle in entry_point.resources.iter() { let res = &self.resources[handle]; - let result = match given_layouts { - Some(layouts) => { - // update the required binding size for this buffer - if let ResourceType::Buffer { size } = res.ty { - match shader_binding_sizes.entry(res.bind.clone()) { - Entry::Occupied(e) => { - *e.into_mut() = size.max(*e.get()); - } - Entry::Vacant(e) => { - e.insert(size); + let result = 'err: { + match layouts { + BindingLayoutSource::Provided(layouts) => { + // update the required binding size for this buffer + if let ResourceType::Buffer { size } = res.ty { + match shader_binding_sizes.entry(res.bind.clone()) { + Entry::Occupied(e) => { + *e.into_mut() = size.max(*e.get()); + } + Entry::Vacant(e) => { + e.insert(size); + } } } + + let Some(map) = layouts.get(res.bind.group as usize) else { + break 'err Err(BindingError::Missing); + }; + + let Some(entry) = map.get(res.bind.binding) else { + break 'err Err(BindingError::Missing); + }; + + if !entry.visibility.contains(stage_bit) { + break 'err Err(BindingError::Invisible); + } + + res.check_binding_use(entry) } - layouts - .get(res.bind.group as usize) - .and_then(|map| map.get(&res.bind.binding)) - .ok_or(BindingError::Missing) - .and_then(|entry| { - if entry.visibility.contains(stage_bit) { - Ok(entry) - } else { - Err(BindingError::Invisible) - } - }) - .and_then(|entry| res.check_binding_use(entry)) - } - None => derived_layouts - .get_mut(res.bind.group as usize) - .ok_or(BindingError::Missing) - .and_then(|set| { - let ty = res.derive_binding_type()?; - match set.entry(res.bind.binding) { - Entry::Occupied(e) if e.get().ty != ty => { - return Err(BindingError::InconsistentlyDerivedType) + BindingLayoutSource::Derived(layouts) => { + let Some(map) = layouts.get_mut(res.bind.group as usize) else { + break 'err Err(BindingError::Missing); + }; + + let ty = match res.derive_binding_type() { + Ok(ty) => ty, + Err(error) => break 'err Err(error), + }; + + match map.entry(res.bind.binding) { + indexmap::map::Entry::Occupied(e) if e.get().ty != ty => { + break 'err Err(BindingError::InconsistentlyDerivedType) } - Entry::Occupied(e) => { + indexmap::map::Entry::Occupied(e) => { e.into_mut().visibility |= stage_bit; } - Entry::Vacant(e) => { + indexmap::map::Entry::Vacant(e) => { e.insert(BindGroupLayoutEntry { binding: res.bind.binding, ty, @@ -994,20 +1039,28 @@ impl Interface { } } Ok(()) - }), + } + } }; if let Err(error) = result { return Err(StageError::Binding(res.bind.clone(), error)); } } - // check the compatibility between textures and samplers - if let Some(layouts) = given_layouts { + // Check the compatibility between textures and samplers + // + // We only need to do this if the binding layout is provided by the user, as derived + // layouts will inherently be correctly tagged. + if let BindingLayoutSource::Provided(layouts) = layouts { for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() { let texture_bind = &self.resources[texture_handle].bind; let sampler_bind = &self.resources[sampler_handle].bind; - let texture_layout = &layouts[texture_bind.group as usize][&texture_bind.binding]; - let sampler_layout = &layouts[sampler_bind.group as usize][&sampler_bind.binding]; + let texture_layout = layouts[texture_bind.group as usize] + .get(texture_bind.binding) + .unwrap(); + let sampler_layout = layouts[sampler_bind.group as usize] + .get(sampler_bind.binding) + .unwrap(); assert!(texture_layout.visibility.contains(stage_bit)); assert!(sampler_layout.visibility.contains(stage_bit)); @@ -1123,7 +1176,12 @@ impl Interface { } // Check all vertex outputs and make sure the fragment shader consumes them. - if shader_stage == naga::ShaderStage::Fragment { + // This requirement is removed if the `SHADER_UNUSED_VERTEX_OUTPUT` feature is enabled. + if shader_stage == naga::ShaderStage::Fragment + && !self + .features + .contains(wgt::Features::SHADER_UNUSED_VERTEX_OUTPUT) + { for &index in inputs.keys() { // This is a linear scan, but the count should be low enough // that this should be fine. @@ -1180,4 +1238,15 @@ impl Interface { .collect(); Ok(outputs) } + + pub fn fragment_uses_dual_source_blending( + &self, + entry_point_name: &str, + ) -> Result { + let pair = (naga::ShaderStage::Fragment, entry_point_name.to_string()); + self.entry_points + .get(&pair) + .ok_or(StageError::MissingEntryPoint(pair.1)) + .map(|ep| ep.dual_source_blending) + } } diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index 51b5e2a9ac..36796b33d8 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "wgpu-hal" -version = "0.17.0" -authors = ["wgpu developers"] +version = "0.18.0" +authors = ["gfx-rs developers"] edition = "2021" description = "WebGPU hardware abstraction layer" homepage = "https://wgpu.rs/" @@ -13,14 +13,14 @@ license = "MIT OR Apache-2.0" # copy the crates it actually uses out of the workspace, so it's meaningful for # them to have less restrictive MSRVs individually than the workspace as a # whole, if their code permits. See `../README.md` for details. -rust-version = "1.60" +rust-version = "1.70" [package.metadata.docs.rs] # Ideally we would enable all the features. # -# However the metal features fails to be documented because the docs.rs runner cross compiling under +# However, the metal features fail to be documented because the docs.rs runner cross-compiling under # x86_64-unknown-linux-gnu and metal-rs cannot compile in that environment at the moment. The same applies -# with the dx11 and dx12 features. +# for the dx12 feature. features = ["vulkan", "gles", "renderdoc"] rustdoc-args = ["--cfg", "docsrs"] targets = [ @@ -35,10 +35,28 @@ targets = [ [features] default = ["link"] metal = ["naga/msl-out", "block"] -vulkan = ["naga/spv-out", "ash", "gpu-alloc", "gpu-descriptor", "libloading", "smallvec"] -gles = ["naga/glsl-out", "glow", "khronos-egl", "libloading"] -dx11 = ["naga/hlsl-out", "d3d12", "libloading", "winapi/d3d11", "winapi/std", "winapi/d3d11_1", "winapi/d3d11_2", "winapi/d3d11sdklayers", "winapi/dxgi1_6"] -dx12 = ["naga/hlsl-out", "d3d12", "bit-set", "libloading", "range-alloc", "winapi/std", "winapi/winbase", "winapi/d3d12", "winapi/d3d12shader", "winapi/d3d12sdklayers", "winapi/dxgi1_6"] +vulkan = [ + "naga/spv-out", + "ash", + "gpu-alloc", + "gpu-descriptor", + "libloading", + "smallvec", +] +gles = ["naga/glsl-out", "glow", "glutin_wgl_sys", "khronos-egl", "libloading"] +dx12 = [ + "naga/hlsl-out", + "d3d12", + "bit-set", + "libloading", + "range-alloc", + "winapi/std", + "winapi/winbase", + "winapi/d3d12", + "winapi/d3d12shader", + "winapi/d3d12sdklayers", + "winapi/dxgi1_6", +] # TODO: This is a separate feature until Mozilla okays windows-rs, see https://github.com/gfx-rs/wgpu/issues/3207 for the tracking issue. windows_rs = ["gpu-allocator"] dxc_shader_compiler = ["hassle-rs"] @@ -57,8 +75,9 @@ required-features = ["gles"] bitflags = "2" parking_lot = ">=0.11,<0.13" profiling = { version = "1", default-features = false } -raw-window-handle = "0.5" +raw-window-handle = "0.6" thiserror = "1" +once_cell = "1.19.0" # backends common arrayvec = "0.7" @@ -66,12 +85,12 @@ rustc-hash = "1.1" log = "0.4" # backend: Gles -glow = { version = "0.12.3", optional = true } +glow = { version = "0.13", git = "https://github.com/grovesNL/glow.git", rev = "29ff917a2b2ff7ce0a81b2cc5681de6d4735b36e", optional = true } [dependencies.wgt] package = "wgpu-types" path = "../wgpu-types" -version = "0.17" +version = "0.18.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # backend: Vulkan @@ -80,37 +99,55 @@ gpu-alloc = { version = "0.6", optional = true } gpu-descriptor = { version = "0.2", optional = true } smallvec = { version = "1", optional = true, features = ["union"] } -khronos-egl = { version = "4.1", features = ["dynamic"], optional = true } -libloading = { version = ">=0.7,<0.9", optional = true } +khronos-egl = { version = "6", features = ["dynamic"], optional = true } +libloading = { version = ">=0.7, <0.9", optional = true } renderdoc-sys = { version = "1.0.0", optional = true } [target.'cfg(target_os = "emscripten")'.dependencies] -khronos-egl = { version = "4.1", features = ["static", "no-pkg-config"] } +khronos-egl = { version = "6", features = ["static", "no-pkg-config"] } #Note: it's unused by emscripten, but we keep it to have single code base in egl.rs -libloading = { version = ">=0.7,<0.9", optional = true } +libloading = { version = ">=0.7, <0.9", optional = true } [target.'cfg(windows)'.dependencies] # backend: Dx12 bit-set = { version = "0.5", optional = true } range-alloc = { version = "0.1", optional = true } -gpu-allocator = { version = "0.22", default_features = false, features = ["d3d12", "windows", "public-winapi"], optional = true } -hassle-rs = { version = "0.10", optional = true } - -winapi = { version = "0.3", features = ["profileapi", "libloaderapi", "windef", "winuser", "dcomp"] } -d3d12 = { version = "0.7", features = ["libloading"], optional = true } +gpu-allocator = { version = "0.25", default_features = false, features = [ + "d3d12", + "public-winapi", +], optional = true } +hassle-rs = { version = "0.11", optional = true } +# backend: Gles +glutin_wgl_sys = { version = "0.5", optional = true } + +winapi = { version = "0.3", features = [ + "profileapi", + "libloaderapi", + "windef", + "winuser", + "dcomp", +] } +d3d12 = { path = "../d3d12/", version = "0.7.0", optional = true, features = [ + "libloading", +] } [target.'cfg(any(target_os="macos", target_os="ios"))'.dependencies] # backend: Metal block = { version = "0.1", optional = true } -metal = "0.26.0" +metal = "0.27.0" objc = "0.2.5" core-graphics-types = "0.1" [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies] wasm-bindgen = "0.2.87" -web-sys = { version = "0.3.64", features = ["Window", "HtmlCanvasElement", "WebGl2RenderingContext", "OffscreenCanvas"] } -js-sys = "0.3.64" +web-sys = { version = "0.3.66", features = [ + "Window", + "HtmlCanvasElement", + "WebGl2RenderingContext", + "OffscreenCanvas", +] } +js-sys = "0.3.66" [target.'cfg(unix)'.dependencies] libc = "0.2" @@ -119,23 +156,26 @@ libc = "0.2" android_system_properties = "0.1.1" [dependencies.naga] -git = "https://github.com/gfx-rs/naga" -rev = "7a19f3af909202c7eafd36633b5584bfbb353ecb" -version = "0.13.0" +path = "../naga" +version = "0.14.0" features = ["clone"] +[build-dependencies] +cfg_aliases.workspace = true + # DEV dependencies [dev-dependencies.naga] -git = "https://github.com/gfx-rs/naga" -rev = "7a19f3af909202c7eafd36633b5584bfbb353ecb" -version = "0.13.0" +path = "../naga" +version = "0.14.0" features = ["wgsl-in"] [dev-dependencies] cfg-if = "1" env_logger = "0.10" -winit = { version = "0.28.6", features = [ "android-native-activity" ] } # for "halmark" example -glam = "0.21.3" # for ray-traced-triangle example +glam = "0.25.0" # for ray-traced-triangle example +winit = { version = "0.29.9", features = [ + "android-native-activity", +] } # for "halmark" example [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] glutin = "0.29.1" # for "gles" example diff --git a/wgpu-hal/README.md b/wgpu-hal/README.md index 0ab7a0283a..588baa3cf5 100644 --- a/wgpu-hal/README.md +++ b/wgpu-hal/README.md @@ -15,7 +15,7 @@ such as running out-of-memory, or losing the device. For the counter-example, there is no error for mapping a buffer that's not mappable. As the buffer creator, the user should already know if they can map it. -The API accept iterators in order to avoid forcing the user to store data in particular containers. The implementation doesn't guarantee that any of the iterators are drained, unless stated otherwise by the function documentation. +The API accepts iterators in order to avoid forcing the user to store data in particular containers. The implementation doesn't guarantee that any of the iterators are drained, unless stated otherwise by the function documentation. For this reason, we recommend that iterators don't do any mutating work. # Debugging diff --git a/wgpu-hal/build.rs b/wgpu-hal/build.rs new file mode 100644 index 0000000000..7d17591605 --- /dev/null +++ b/wgpu-hal/build.rs @@ -0,0 +1,15 @@ +fn main() { + cfg_aliases::cfg_aliases! { + native: { not(target_arch = "wasm32") }, + send_sync: { any( + not(target_arch = "wasm32"), + all(feature = "fragile-send-sync-non-atomic-wasm", not(target_feature = "atomics")) + ) }, + webgl: { all(target_arch = "wasm32", not(target_os = "emscripten"), gles) }, + Emscripten: { all(target_os = "emscripten", gles) }, + dx12: { all(target_os = "windows", feature = "dx12") }, + gles: { all(feature = "gles") }, + metal: { all(any(target_os = "ios", target_os = "macos"), feature = "metal") }, + vulkan: { all(not(target_arch = "wasm32"), feature = "vulkan") } + } +} diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index 2b0081d20f..18f283d8e7 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -5,7 +5,12 @@ extern crate wgpu_hal as hal; use hal::{ Adapter as _, CommandEncoder as _, Device as _, Instance as _, Queue as _, Surface as _, }; -use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; +use winit::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::ControlFlow, + keyboard::{Key, NamedKey}, +}; use std::{ borrow::{Borrow, Cow}, @@ -89,20 +94,21 @@ impl Example { fn init(window: &winit::window::Window) -> Result> { let instance_desc = hal::InstanceDescriptor { name: "example", - flags: if cfg!(debug_assertions) { - hal::InstanceFlags::all() - } else { - hal::InstanceFlags::empty() - }, + flags: wgt::InstanceFlags::from_build_config().with_env(), // Can't rely on having DXC available, so use FXC instead dx12_shader_compiler: wgt::Dx12Compiler::Fxc, gles_minor_version: wgt::Gles3MinorVersion::default(), }; let instance = unsafe { A::Instance::init(&instance_desc)? }; - let mut surface = unsafe { - instance - .create_surface(window.raw_display_handle(), window.raw_window_handle()) - .unwrap() + let surface = { + let raw_window_handle = window.window_handle()?.as_raw(); + let raw_display_handle = window.display_handle()?.as_raw(); + + unsafe { + instance + .create_surface(raw_display_handle, raw_window_handle) + .unwrap() + } }; let (adapter, capabilities) = unsafe { @@ -113,11 +119,12 @@ impl Example { let exposed = adapters.swap_remove(0); (exposed.adapter, exposed.capabilities) }; + let surface_caps = unsafe { adapter.surface_capabilities(&surface) } .ok_or("failed to get surface capabilities")?; log::info!("Surface caps: {:#?}", surface_caps); - let hal::OpenDevice { device, mut queue } = unsafe { + let hal::OpenDevice { device, queue } = unsafe { adapter .open(wgt::Features::empty(), &wgt::Limits::default()) .unwrap() @@ -160,6 +167,7 @@ impl Example { hal::NagaShader { module: Cow::Owned(module), info, + debug_source: None, } }; let shader_desc = hal::ShaderModuleDescriptor { @@ -264,7 +272,7 @@ impl Example { }; let pipeline = unsafe { device.create_render_pipeline(&pipeline_desc).unwrap() }; - let texture_data = vec![0xFFu8; 4]; + let texture_data = [0xFFu8; 4]; let staging_buffer_desc = hal::BufferDescriptor { label: Some("stage"), @@ -568,10 +576,10 @@ impl Example { fn update(&mut self, event: winit::event::WindowEvent) { if let winit::event::WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(winit::event::VirtualKeyCode::Space), - state: winit::event::ElementState::Pressed, + event: + KeyEvent { + logical_key: Key::Named(NamedKey::Space), + state: ElementState::Pressed, .. }, .. @@ -722,13 +730,13 @@ impl Example { None }; self.queue.submit(&[&cmd_buf], fence_param).unwrap(); - self.queue.present(&mut self.surface, surface_tex).unwrap(); + self.queue.present(&self.surface, surface_tex).unwrap(); ctx.used_cmd_bufs.push(cmd_buf); ctx.used_views.push(surface_tex_view); }; if do_fence { - log::info!("Context switch from {}", self.context_index); + log::debug!("Context switch from {}", self.context_index); let old_fence_value = ctx.fence_value; if self.contexts.len() == 1 { let hal_desc = hal::CommandEncoderDescriptor { @@ -782,7 +790,7 @@ cfg_if::cfg_if! { fn main() { env_logger::init(); - let event_loop = winit::event_loop::EventLoop::new(); + let event_loop = winit::event_loop::EventLoop::new().unwrap(); let window = winit::window::WindowBuilder::new() .with_title("hal-bunnymark") .build(&event_loop) @@ -794,51 +802,49 @@ fn main() { let mut last_frame_inst = Instant::now(); let (mut frame_count, mut accum_time) = (0, 0.0); - event_loop.run(move |event, _, control_flow| { - let _ = &window; // force ownership by the closure - *control_flow = winit::event_loop::ControlFlow::Poll; - match event { - winit::event::Event::RedrawEventsCleared => { - window.request_redraw(); - } - winit::event::Event::WindowEvent { event, .. } => match event { - winit::event::WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(winit::event::VirtualKeyCode::Escape), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } - | winit::event::WindowEvent::CloseRequested => { - *control_flow = winit::event_loop::ControlFlow::Exit; - } - _ => { - example.as_mut().unwrap().update(event); + event_loop + .run(move |event, target| { + let _ = &window; // force ownership by the closure + target.set_control_flow(ControlFlow::Poll); + + match event { + Event::LoopExiting => { + example.take().unwrap().exit(); } - }, - winit::event::Event::RedrawRequested(_) => { - let ex = example.as_mut().unwrap(); - { - accum_time += last_frame_inst.elapsed().as_secs_f32(); - last_frame_inst = Instant::now(); - frame_count += 1; - if frame_count == 100 && !ex.is_empty() { - println!( - "Avg frame time {}ms", - accum_time * 1000.0 / frame_count as f32 - ); - accum_time = 0.0; - frame_count = 0; + Event::WindowEvent { event, .. } => match event { + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Named(NamedKey::Escape), + state: ElementState::Pressed, + .. + }, + .. } - } - ex.render(); - } - winit::event::Event::LoopDestroyed => { - example.take().unwrap().exit(); + | WindowEvent::CloseRequested => target.exit(), + WindowEvent::RedrawRequested => { + let ex = example.as_mut().unwrap(); + { + accum_time += last_frame_inst.elapsed().as_secs_f32(); + last_frame_inst = Instant::now(); + frame_count += 1; + if frame_count == 100 && !ex.is_empty() { + println!( + "Avg frame time {}ms", + accum_time * 1000.0 / frame_count as f32 + ); + accum_time = 0.0; + frame_count = 0; + } + } + ex.render(); + } + _ => { + example.as_mut().unwrap().update(event); + } + }, + _ => {} } - _ => {} - } - }); + }) + .unwrap(); } diff --git a/wgpu-hal/examples/raw-gles.rs b/wgpu-hal/examples/raw-gles.rs index 0e89783aa1..81ab4171e3 100644 --- a/wgpu-hal/examples/raw-gles.rs +++ b/wgpu-hal/examples/raw-gles.rs @@ -10,7 +10,7 @@ extern crate wgpu_hal as hal; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(windows, target_arch = "wasm32")))] fn main() { env_logger::init(); println!("Initializing external GL context"); @@ -72,7 +72,7 @@ fn main() { println!("Initializing external GL context"); let egl = khronos_egl::Instance::new(khronos_egl::Static); - let display = egl.get_display(khronos_egl::DEFAULT_DISPLAY).unwrap(); + let display = unsafe { egl.get_display(khronos_egl::DEFAULT_DISPLAY) }.unwrap(); egl.initialize(display) .expect("unable to initialize display"); @@ -116,14 +116,14 @@ fn main() { fill_screen(&exposed, 640, 400); } -#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] +#[cfg(any(windows, all(target_arch = "wasm32", not(target_os = "emscripten"))))] fn main() {} -#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] +#[cfg(any(not(any(windows, target_arch = "wasm32")), target_os = "emscripten"))] fn fill_screen(exposed: &hal::ExposedAdapter, width: u32, height: u32) { use hal::{Adapter as _, CommandEncoder as _, Device as _, Queue as _}; - let mut od = unsafe { + let od = unsafe { exposed .adapter .open(wgt::Features::empty(), &wgt::Limits::downlevel_defaults()) diff --git a/wgpu-hal/examples/ray-traced-triangle/main.rs b/wgpu-hal/examples/ray-traced-triangle/main.rs index f4a65e7d61..6454cb8998 100644 --- a/wgpu-hal/examples/ray-traced-triangle/main.rs +++ b/wgpu-hal/examples/ray-traced-triangle/main.rs @@ -3,16 +3,15 @@ extern crate wgpu_hal as hal; use hal::{ Adapter as _, CommandEncoder as _, Device as _, Instance as _, Queue as _, Surface as _, }; -use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use glam::{Affine3A, Mat4, Vec3}; use std::{ borrow::{Borrow, Cow}, - iter, mem, - mem::{align_of, size_of}, - ptr::{self, copy_nonoverlapping}, + iter, mem, ptr, time::Instant, }; +use winit::window::WindowButtons; const COMMAND_BUFFER_PER_CONTEXT: usize = 100; const DESIRED_FRAMES: u32 = 3; @@ -216,38 +215,42 @@ struct Example { } impl Example { - fn init(window: &winit::window::Window) -> Result { + fn init(window: &winit::window::Window) -> Result> { let instance_desc = hal::InstanceDescriptor { name: "example", - flags: if cfg!(debug_assertions) { - hal::InstanceFlags::all() - } else { - hal::InstanceFlags::empty() + flags: wgt::InstanceFlags::default(), + dx12_shader_compiler: wgt::Dx12Compiler::Dxc { + dxil_path: None, + dxc_path: None, }, - dx12_shader_compiler: wgt::Dx12Compiler::Fxc, gles_minor_version: wgt::Gles3MinorVersion::default(), }; let instance = unsafe { A::Instance::init(&instance_desc)? }; - let mut surface = unsafe { - instance - .create_surface(window.raw_display_handle(), window.raw_window_handle()) - .unwrap() + let surface = { + let raw_window_handle = window.window_handle()?.as_raw(); + let raw_display_handle = window.display_handle()?.as_raw(); + + unsafe { + instance + .create_surface(raw_display_handle, raw_window_handle) + .unwrap() + } }; let (adapter, features) = unsafe { let mut adapters = instance.enumerate_adapters(); if adapters.is_empty() { - return Err(hal::InstanceError); + panic!("No adapters found"); } let exposed = adapters.swap_remove(0); dbg!(exposed.features); (exposed.adapter, exposed.features) }; - let surface_caps = - unsafe { adapter.surface_capabilities(&surface) }.ok_or(hal::InstanceError)?; + let surface_caps = unsafe { adapter.surface_capabilities(&surface) } + .expect("Surface doesn't support presentation"); log::info!("Surface caps: {:#?}", surface_caps); - let hal::OpenDevice { device, mut queue } = + let hal::OpenDevice { device, queue } = unsafe { adapter.open(features, &wgt::Limits::default()).unwrap() }; let window_size: (u32, u32) = window.inner_size().into(); @@ -320,47 +323,32 @@ impl Example { let bgl = unsafe { device.create_bind_group_layout(&bgl_desc).unwrap() }; - pub fn make_spirv_raw(data: &[u8]) -> Cow<[u32]> { - const MAGIC_NUMBER: u32 = 0x0723_0203; - assert_eq!( - data.len() % size_of::(), - 0, - "data size is not a multiple of 4" - ); - - //If the data happens to be aligned, directly use the byte array, - // otherwise copy the byte array in an owned vector and use that instead. - let words = if data.as_ptr().align_offset(align_of::()) == 0 { - let (pre, words, post) = unsafe { data.align_to::() }; - debug_assert!(pre.is_empty()); - debug_assert!(post.is_empty()); - Cow::from(words) - } else { - let mut words = vec![0u32; data.len() / size_of::()]; - unsafe { - copy_nonoverlapping(data.as_ptr(), words.as_mut_ptr() as *mut u8, data.len()); - } - Cow::from(words) - }; - - assert_eq!( - words[0], MAGIC_NUMBER, - "wrong magic word {:x}. Make sure you are using a binary SPIRV file.", - words[0] - ); - - words - } - + let naga_shader = { + let shader_file = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("examples") + .join("ray-traced-triangle") + .join("shader.wgsl"); + let source = std::fs::read_to_string(shader_file).unwrap(); + let module = naga::front::wgsl::Frontend::new().parse(&source).unwrap(); + let info = naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::RAY_QUERY, + ) + .validate(&module) + .unwrap(); + hal::NagaShader { + module: Cow::Owned(module), + info, + debug_source: None, + } + }; + let shader_desc = hal::ShaderModuleDescriptor { + label: None, + runtime_checks: false, + }; let shader_module = unsafe { device - .create_shader_module( - &hal::ShaderModuleDescriptor { - label: None, - runtime_checks: false, - }, - hal::ShaderInput::SpirV(&make_spirv_raw(include_bytes!("shader.comp.spv"))), - ) + .create_shader_module(&shader_desc, hal::ShaderInput::Naga(naga_shader)) .unwrap() }; @@ -940,7 +928,7 @@ impl Example { ctx.encoder.copy_texture_to_texture( &self.texture, hal::TextureUses::COPY_SRC, - &surface_tex.borrow(), + surface_tex.borrow(), std::iter::once(hal::TextureCopy { src_base: hal::TextureCopyBase { mip_level: 0, @@ -973,7 +961,7 @@ impl Example { None }; self.queue.submit(&[&cmd_buf], fence_param).unwrap(); - self.queue.present(&mut self.surface, surface_tex).unwrap(); + self.queue.present(&self.surface, surface_tex).unwrap(); ctx.used_cmd_bufs.push(cmd_buf); ctx.used_views.push(surface_tex_view); }; @@ -1070,53 +1058,54 @@ cfg_if::cfg_if! { fn main() { env_logger::init(); - let event_loop = winit::event_loop::EventLoop::new(); + let event_loop = winit::event_loop::EventLoop::new().unwrap(); let window = winit::window::WindowBuilder::new() - .with_title("hal-bunnymark") + .with_title("hal-ray-traced-triangle") .with_inner_size(winit::dpi::PhysicalSize { width: 512, height: 512, }) .with_resizable(false) + .with_enabled_buttons(WindowButtons::CLOSE) .build(&event_loop) .unwrap(); let example_result = Example::::init(&window); let mut example = Some(example_result.expect("Selected backend is not supported")); - event_loop.run(move |event, _, control_flow| { - let _ = &window; // force ownership by the closure - *control_flow = winit::event_loop::ControlFlow::Poll; - match event { - winit::event::Event::RedrawEventsCleared => { - window.request_redraw(); - } - winit::event::Event::WindowEvent { event, .. } => match event { - winit::event::WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(winit::event::VirtualKeyCode::Escape), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } - | winit::event::WindowEvent::CloseRequested => { - *control_flow = winit::event_loop::ControlFlow::Exit; + event_loop + .run(move |event, target| { + let _ = &window; // force ownership by the closure + target.set_control_flow(winit::event_loop::ControlFlow::Poll); + match event { + winit::event::Event::WindowEvent { event, .. } => match event { + winit::event::WindowEvent::CloseRequested => { + target.exit(); + } + winit::event::WindowEvent::KeyboardInput { event, .. } + if event.physical_key + == winit::keyboard::PhysicalKey::Code( + winit::keyboard::KeyCode::Escape, + ) => + { + target.exit(); + } + winit::event::WindowEvent::RedrawRequested => { + let ex = example.as_mut().unwrap(); + ex.render(); + } + _ => { + example.as_mut().unwrap().update(event); + } + }, + winit::event::Event::LoopExiting => { + example.take().unwrap().exit(); } - _ => { - example.as_mut().unwrap().update(event); + winit::event::Event::AboutToWait => { + window.request_redraw(); } - }, - winit::event::Event::RedrawRequested(_) => { - let ex = example.as_mut().unwrap(); - - ex.render(); + _ => {} } - winit::event::Event::LoopDestroyed => { - example.take().unwrap().exit(); - } - _ => {} - } - }); + }) + .unwrap(); } diff --git a/wgpu-hal/examples/ray-traced-triangle/shader.comp b/wgpu-hal/examples/ray-traced-triangle/shader.comp deleted file mode 100644 index d31f29115f..0000000000 --- a/wgpu-hal/examples/ray-traced-triangle/shader.comp +++ /dev/null @@ -1,44 +0,0 @@ -#version 460 -#extension GL_EXT_ray_query : enable - -layout(set = 0, binding = 0) uniform Uniforms -{ - mat4 viewInverse; - mat4 projInverse; -} cam; -layout(set = 0, binding = 1, rgba8) uniform image2D image; -layout(set = 0, binding = 2) uniform accelerationStructureEXT tlas; - -layout(local_size_x = 8, local_size_y = 8) in; - -void main() -{ - uvec2 launch_id = gl_GlobalInvocationID.xy; - uvec2 launch_size = gl_NumWorkGroups.xy * 8; - - const vec2 pixelCenter = vec2(launch_id) + vec2(0.5); - const vec2 inUV = pixelCenter/vec2(launch_size); - vec2 d = inUV * 2.0 - 1.0; - - vec4 origin = cam.viewInverse * vec4(0,0,0,1); - vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1) ; - vec4 direction = cam.viewInverse*vec4(normalize(target.xyz), 0) ; - - float tmin = 0.001; - float tmax = 10000.0; - - rayQueryEXT rayQuery; - rayQueryInitializeEXT(rayQuery, tlas, gl_RayFlagsOpaqueEXT, 0xff, origin.xyz, tmin, direction.xyz, tmax); - - rayQueryProceedEXT(rayQuery); - - vec3 out_colour = vec3(0.0, 0.0, 0.0); - - if (rayQueryGetIntersectionTypeEXT(rayQuery, true) == gl_RayQueryCommittedIntersectionTriangleEXT ) { - vec2 barycentrics = rayQueryGetIntersectionBarycentricsEXT(rayQuery, true); - - out_colour = vec3(barycentrics.x, barycentrics.y, 1.0 - barycentrics.x - barycentrics.y); - } - - imageStore(image, ivec2(launch_id), vec4(out_colour, 1.0)); -} \ No newline at end of file diff --git a/wgpu-hal/examples/ray-traced-triangle/shader.comp.spv b/wgpu-hal/examples/ray-traced-triangle/shader.comp.spv deleted file mode 100644 index 345085c948..0000000000 Binary files a/wgpu-hal/examples/ray-traced-triangle/shader.comp.spv and /dev/null differ diff --git a/wgpu-hal/examples/ray-traced-triangle/shader.wgsl b/wgpu-hal/examples/ray-traced-triangle/shader.wgsl new file mode 100644 index 0000000000..8d9e475e3e --- /dev/null +++ b/wgpu-hal/examples/ray-traced-triangle/shader.wgsl @@ -0,0 +1,37 @@ +struct Uniforms { + view_inv: mat4x4, + proj_inv: mat4x4, +}; +@group(0) @binding(0) +var uniforms: Uniforms; + +@group(0) @binding(1) +var output: texture_storage_2d; + +@group(0) @binding(2) +var acc_struct: acceleration_structure; + +@compute @workgroup_size(8, 8) +fn main(@builtin(global_invocation_id) global_id: vec3) { + let target_size = textureDimensions(output); + + let pixel_center = vec2(global_id.xy) + vec2(0.5); + let in_uv = pixel_center / vec2(target_size.xy); + let d = in_uv * 2.0 - 1.0; + + let origin = (uniforms.view_inv * vec4(0.0, 0.0, 0.0, 1.0)).xyz; + let temp = uniforms.proj_inv * vec4(d.x, d.y, 1.0, 1.0); + let direction = (uniforms.view_inv * vec4(normalize(temp.xyz), 0.0)).xyz; + + var rq: ray_query; + rayQueryInitialize(&rq, acc_struct, RayDesc(0u, 0xFFu, 0.1, 200.0, origin, direction)); + rayQueryProceed(&rq); + + var color = vec4(0.0, 0.0, 0.0, 1.0); + let intersection = rayQueryGetCommittedIntersection(&rq); + if intersection.kind != RAY_QUERY_INTERSECTION_NONE { + color = vec4(intersection.barycentrics, 1.0 - intersection.barycentrics.x - intersection.barycentrics.y, 1.0); + } + + textureStore(output, global_id.xy, color); +} \ No newline at end of file diff --git a/wgpu-hal/src/auxil/dxgi/conv.rs b/wgpu-hal/src/auxil/dxgi/conv.rs index 0456cd719b..6af4b77bb3 100644 --- a/wgpu-hal/src/auxil/dxgi/conv.rs +++ b/wgpu-hal/src/auxil/dxgi/conv.rs @@ -1,5 +1,13 @@ +use std::{ffi::OsString, os::windows::ffi::OsStringExt}; use winapi::shared::dxgiformat; +// Helper to convert DXGI adapter name to a normal string +pub fn map_adapter_name(name: [u16; 128]) -> String { + let len = name.iter().take_while(|&&c| c != 0).count(); + let name = OsString::from_wide(&name[..len]); + name.to_string_lossy().into_owned() +} + pub fn map_texture_format_failable(format: wgt::TextureFormat) -> Option { use wgt::TextureFormat as Tf; use winapi::shared::dxgiformat::*; @@ -34,6 +42,7 @@ pub fn map_texture_format_failable(format: wgt::TextureFormat) -> Option DXGI_FORMAT_R8G8B8A8_UINT, Tf::Rgba8Sint => DXGI_FORMAT_R8G8B8A8_SINT, Tf::Rgb9e5Ufloat => DXGI_FORMAT_R9G9B9E5_SHAREDEXP, + Tf::Rgb10a2Uint => DXGI_FORMAT_R10G10B10A2_UINT, Tf::Rgb10a2Unorm => DXGI_FORMAT_R10G10B10A2_UNORM, Tf::Rg11b10Float => DXGI_FORMAT_R11G11B10_FLOAT, Tf::Rg32Uint => DXGI_FORMAT_R32G32_UINT, @@ -53,6 +62,7 @@ pub fn map_texture_format_failable(format: wgt::TextureFormat) -> Option DXGI_FORMAT_D24_UNORM_S8_UINT, Tf::Depth32Float => DXGI_FORMAT_D32_FLOAT, Tf::Depth32FloatStencil8 => DXGI_FORMAT_D32_FLOAT_S8X24_UINT, + Tf::NV12 => DXGI_FORMAT_NV12, Tf::Bc1RgbaUnorm => DXGI_FORMAT_BC1_UNORM, Tf::Bc1RgbaUnormSrgb => DXGI_FORMAT_BC1_UNORM_SRGB, Tf::Bc2RgbaUnorm => DXGI_FORMAT_BC2_UNORM, @@ -130,9 +140,11 @@ pub fn map_texture_format_for_srv_uav( crate::FormatAspects::STENCIL, ) => dxgiformat::DXGI_FORMAT_X24_TYPELESS_G8_UINT, - (format, crate::FormatAspects::COLOR) => map_texture_format(format), + (_, crate::FormatAspects::DEPTH) + | (_, crate::FormatAspects::STENCIL) + | (_, crate::FormatAspects::DEPTH_STENCIL) => return None, - _ => return None, + _ => map_texture_format(format), }) } diff --git a/wgpu-hal/src/auxil/dxgi/exception.rs b/wgpu-hal/src/auxil/dxgi/exception.rs index 1636989b17..a7cab7fe55 100644 --- a/wgpu-hal/src/auxil/dxgi/exception.rs +++ b/wgpu-hal/src/auxil/dxgi/exception.rs @@ -98,7 +98,7 @@ unsafe extern "system" fn output_debug_string_handler( if cfg!(debug_assertions) && level == log::Level::Error { // Set canary and continue - crate::VALIDATION_CANARY.set(); + crate::VALIDATION_CANARY.add(message.to_string()); } excpt::EXCEPTION_CONTINUE_EXECUTION diff --git a/wgpu-hal/src/auxil/dxgi/factory.rs b/wgpu-hal/src/auxil/dxgi/factory.rs index 7ae6e745f0..269329304f 100644 --- a/wgpu-hal/src/auxil/dxgi/factory.rs +++ b/wgpu-hal/src/auxil/dxgi/factory.rs @@ -7,13 +7,50 @@ use super::result::HResult as _; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum DxgiFactoryType { - #[cfg(feature = "dx11")] - Factory1, Factory2, Factory4, Factory6, } +fn should_keep_adapter(adapter: &dxgi::IDXGIAdapter1) -> bool { + let mut desc = unsafe { std::mem::zeroed() }; + unsafe { adapter.GetDesc1(&mut desc) }; + + // The Intel Haswell family of iGPUs had support for the D3D12 API but it was later + // removed due to a security vulnerability. + // + // We are explicitly filtering out all the devices in the family because we are now + // getting reports of device loss at a later time than at device creation time (`D3D12CreateDevice`). + // + // See https://www.intel.com/content/www/us/en/support/articles/000057520/graphics.html + // This list of device IDs is from https://dgpu-docs.intel.com/devices/hardware-table.html + let haswell_device_ids = [ + 0x0422, 0x0426, 0x042A, 0x042B, 0x042E, 0x0C22, 0x0C26, 0x0C2A, 0x0C2B, 0x0C2E, 0x0A22, + 0x0A2A, 0x0A2B, 0x0D2A, 0x0D2B, 0x0D2E, 0x0A26, 0x0A2E, 0x0D22, 0x0D26, 0x0412, 0x0416, + 0x0D12, 0x041A, 0x041B, 0x0C12, 0x0C16, 0x0C1A, 0x0C1B, 0x0C1E, 0x0A12, 0x0A1A, 0x0A1B, + 0x0D16, 0x0D1A, 0x0D1B, 0x0D1E, 0x041E, 0x0A16, 0x0A1E, 0x0402, 0x0406, 0x040A, 0x040B, + 0x040E, 0x0C02, 0x0C06, 0x0C0A, 0x0C0B, 0x0C0E, 0x0A02, 0x0A06, 0x0A0A, 0x0A0B, 0x0A0E, + 0x0D02, 0x0D06, 0x0D0A, 0x0D0B, 0x0D0E, + ]; + if desc.VendorId == 0x8086 && haswell_device_ids.contains(&desc.DeviceId) { + return false; + } + + // If run completely headless, windows will show two different WARP adapters, one + // which is lying about being an integrated card. This is so that programs + // that ignore software adapters will actually run on headless/gpu-less machines. + // + // We don't want that and discorage that kind of filtering anyway, so we skip the integrated WARP. + if desc.VendorId == 5140 && (desc.Flags & dxgi::DXGI_ADAPTER_FLAG_SOFTWARE) == 0 { + let adapter_name = super::conv::map_adapter_name(desc.Description); + if adapter_name.contains("Microsoft Basic Render Driver") { + return false; + } + } + + true +} + pub fn enumerate_adapters(factory: d3d12::DxgiFactory) -> Vec { let mut adapters = Vec::with_capacity(8); @@ -39,6 +76,10 @@ pub fn enumerate_adapters(factory: d3d12::DxgiFactory) -> Vec Vec Adapter3 @@ -65,7 +110,7 @@ pub fn enumerate_adapters(factory: d3d12::DxgiFactory) -> Vec { - log::info!("Failed casting Adapter1 to Adapter3: {}", err); + log::warn!("Failed casting Adapter1 to Adapter3: {}", err); } } } @@ -78,7 +123,7 @@ pub fn enumerate_adapters(factory: d3d12::DxgiFactory) -> Vec { - log::info!("Failed casting Adapter1 to Adapter2: {}", err); + log::warn!("Failed casting Adapter1 to Adapter2: {}", err); } } } @@ -94,7 +139,7 @@ pub fn enumerate_adapters(factory: d3d12::DxgiFactory) -> Vec Result<(d3d12::DxgiLib, d3d12::DxgiFactory), crate::InstanceError> { let lib_dxgi = d3d12::DxgiLib::new().map_err(|e| { crate::InstanceError::with_source(String::from("failed to load dxgi.dll"), e) @@ -102,7 +147,7 @@ pub fn create_factory( let mut factory_flags = d3d12::FactoryCreationFlags::empty(); - if instance_flags.contains(crate::InstanceFlags::VALIDATION) { + if instance_flags.contains(wgt::InstanceFlags::VALIDATION) { // The `DXGI_CREATE_FACTORY_DEBUG` flag is only allowed to be passed to // `CreateDXGIFactory2` if the debug interface is actually available. So // we check for whether it exists first. @@ -143,9 +188,9 @@ pub fn create_factory( err, )); } - // If we don't print it to info as all win7 will hit this case. + // If we don't print it to warn as all win7 will hit this case. Err(err) => { - log::info!("IDXGIFactory1 creation function not found: {err:?}"); + log::warn!("IDXGIFactory1 creation function not found: {err:?}"); None } }; @@ -164,9 +209,9 @@ pub fn create_factory( "failed to cast IDXGIFactory4 to IDXGIFactory6: {err:?}" ))); } - // If we don't print it to info. + // If we don't print it to warn. Err(err) => { - log::info!("Failed to cast IDXGIFactory4 to IDXGIFactory6: {:?}", err); + log::warn!("Failed to cast IDXGIFactory4 to IDXGIFactory6: {:?}", err); return Ok((lib_dxgi, d3d12::DxgiFactory::Factory4(factory4))); } } @@ -205,9 +250,9 @@ pub fn create_factory( "failed to cast IDXGIFactory1 to IDXGIFactory2: {err:?}" ))); } - // If we don't print it to info. + // If we don't print it to warn. Err(err) => { - log::info!("Failed to cast IDXGIFactory1 to IDXGIFactory2: {:?}", err); + log::warn!("Failed to cast IDXGIFactory1 to IDXGIFactory2: {:?}", err); } } diff --git a/wgpu-hal/src/auxil/mod.rs b/wgpu-hal/src/auxil/mod.rs index f0aa6a4a89..f70a8bbe03 100644 --- a/wgpu-hal/src/auxil/mod.rs +++ b/wgpu-hal/src/auxil/mod.rs @@ -1,7 +1,7 @@ -#[cfg(all(any(feature = "dx11", feature = "dx12"), windows))] +#[cfg(dx12)] pub(super) mod dxgi; -#[cfg(all(not(target_arch = "wasm32"), feature = "renderdoc"))] +#[cfg(all(native, feature = "renderdoc"))] pub(super) mod renderdoc; pub mod db { diff --git a/wgpu-hal/src/dx11/adapter.rs b/wgpu-hal/src/dx11/adapter.rs deleted file mode 100644 index 290a9ade22..0000000000 --- a/wgpu-hal/src/dx11/adapter.rs +++ /dev/null @@ -1,295 +0,0 @@ -use std::num::NonZeroU64; - -use winapi::um::{d3d11, d3dcommon}; - -impl crate::Adapter for super::Adapter { - unsafe fn open( - &self, - features: wgt::Features, - limits: &wgt::Limits, - ) -> Result, crate::DeviceError> { - todo!() - } - - unsafe fn texture_format_capabilities( - &self, - format: wgt::TextureFormat, - ) -> crate::TextureFormatCapabilities { - todo!() - } - - unsafe fn surface_capabilities( - &self, - surface: &super::Surface, - ) -> Option { - todo!() - } - - unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp { - todo!() - } -} - -impl super::Adapter { - pub(super) fn expose( - instance: &super::library::D3D11Lib, - adapter: d3d12::DxgiAdapter, - ) -> Option> { - use d3dcommon::{ - D3D_FEATURE_LEVEL_10_0 as FL10_0, D3D_FEATURE_LEVEL_10_1 as FL10_1, - D3D_FEATURE_LEVEL_11_0 as FL11_0, D3D_FEATURE_LEVEL_11_1 as FL11_1, - D3D_FEATURE_LEVEL_9_1 as FL9_1, D3D_FEATURE_LEVEL_9_2 as FL9_2, - D3D_FEATURE_LEVEL_9_3 as FL9_3, - }; - - let (device, feature_level) = instance.create_device(adapter)?; - - // - // Query Features from d3d11 - // - - let d3d9_features = unsafe { - device.check_feature_support::( - d3d11::D3D11_FEATURE_D3D9_OPTIONS1, - ) - }; - - let d3d10_features = unsafe { - device.check_feature_support::( - d3d11::D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, - ) - }; - - let d3d11_features = unsafe { - device.check_feature_support::( - d3d11::D3D11_FEATURE_D3D11_OPTIONS, - ) - }; - - let d3d11_features1 = unsafe { - device.check_feature_support::( - d3d11::D3D11_FEATURE_D3D11_OPTIONS1, - ) - }; - - let d3d11_features2 = unsafe { - device.check_feature_support::( - d3d11::D3D11_FEATURE_D3D11_OPTIONS2, - ) - }; - - let d3d11_features3 = unsafe { - device.check_feature_support::( - d3d11::D3D11_FEATURE_D3D11_OPTIONS3, - ) - }; - - // - // Fill out features and downlevel features - // - // TODO(cwfitzgerald): Needed downlevel features: 3D dispatch - - let mut features = wgt::Features::DEPTH_CLIP_CONTROL - | wgt::Features::PUSH_CONSTANTS - | wgt::Features::POLYGON_MODE_LINE - | wgt::Features::CLEAR_TEXTURE - | wgt::Features::TEXTURE_FORMAT_16BIT_NORM - | wgt::Features::ADDRESS_MODE_CLAMP_TO_ZERO - | wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER; - let mut downlevel = wgt::DownlevelFlags::BASE_VERTEX - | wgt::DownlevelFlags::READ_ONLY_DEPTH_STENCIL - | wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER - | wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES; - - // Features from queries - downlevel.set( - wgt::DownlevelFlags::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES, - d3d9_features.FullNonPow2TextureSupported == 1, - ); - downlevel.set( - wgt::DownlevelFlags::COMPUTE_SHADERS, - d3d10_features.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x == 1, - ); - - // Features from feature level - if feature_level >= FL9_2 { - downlevel |= wgt::DownlevelFlags::INDEPENDENT_BLEND; - // formally FL9_1 supports aniso 2, but we don't support that level of distinction - downlevel |= wgt::DownlevelFlags::ANISOTROPIC_FILTERING; - // this is actually the first FL that supports u32 at all - downlevel |= wgt::DownlevelFlags::FULL_DRAW_INDEX_UINT32; - } - - if feature_level >= FL9_3 { - downlevel |= wgt::DownlevelFlags::COMPARISON_SAMPLERS; - } - - if feature_level >= FL10_0 { - downlevel |= wgt::DownlevelFlags::FRAGMENT_STORAGE; - downlevel |= wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE; - downlevel |= wgt::DownlevelFlags::DEPTH_BIAS_CLAMP; - downlevel |= wgt::DownlevelFlags::VERTEX_STORAGE; - features |= wgt::Features::DEPTH_CLIP_CONTROL; - features |= wgt::Features::TIMESTAMP_QUERY; - features |= wgt::Features::PIPELINE_STATISTICS_QUERY; - features |= wgt::Features::SHADER_PRIMITIVE_INDEX; - features |= wgt::Features::DEPTH32FLOAT_STENCIL8; - features |= wgt::Features::RG11B10UFLOAT_RENDERABLE; - } - - if feature_level >= FL10_1 { - downlevel |= wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES; - downlevel |= wgt::DownlevelFlags::MULTISAMPLED_SHADING; - } - - if feature_level >= FL11_0 { - downlevel |= wgt::DownlevelFlags::INDIRECT_EXECUTION; - downlevel |= wgt::DownlevelFlags::WEBGPU_TEXTURE_FORMAT_SUPPORT; - features |= wgt::Features::TEXTURE_COMPRESSION_BC; - } - - if feature_level >= FL11_1 { - features |= wgt::Features::VERTEX_WRITABLE_STORAGE; - } - - // - // Fill out limits and alignments - // - - let max_texture_dimension_2d = match feature_level { - FL9_1 | FL9_2 => 2048, - FL9_3 => 4096, - FL10_0 | FL10_1 => 8192, - _ => d3d11::D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION, - }; - - let max_texture_dimension_3d = match feature_level { - FL9_1..=FL9_3 => 256, - _ => d3d11::D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION, - }; - let max_vertex_buffers = match feature_level { - FL9_1..=FL9_3 => 16, - _ => 32, - }; - let max_compute_workgroup_storage_size = match feature_level { - FL9_1..=FL9_3 => 0, - FL10_0 | FL10_1 => 4096 * 4, // This doesn't have an equiv SM4 constant :\ - _ => d3d11::D3D11_CS_TGSM_REGISTER_COUNT * 4, - }; - let max_workgroup_size_xy = match feature_level { - FL9_1..=FL9_3 => 0, - FL10_0 | FL10_1 => d3d11::D3D11_CS_4_X_THREAD_GROUP_MAX_X, - _ => d3d11::D3D11_CS_THREAD_GROUP_MAX_X, - }; - let max_workgroup_size_z = match feature_level { - FL9_1..=FL9_3 => 0, - FL10_0 | FL10_1 => 1, - _ => d3d11::D3D11_CS_THREAD_GROUP_MAX_Z, - }; - // let max_workgroup_count_z = match feature_level { - // FL9_1..=FL9_3 => 0, - // FL10_0 | FL10_1 => 1, - // _ => d3d11::D3D11_CS_THREAD_GROUP_MAX_Z, - // }; - - let max_sampled_textures = d3d11::D3D11_COMMONSHADER_INPUT_RESOURCE_REGISTER_COUNT; - let max_samplers = d3d11::D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT; - let max_constant_buffers = d3d11::D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT - 1; - let max_uavs = if device.as_device1().is_some() { - d3d11::D3D11_1_UAV_SLOT_COUNT - } else { - d3d11::D3D11_PS_CS_UAV_REGISTER_COUNT - }; - let max_output_registers = d3d11::D3D11_VS_OUTPUT_REGISTER_COMPONENTS; - let max_compute_invocations_per_workgroup = - d3d11::D3D11_CS_THREAD_GROUP_MAX_THREADS_PER_GROUP; - let max_compute_workgroups_per_dimension = - d3d11::D3D11_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION; - - let limits = wgt::Limits { - max_texture_dimension_1d: max_texture_dimension_2d, - max_texture_dimension_2d, - max_texture_dimension_3d, - max_texture_array_layers: max_texture_dimension_3d, - max_bind_groups: u32::MAX, - max_bindings_per_bind_group: 65535, - max_dynamic_uniform_buffers_per_pipeline_layout: max_constant_buffers, - max_dynamic_storage_buffers_per_pipeline_layout: 0, - max_sampled_textures_per_shader_stage: max_sampled_textures, - max_samplers_per_shader_stage: max_samplers, - max_storage_buffers_per_shader_stage: max_uavs, - max_storage_textures_per_shader_stage: max_uavs, - max_uniform_buffers_per_shader_stage: max_constant_buffers, - max_uniform_buffer_binding_size: 1 << 16, - max_storage_buffer_binding_size: u32::MAX, - max_vertex_buffers, - max_vertex_attributes: max_vertex_buffers, - max_vertex_buffer_array_stride: u32::MAX, - max_push_constant_size: 1 << 16, - min_uniform_buffer_offset_alignment: 256, - min_storage_buffer_offset_alignment: 1, - max_inter_stage_shader_components: max_output_registers, - max_compute_workgroup_storage_size, - max_compute_invocations_per_workgroup, - max_compute_workgroup_size_x: max_workgroup_size_xy, - max_compute_workgroup_size_y: max_workgroup_size_xy, - max_compute_workgroup_size_z: max_workgroup_size_z, - max_compute_workgroups_per_dimension, - // D3D11_BUFFER_DESC represents the buffer size as a 32 bit int. - max_buffer_size: u32::MAX as u64, - max_non_sampler_bindings: u32::MAX, - }; - - // - // Other capabilities - // - - let shader_model = match feature_level { - FL9_1..=FL9_3 => wgt::ShaderModel::Sm2, - FL10_0 | FL10_1 => wgt::ShaderModel::Sm4, - _ => wgt::ShaderModel::Sm5, - }; - - let device_info = wgt::AdapterInfo { - name: String::new(), - vendor: 0, - device: 0, - device_type: match d3d11_features2.UnifiedMemoryArchitecture { - 0 => wgt::DeviceType::DiscreteGpu, - 1 => wgt::DeviceType::IntegratedGpu, - _ => unreachable!(), - }, - driver: String::new(), - driver_info: String::new(), - backend: wgt::Backend::Dx11, - }; - - // - // Build up the structs - // - - let api_adapter = super::Adapter { device }; - - let alignments = crate::Alignments { - buffer_copy_offset: NonZeroU64::new(1).unwrap(), // todo - buffer_copy_pitch: NonZeroU64::new(1).unwrap(), // todo - }; - - let capabilities = crate::Capabilities { - limits, - alignments, - downlevel: wgt::DownlevelCapabilities { - flags: downlevel, - limits: wgt::DownlevelLimits {}, - shader_model, - }, - }; - - Some(crate::ExposedAdapter { - adapter: api_adapter, - info: device_info, - features, - capabilities, - }) - } -} diff --git a/wgpu-hal/src/dx11/command.rs b/wgpu-hal/src/dx11/command.rs deleted file mode 100644 index b2c83d8749..0000000000 --- a/wgpu-hal/src/dx11/command.rs +++ /dev/null @@ -1,289 +0,0 @@ -impl crate::CommandEncoder for super::CommandEncoder { - unsafe fn begin_encoding(&mut self, label: crate::Label) -> Result<(), crate::DeviceError> { - todo!() - } - - unsafe fn discard_encoding(&mut self) { - todo!() - } - - unsafe fn end_encoding(&mut self) -> Result { - todo!() - } - - unsafe fn reset_all(&mut self, command_buffers: I) - where - I: Iterator, - { - todo!() - } - - unsafe fn transition_buffers<'a, T>(&mut self, barriers: T) - where - T: Iterator>, - { - todo!() - } - - unsafe fn transition_textures<'a, T>(&mut self, barriers: T) - where - T: Iterator>, - { - todo!() - } - - unsafe fn clear_buffer(&mut self, buffer: &super::Buffer, range: crate::MemoryRange) { - todo!() - } - - unsafe fn copy_buffer_to_buffer( - &mut self, - src: &super::Buffer, - dst: &super::Buffer, - regions: T, - ) where - T: Iterator, - { - todo!() - } - - unsafe fn copy_texture_to_texture( - &mut self, - src: &super::Texture, - src_usage: crate::TextureUses, - dst: &super::Texture, - regions: T, - ) where - T: Iterator, - { - todo!() - } - - unsafe fn copy_buffer_to_texture( - &mut self, - src: &super::Buffer, - dst: &super::Texture, - regions: T, - ) where - T: Iterator, - { - todo!() - } - - unsafe fn copy_texture_to_buffer( - &mut self, - src: &super::Texture, - src_usage: crate::TextureUses, - dst: &super::Buffer, - regions: T, - ) where - T: Iterator, - { - todo!() - } - - unsafe fn set_bind_group( - &mut self, - layout: &super::PipelineLayout, - index: u32, - group: &super::BindGroup, - dynamic_offsets: &[wgt::DynamicOffset], - ) { - todo!() - } - - unsafe fn set_push_constants( - &mut self, - layout: &super::PipelineLayout, - stages: wgt::ShaderStages, - offset: u32, - data: &[u32], - ) { - todo!() - } - - unsafe fn insert_debug_marker(&mut self, label: &str) { - todo!() - } - - unsafe fn begin_debug_marker(&mut self, group_label: &str) { - todo!() - } - - unsafe fn end_debug_marker(&mut self) { - todo!() - } - - unsafe fn begin_query(&mut self, set: &super::QuerySet, index: u32) { - todo!() - } - - unsafe fn end_query(&mut self, set: &super::QuerySet, index: u32) { - todo!() - } - - unsafe fn write_timestamp(&mut self, set: &super::QuerySet, index: u32) { - todo!() - } - - unsafe fn reset_queries(&mut self, set: &super::QuerySet, range: std::ops::Range) { - todo!() - } - - unsafe fn copy_query_results( - &mut self, - set: &super::QuerySet, - range: std::ops::Range, - buffer: &super::Buffer, - offset: wgt::BufferAddress, - stride: wgt::BufferSize, - ) { - todo!() - } - - unsafe fn begin_render_pass(&mut self, desc: &crate::RenderPassDescriptor) { - todo!() - } - - unsafe fn end_render_pass(&mut self) { - todo!() - } - - unsafe fn set_render_pipeline(&mut self, pipeline: &super::RenderPipeline) { - todo!() - } - - unsafe fn set_index_buffer<'a>( - &mut self, - binding: crate::BufferBinding<'a, super::Api>, - format: wgt::IndexFormat, - ) { - todo!() - } - - unsafe fn set_vertex_buffer<'a>( - &mut self, - index: u32, - binding: crate::BufferBinding<'a, super::Api>, - ) { - todo!() - } - - unsafe fn set_viewport(&mut self, rect: &crate::Rect, depth_range: std::ops::Range) { - todo!() - } - - unsafe fn set_scissor_rect(&mut self, rect: &crate::Rect) { - todo!() - } - - unsafe fn set_stencil_reference(&mut self, value: u32) { - todo!() - } - - unsafe fn set_blend_constants(&mut self, color: &[f32; 4]) { - todo!() - } - - unsafe fn draw( - &mut self, - start_vertex: u32, - vertex_count: u32, - start_instance: u32, - instance_count: u32, - ) { - todo!() - } - - unsafe fn draw_indexed( - &mut self, - start_index: u32, - index_count: u32, - base_vertex: i32, - start_instance: u32, - instance_count: u32, - ) { - todo!() - } - - unsafe fn draw_indirect( - &mut self, - buffer: &super::Buffer, - offset: wgt::BufferAddress, - draw_count: u32, - ) { - todo!() - } - - unsafe fn draw_indexed_indirect( - &mut self, - buffer: &super::Buffer, - offset: wgt::BufferAddress, - draw_count: u32, - ) { - todo!() - } - - unsafe fn draw_indirect_count( - &mut self, - buffer: &super::Buffer, - offset: wgt::BufferAddress, - count_buffer: &super::Buffer, - count_offset: wgt::BufferAddress, - max_count: u32, - ) { - todo!() - } - - unsafe fn draw_indexed_indirect_count( - &mut self, - buffer: &super::Buffer, - offset: wgt::BufferAddress, - count_buffer: &super::Buffer, - count_offset: wgt::BufferAddress, - max_count: u32, - ) { - todo!() - } - - unsafe fn begin_compute_pass<'a>( - &mut self, - desc: &crate::ComputePassDescriptor<'a, super::Api>, - ) { - todo!() - } - - unsafe fn end_compute_pass(&mut self) { - todo!() - } - - unsafe fn set_compute_pipeline(&mut self, pipeline: &super::ComputePipeline) { - todo!() - } - - unsafe fn dispatch(&mut self, count: [u32; 3]) { - todo!() - } - - unsafe fn dispatch_indirect(&mut self, buffer: &super::Buffer, offset: wgt::BufferAddress) { - todo!() - } - - unsafe fn build_acceleration_structures<'a, T>( - &mut self, - _descriptor_count: u32, - _descriptors: T, - ) where - super::Api: 'a, - T: IntoIterator>, - { - unimplemented!() - } - - unsafe fn place_acceleration_structure_barrier( - &mut self, - _barriers: crate::AccelerationStructureBarrier, - ) { - unimplemented!() - } -} diff --git a/wgpu-hal/src/dx11/device.rs b/wgpu-hal/src/dx11/device.rs deleted file mode 100644 index cdfaba26f4..0000000000 --- a/wgpu-hal/src/dx11/device.rs +++ /dev/null @@ -1,267 +0,0 @@ -use std::{ffi::c_void, mem}; - -use winapi::um::d3d11; - -use crate::auxil::dxgi::result::HResult; - -impl crate::Device for super::Device { - unsafe fn exit(self, queue: super::Queue) { - todo!() - } - - unsafe fn create_buffer( - &self, - desc: &crate::BufferDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_buffer(&self, buffer: super::Buffer) { - todo!() - } - - unsafe fn map_buffer( - &self, - buffer: &super::Buffer, - range: crate::MemoryRange, - ) -> Result { - todo!() - } - - unsafe fn unmap_buffer(&self, buffer: &super::Buffer) -> Result<(), crate::DeviceError> { - todo!() - } - - unsafe fn flush_mapped_ranges(&self, buffer: &super::Buffer, ranges: I) - where - I: Iterator, - { - todo!() - } - - unsafe fn invalidate_mapped_ranges(&self, buffer: &super::Buffer, ranges: I) - where - I: Iterator, - { - todo!() - } - - unsafe fn create_texture( - &self, - desc: &crate::TextureDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_texture(&self, texture: super::Texture) { - todo!() - } - - unsafe fn create_texture_view( - &self, - texture: &super::Texture, - desc: &crate::TextureViewDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_texture_view(&self, view: super::TextureView) { - todo!() - } - - unsafe fn create_sampler( - &self, - desc: &crate::SamplerDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_sampler(&self, sampler: super::Sampler) { - todo!() - } - - unsafe fn create_command_encoder( - &self, - desc: &crate::CommandEncoderDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_command_encoder(&self, pool: super::CommandEncoder) { - todo!() - } - - unsafe fn create_bind_group_layout( - &self, - desc: &crate::BindGroupLayoutDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_bind_group_layout(&self, bg_layout: super::BindGroupLayout) { - todo!() - } - - unsafe fn create_pipeline_layout( - &self, - desc: &crate::PipelineLayoutDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_pipeline_layout(&self, pipeline_layout: super::PipelineLayout) { - todo!() - } - - unsafe fn create_bind_group( - &self, - desc: &crate::BindGroupDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_bind_group(&self, group: super::BindGroup) { - todo!() - } - - unsafe fn create_shader_module( - &self, - desc: &crate::ShaderModuleDescriptor, - shader: crate::ShaderInput, - ) -> Result { - todo!() - } - - unsafe fn destroy_shader_module(&self, module: super::ShaderModule) { - todo!() - } - - unsafe fn create_render_pipeline( - &self, - desc: &crate::RenderPipelineDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_render_pipeline(&self, pipeline: super::RenderPipeline) { - todo!() - } - - unsafe fn create_compute_pipeline( - &self, - desc: &crate::ComputePipelineDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_compute_pipeline(&self, pipeline: super::ComputePipeline) { - todo!() - } - - unsafe fn create_query_set( - &self, - desc: &wgt::QuerySetDescriptor, - ) -> Result { - todo!() - } - - unsafe fn destroy_query_set(&self, set: super::QuerySet) { - todo!() - } - - unsafe fn create_fence(&self) -> Result { - todo!() - } - - unsafe fn destroy_fence(&self, fence: super::Fence) { - todo!() - } - - unsafe fn get_fence_value( - &self, - fence: &super::Fence, - ) -> Result { - todo!() - } - - unsafe fn wait( - &self, - fence: &super::Fence, - value: crate::FenceValue, - timeout_ms: u32, - ) -> Result { - todo!() - } - - unsafe fn start_capture(&self) -> bool { - todo!() - } - - unsafe fn stop_capture(&self) { - todo!() - } - - unsafe fn create_acceleration_structure( - &self, - desc: &crate::AccelerationStructureDescriptor, - ) -> Result { - unimplemented!() - } - unsafe fn get_acceleration_structure_build_sizes<'a>( - &self, - _desc: &crate::GetAccelerationStructureBuildSizesDescriptor<'a, super::Api>, - ) -> crate::AccelerationStructureBuildSizes { - unimplemented!() - } - unsafe fn get_acceleration_structure_device_address( - &self, - acceleration_structure: &super::AccelerationStructure, - ) -> wgt::BufferAddress { - unimplemented!() - } - unsafe fn destroy_acceleration_structure( - &self, - acceleration_structure: super::AccelerationStructure, - ) { - unimplemented!() - } -} - -impl crate::Queue for super::Queue { - unsafe fn submit( - &mut self, - command_buffers: &[&super::CommandBuffer], - signal_fence: Option<(&mut super::Fence, crate::FenceValue)>, - ) -> Result<(), crate::DeviceError> { - todo!() - } - - unsafe fn present( - &mut self, - surface: &mut super::Surface, - texture: super::SurfaceTexture, - ) -> Result<(), crate::SurfaceError> { - todo!() - } - - unsafe fn get_timestamp_period(&self) -> f32 { - todo!() - } -} - -impl super::D3D11Device { - #[allow(trivial_casts)] // come on - pub unsafe fn check_feature_support(&self, feature: d3d11::D3D11_FEATURE) -> T { - unsafe { - let mut value = mem::zeroed::(); - let ret = self.CheckFeatureSupport( - feature, - &mut value as *mut T as *mut c_void, - mem::size_of::() as u32, - ); - assert_eq!(ret.into_result(), Ok(())); - - value - } - } -} diff --git a/wgpu-hal/src/dx11/instance.rs b/wgpu-hal/src/dx11/instance.rs deleted file mode 100644 index e7a4e2e705..0000000000 --- a/wgpu-hal/src/dx11/instance.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::auxil; - -impl crate::Instance for super::Instance { - unsafe fn init(desc: &crate::InstanceDescriptor) -> Result { - let enable_dx11 = match std::env::var("WGPU_UNSTABLE_DX11_BACKEND") { - Ok(string) => string == "1" || string == "true", - Err(_) => false, - }; - - if !enable_dx11 { - return Err(crate::InstanceError::new(String::from( - "DX11 support is unstable; set WGPU_UNSTABLE_DX11_BACKEND=1 to enable anyway", - ))); - } - - let lib_d3d11 = super::library::D3D11Lib::new() - .ok_or_else(|| crate::InstanceError::new(String::from("failed to load d3d11.dll")))?; - - let (lib_dxgi, factory) = auxil::dxgi::factory::create_factory( - auxil::dxgi::factory::DxgiFactoryType::Factory1, - desc.flags, - )?; - - Ok(super::Instance { - lib_d3d11, - lib_dxgi, - factory, - }) - } - - unsafe fn create_surface( - &self, - display_handle: raw_window_handle::RawDisplayHandle, - window_handle: raw_window_handle::RawWindowHandle, - ) -> Result { - todo!() - } - - unsafe fn destroy_surface(&self, surface: super::Surface) { - todo!() - } - - unsafe fn enumerate_adapters(&self) -> Vec> { - let adapters = auxil::dxgi::factory::enumerate_adapters(self.factory.clone()); - - adapters - .into_iter() - .filter_map(|adapter| super::Adapter::expose(&self.lib_d3d11, adapter)) - .collect() - } -} diff --git a/wgpu-hal/src/dx11/library.rs b/wgpu-hal/src/dx11/library.rs deleted file mode 100644 index c2b5315ba1..0000000000 --- a/wgpu-hal/src/dx11/library.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::ptr; - -use winapi::{ - shared::{ - dxgi, - minwindef::{HMODULE, UINT}, - winerror, - }, - um::{d3d11, d3d11_1, d3d11_2, d3dcommon}, -}; - -use crate::auxil::dxgi::result::HResult; - -type D3D11CreateDeviceFun = unsafe extern "system" fn( - *mut dxgi::IDXGIAdapter, - d3dcommon::D3D_DRIVER_TYPE, - HMODULE, - UINT, - *const d3dcommon::D3D_FEATURE_LEVEL, - UINT, - UINT, - *mut *mut d3d11::ID3D11Device, - *mut d3dcommon::D3D_FEATURE_LEVEL, - *mut *mut d3d11::ID3D11DeviceContext, -) -> d3d12::HRESULT; - -pub(super) struct D3D11Lib { - // We use the os specific symbol to drop the lifetime parameter. - // - // SAFETY: we must ensure this outlives the Library. - d3d11_create_device: libloading::os::windows::Symbol, - - lib: libloading::Library, -} -impl D3D11Lib { - pub fn new() -> Option { - unsafe { - let lib = libloading::Library::new("d3d11.dll").ok()?; - - let d3d11_create_device = lib - .get::(b"D3D11CreateDevice") - .ok()? - .into_raw(); - - Some(Self { - lib, - d3d11_create_device, - }) - } - } - - pub fn create_device( - &self, - adapter: d3d12::DxgiAdapter, - ) -> Option<(super::D3D11Device, d3dcommon::D3D_FEATURE_LEVEL)> { - let feature_levels = [ - d3dcommon::D3D_FEATURE_LEVEL_11_1, - d3dcommon::D3D_FEATURE_LEVEL_11_0, - d3dcommon::D3D_FEATURE_LEVEL_10_1, - d3dcommon::D3D_FEATURE_LEVEL_10_0, - d3dcommon::D3D_FEATURE_LEVEL_9_3, - d3dcommon::D3D_FEATURE_LEVEL_9_2, - d3dcommon::D3D_FEATURE_LEVEL_9_1, - ]; - - let mut device = d3d12::ComPtr::::null(); - let mut feature_level: d3dcommon::D3D_FEATURE_LEVEL = 0; - - // We need to try this twice. If the first time fails due to E_INVALIDARG - // we are running on a machine without a D3D11.1 runtime, and need to - // retry without the feature level 11_1 feature level. - // - // Why they thought this was a good API, who knows. - - let mut hr = unsafe { - (self.d3d11_create_device)( - adapter.as_mut_ptr() as *mut _, - d3dcommon::D3D_DRIVER_TYPE_UNKNOWN, - ptr::null_mut(), // software implementation DLL??? - 0, // flags - feature_levels.as_ptr(), - feature_levels.len() as u32, - d3d11::D3D11_SDK_VERSION, - device.mut_self(), - &mut feature_level, - ptr::null_mut(), // device context - ) - }; - - // Try again without FL11_1 - if hr == winerror::E_INVALIDARG { - hr = unsafe { - (self.d3d11_create_device)( - adapter.as_mut_ptr() as *mut _, - d3dcommon::D3D_DRIVER_TYPE_UNKNOWN, - ptr::null_mut(), // software implementation DLL??? - 0, // flags - feature_levels[1..].as_ptr(), - feature_levels[1..].len() as u32, - d3d11::D3D11_SDK_VERSION, - device.mut_self(), - &mut feature_level, - ptr::null_mut(), // device context - ) - }; - } - - // Any errors here are real and we should complain about - if let Err(err) = hr.into_result() { - log::error!("Failed to make a D3D11 device: {}", err); - return None; - } - - // We always try to upcast in highest -> lowest order - - // Device -> Device2 - unsafe { - match device.cast::().into_result() { - Ok(device2) => { - return Some((super::D3D11Device::Device2(device2), feature_level)); - } - Err(hr) => { - log::info!("Failed to cast device to ID3D11Device2: {}", hr) - } - } - } - - // Device -> Device1 - unsafe { - match device.cast::().into_result() { - Ok(device1) => { - return Some((super::D3D11Device::Device1(device1), feature_level)); - } - Err(hr) => { - log::info!("Failed to cast device to ID3D11Device1: {}", hr) - } - } - } - - Some((super::D3D11Device::Device(device), feature_level)) - } -} diff --git a/wgpu-hal/src/dx11/mod.rs b/wgpu-hal/src/dx11/mod.rs deleted file mode 100644 index 3a64da6b31..0000000000 --- a/wgpu-hal/src/dx11/mod.rs +++ /dev/null @@ -1,141 +0,0 @@ -#![allow(dead_code)] -#![allow(unused_variables)] - -use winapi::um::{d3d11, d3d11_1, d3d11_2}; - -mod adapter; -mod command; -mod device; -mod instance; -mod library; - -#[derive(Clone)] -pub struct Api; - -impl crate::Api for Api { - type Instance = Instance; - type Surface = Surface; - type Adapter = Adapter; - type Device = Device; - - type Queue = Queue; - type CommandEncoder = CommandEncoder; - type CommandBuffer = CommandBuffer; - - type Buffer = Buffer; - type Texture = Texture; - type SurfaceTexture = SurfaceTexture; - type TextureView = TextureView; - type Sampler = Sampler; - type QuerySet = QuerySet; - type Fence = Fence; - - type BindGroupLayout = BindGroupLayout; - type BindGroup = BindGroup; - type PipelineLayout = PipelineLayout; - type ShaderModule = ShaderModule; - type RenderPipeline = RenderPipeline; - type ComputePipeline = ComputePipeline; - - type AccelerationStructure = AccelerationStructure; -} - -pub struct Instance { - lib_d3d11: library::D3D11Lib, - lib_dxgi: d3d12::DxgiLib, - factory: d3d12::DxgiFactory, -} - -unsafe impl Send for Instance {} -unsafe impl Sync for Instance {} - -pub struct Surface {} - -pub struct Adapter { - device: D3D11Device, -} - -unsafe impl Send for Adapter {} -unsafe impl Sync for Adapter {} - -d3d12::weak_com_inheritance_chain! { - #[derive(Debug, Clone, PartialEq)] - enum D3D11Device { - Device(d3d11::ID3D11Device), from_device, as_device, device; - Device1(d3d11_1::ID3D11Device1), from_device1, as_device1, unwrap_device1; - Device2(d3d11_2::ID3D11Device2), from_device2, as_device2, unwrap_device2; - } -} - -pub struct Device {} - -unsafe impl Send for Device {} -unsafe impl Sync for Device {} - -pub struct Queue {} - -#[derive(Debug)] -pub struct CommandEncoder {} - -#[derive(Debug)] -pub struct CommandBuffer {} - -#[derive(Debug)] -pub struct Buffer {} -#[derive(Debug)] -pub struct Texture {} -#[derive(Debug)] -pub struct SurfaceTexture {} - -impl std::borrow::Borrow for SurfaceTexture { - fn borrow(&self) -> &Texture { - todo!() - } -} - -#[derive(Debug)] -pub struct TextureView {} -#[derive(Debug)] -pub struct Sampler {} -#[derive(Debug)] -pub struct QuerySet {} -#[derive(Debug)] -pub struct Fence {} -#[derive(Debug)] - -pub struct BindGroupLayout {} -#[derive(Debug)] -pub struct BindGroup {} -#[derive(Debug)] -pub struct PipelineLayout {} -#[derive(Debug)] -pub struct ShaderModule {} -#[derive(Debug)] -pub struct AccelerationStructure {} -pub struct RenderPipeline {} -pub struct ComputePipeline {} - -impl crate::Surface for Surface { - unsafe fn configure( - &mut self, - device: &Device, - config: &crate::SurfaceConfiguration, - ) -> Result<(), crate::SurfaceError> { - todo!() - } - - unsafe fn unconfigure(&mut self, device: &Device) { - todo!() - } - - unsafe fn acquire_texture( - &mut self, - _timeout: Option, - ) -> Result>, crate::SurfaceError> { - todo!() - } - - unsafe fn discard_texture(&mut self, texture: SurfaceTexture) { - todo!() - } -} diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 02cde913ca..1db9b0877d 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -1,10 +1,13 @@ use crate::{ auxil::{self, dxgi::result::HResult as _}, - dx12::SurfaceTarget, + dx12::{shader_compilation, SurfaceTarget}, }; +use parking_lot::Mutex; use std::{mem, ptr, sync::Arc, thread}; use winapi::{ - shared::{dxgi, dxgi1_2, minwindef::DWORD, windef, winerror}, + shared::{ + dxgi, dxgi1_2, dxgiformat::DXGI_FORMAT_B8G8R8A8_UNORM, minwindef::DWORD, windef, winerror, + }, um::{d3d12 as d3d12_ty, d3d12sdklayers, winuser}, }; @@ -15,7 +18,7 @@ impl Drop for super::Adapter { && self .private_caps .instance_flags - .contains(crate::InstanceFlags::VALIDATION) + .contains(wgt::InstanceFlags::VALIDATION) { unsafe { self.report_live_objects(); @@ -47,8 +50,8 @@ impl super::Adapter { pub(super) fn expose( adapter: d3d12::DxgiAdapter, library: &Arc, - instance_flags: crate::InstanceFlags, - dx12_shader_compiler: &wgt::Dx12Compiler, + instance_flags: wgt::InstanceFlags, + dxc_container: Option>, ) -> Option> { // Create the device so that we can get the capabilities. let device = { @@ -100,12 +103,7 @@ impl super::Adapter { adapter.unwrap_adapter2().GetDesc2(&mut desc); } - let device_name = { - use std::{ffi::OsString, os::windows::ffi::OsStringExt}; - let len = desc.Description.iter().take_while(|&&c| c != 0).count(); - let name = OsString::from_wide(&desc.Description[..len]); - name.to_string_lossy().into_owned() - }; + let device_name = auxil::dxgi::conv::map_adapter_name(desc.Description); let mut features_architecture: d3d12_ty::D3D12_FEATURE_DATA_ARCHITECTURE = unsafe { mem::zeroed() }; @@ -250,7 +248,10 @@ impl super::Adapter { | wgt::Features::TEXTURE_FORMAT_16BIT_NORM | wgt::Features::PUSH_CONSTANTS | wgt::Features::SHADER_PRIMITIVE_INDEX - | wgt::Features::RG11B10UFLOAT_RENDERABLE; + | wgt::Features::RG11B10UFLOAT_RENDERABLE + | wgt::Features::DUAL_SOURCE_BLENDING + | wgt::Features::TEXTURE_FORMAT_NV12; + //TODO: in order to expose this, we need to run a compute shader // that extract the necessary statistics out of the D3D12 result. // Alternatively, we could allocate a buffer for the query set, @@ -274,11 +275,38 @@ impl super::Adapter { shader_model_support.HighestShaderModel >= d3d12_ty::D3D_SHADER_MODEL_5_1, ); + let bgra8unorm_storage_supported = { + let mut bgra8unorm_info: d3d12_ty::D3D12_FEATURE_DATA_FORMAT_SUPPORT = + unsafe { mem::zeroed() }; + bgra8unorm_info.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + let hr = unsafe { + device.CheckFeatureSupport( + d3d12_ty::D3D12_FEATURE_FORMAT_SUPPORT, + &mut bgra8unorm_info as *mut _ as *mut _, + mem::size_of::() as _, + ) + }; + hr == 0 + && (bgra8unorm_info.Support2 & d3d12_ty::D3D12_FORMAT_SUPPORT2_UAV_TYPED_STORE != 0) + }; + features.set( + wgt::Features::BGRA8UNORM_STORAGE, + bgra8unorm_storage_supported, + ); + + // float32-filterable should always be available on d3d12 + features.set(wgt::Features::FLOAT32_FILTERABLE, true); + // TODO: Determine if IPresentationManager is supported let presentation_timer = auxil::dxgi::time::PresentationTimer::new_dxgi(); let base = wgt::Limits::default(); + let mut downlevel = wgt::DownlevelCapabilities::default(); + // https://github.com/gfx-rs/wgpu/issues/2471 + downlevel.flags -= + wgt::DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW; + Some(crate::ExposedAdapter { adapter: super::Adapter { raw: adapter, @@ -287,7 +315,7 @@ impl super::Adapter { private_caps, presentation_timer, workarounds, - dx12_shader_compiler: dx12_shader_compiler.clone(), + dxc_container, }, info, features, @@ -373,7 +401,7 @@ impl super::Adapter { ) .unwrap(), }, - downlevel: wgt::DownlevelCapabilities::default(), + downlevel, }, }) } @@ -403,13 +431,13 @@ impl crate::Adapter for super::Adapter { limits, self.private_caps, &self.library, - self.dx12_shader_compiler.clone(), + self.dxc_container.clone(), )?; Ok(crate::OpenDevice { device, queue: super::Queue { raw: queue, - temp_lists: Vec::new(), + temp_lists: Mutex::new(Vec::new()), }, }) } @@ -578,7 +606,9 @@ impl crate::Adapter for super::Adapter { None } } - SurfaceTarget::Visual(_) | SurfaceTarget::SurfaceHandle(_) => None, + SurfaceTarget::Visual(_) + | SurfaceTarget::SurfaceHandle(_) + | SurfaceTarget::SwapChainPanel(_) => None, } }; @@ -599,16 +629,6 @@ impl crate::Adapter for super::Adapter { // we currently use a flip effect which supports 2..=16 buffers swap_chain_sizes: 2..=16, current_extent, - // TODO: figure out the exact bounds - extents: wgt::Extent3d { - width: 16, - height: 16, - depth_or_array_layers: 1, - }..=wgt::Extent3d { - width: 4096, - height: 4096, - depth_or_array_layers: 1, - }, usage: crate::TextureUses::COLOR_TARGET | crate::TextureUses::COPY_SRC | crate::TextureUses::COPY_DST, diff --git a/wgpu-hal/src/dx12/command.rs b/wgpu-hal/src/dx12/command.rs index ca47542891..3d05813ed7 100644 --- a/wgpu-hal/src/dx12/command.rs +++ b/wgpu-hal/src/dx12/command.rs @@ -20,7 +20,7 @@ impl crate::BufferTextureCopy { &self, format: wgt::TextureFormat, ) -> d3d12_ty::D3D12_PLACED_SUBRESOURCE_FOOTPRINT { - let (block_width, block_height) = format.block_dimensions(); + let (block_width, _) = format.block_dimensions(); d3d12_ty::D3D12_PLACED_SUBRESOURCE_FOOTPRINT { Offset: self.buffer_layout.offset, Footprint: d3d12_ty::D3D12_SUBRESOURCE_FOOTPRINT { @@ -30,16 +30,13 @@ impl crate::BufferTextureCopy { ) .unwrap(), Width: self.size.width, - Height: self - .buffer_layout - .rows_per_image - .map_or(self.size.height, |count| count * block_height), + Height: self.size.height, Depth: self.size.depth, RowPitch: { let actual = self.buffer_layout.bytes_per_row.unwrap_or_else(|| { // this may happen for single-line updates let block_size = format - .block_size(Some(self.texture_base.aspect.map())) + .block_copy_size(Some(self.texture_base.aspect.map())) .unwrap(); (self.size.width / block_width) * block_size }); @@ -85,7 +82,7 @@ impl super::CommandEncoder { self.pass.clear(); } - unsafe fn prepare_draw(&mut self, base_vertex: i32, base_instance: u32) { + unsafe fn prepare_draw(&mut self, first_vertex: i32, first_instance: u32) { while self.pass.dirty_vertex_buffers != 0 { let list = self.list.as_ref().unwrap(); let index = self.pass.dirty_vertex_buffers.trailing_zeros(); @@ -101,18 +98,18 @@ impl super::CommandEncoder { if let Some(root_index) = self.pass.layout.special_constants_root_index { let needs_update = match self.pass.root_elements[root_index as usize] { super::RootElement::SpecialConstantBuffer { - base_vertex: other_vertex, - base_instance: other_instance, + first_vertex: other_vertex, + first_instance: other_instance, other: _, - } => base_vertex != other_vertex || base_instance != other_instance, + } => first_vertex != other_vertex || first_instance != other_instance, _ => true, }; if needs_update { self.pass.dirty_root_elements |= 1 << root_index; self.pass.root_elements[root_index as usize] = super::RootElement::SpecialConstantBuffer { - base_vertex, - base_instance, + first_vertex, + first_instance, other: 0, }; } @@ -124,18 +121,18 @@ impl super::CommandEncoder { if let Some(root_index) = self.pass.layout.special_constants_root_index { let needs_update = match self.pass.root_elements[root_index as usize] { super::RootElement::SpecialConstantBuffer { - base_vertex, - base_instance, + first_vertex, + first_instance, other, - } => [base_vertex as u32, base_instance, other] != count, + } => [first_vertex as u32, first_instance, other] != count, _ => true, }; if needs_update { self.pass.dirty_root_elements |= 1 << root_index; self.pass.root_elements[root_index as usize] = super::RootElement::SpecialConstantBuffer { - base_vertex: count[0] as i32, - base_instance: count[1], + first_vertex: count[0] as i32, + first_instance: count[1], other: count[2], }; } @@ -168,17 +165,17 @@ impl super::CommandEncoder { } } super::RootElement::SpecialConstantBuffer { - base_vertex, - base_instance, + first_vertex, + first_instance, other, } => match self.pass.kind { Pk::Render => { - list.set_graphics_root_constant(index, base_vertex as u32, 0); - list.set_graphics_root_constant(index, base_instance, 1); + list.set_graphics_root_constant(index, first_vertex as u32, 0); + list.set_graphics_root_constant(index, first_instance, 1); } Pk::Compute => { - list.set_compute_root_constant(index, base_vertex as u32, 0); - list.set_compute_root_constant(index, base_instance, 1); + list.set_compute_root_constant(index, first_vertex as u32, 0); + list.set_compute_root_constant(index, first_instance, 1); list.set_compute_root_constant(index, other, 2); } Pk::Transfer => (), @@ -220,8 +217,8 @@ impl super::CommandEncoder { if let Some(root_index) = layout.special_constants_root_index { self.pass.root_elements[root_index as usize] = super::RootElement::SpecialConstantBuffer { - base_vertex: 0, - base_instance: 0, + first_vertex: 0, + first_instance: 0, other: 0, }; } @@ -415,6 +412,15 @@ impl crate::CommandEncoder for super::CommandEncoder { wgt::TextureAspect::All => 0..2, wgt::TextureAspect::DepthOnly => 0..1, wgt::TextureAspect::StencilOnly => 1..2, + _ => unreachable!(), + } + } else if let Some(planes) = barrier.texture.format.planes() { + match barrier.range.aspect { + wgt::TextureAspect::All => 0..planes, + wgt::TextureAspect::Plane0 => 0..1, + wgt::TextureAspect::Plane1 => 1..2, + wgt::TextureAspect::Plane2 => 2..3, + _ => unreachable!(), } } else { match barrier.texture.format { @@ -911,15 +917,16 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, layout: &super::PipelineLayout, _stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ) { + let offset_words = offset_bytes as usize / 4; + let info = layout.shared.root_constant_info.as_ref().unwrap(); self.pass.root_elements[info.root_index as usize] = super::RootElement::Constant; - self.pass.constant_data[(offset as usize)..(offset as usize + data.len())] - .copy_from_slice(data); + self.pass.constant_data[offset_words..(offset_words + data.len())].copy_from_slice(data); if self.pass.layout.signature == layout.shared.signature { self.pass.dirty_root_elements |= 1 << info.root_index; @@ -1030,34 +1037,34 @@ impl crate::CommandEncoder for super::CommandEncoder { unsafe fn draw( &mut self, - start_vertex: u32, + first_vertex: u32, vertex_count: u32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { - unsafe { self.prepare_draw(start_vertex as i32, start_instance) }; + unsafe { self.prepare_draw(first_vertex as i32, first_instance) }; self.list.as_ref().unwrap().draw( vertex_count, instance_count, - start_vertex, - start_instance, + first_vertex, + first_instance, ); } unsafe fn draw_indexed( &mut self, - start_index: u32, + first_index: u32, index_count: u32, base_vertex: i32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { - unsafe { self.prepare_draw(base_vertex, start_instance) }; + unsafe { self.prepare_draw(base_vertex, first_instance) }; self.list.as_ref().unwrap().draw_indexed( index_count, instance_count, - start_index, + first_index, base_vertex, - start_instance, + first_instance, ); } unsafe fn draw_indirect( diff --git a/wgpu-hal/src/dx12/conv.rs b/wgpu-hal/src/dx12/conv.rs index 26882e4086..2b6c1d959e 100644 --- a/wgpu-hal/src/dx12/conv.rs +++ b/wgpu-hal/src/dx12/conv.rs @@ -223,6 +223,10 @@ pub fn map_polygon_mode(mode: wgt::PolygonMode) -> d3d12_ty::D3D12_FILL_MODE { } } +/// D3D12 doesn't support passing factors ending in `_COLOR` for alpha blending +/// (see https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_render_target_blend_desc). +/// Therefore this function takes an additional `is_alpha` argument +/// which if set will return an equivalent `_ALPHA` factor. fn map_blend_factor(factor: wgt::BlendFactor, is_alpha: bool) -> d3d12_ty::D3D12_BLEND { use wgt::BlendFactor as Bf; match factor { @@ -243,12 +247,12 @@ fn map_blend_factor(factor: wgt::BlendFactor, is_alpha: bool) -> d3d12_ty::D3D12 Bf::Constant => d3d12_ty::D3D12_BLEND_BLEND_FACTOR, Bf::OneMinusConstant => d3d12_ty::D3D12_BLEND_INV_BLEND_FACTOR, Bf::SrcAlphaSaturated => d3d12_ty::D3D12_BLEND_SRC_ALPHA_SAT, - //Bf::Src1Color if is_alpha => d3d12_ty::D3D12_BLEND_SRC1_ALPHA, - //Bf::Src1Color => d3d12_ty::D3D12_BLEND_SRC1_COLOR, - //Bf::OneMinusSrc1Color if is_alpha => d3d12_ty::D3D12_BLEND_INV_SRC1_ALPHA, - //Bf::OneMinusSrc1Color => d3d12_ty::D3D12_BLEND_INV_SRC1_COLOR, - //Bf::Src1Alpha => d3d12_ty::D3D12_BLEND_SRC1_ALPHA, - //Bf::OneMinusSrc1Alpha => d3d12_ty::D3D12_BLEND_INV_SRC1_ALPHA, + Bf::Src1 if is_alpha => d3d12_ty::D3D12_BLEND_SRC1_ALPHA, + Bf::Src1 => d3d12_ty::D3D12_BLEND_SRC1_COLOR, + Bf::OneMinusSrc1 if is_alpha => d3d12_ty::D3D12_BLEND_INV_SRC1_ALPHA, + Bf::OneMinusSrc1 => d3d12_ty::D3D12_BLEND_INV_SRC1_COLOR, + Bf::Src1Alpha => d3d12_ty::D3D12_BLEND_SRC1_ALPHA, + Bf::OneMinusSrc1Alpha => d3d12_ty::D3D12_BLEND_INV_SRC1_ALPHA, } } diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index 467fb5586e..2346dff266 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -1,8 +1,17 @@ -use crate::auxil::{self, dxgi::result::HResult as _}; +use crate::{ + auxil::{self, dxgi::result::HResult as _}, + dx12::shader_compilation, +}; use super::{conv, descriptor, view}; use parking_lot::Mutex; -use std::{ffi, mem, num::NonZeroU32, ptr, sync::Arc}; +use std::{ + ffi, mem, + num::NonZeroU32, + ptr, + sync::Arc, + time::{Duration, Instant}, +}; use winapi::{ shared::{dxgiformat, dxgitype, minwindef::BOOL, winerror}, um::{d3d12 as d3d12_ty, synchapi, winbase}, @@ -19,7 +28,7 @@ impl super::Device { limits: &wgt::Limits, private_caps: super::PrivateCapabilities, library: &Arc, - dx12_shader_compiler: wgt::Dx12Compiler, + dxc_container: Option>, ) -> Result { let mem_allocator = if private_caps.suballocation_supported { super::suballocation::create_allocator_wrapper(&raw)? @@ -27,14 +36,6 @@ impl super::Device { None }; - let dxc_container = match dx12_shader_compiler { - wgt::Dx12Compiler::Dxc { - dxil_path, - dxc_path, - } => super::shader_compilation::get_dxc_container(dxc_path, dxil_path)?, - wgt::Dx12Compiler::Fxc => None, - }; - let mut idle_fence = d3d12::Fence::null(); let hr = unsafe { profiling::scope!("ID3D12Device::CreateFence"); @@ -181,14 +182,17 @@ impl super::Device { }) } - pub(super) unsafe fn wait_idle(&self) -> Result<(), crate::DeviceError> { + // Blocks until the dedicated present queue is finished with all of its work. + // + // Once this method completes, the surface is able to be resized or deleted. + pub(super) unsafe fn wait_for_present_queue_idle(&self) -> Result<(), crate::DeviceError> { let cur_value = self.idler.fence.get_value(); if cur_value == !0 { return Err(crate::DeviceError::Lost); } let value = cur_value + 1; - log::info!("Waiting for idle with value {}", value); + log::debug!("Waiting for idle with value {}", value); self.present_queue.signal(&self.idler.fence, value); let hr = self .idler @@ -986,7 +990,7 @@ impl crate::Device for super::Device { debug_assert_eq!(ranges.len(), total_non_dynamic_entries); let (special_constants_root_index, special_constants_binding) = if desc.flags.intersects( - crate::PipelineLayoutFlags::BASE_VERTEX_INSTANCE + crate::PipelineLayoutFlags::FIRST_VERTEX_INSTANCE | crate::PipelineLayoutFlags::NUM_WORK_GROUPS, ) { let parameter_index = parameters.len(); @@ -994,7 +998,7 @@ impl crate::Device for super::Device { parameters.push(d3d12::RootParameter::constants( d3d12::ShaderVisibility::All, // really needed for VS and CS only native_binding(&bind_cbv), - 3, // 0 = base vertex, 1 = base instance, 2 = other + 3, // 0 = first_vertex, 1 = first_instance, 2 = other )); let binding = bind_cbv.clone(); bind_cbv.register += 1; @@ -1516,7 +1520,7 @@ impl crate::Device for super::Device { let hr = unsafe { self.raw.CreateFence( 0, - d3d12_ty::D3D12_FENCE_FLAG_NONE, + d3d12_ty::D3D12_FENCE_FLAG_SHARED, &d3d12_ty::ID3D12Fence::uuidof(), raw.mut_void(), ) @@ -1537,19 +1541,80 @@ impl crate::Device for super::Device { value: crate::FenceValue, timeout_ms: u32, ) -> Result { - if unsafe { fence.raw.GetCompletedValue() } >= value { + let timeout_duration = Duration::from_millis(timeout_ms as u64); + + // We first check if the fence has already reached the value we're waiting for. + let mut fence_value = unsafe { fence.raw.GetCompletedValue() }; + if fence_value >= value { return Ok(true); } - let hr = fence.raw.set_event_on_completion(self.idler.event, value); - hr.into_device_result("Set event")?; - match unsafe { synchapi::WaitForSingleObject(self.idler.event.0, timeout_ms) } { - winbase::WAIT_ABANDONED | winbase::WAIT_FAILED => Err(crate::DeviceError::Lost), - winbase::WAIT_OBJECT_0 => Ok(true), - winerror::WAIT_TIMEOUT => Ok(false), - other => { - log::error!("Unexpected wait status: 0x{:x}", other); - Err(crate::DeviceError::Lost) + fence + .raw + .set_event_on_completion(self.idler.event, value) + .into_device_result("Set event")?; + + let start_time = Instant::now(); + + // We need to loop to get correct behavior when timeouts are involved. + // + // wait(0): + // - We set the event from the fence value 0. + // - WaitForSingleObject times out, we return false. + // + // wait(1): + // - We set the event from the fence value 1. + // - WaitForSingleObject returns. However we do not know if the fence value is 0 or 1, + // just that _something_ triggered the event. We check the fence value, and if it is + // 1, we return true. Otherwise, we loop and wait again. + loop { + let elapsed = start_time.elapsed(); + + // We need to explicitly use checked_sub. Overflow with duration panics, and if the + // timing works out just right, we can get a negative remaining wait duration. + // + // This happens when a previous iteration WaitForSingleObject succeeded with a previous fence value, + // right before the timeout would have been hit. + let remaining_wait_duration = match timeout_duration.checked_sub(elapsed) { + Some(remaining) => remaining, + None => { + log::trace!("Timeout elapsed inbetween waits!"); + break Ok(false); + } + }; + + log::trace!( + "Waiting for fence value {} for {:?}", + value, + remaining_wait_duration + ); + + match unsafe { + synchapi::WaitForSingleObject( + self.idler.event.0, + remaining_wait_duration.as_millis().try_into().unwrap(), + ) + } { + winbase::WAIT_OBJECT_0 => {} + winbase::WAIT_ABANDONED | winbase::WAIT_FAILED => { + log::error!("Wait failed!"); + break Err(crate::DeviceError::Lost); + } + winerror::WAIT_TIMEOUT => { + log::trace!("Wait timed out!"); + break Ok(false); + } + other => { + log::error!("Unexpected wait status: 0x{:x}", other); + break Err(crate::DeviceError::Lost); + } + }; + + fence_value = unsafe { fence.raw.GetCompletedValue() }; + log::trace!("Wait complete! Fence actual value: {}", fence_value); + + if fence_value >= value { + break Ok(true); } } } diff --git a/wgpu-hal/src/dx12/instance.rs b/wgpu-hal/src/dx12/instance.rs index 32d6f1690c..7bf5f3ef75 100644 --- a/wgpu-hal/src/dx12/instance.rs +++ b/wgpu-hal/src/dx12/instance.rs @@ -1,3 +1,4 @@ +use parking_lot::RwLock; use winapi::shared::{dxgi1_5, minwindef}; use super::SurfaceTarget; @@ -12,11 +13,12 @@ impl Drop for super::Instance { impl crate::Instance for super::Instance { unsafe fn init(desc: &crate::InstanceDescriptor) -> Result { + profiling::scope!("Init DX12 Backend"); let lib_main = d3d12::D3D12Lib::new().map_err(|e| { crate::InstanceError::with_source(String::from("failed to load d3d12.dll"), e) })?; - if desc.flags.contains(crate::InstanceFlags::VALIDATION) { + if desc.flags.contains(wgt::InstanceFlags::VALIDATION) { // Enable debug layer match lib_main.get_debug_interface() { Ok(pair) => match pair.into_result() { @@ -49,7 +51,7 @@ impl crate::Instance for super::Instance { } }, Err(err) => { - log::info!("IDXGIFactory1 creation function not found: {:?}", err); + log::warn!("IDXGIFactory1 creation function not found: {:?}", err); None } }; @@ -72,6 +74,27 @@ impl crate::Instance for super::Instance { } } + // Initialize DXC shader compiler + let dxc_container = match desc.dx12_shader_compiler.clone() { + wgt::Dx12Compiler::Dxc { + dxil_path, + dxc_path, + } => { + let container = super::shader_compilation::get_dxc_container(dxc_path, dxil_path) + .map_err(|e| { + crate::InstanceError::with_source(String::from("Failed to load DXC"), e) + })?; + + container.map(Arc::new) + } + wgt::Dx12Compiler::Fxc => None, + }; + + match dxc_container { + Some(_) => log::debug!("Using DXC for shader compilation"), + None => log::debug!("Using FXC for shader compilation"), + } + Ok(Self { // The call to create_factory will only succeed if we get a factory4, so this is safe. factory, @@ -80,7 +103,7 @@ impl crate::Instance for super::Instance { _lib_dxgi: lib_dxgi, supports_allow_tearing, flags: desc.flags, - dx12_shader_compiler: desc.dx12_shader_compiler.clone(), + dxc_container, }) } @@ -93,9 +116,9 @@ impl crate::Instance for super::Instance { raw_window_handle::RawWindowHandle::Win32(handle) => Ok(super::Surface { factory: self.factory.clone(), factory_media: self.factory_media.clone(), - target: SurfaceTarget::WndHandle(handle.hwnd as *mut _), + target: SurfaceTarget::WndHandle(handle.hwnd.get() as *mut _), supports_allow_tearing: self.supports_allow_tearing, - swap_chain: None, + swap_chain: RwLock::new(None), }), _ => Err(crate::InstanceError::new(format!( "window handle {window_handle:?} is not a Win32 handle" @@ -112,7 +135,7 @@ impl crate::Instance for super::Instance { adapters .into_iter() .filter_map(|raw| { - super::Adapter::expose(raw, &self.library, self.flags, &self.dx12_shader_compiler) + super::Adapter::expose(raw, &self.library, self.flags, self.dxc_container.clone()) }) .collect() } diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index 2178998d47..e0cd1c15cf 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -47,7 +47,7 @@ mod view; use crate::auxil::{self, dxgi::result::HResult as _}; use arrayvec::ArrayVec; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use std::{ffi, fmt, mem, num::NonZeroU32, sync::Arc}; use winapi::{ shared::{dxgi, dxgi1_4, dxgitype, windef, winerror}, @@ -55,7 +55,7 @@ use winapi::{ Interface as _, }; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Api; impl crate::Api for Api { @@ -96,8 +96,8 @@ pub struct Instance { library: Arc, supports_allow_tearing: bool, _lib_dxgi: d3d12::DxgiLib, - flags: crate::InstanceFlags, - dx12_shader_compiler: wgt::Dx12Compiler, + flags: wgt::InstanceFlags, + dxc_container: Option>, } impl Instance { @@ -110,7 +110,7 @@ impl Instance { factory_media: self.factory_media.clone(), target: SurfaceTarget::Visual(unsafe { d3d12::ComPtr::from_raw(visual) }), supports_allow_tearing: self.supports_allow_tearing, - swap_chain: None, + swap_chain: RwLock::new(None), } } @@ -123,7 +123,22 @@ impl Instance { factory_media: self.factory_media.clone(), target: SurfaceTarget::SurfaceHandle(surface_handle), supports_allow_tearing: self.supports_allow_tearing, - swap_chain: None, + swap_chain: RwLock::new(None), + } + } + + pub unsafe fn create_surface_from_swap_chain_panel( + &self, + swap_chain_panel: *mut types::ISwapChainPanelNative, + ) -> Surface { + Surface { + factory: self.factory.clone(), + factory_media: self.factory_media.clone(), + target: SurfaceTarget::SwapChainPanel(unsafe { + d3d12::ComPtr::from_raw(swap_chain_panel) + }), + supports_allow_tearing: self.supports_allow_tearing, + swap_chain: RwLock::new(None), } } } @@ -147,6 +162,7 @@ enum SurfaceTarget { WndHandle(windef::HWND), Visual(d3d12::ComPtr), SurfaceHandle(winnt::HANDLE), + SwapChainPanel(d3d12::ComPtr), } pub struct Surface { @@ -154,7 +170,7 @@ pub struct Surface { factory_media: Option, target: SurfaceTarget, supports_allow_tearing: bool, - swap_chain: Option, + swap_chain: RwLock>, } unsafe impl Send for Surface {} @@ -171,7 +187,7 @@ enum MemoryArchitecture { #[derive(Debug, Clone, Copy)] struct PrivateCapabilities { - instance_flags: crate::InstanceFlags, + instance_flags: wgt::InstanceFlags, #[allow(unused)] heterogeneous_resource_heaps: bool, memory_architecture: MemoryArchitecture, @@ -197,7 +213,7 @@ pub struct Adapter { //Note: this isn't used right now, but we'll need it later. #[allow(unused)] workarounds: Workarounds, - dx12_shader_compiler: wgt::Dx12Compiler, + dxc_container: Option>, } unsafe impl Send for Adapter {} @@ -239,7 +255,7 @@ pub struct Device { render_doc: crate::auxil::renderdoc::RenderDoc, null_rtv_handle: descriptor::Handle, mem_allocator: Option>, - dxc_container: Option, + dxc_container: Option>, } unsafe impl Send for Device {} @@ -247,7 +263,7 @@ unsafe impl Sync for Device {} pub struct Queue { raw: d3d12::CommandQueue, - temp_lists: Vec, + temp_lists: Mutex>, } unsafe impl Send for Queue {} @@ -277,8 +293,8 @@ enum RootElement { Empty, Constant, SpecialConstantBuffer { - base_vertex: i32, - base_instance: u32, + first_vertex: i32, + first_instance: u32, other: u32, }, /// Descriptor table. @@ -440,6 +456,7 @@ impl Texture { pub struct TextureView { raw_format: d3d12::Format, aspects: crate::FormatAspects, + /// only used by resolve target_base: (d3d12::Resource, u32), handle_srv: Option, handle_uav: Option, @@ -476,6 +493,13 @@ pub struct Fence { unsafe impl Send for Fence {} unsafe impl Sync for Fence {} +impl Fence { + pub fn raw_fence(&self) -> &d3d12::Fence { + &self.raw + } +} + +#[derive(Debug)] pub struct BindGroupLayout { /// Sorted list of entries. entries: Vec, @@ -484,7 +508,7 @@ pub struct BindGroupLayout { copy_counts: Vec, // all 1's } -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] enum BufferViewKind { Constant, ShaderResource, @@ -509,19 +533,20 @@ bitflags::bitflags! { // Element (also known as parameter) index into the root signature. type RootIndex = u32; +#[derive(Debug)] struct BindGroupInfo { base_root_index: RootIndex, tables: TableTypes, dynamic_buffers: Vec, } -#[derive(Clone)] +#[derive(Debug, Clone)] struct RootConstantInfo { root_index: RootIndex, range: std::ops::Range, } -#[derive(Clone)] +#[derive(Debug, Clone)] struct PipelineLayoutShared { signature: d3d12::RootSignature, total_root_elements: RootIndex, @@ -532,6 +557,7 @@ struct PipelineLayoutShared { unsafe impl Send for PipelineLayoutShared {} unsafe impl Sync for PipelineLayoutShared {} +#[derive(Debug)] pub struct PipelineLayout { shared: PipelineLayoutShared, // Storing for each associated bind group, which tables we created @@ -563,6 +589,7 @@ impl CompiledShader { unsafe fn destroy(self) {} } +#[derive(Debug)] pub struct RenderPipeline { raw: d3d12::PipelineState, layout: PipelineLayoutShared, @@ -573,6 +600,7 @@ pub struct RenderPipeline { unsafe impl Send for RenderPipeline {} unsafe impl Sync for RenderPipeline {} +#[derive(Debug)] pub struct ComputePipeline { raw: d3d12::PipelineState, layout: PipelineLayoutShared, @@ -611,26 +639,30 @@ impl SwapChain { impl crate::Surface for Surface { unsafe fn configure( - &mut self, + &self, device: &Device, config: &crate::SurfaceConfiguration, ) -> Result<(), crate::SurfaceError> { let mut flags = dxgi::DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; // We always set ALLOW_TEARING on the swapchain no matter // what kind of swapchain we want because ResizeBuffers - // cannot change if ALLOW_TEARING is applied to the swapchain. + // cannot change the swapchain's ALLOW_TEARING flag. + // + // This does not change the behavior of the swapchain, just + // allow present calls to use tearing. if self.supports_allow_tearing { flags |= dxgi::DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; } + // While `configure`s contract ensures that no work on the GPU's main queues + // are in flight, we still need to wait for the present queue to be idle. + unsafe { device.wait_for_present_queue_idle() }?; + let non_srgb_format = auxil::dxgi::conv::map_texture_format_nosrgb(config.format); - let swap_chain = match self.swap_chain.take() { + let swap_chain = match self.swap_chain.write().take() { //Note: this path doesn't properly re-initialize all of the things Some(sc) => { - // can't have image resources in flight used by GPU - let _ = unsafe { device.wait_idle() }; - let raw = unsafe { sc.release_resources() }; let result = unsafe { raw.ResizeBuffers( @@ -667,7 +699,7 @@ impl crate::Surface for Surface { flags, }; let swap_chain1 = match self.target { - SurfaceTarget::Visual(_) => { + SurfaceTarget::Visual(_) | SurfaceTarget::SwapChainPanel(_) => { profiling::scope!("IDXGIFactory4::CreateSwapChainForComposition"); self.factory .unwrap_factory2() @@ -725,6 +757,17 @@ impl crate::Surface for Surface { )); } } + &SurfaceTarget::SwapChainPanel(ref swap_chain_panel) => { + if let Err(err) = + unsafe { swap_chain_panel.SetSwapChain(swap_chain1.as_ptr()) } + .into_result() + { + log::error!("Unable to SetSwapChain: {}", err); + return Err(crate::SurfaceError::Other( + "ISwapChainPanelNative::SetSwapChain", + )); + } + } } match unsafe { swap_chain1.cast::() }.into_result() { @@ -749,7 +792,9 @@ impl crate::Surface for Surface { ) }; } - SurfaceTarget::Visual(_) | SurfaceTarget::SurfaceHandle(_) => {} + SurfaceTarget::Visual(_) + | SurfaceTarget::SurfaceHandle(_) + | SurfaceTarget::SwapChainPanel(_) => {} } unsafe { swap_chain.SetMaximumFrameLatency(config.swap_chain_size) }; @@ -764,7 +809,8 @@ impl crate::Surface for Surface { resources.push(resource); } - self.swap_chain = Some(SwapChain { + let mut swapchain = self.swap_chain.write(); + *swapchain = Some(SwapChain { raw: swap_chain, resources, waitable, @@ -777,23 +823,28 @@ impl crate::Surface for Surface { Ok(()) } - unsafe fn unconfigure(&mut self, device: &Device) { - if let Some(mut sc) = self.swap_chain.take() { + unsafe fn unconfigure(&self, device: &Device) { + if let Some(sc) = self.swap_chain.write().take() { unsafe { - let _ = sc.wait(None); - //TODO: this shouldn't be needed, - // but it complains that the queue is still used otherwise - let _ = device.wait_idle(); + // While `unconfigure`s contract ensures that no work on the GPU's main queues + // are in flight, we still need to wait for the present queue to be idle. + + // The major failure mode of this function is device loss, + // which if we have lost the device, we should just continue + // cleaning up, without error. + let _ = device.wait_for_present_queue_idle(); + let _raw = sc.release_resources(); } } } unsafe fn acquire_texture( - &mut self, + &self, timeout: Option, ) -> Result>, crate::SurfaceError> { - let sc = self.swap_chain.as_mut().unwrap(); + let mut swapchain = self.swap_chain.write(); + let sc = swapchain.as_mut().unwrap(); unsafe { sc.wait(timeout) }?; @@ -815,26 +866,28 @@ impl crate::Surface for Surface { suboptimal: false, })) } - unsafe fn discard_texture(&mut self, _texture: Texture) { - let sc = self.swap_chain.as_mut().unwrap(); + unsafe fn discard_texture(&self, _texture: Texture) { + let mut swapchain = self.swap_chain.write(); + let sc = swapchain.as_mut().unwrap(); sc.acquired_count -= 1; } } impl crate::Queue for Queue { unsafe fn submit( - &mut self, + &self, command_buffers: &[&CommandBuffer], signal_fence: Option<(&mut Fence, crate::FenceValue)>, ) -> Result<(), crate::DeviceError> { - self.temp_lists.clear(); + let mut temp_lists = self.temp_lists.lock(); + temp_lists.clear(); for cmd_buf in command_buffers { - self.temp_lists.push(cmd_buf.raw.as_list()); + temp_lists.push(cmd_buf.raw.as_list()); } { profiling::scope!("ID3D12CommandQueue::ExecuteCommandLists"); - self.raw.execute_command_lists(&self.temp_lists); + self.raw.execute_command_lists(&temp_lists); } if let Some((fence, value)) = signal_fence { @@ -842,14 +895,22 @@ impl crate::Queue for Queue { .signal(&fence.raw, value) .into_device_result("Signal fence")?; } + + // Note the lack of synchronization here between the main Direct queue + // and the dedicated presentation queue. This is automatically handled + // by the D3D runtime by detecting uses of resources derived from the + // swapchain. This automatic detection is why you cannot use a swapchain + // as an UAV in D3D12. + Ok(()) } unsafe fn present( - &mut self, - surface: &mut Surface, + &self, + surface: &Surface, _texture: Texture, ) -> Result<(), crate::SurfaceError> { - let sc = surface.swap_chain.as_mut().unwrap(); + let mut swapchain = surface.swap_chain.write(); + let sc = swapchain.as_mut().unwrap(); sc.acquired_count -= 1; let (interval, flags) = match sc.present_mode { diff --git a/wgpu-hal/src/dx12/shader_compilation.rs b/wgpu-hal/src/dx12/shader_compilation.rs index 55a8f595d1..df040dba15 100644 --- a/wgpu-hal/src/dx12/shader_compilation.rs +++ b/wgpu-hal/src/dx12/shader_compilation.rs @@ -28,7 +28,7 @@ pub(super) fn compile_fxc( if device .private_caps .instance_flags - .contains(crate::InstanceFlags::DEBUG) + .contains(wgt::InstanceFlags::DEBUG) { compile_flags |= d3dcompiler::D3DCOMPILE_DEBUG | d3dcompiler::D3DCOMPILE_SKIP_OPTIMIZATION; } @@ -99,7 +99,7 @@ mod dxc { let dxil = match hassle_rs::Dxil::new(dxil_path) { Ok(dxil) => dxil, Err(e) => { - log::warn!("Failed to load dxil.dll. Defaulting to Fxc instead: {}", e); + log::warn!("Failed to load dxil.dll. Defaulting to FXC instead: {}", e); return Ok(None); } }; @@ -111,7 +111,7 @@ mod dxc { Ok(dxc) => dxc, Err(e) => { log::warn!( - "Failed to load dxcompiler.dll. Defaulting to Fxc instead: {}", + "Failed to load dxcompiler.dll. Defaulting to FXC instead: {}", e ); return Ok(None); @@ -142,13 +142,16 @@ mod dxc { log::Level, ) { profiling::scope!("compile_dxc"); - let mut compile_flags = arrayvec::ArrayVec::<&str, 4>::new_const(); + let mut compile_flags = arrayvec::ArrayVec::<&str, 6>::new_const(); compile_flags.push("-Ges"); // d3dcompiler::D3DCOMPILE_ENABLE_STRICTNESS compile_flags.push("-Vd"); // Disable implicit validation to work around bugs when dxil.dll isn't in the local directory. + compile_flags.push("-HV"); // Use HLSL 2018, Naga doesn't supported 2021 yet. + compile_flags.push("2018"); + if device .private_caps .instance_flags - .contains(crate::InstanceFlags::DEBUG) + .contains(wgt::InstanceFlags::DEBUG) { compile_flags.push("-Zi"); // d3dcompiler::D3DCOMPILE_SKIP_OPTIMIZATION compile_flags.push("-Od"); // d3dcompiler::D3DCOMPILE_DEBUG diff --git a/wgpu-hal/src/dx12/suballocation.rs b/wgpu-hal/src/dx12/suballocation.rs index 9625b2ae3a..3b9696e455 100644 --- a/wgpu-hal/src/dx12/suballocation.rs +++ b/wgpu-hal/src/dx12/suballocation.rs @@ -49,8 +49,9 @@ mod placed { let device = raw.as_ptr(); match gpu_allocator::d3d12::Allocator::new(&gpu_allocator::d3d12::AllocatorCreateDesc { - device: device.as_windows().clone(), + device: gpu_allocator::d3d12::ID3D12DeviceVersion::Device(device.as_windows().clone()), debug_settings: Default::default(), + allocation_sizes: gpu_allocator::AllocationSizes::default(), }) { Ok(allocator) => Ok(Some(Mutex::new(GpuAllocatorWrapper { allocator }))), Err(e) => { @@ -213,6 +214,7 @@ mod placed { log::error!("DX12 gpu-allocator: Internal Error: {}", e); Self::Lost } + gpu_allocator::AllocationError::BarrierLayoutNeedsDevice10 => todo!(), } } } diff --git a/wgpu-hal/src/dx12/types.rs b/wgpu-hal/src/dx12/types.rs index dec4e71337..b4ad38324a 100644 --- a/wgpu-hal/src/dx12/types.rs +++ b/wgpu-hal/src/dx12/types.rs @@ -1,6 +1,15 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] +// use here so that the recursive RIDL macro can find the crate +use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl}; +use winapi::RIDL; + +RIDL! {#[uuid(0x63aad0b8, 0x7c24, 0x40ff, 0x85, 0xa8, 0x64, 0x0d, 0x94, 0x4c, 0xc3, 0x25)] +interface ISwapChainPanelNative(ISwapChainPanelNativeVtbl): IUnknown(IUnknownVtbl) { + fn SetSwapChain(swapChain: *const winapi::shared::dxgi1_2::IDXGISwapChain1,) -> winapi::um::winnt::HRESULT, +}} + winapi::ENUM! { enum D3D12_VIEW_INSTANCING_TIER { D3D12_VIEW_INSTANCING_TIER_NOT_SUPPORTED = 0, diff --git a/wgpu-hal/src/dx12/view.rs b/wgpu-hal/src/dx12/view.rs index e7a051b535..6cbad7bd1d 100644 --- a/wgpu-hal/src/dx12/view.rs +++ b/wgpu-hal/src/dx12/view.rs @@ -18,7 +18,7 @@ pub(super) struct ViewDescriptor { impl crate::TextureViewDescriptor<'_> { pub(super) fn to_internal(&self, texture: &super::Texture) -> ViewDescriptor { - let aspects = crate::FormatAspects::new(self.format, self.range.aspect); + let aspects = crate::FormatAspects::new(texture.format, self.range.aspect); ViewDescriptor { dimension: self.dimension, @@ -34,6 +34,14 @@ impl crate::TextureViewDescriptor<'_> { } } +fn aspects_to_plane(aspects: crate::FormatAspects) -> u32 { + match aspects { + crate::FormatAspects::PLANE_1 => 1, + crate::FormatAspects::PLANE_2 => 2, + _ => 0, + } +} + impl ViewDescriptor { pub(crate) unsafe fn to_srv(&self) -> Option { let mut desc = d3d12_ty::D3D12_SHADER_RESOURCE_VIEW_DESC { @@ -79,7 +87,7 @@ impl ViewDescriptor { *desc.u.Texture2D_mut() = d3d12_ty::D3D12_TEX2D_SRV { MostDetailedMip: self.mip_level_base, MipLevels: self.mip_level_count, - PlaneSlice: 0, + PlaneSlice: aspects_to_plane(self.aspects), ResourceMinLODClamp: 0.0, } } @@ -103,7 +111,7 @@ impl ViewDescriptor { MipLevels: self.mip_level_count, FirstArraySlice: self.array_layer_base, ArraySize: self.array_layer_count, - PlaneSlice: 0, + PlaneSlice: aspects_to_plane(self.aspects), ResourceMinLODClamp: 0.0, } } @@ -179,7 +187,7 @@ impl ViewDescriptor { unsafe { *desc.u.Texture2D_mut() = d3d12_ty::D3D12_TEX2D_UAV { MipSlice: self.mip_level_base, - PlaneSlice: 0, + PlaneSlice: aspects_to_plane(self.aspects), } } } @@ -190,7 +198,7 @@ impl ViewDescriptor { MipSlice: self.mip_level_base, FirstArraySlice: self.array_layer_base, ArraySize: self.array_layer_count, - PlaneSlice: 0, + PlaneSlice: aspects_to_plane(self.aspects), } } } @@ -250,7 +258,7 @@ impl ViewDescriptor { unsafe { *desc.u.Texture2D_mut() = d3d12_ty::D3D12_TEX2D_RTV { MipSlice: self.mip_level_base, - PlaneSlice: 0, + PlaneSlice: aspects_to_plane(self.aspects), } } } @@ -272,7 +280,7 @@ impl ViewDescriptor { MipSlice: self.mip_level_base, FirstArraySlice: self.array_layer_base, ArraySize: self.array_layer_count, - PlaneSlice: 0, + PlaneSlice: aspects_to_plane(self.aspects), } } } diff --git a/wgpu-hal/src/empty.rs b/wgpu-hal/src/empty.rs index 8f19d626dd..12f86e6f31 100644 --- a/wgpu-hal/src/empty.rs +++ b/wgpu-hal/src/empty.rs @@ -2,7 +2,7 @@ use std::ops::Range; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Api; pub struct Context; #[derive(Debug)] @@ -58,22 +58,22 @@ impl crate::Instance for Context { impl crate::Surface for Context { unsafe fn configure( - &mut self, + &self, device: &Context, config: &crate::SurfaceConfiguration, ) -> Result<(), crate::SurfaceError> { Ok(()) } - unsafe fn unconfigure(&mut self, device: &Context) {} + unsafe fn unconfigure(&self, device: &Context) {} unsafe fn acquire_texture( - &mut self, + &self, timeout: Option, ) -> Result>, crate::SurfaceError> { Ok(None) } - unsafe fn discard_texture(&mut self, texture: Resource) {} + unsafe fn discard_texture(&self, texture: Resource) {} } impl crate::Adapter for Context { @@ -102,15 +102,15 @@ impl crate::Adapter for Context { impl crate::Queue for Context { unsafe fn submit( - &mut self, + &self, command_buffers: &[&Resource], signal_fence: Option<(&mut Resource, crate::FenceValue)>, ) -> DeviceResult<()> { Ok(()) } unsafe fn present( - &mut self, - surface: &mut Context, + &self, + surface: &Context, texture: Resource, ) -> Result<(), crate::SurfaceError> { Ok(()) @@ -284,7 +284,7 @@ impl crate::CommandEncoder for Encoder { unsafe fn copy_buffer_to_buffer(&mut self, src: &Resource, dst: &Resource, regions: T) {} - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] unsafe fn copy_external_image_to_texture( &mut self, src: &wgt::ImageCopyExternalImage, @@ -347,7 +347,7 @@ impl crate::CommandEncoder for Encoder { &mut self, layout: &Resource, stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ) { } @@ -373,18 +373,18 @@ impl crate::CommandEncoder for Encoder { unsafe fn draw( &mut self, - start_vertex: u32, + first_vertex: u32, vertex_count: u32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { } unsafe fn draw_indexed( &mut self, - start_index: u32, + first_index: u32, index_count: u32, base_vertex: i32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { } diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 348f62bc03..5f92d2c4ab 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -1,5 +1,6 @@ use glow::HasContext; -use std::sync::Arc; +use parking_lot::Mutex; +use std::sync::{atomic::AtomicU8, Arc}; use wgt::AstcChannel; use crate::auxil::db; @@ -10,18 +11,6 @@ const GL_UNMASKED_VENDOR_WEBGL: u32 = 0x9245; const GL_UNMASKED_RENDERER_WEBGL: u32 = 0x9246; impl super::Adapter { - /// According to the OpenGL specification, the version information is - /// expected to follow the following syntax: - /// - /// ~~~bnf - /// ::= - /// ::= - /// ::= - /// ::= - /// ::= "." ["." ] - /// ::= [" " ] - /// ~~~ - /// /// Note that this function is intentionally lenient in regards to parsing, /// and will try to recover at least the first two version numbers without /// resulting in an `Err`. @@ -59,6 +48,35 @@ impl super::Adapter { None => false, }; + Self::parse_full_version(src).map(|(major, minor)| { + ( + // Return WebGL 2.0 version as OpenGL ES 3.0 + if is_webgl && !is_glsl { + major + 1 + } else { + major + }, + minor, + ) + }) + } + + /// According to the OpenGL specification, the version information is + /// expected to follow the following syntax: + /// + /// ~~~bnf + /// ::= + /// ::= + /// ::= + /// ::= + /// ::= "." ["." ] + /// ::= [" " ] + /// ~~~ + /// + /// Note that this function is intentionally lenient in regards to parsing, + /// and will try to recover at least the first two version numbers without + /// resulting in an `Err`. + pub(super) fn parse_full_version(src: &str) -> Result<(u8, u8), crate::InstanceError> { let (version, _vendor_info) = match src.find(' ') { Some(i) => (&src[..i], src[i + 1..].to_string()), None => (src, String::new()), @@ -78,15 +96,7 @@ impl super::Adapter { }); match (major, minor) { - (Some(major), Some(minor)) => Ok(( - // Return WebGL 2.0 version as OpenGL ES 3.0 - if is_webgl && !is_glsl { - major + 1 - } else { - major - }, - minor, - )), + (Some(major), Some(minor)) => Ok((major, minor)), _ => Err(crate::InstanceError::new(format!( "unable to extract OpenGL version from {version:?}" ))), @@ -188,61 +198,132 @@ impl super::Adapter { let (vendor_const, renderer_const) = if extensions.contains("WEBGL_debug_renderer_info") { // emscripten doesn't enable "WEBGL_debug_renderer_info" extension by default. so, we do it manually. // See https://github.com/gfx-rs/wgpu/issues/3245 for context - #[cfg(target_os = "emscripten")] + #[cfg(Emscripten)] if unsafe { super::emscripten::enable_extension("WEBGL_debug_renderer_info\0") } { (GL_UNMASKED_VENDOR_WEBGL, GL_UNMASKED_RENDERER_WEBGL) } else { (glow::VENDOR, glow::RENDERER) } // glow already enables WEBGL_debug_renderer_info on wasm32-unknown-unknown target by default. - #[cfg(not(target_os = "emscripten"))] + #[cfg(not(Emscripten))] (GL_UNMASKED_VENDOR_WEBGL, GL_UNMASKED_RENDERER_WEBGL) } else { (glow::VENDOR, glow::RENDERER) }; - let (vendor, renderer) = { - let vendor = unsafe { gl.get_parameter_string(vendor_const) }; - let renderer = unsafe { gl.get_parameter_string(renderer_const) }; - - (vendor, renderer) - }; + let vendor = unsafe { gl.get_parameter_string(vendor_const) }; + let renderer = unsafe { gl.get_parameter_string(renderer_const) }; let version = unsafe { gl.get_parameter_string(glow::VERSION) }; - log::info!("Vendor: {}", vendor); - log::info!("Renderer: {}", renderer); - log::info!("Version: {}", version); - - log::debug!("Extensions: {:#?}", extensions); - - let ver = Self::parse_version(&version).ok()?; - if ver < (3, 0) { - log::warn!( - "Returned GLES context is {}.{}, when 3.0+ was requested", - ver.0, - ver.1 + log::debug!("Vendor: {}", vendor); + log::debug!("Renderer: {}", renderer); + log::debug!("Version: {}", version); + + let full_ver = Self::parse_full_version(&version).ok(); + let es_ver = full_ver.map_or_else(|| Self::parse_version(&version).ok(), |_| None); + + if let Some(full_ver) = full_ver { + let core_profile = (full_ver >= (3, 2)).then(|| unsafe { + gl.get_parameter_i32(glow::CONTEXT_PROFILE_MASK) + & glow::CONTEXT_CORE_PROFILE_BIT as i32 + != 0 + }); + log::trace!( + "Profile: {}", + core_profile + .map(|core_profile| if core_profile { + "Core" + } else { + "Compatibility" + }) + .unwrap_or("Legacy") ); + } + + if es_ver.is_none() && full_ver.is_none() { + log::warn!("Unable to parse OpenGL version"); return None; } - let supports_storage = ver >= (3, 1); - let supports_work_group_params = ver >= (3, 1); + if let Some(es_ver) = es_ver { + if es_ver < (3, 0) { + log::warn!( + "Returned GLES context is {}.{}, when 3.0+ was requested", + es_ver.0, + es_ver.1 + ); + return None; + } + } + + if let Some(full_ver) = full_ver { + if full_ver < (3, 3) { + log::warn!( + "Returned GL context is {}.{}, when 3.3+ is needed", + full_ver.0, + full_ver.1 + ); + return None; + } + } let shading_language_version = { let sl_version = unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) }; - log::info!("SL version: {}", &sl_version); - let (sl_major, sl_minor) = Self::parse_version(&sl_version).ok()?; - let value = sl_major as u16 * 100 + sl_minor as u16 * 10; - naga::back::glsl::Version::Embedded { - version: value, - is_webgl: cfg!(target_arch = "wasm32"), + log::debug!("SL version: {}", &sl_version); + if full_ver.is_some() { + let (sl_major, sl_minor) = Self::parse_full_version(&sl_version).ok()?; + let mut value = sl_major as u16 * 100 + sl_minor as u16 * 10; + // Naga doesn't think it supports GL 460+, so we cap it at 450 + if value > 450 { + value = 450; + } + naga::back::glsl::Version::Desktop(value) + } else { + let (sl_major, sl_minor) = Self::parse_version(&sl_version).ok()?; + let value = sl_major as u16 * 100 + sl_minor as u16 * 10; + naga::back::glsl::Version::Embedded { + version: value, + is_webgl: cfg!(any(webgl, Emscripten)), + } } }; + log::debug!("Supported GL Extensions: {:#?}", extensions); + + let supported = |(req_es_major, req_es_minor), (req_full_major, req_full_minor)| { + let es_supported = es_ver + .map(|es_ver| es_ver >= (req_es_major, req_es_minor)) + .unwrap_or_default(); + + let full_supported = full_ver + .map(|full_ver| full_ver >= (req_full_major, req_full_minor)) + .unwrap_or_default(); + + es_supported || full_supported + }; + + let supports_storage = + supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_shader_storage_buffer_object"); + let supports_compute = + supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_compute_shader"); + let supports_work_group_params = supports_compute; + // ANGLE provides renderer strings like: "ANGLE (Apple, Apple M1 Pro, OpenGL 4.1)" let is_angle = renderer.contains("ANGLE"); let vertex_shader_storage_blocks = if supports_storage { - (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_SHADER_STORAGE_BLOCKS) } as u32) + let value = + (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_SHADER_STORAGE_BLOCKS) } as u32); + + if value == 0 && extensions.contains("GL_ARB_shader_storage_buffer_object") { + // The driver for AMD Radeon HD 5870 returns zero here, so assume the value matches the compute shader storage block count. + // Windows doesn't recognize `GL_MAX_VERTEX_ATTRIB_STRIDE`. + let new = (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_SHADER_STORAGE_BLOCKS) } + as u32); + log::warn!("Max vertex shader storage blocks is zero, but GL_ARB_shader_storage_buffer_object is specified. Assuming the compute value {new}"); + new + } else { + value + } } else { 0 }; @@ -294,19 +375,21 @@ impl super::Adapter { let mut downlevel_flags = wgt::DownlevelFlags::empty() | wgt::DownlevelFlags::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES | wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES - | wgt::DownlevelFlags::COMPARISON_SAMPLERS; - downlevel_flags.set(wgt::DownlevelFlags::COMPUTE_SHADERS, ver >= (3, 1)); + | wgt::DownlevelFlags::COMPARISON_SAMPLERS + | wgt::DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW; + downlevel_flags.set(wgt::DownlevelFlags::COMPUTE_SHADERS, supports_compute); downlevel_flags.set( wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE, max_storage_block_size != 0, ); - downlevel_flags.set(wgt::DownlevelFlags::INDIRECT_EXECUTION, ver >= (3, 1)); - //TODO: we can actually support positive `base_vertex` in the same way - // as we emulate the `start_instance`. But we can't deal with negatives... - downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, ver >= (3, 2)); + downlevel_flags.set( + wgt::DownlevelFlags::INDIRECT_EXECUTION, + supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_multi_draw_indirect"), + ); + downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, supported((3, 2), (3, 2))); downlevel_flags.set( wgt::DownlevelFlags::INDEPENDENT_BLEND, - ver >= (3, 2) || extensions.contains("GL_EXT_draw_buffers_indexed"), + supported((3, 2), (4, 0)) || extensions.contains("GL_EXT_draw_buffers_indexed"), ); downlevel_flags.set( wgt::DownlevelFlags::VERTEX_STORAGE, @@ -315,23 +398,25 @@ impl super::Adapter { && (vertex_shader_storage_blocks != 0 || vertex_ssbo_false_zero), ); downlevel_flags.set(wgt::DownlevelFlags::FRAGMENT_STORAGE, supports_storage); - if extensions.contains("EXT_texture_filter_anisotropic") { + if extensions.contains("EXT_texture_filter_anisotropic") + || extensions.contains("GL_EXT_texture_filter_anisotropic") + { let max_aniso = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_MAX_ANISOTROPY_EXT) } as u32; downlevel_flags.set(wgt::DownlevelFlags::ANISOTROPIC_FILTERING, max_aniso >= 16); } downlevel_flags.set( wgt::DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED, - !(cfg!(target_arch = "wasm32") || is_angle), + !(cfg!(any(webgl, Emscripten)) || is_angle), ); // see https://registry.khronos.org/webgl/specs/latest/2.0/#BUFFER_OBJECT_BINDING downlevel_flags.set( wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER, - !cfg!(target_arch = "wasm32"), + !cfg!(any(webgl, Emscripten)), ); downlevel_flags.set( wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES, - !cfg!(target_arch = "wasm32"), + !cfg!(any(webgl, Emscripten)), ); downlevel_flags.set( wgt::DownlevelFlags::FULL_DRAW_INDEX_UINT32, @@ -339,8 +424,13 @@ impl super::Adapter { ); downlevel_flags.set( wgt::DownlevelFlags::MULTISAMPLED_SHADING, - ver >= (3, 2) || extensions.contains("OES_sample_variables"), + supported((3, 2), (4, 0)) || extensions.contains("OES_sample_variables"), ); + let query_buffers = extensions.contains("GL_ARB_query_buffer_object") + || extensions.contains("GL_AMD_query_buffer_object"); + if query_buffers { + downlevel_flags.set(wgt::DownlevelFlags::NONBLOCKING_QUERY_RESOLVE, true); + } let mut features = wgt::Features::empty() | wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES @@ -348,11 +438,12 @@ impl super::Adapter { | wgt::Features::PUSH_CONSTANTS; features.set( wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER | wgt::Features::ADDRESS_MODE_CLAMP_TO_ZERO, - extensions.contains("GL_EXT_texture_border_clamp"), + extensions.contains("GL_EXT_texture_border_clamp") + || extensions.contains("GL_ARB_texture_border_clamp"), ); features.set( wgt::Features::DEPTH_CLIP_CONTROL, - extensions.contains("GL_EXT_depth_clamp"), + extensions.contains("GL_EXT_depth_clamp") || extensions.contains("GL_ARB_depth_clamp"), ); features.set( wgt::Features::VERTEX_WRITABLE_STORAGE, @@ -361,13 +452,33 @@ impl super::Adapter { ); features.set( wgt::Features::MULTIVIEW, - extensions.contains("OVR_multiview2"), + extensions.contains("OVR_multiview2") || extensions.contains("GL_OVR_multiview2"), + ); + features.set( + wgt::Features::DUAL_SOURCE_BLENDING, + extensions.contains("GL_EXT_blend_func_extended") + || extensions.contains("GL_ARB_blend_func_extended"), ); features.set( wgt::Features::SHADER_PRIMITIVE_INDEX, - ver >= (3, 2) || extensions.contains("OES_geometry_shader"), + supported((3, 2), (3, 2)) + || extensions.contains("OES_geometry_shader") + || extensions.contains("GL_ARB_geometry_shader4"), + ); + features.set( + wgt::Features::SHADER_EARLY_DEPTH_TEST, + supported((3, 1), (4, 2)) || extensions.contains("GL_ARB_shader_image_load_store"), ); - features.set(wgt::Features::SHADER_EARLY_DEPTH_TEST, ver >= (3, 1)); + features.set(wgt::Features::SHADER_UNUSED_VERTEX_OUTPUT, true); + if extensions.contains("GL_ARB_timer_query") { + features.set(wgt::Features::TIMESTAMP_QUERY, true); + features.set(wgt::Features::TIMESTAMP_QUERY_INSIDE_PASSES, true); + } + let gl_bcn_exts = [ + "GL_EXT_texture_compression_s3tc", + "GL_EXT_texture_compression_rgtc", + "GL_ARB_texture_compression_bptc", + ]; let gles_bcn_exts = [ "GL_EXT_texture_compression_s3tc_srgb", "GL_EXT_texture_compression_rgtc", @@ -379,25 +490,30 @@ impl super::Adapter { "EXT_texture_compression_rgtc", "EXT_texture_compression_bptc", ]; - let bcn_exts = if cfg!(target_arch = "wasm32") { + let bcn_exts = if cfg!(any(webgl, Emscripten)) { &webgl_bcn_exts[..] - } else { + } else if es_ver.is_some() { &gles_bcn_exts[..] + } else { + &gl_bcn_exts[..] }; features.set( wgt::Features::TEXTURE_COMPRESSION_BC, bcn_exts.iter().all(|&ext| extensions.contains(ext)), ); - features.set( - wgt::Features::TEXTURE_COMPRESSION_ETC2, - // This is a part of GLES-3 but not WebGL2 core - !cfg!(target_arch = "wasm32") || extensions.contains("WEBGL_compressed_texture_etc"), - ); + let has_etc = if cfg!(any(webgl, Emscripten)) { + extensions.contains("WEBGL_compressed_texture_etc") + } else { + // This is a required part of GLES3, but not part of Desktop GL at all. + es_ver.is_some() + }; + features.set(wgt::Features::TEXTURE_COMPRESSION_ETC2, has_etc); + // `OES_texture_compression_astc` provides 2D + 3D, LDR + HDR support if extensions.contains("WEBGL_compressed_texture_astc") || extensions.contains("GL_OES_texture_compression_astc") { - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] { if context .glow_context @@ -413,7 +529,7 @@ impl super::Adapter { } } - #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] + #[cfg(any(native, Emscripten))] { features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC); features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR); @@ -429,39 +545,54 @@ impl super::Adapter { ); } + features.set( + wgt::Features::FLOAT32_FILTERABLE, + extensions.contains("GL_ARB_color_buffer_float") + || extensions.contains("GL_EXT_color_buffer_float") + || extensions.contains("OES_texture_float_linear"), + ); + + if es_ver.is_none() { + features |= wgt::Features::POLYGON_MODE_LINE | wgt::Features::POLYGON_MODE_POINT; + } + + // We *might* be able to emulate bgra8unorm-storage but currently don't attempt to. + let mut private_caps = super::PrivateCapabilities::empty(); private_caps.set( super::PrivateCapabilities::BUFFER_ALLOCATION, - extensions.contains("GL_EXT_buffer_storage"), + extensions.contains("GL_EXT_buffer_storage") + || extensions.contains("GL_ARB_buffer_storage"), ); private_caps.set( super::PrivateCapabilities::SHADER_BINDING_LAYOUT, - ver >= (3, 1), + supports_compute, ); private_caps.set( super::PrivateCapabilities::SHADER_TEXTURE_SHADOW_LOD, extensions.contains("GL_EXT_texture_shadow_lod"), ); - private_caps.set(super::PrivateCapabilities::MEMORY_BARRIERS, ver >= (3, 1)); private_caps.set( - super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT, - ver >= (3, 1), + super::PrivateCapabilities::MEMORY_BARRIERS, + supported((3, 1), (4, 2)), ); private_caps.set( - super::PrivateCapabilities::INDEX_BUFFER_ROLE_CHANGE, - !cfg!(target_arch = "wasm32"), + super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT, + supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_vertex_attrib_binding"), ); private_caps.set( - super::PrivateCapabilities::CAN_DISABLE_DRAW_BUFFER, - !cfg!(target_arch = "wasm32"), + super::PrivateCapabilities::INDEX_BUFFER_ROLE_CHANGE, + !cfg!(any(webgl, Emscripten)), ); private_caps.set( super::PrivateCapabilities::GET_BUFFER_SUB_DATA, - cfg!(target_arch = "wasm32"), + cfg!(any(webgl, Emscripten)) || full_ver.is_some(), ); let color_buffer_float = extensions.contains("GL_EXT_color_buffer_float") + || extensions.contains("GL_ARB_color_buffer_float") || extensions.contains("EXT_color_buffer_float"); - let color_buffer_half_float = extensions.contains("GL_EXT_color_buffer_half_float"); + let color_buffer_half_float = extensions.contains("GL_EXT_color_buffer_half_float") + || extensions.contains("GL_ARB_half_float_pixel"); private_caps.set( super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT, color_buffer_half_float || color_buffer_float, @@ -470,17 +601,39 @@ impl super::Adapter { super::PrivateCapabilities::COLOR_BUFFER_FLOAT, color_buffer_float, ); + private_caps.set(super::PrivateCapabilities::QUERY_BUFFERS, query_buffers); + private_caps.set(super::PrivateCapabilities::QUERY_64BIT, full_ver.is_some()); + private_caps.set( + super::PrivateCapabilities::TEXTURE_STORAGE, + supported((3, 0), (4, 2)), + ); + private_caps.set(super::PrivateCapabilities::DEBUG_FNS, gl.supports_debug()); private_caps.set( - super::PrivateCapabilities::TEXTURE_FLOAT_LINEAR, - extensions.contains("OES_texture_float_linear"), + super::PrivateCapabilities::INVALIDATE_FRAMEBUFFER, + supported((3, 0), (4, 3)), ); + if let Some(full_ver) = full_ver { + let supported = + full_ver >= (4, 2) && extensions.contains("GL_ARB_shader_draw_parameters"); + private_caps.set( + super::PrivateCapabilities::FULLY_FEATURED_INSTANCING, + supported, + ); + // Desktop 4.2 and greater specify the first instance parameter. + // + // For all other versions, the behavior is undefined. + // + // We only support indirect first instance when we also have ARB_shader_draw_parameters as + // that's the only way to get gl_InstanceID to work correctly. + features.set(wgt::Features::INDIRECT_FIRST_INSTANCE, supported); + } let max_texture_size = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as u32; let max_texture_3d_size = unsafe { gl.get_parameter_i32(glow::MAX_3D_TEXTURE_SIZE) } as u32; let min_uniform_buffer_offset_alignment = (unsafe { gl.get_parameter_i32(glow::UNIFORM_BUFFER_OFFSET_ALIGNMENT) } as u32); - let min_storage_buffer_offset_alignment = if ver >= (3, 1) { + let min_storage_buffer_offset_alignment = if supports_storage { (unsafe { gl.get_parameter_i32(glow::SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT) } as u32) } else { 256 @@ -518,7 +671,7 @@ impl super::Adapter { max_uniform_buffer_binding_size: unsafe { gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE) } as u32, - max_storage_buffer_binding_size: if ver >= (3, 1) { + max_storage_buffer_binding_size: if supports_storage { unsafe { gl.get_parameter_i32(glow::MAX_SHADER_STORAGE_BLOCK_SIZE) } } else { 0 @@ -529,14 +682,37 @@ impl super::Adapter { (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_BINDINGS) } as u32) } else { 16 // should this be different? - }, + } + .min(crate::MAX_VERTEX_BUFFERS as u32), max_vertex_attributes: (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIBS) } as u32) .min(super::MAX_VERTEX_ATTRIBUTES as u32), max_vertex_buffer_array_stride: if private_caps .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT) { - (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) } as u32) + if let Some(full_ver) = full_ver { + if full_ver >= (4, 4) { + // We can query `GL_MAX_VERTEX_ATTRIB_STRIDE` in OpenGL 4.4+ + let value = + (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) }) + as u32; + + if value == 0 { + // This should be at least 2048, but the driver for AMD Radeon HD 5870 on + // Windows doesn't recognize `GL_MAX_VERTEX_ATTRIB_STRIDE`. + + log::warn!("Max vertex attribute stride is 0. Assuming it is 2048"); + 2048 + } else { + value + } + } else { + log::warn!("Max vertex attribute stride unknown. Assuming it is 2048"); + 2048 + } + } else { + (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) }) as u32 + } } else { !0 }, @@ -583,7 +759,7 @@ impl super::Adapter { workarounds.set( super::Workarounds::EMULATE_BUFFER_MAP, - cfg!(target_arch = "wasm32"), + cfg!(any(webgl, Emscripten)), ); let r = renderer.to_lowercase(); @@ -607,7 +783,7 @@ impl super::Adapter { // Drop the GL guard so we can move the context into AdapterShared // ( on Wasm the gl handle is just a ref so we tell clippy to allow // dropping the ref ) - #[cfg_attr(target_arch = "wasm32", allow(clippy::drop_ref))] + #[cfg_attr(target_arch = "wasm32", allow(dropping_references))] drop(gl); Some(crate::ExposedAdapter { @@ -618,9 +794,9 @@ impl super::Adapter { workarounds, features, shading_language_version, - max_texture_size, next_shader_id: Default::default(), program_cache: Default::default(), + es: es_ver.is_some(), }), }, info: Self::make_info(vendor, renderer), @@ -640,27 +816,73 @@ impl super::Adapter { }) } + unsafe fn compile_shader( + source: &str, + gl: &glow::Context, + shader_type: u32, + es: bool, + ) -> Option { + let source = if es { + format!("#version 300 es\nprecision lowp float;\n{source}") + } else { + format!("#version 130\n{source}") + }; + let shader = unsafe { gl.create_shader(shader_type) }.expect("Could not create shader"); + unsafe { gl.shader_source(shader, &source) }; + unsafe { gl.compile_shader(shader) }; + + if !unsafe { gl.get_shader_compile_status(shader) } { + let msg = unsafe { gl.get_shader_info_log(shader) }; + if !msg.is_empty() { + log::error!("\tShader compile error: {}", msg); + } + unsafe { gl.delete_shader(shader) }; + None + } else { + Some(shader) + } + } + unsafe fn create_shader_clear_program( gl: &glow::Context, - ) -> (glow::Program, glow::UniformLocation) { + es: bool, + ) -> Option<(glow::Program, glow::UniformLocation)> { let program = unsafe { gl.create_program() }.expect("Could not create shader program"); - let vertex = - unsafe { gl.create_shader(glow::VERTEX_SHADER) }.expect("Could not create shader"); - unsafe { gl.shader_source(vertex, include_str!("./shaders/clear.vert")) }; - unsafe { gl.compile_shader(vertex) }; - let fragment = - unsafe { gl.create_shader(glow::FRAGMENT_SHADER) }.expect("Could not create shader"); - unsafe { gl.shader_source(fragment, include_str!("./shaders/clear.frag")) }; - unsafe { gl.compile_shader(fragment) }; + let vertex = unsafe { + Self::compile_shader( + include_str!("./shaders/clear.vert"), + gl, + glow::VERTEX_SHADER, + es, + )? + }; + let fragment = unsafe { + Self::compile_shader( + include_str!("./shaders/clear.frag"), + gl, + glow::FRAGMENT_SHADER, + es, + )? + }; unsafe { gl.attach_shader(program, vertex) }; unsafe { gl.attach_shader(program, fragment) }; unsafe { gl.link_program(program) }; + + let linked_ok = unsafe { gl.get_program_link_status(program) }; + let msg = unsafe { gl.get_program_info_log(program) }; + if !msg.is_empty() { + log::warn!("Shader link error: {}", msg); + } + if !linked_ok { + return None; + } + let color_uniform_location = unsafe { gl.get_uniform_location(program, "color") } .expect("Could not find color uniform in shader clear shader"); unsafe { gl.delete_shader(vertex) }; unsafe { gl.delete_shader(fragment) }; - (program, color_uniform_location) + Some((program, color_uniform_location)) } } @@ -685,14 +907,17 @@ impl crate::Adapter for super::Adapter { // Compile the shader program we use for doing manual clears to work around Mesa fastclear // bug. - let (shader_clear_program, shader_clear_program_color_uniform_location) = - unsafe { Self::create_shader_clear_program(gl) }; + + let (shader_clear_program, shader_clear_program_color_uniform_location) = unsafe { + Self::create_shader_clear_program(gl, self.shared.es) + .ok_or(crate::DeviceError::ResourceCreationFailed)? + }; Ok(crate::OpenDevice { device: super::Device { shared: Arc::clone(&self.shared), main_vao, - #[cfg(all(not(target_arch = "wasm32"), feature = "renderdoc"))] + #[cfg(all(native, feature = "renderdoc"))] render_doc: Default::default(), }, queue: super::Queue { @@ -705,9 +930,9 @@ impl crate::Adapter for super::Adapter { shader_clear_program, shader_clear_program_color_uniform_location, zero_buffer, - temp_query_results: Vec::new(), - draw_buffer_count: 1, - current_index_buffer: None, + temp_query_results: Mutex::new(Vec::new()), + draw_buffer_count: AtomicU8::new(1), + current_index_buffer: Mutex::new(None), }, }) } @@ -793,8 +1018,7 @@ impl crate::Adapter for super::Adapter { | Tfc::MULTISAMPLE_RESOLVE, ); - let texture_float_linear = - private_caps_fn(super::PrivateCapabilities::TEXTURE_FLOAT_LINEAR, filterable); + let texture_float_linear = feature_fn(wgt::Features::FLOAT32_FILTERABLE, filterable); match format { Tf::R8Unorm => filterable_renderable, @@ -818,11 +1042,13 @@ impl crate::Adapter for super::Adapter { Tf::Rg16Unorm => empty, Tf::Rg16Snorm => empty, Tf::Rg16Float => filterable | half_float_renderable, - Tf::Rgba8Unorm | Tf::Rgba8UnormSrgb => filterable_renderable | storage, + Tf::Rgba8Unorm => filterable_renderable | storage, + Tf::Rgba8UnormSrgb => filterable_renderable, Tf::Bgra8Unorm | Tf::Bgra8UnormSrgb => filterable_renderable, - Tf::Rgba8Snorm => filterable, + Tf::Rgba8Snorm => filterable | storage, Tf::Rgba8Uint => renderable | storage, Tf::Rgba8Sint => renderable | storage, + Tf::Rgb10a2Uint => renderable, Tf::Rgb10a2Unorm => filterable_renderable, Tf::Rg11b10Float => filterable | float_renderable, Tf::Rg32Uint => renderable, @@ -842,6 +1068,7 @@ impl crate::Adapter for super::Adapter { | Tf::Depth32FloatStencil8 | Tf::Depth24Plus | Tf::Depth24PlusStencil8 => depth, + Tf::NV12 => empty, Tf::Rgb9e5Ufloat => filterable, Tf::Bc1RgbaUnorm | Tf::Bc1RgbaUnormSrgb @@ -885,13 +1112,13 @@ impl crate::Adapter for super::Adapter { if surface.presentable { let mut formats = vec![ wgt::TextureFormat::Rgba8Unorm, - #[cfg(not(target_arch = "wasm32"))] + #[cfg(native)] wgt::TextureFormat::Bgra8Unorm, ]; if surface.supports_srgb() { formats.extend([ wgt::TextureFormat::Rgba8UnormSrgb, - #[cfg(not(target_arch = "wasm32"))] + #[cfg(native)] wgt::TextureFormat::Bgra8UnormSrgb, ]) } @@ -905,19 +1132,14 @@ impl crate::Adapter for super::Adapter { Some(crate::SurfaceCapabilities { formats, - present_modes: vec![wgt::PresentMode::Fifo], //TODO + present_modes: if cfg!(windows) { + vec![wgt::PresentMode::Fifo, wgt::PresentMode::Immediate] + } else { + vec![wgt::PresentMode::Fifo] //TODO + }, composite_alpha_modes: vec![wgt::CompositeAlphaMode::Opaque], //TODO swap_chain_sizes: 2..=2, current_extent: None, - extents: wgt::Extent3d { - width: 4, - height: 4, - depth_or_array_layers: 1, - }..=wgt::Extent3d { - width: self.shared.max_texture_size, - height: self.shared.max_texture_size, - depth_or_array_layers: 1, - }, usage: crate::TextureUses::COLOR_TARGET, }) } else { @@ -956,17 +1178,9 @@ impl super::AdapterShared { } } -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for super::Adapter {} -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for super::Adapter {} #[cfg(test)] diff --git a/wgpu-hal/src/gles/command.rs b/wgpu-hal/src/gles/command.rs index 7204596a65..08083894c3 100644 --- a/wgpu-hal/src/gles/command.rs +++ b/wgpu-hal/src/gles/command.rs @@ -8,7 +8,6 @@ struct TextureSlotDesc { sampler_index: Option, } -#[derive(Default)] pub(super) struct State { topology: u32, primitive: super::PrimitiveState, @@ -30,7 +29,41 @@ pub(super) struct State { instance_vbuf_mask: usize, dirty_vbuf_mask: usize, active_first_instance: u32, - push_offset_to_uniform: ArrayVec, + first_instance_location: Option, + push_constant_descs: ArrayVec, + // The current state of the push constant data block. + current_push_constant_data: [u32; super::MAX_PUSH_CONSTANTS], + end_of_pass_timestamp: Option, +} + +impl Default for State { + fn default() -> Self { + Self { + topology: Default::default(), + primitive: Default::default(), + index_format: Default::default(), + index_offset: Default::default(), + vertex_buffers: Default::default(), + vertex_attributes: Default::default(), + color_targets: Default::default(), + stencil: Default::default(), + depth_bias: Default::default(), + alpha_to_coverage_enabled: Default::default(), + samplers: Default::default(), + texture_slots: Default::default(), + render_size: Default::default(), + resolve_attachments: Default::default(), + invalidate_attachments: Default::default(), + has_pass_label: Default::default(), + instance_vbuf_mask: Default::default(), + dirty_vbuf_mask: Default::default(), + active_first_instance: Default::default(), + first_instance_location: Default::default(), + push_constant_descs: Default::default(), + current_push_constant_data: [0; super::MAX_PUSH_CONSTANTS], + end_of_pass_timestamp: Default::default(), + } + } } impl super::CommandBuffer { @@ -162,23 +195,33 @@ impl super::CommandEncoder { } fn prepare_draw(&mut self, first_instance: u32) { - if first_instance != self.state.active_first_instance { + // If we support fully featured instancing, we want to bind everything as normal + // and let the draw call sort it out. + let emulated_first_instance_value = if self + .private_caps + .contains(super::PrivateCapabilities::FULLY_FEATURED_INSTANCING) + { + 0 + } else { + first_instance + }; + + if emulated_first_instance_value != self.state.active_first_instance { // rebind all per-instance buffers on first-instance change self.state.dirty_vbuf_mask |= self.state.instance_vbuf_mask; - self.state.active_first_instance = first_instance; + self.state.active_first_instance = emulated_first_instance_value; } if self.state.dirty_vbuf_mask != 0 { - self.rebind_vertex_data(first_instance); + self.rebind_vertex_data(emulated_first_instance_value); } } + #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation fn set_pipeline_inner(&mut self, inner: &super::PipelineInner) { self.cmd_buffer.commands.push(C::SetProgram(inner.program)); - self.state.push_offset_to_uniform.clear(); - self.state - .push_offset_to_uniform - .extend(inner.uniforms.iter().cloned()); + self.state.first_instance_location = inner.first_instance_location.clone(); + self.state.push_constant_descs = inner.push_constant_descs.clone(); // rebind textures, if needed let mut dirty_textures = 0u32; @@ -306,7 +349,7 @@ impl crate::CommandEncoder for super::CommandEncoder { } } - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] unsafe fn copy_external_image_to_texture( &mut self, src: &wgt::ImageCopyExternalImage, @@ -350,7 +393,6 @@ impl crate::CommandEncoder for super::CommandEncoder { dst: dst_raw, dst_target, copy, - dst_is_cubemap: dst.is_cubemap, }) } } @@ -410,8 +452,9 @@ impl crate::CommandEncoder for super::CommandEncoder { unsafe fn end_query(&mut self, set: &super::QuerySet, _index: u32) { self.cmd_buffer.commands.push(C::EndQuery(set.target)); } - unsafe fn write_timestamp(&mut self, _set: &super::QuerySet, _index: u32) { - unimplemented!() + unsafe fn write_timestamp(&mut self, set: &super::QuerySet, index: u32) { + let query = set.queries[index as usize]; + self.cmd_buffer.commands.push(C::TimestampQuery(query)); } unsafe fn reset_queries(&mut self, _set: &super::QuerySet, _range: Range) { //TODO: what do we do here? @@ -440,6 +483,16 @@ impl crate::CommandEncoder for super::CommandEncoder { // render unsafe fn begin_render_pass(&mut self, desc: &crate::RenderPassDescriptor) { + debug_assert!(self.state.end_of_pass_timestamp.is_none()); + if let Some(ref t) = desc.timestamp_writes { + if let Some(index) = t.beginning_of_pass_write_index { + unsafe { self.write_timestamp(t.query_set, index) } + } + self.state.end_of_pass_timestamp = t + .end_of_pass_write_index + .map(|index| t.query_set.queries[index as usize]); + } + self.state.render_size = desc.extent; self.state.resolve_attachments.clear(); self.state.invalidate_attachments.clear(); @@ -454,7 +507,7 @@ impl crate::CommandEncoder for super::CommandEncoder { .iter() .filter_map(|at| at.as_ref()) .any(|at| match at.target.view.inner { - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] super::TextureInner::ExternalFramebuffer { .. } => true, _ => false, }); @@ -463,6 +516,9 @@ impl crate::CommandEncoder for super::CommandEncoder { panic!("Multiple render attachments with external framebuffers are not supported."); } + // `COLOR_ATTACHMENT0` to `COLOR_ATTACHMENT31` gives 32 possible color attachments. + assert!(desc.color_attachments.len() <= 32); + match desc .color_attachments .first() @@ -524,13 +580,6 @@ impl crate::CommandEncoder for super::CommandEncoder { .push(glow::STENCIL_ATTACHMENT); } } - - if !rendering_to_external_framebuffer { - // set the draw buffers and states - self.cmd_buffer - .commands - .push(C::SetDrawColorBuffers(desc.color_attachments.len() as u8)); - } } } @@ -556,7 +605,7 @@ impl crate::CommandEncoder for super::CommandEncoder { if !cat.ops.contains(crate::AttachmentOps::LOAD) { let c = &cat.clear_value; self.cmd_buffer.commands.push( - match cat.target.view.format.sample_type(None).unwrap() { + match cat.target.view.format.sample_type(None, None).unwrap() { wgt::TextureSampleType::Float { .. } => C::ClearColorF { draw_buffer: i as u32, color: [c.r as f32, c.g as f32, c.b as f32, c.a as f32], @@ -575,6 +624,14 @@ impl crate::CommandEncoder for super::CommandEncoder { ); } } + + if !rendering_to_external_framebuffer { + // set the draw buffers and states + self.cmd_buffer + .commands + .push(C::SetDrawColorBuffers(desc.color_attachments.len() as u8)); + } + if let Some(ref dsat) = desc.depth_stencil_attachment { let clear_depth = !dsat.depth_ops.contains(crate::AttachmentOps::LOAD); let clear_stencil = !dsat.stencil_ops.contains(crate::AttachmentOps::LOAD); @@ -624,6 +681,10 @@ impl crate::CommandEncoder for super::CommandEncoder { } self.state.vertex_attributes.clear(); self.state.primitive = super::PrimitiveState::default(); + + if let Some(query) = self.state.end_of_pass_timestamp.take() { + self.cmd_buffer.commands.push(C::TimestampQuery(query)); + } } unsafe fn set_bind_group( @@ -682,6 +743,7 @@ impl crate::CommandEncoder for super::CommandEncoder { raw, target, aspects, + ref mip_levels, } => { dirty_textures |= 1 << slot; self.state.texture_slots[slot as usize].tex_target = target; @@ -690,6 +752,7 @@ impl crate::CommandEncoder for super::CommandEncoder { texture: raw, target, aspects, + mip_levels: mip_levels.clone(), }); } super::RawBinding::Image(ref binding) => { @@ -708,24 +771,46 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, _layout: &super::PipelineLayout, _stages: wgt::ShaderStages, - start_offset: u32, + offset_bytes: u32, data: &[u32], ) { - let range = self.cmd_buffer.add_push_constant_data(data); - - let end = start_offset + data.len() as u32 * 4; - let mut offset = start_offset; - while offset < end { - let uniform = self.state.push_offset_to_uniform[offset as usize / 4].clone(); - let size = uniform.size; - if uniform.location.is_none() { - panic!("No uniform for push constant"); + // There is nothing preventing the user from trying to update a single value within + // a vector or matrix in the set_push_constant call, as to the user, all of this is + // just memory. However OpenGL does not allow parital uniform updates. + // + // As such, we locally keep a copy of the current state of the push constant memory + // block. If the user tries to update a single value, we have the data to update the entirety + // of the uniform. + let start_words = offset_bytes / 4; + let end_words = start_words + data.len() as u32; + self.state.current_push_constant_data[start_words as usize..end_words as usize] + .copy_from_slice(data); + + // We iterate over the uniform list as there may be multiple uniforms that need + // updating from the same push constant memory (one for each shader stage). + // + // Additionally, any statically unused uniform descs will have been removed from this list + // by OpenGL, so the uniform list is not contiguous. + for uniform in self.state.push_constant_descs.iter().cloned() { + let uniform_size_words = uniform.size_bytes / 4; + let uniform_start_words = uniform.offset / 4; + let uniform_end_words = uniform_start_words + uniform_size_words; + + // Is true if any word within the uniform binding was updated + let needs_updating = + start_words < uniform_end_words || uniform_start_words <= end_words; + + if needs_updating { + let uniform_data = &self.state.current_push_constant_data + [uniform_start_words as usize..uniform_end_words as usize]; + + let range = self.cmd_buffer.add_push_constant_data(uniform_data); + + self.cmd_buffer.commands.push(C::SetPushConstants { + uniform, + offset: range.start, + }); } - self.cmd_buffer.commands.push(C::SetPushConstants { - uniform, - offset: range.start + offset, - }); - offset += size; } } @@ -932,40 +1017,46 @@ impl crate::CommandEncoder for super::CommandEncoder { unsafe fn draw( &mut self, - start_vertex: u32, + first_vertex: u32, vertex_count: u32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { - self.prepare_draw(start_instance); + self.prepare_draw(first_instance); + #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation self.cmd_buffer.commands.push(C::Draw { topology: self.state.topology, - start_vertex, + first_vertex, vertex_count, + first_instance, instance_count, + first_instance_location: self.state.first_instance_location.clone(), }); } unsafe fn draw_indexed( &mut self, - start_index: u32, + first_index: u32, index_count: u32, base_vertex: i32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { - self.prepare_draw(start_instance); + self.prepare_draw(first_instance); let (index_size, index_type) = match self.state.index_format { wgt::IndexFormat::Uint16 => (2, glow::UNSIGNED_SHORT), wgt::IndexFormat::Uint32 => (4, glow::UNSIGNED_INT), }; - let index_offset = self.state.index_offset + index_size * start_index as wgt::BufferAddress; + let index_offset = self.state.index_offset + index_size * first_index as wgt::BufferAddress; + #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation self.cmd_buffer.commands.push(C::DrawIndexed { topology: self.state.topology, index_type, index_offset, index_count, base_vertex, + first_instance, instance_count, + first_instance_location: self.state.first_instance_location.clone(), }); } unsafe fn draw_indirect( @@ -978,10 +1069,12 @@ impl crate::CommandEncoder for super::CommandEncoder { for draw in 0..draw_count as wgt::BufferAddress { let indirect_offset = offset + draw * mem::size_of::() as wgt::BufferAddress; + #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation self.cmd_buffer.commands.push(C::DrawIndirect { topology: self.state.topology, indirect_buf: buffer.raw.unwrap(), indirect_offset, + first_instance_location: self.state.first_instance_location.clone(), }); } } @@ -999,11 +1092,13 @@ impl crate::CommandEncoder for super::CommandEncoder { for draw in 0..draw_count as wgt::BufferAddress { let indirect_offset = offset + draw * mem::size_of::() as wgt::BufferAddress; + #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation self.cmd_buffer.commands.push(C::DrawIndexedIndirect { topology: self.state.topology, index_type, indirect_buf: buffer.raw.unwrap(), indirect_offset, + first_instance_location: self.state.first_instance_location.clone(), }); } } @@ -1031,6 +1126,16 @@ impl crate::CommandEncoder for super::CommandEncoder { // compute unsafe fn begin_compute_pass(&mut self, desc: &crate::ComputePassDescriptor) { + debug_assert!(self.state.end_of_pass_timestamp.is_none()); + if let Some(ref t) = desc.timestamp_writes { + if let Some(index) = t.beginning_of_pass_write_index { + unsafe { self.write_timestamp(t.query_set, index) } + } + self.state.end_of_pass_timestamp = t + .end_of_pass_write_index + .map(|index| t.query_set.queries[index as usize]); + } + if let Some(label) = desc.label { let range = self.cmd_buffer.add_marker(label); self.cmd_buffer.commands.push(C::PushDebugGroup(range)); @@ -1042,6 +1147,10 @@ impl crate::CommandEncoder for super::CommandEncoder { self.cmd_buffer.commands.push(C::PopDebugGroup); self.state.has_pass_label = false; } + + if let Some(query) = self.state.end_of_pass_timestamp.take() { + self.cmd_buffer.commands.push(C::TimestampQuery(query)); + } } unsafe fn set_compute_pipeline(&mut self, pipeline: &super::ComputePipeline) { diff --git a/wgpu-hal/src/gles/conv.rs b/wgpu-hal/src/gles/conv.rs index dd5d764c6a..7b3bf6c8f8 100644 --- a/wgpu-hal/src/gles/conv.rs +++ b/wgpu-hal/src/gles/conv.rs @@ -35,6 +35,11 @@ impl super::AdapterShared { Tf::Bgra8Unorm => (glow::RGBA8, glow::BGRA, glow::UNSIGNED_BYTE), //TODO? Tf::Rgba8Uint => (glow::RGBA8UI, glow::RGBA_INTEGER, glow::UNSIGNED_BYTE), Tf::Rgba8Sint => (glow::RGBA8I, glow::RGBA_INTEGER, glow::BYTE), + Tf::Rgb10a2Uint => ( + glow::RGB10_A2UI, + glow::RGBA_INTEGER, + glow::UNSIGNED_INT_2_10_10_10_REV, + ), Tf::Rgb10a2Unorm => ( glow::RGB10_A2, glow::RGBA, @@ -82,6 +87,7 @@ impl super::AdapterShared { glow::DEPTH_STENCIL, glow::UNSIGNED_INT_24_8, ), + Tf::NV12 => unreachable!(), Tf::Rgb9e5Ufloat => (glow::RGB9_E5, glow::RGB, glow::UNSIGNED_INT_5_9_9_9_REV), Tf::Bc1RgbaUnorm => (glow::COMPRESSED_RGBA_S3TC_DXT1_EXT, glow::RGBA, 0), Tf::Bc1RgbaUnormSrgb => (glow::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, glow::RGBA, 0), @@ -279,18 +285,6 @@ pub fn map_primitive_topology(topology: wgt::PrimitiveTopology) -> u32 { } pub(super) fn map_primitive_state(state: &wgt::PrimitiveState) -> super::PrimitiveState { - match state.polygon_mode { - wgt::PolygonMode::Fill => {} - wgt::PolygonMode::Line => panic!( - "{:?} is not enabled for this backend", - wgt::Features::POLYGON_MODE_LINE - ), - wgt::PolygonMode::Point => panic!( - "{:?} is not enabled for this backend", - wgt::Features::POLYGON_MODE_POINT - ), - } - super::PrimitiveState { //Note: we are flipping the front face, so that // the Y-flip in the generated GLSL keeps the same visibility. @@ -305,6 +299,11 @@ pub(super) fn map_primitive_state(state: &wgt::PrimitiveState) -> super::Primiti None => 0, }, unclipped_depth: state.unclipped_depth, + polygon_mode: match state.polygon_mode { + wgt::PolygonMode::Fill => glow::FILL, + wgt::PolygonMode::Line => glow::LINE, + wgt::PolygonMode::Point => glow::POINT, + }, } } @@ -376,6 +375,10 @@ fn map_blend_factor(factor: wgt::BlendFactor) -> u32 { Bf::Constant => glow::CONSTANT_COLOR, Bf::OneMinusConstant => glow::ONE_MINUS_CONSTANT_COLOR, Bf::SrcAlphaSaturated => glow::SRC_ALPHA_SATURATE, + Bf::Src1 => glow::SRC1_COLOR, + Bf::OneMinusSrc1 => glow::ONE_MINUS_SRC1_COLOR, + Bf::Src1Alpha => glow::SRC1_ALPHA, + Bf::OneMinusSrc1Alpha => glow::ONE_MINUS_SRC1_ALPHA, } } @@ -408,104 +411,10 @@ pub(super) fn map_storage_access(access: wgt::StorageTextureAccess) -> u32 { } } -pub(super) fn is_sampler(glsl_uniform_type: u32) -> bool { - match glsl_uniform_type { - glow::INT_SAMPLER_1D - | glow::INT_SAMPLER_1D_ARRAY - | glow::INT_SAMPLER_2D - | glow::INT_SAMPLER_2D_ARRAY - | glow::INT_SAMPLER_2D_MULTISAMPLE - | glow::INT_SAMPLER_2D_MULTISAMPLE_ARRAY - | glow::INT_SAMPLER_2D_RECT - | glow::INT_SAMPLER_3D - | glow::INT_SAMPLER_CUBE - | glow::INT_SAMPLER_CUBE_MAP_ARRAY - | glow::UNSIGNED_INT_SAMPLER_1D - | glow::UNSIGNED_INT_SAMPLER_1D_ARRAY - | glow::UNSIGNED_INT_SAMPLER_2D - | glow::UNSIGNED_INT_SAMPLER_2D_ARRAY - | glow::UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE - | glow::UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY - | glow::UNSIGNED_INT_SAMPLER_2D_RECT - | glow::UNSIGNED_INT_SAMPLER_3D - | glow::UNSIGNED_INT_SAMPLER_CUBE - | glow::UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY - | glow::SAMPLER_1D - | glow::SAMPLER_1D_SHADOW - | glow::SAMPLER_1D_ARRAY - | glow::SAMPLER_1D_ARRAY_SHADOW - | glow::SAMPLER_2D - | glow::SAMPLER_2D_SHADOW - | glow::SAMPLER_2D_ARRAY - | glow::SAMPLER_2D_ARRAY_SHADOW - | glow::SAMPLER_2D_MULTISAMPLE - | glow::SAMPLER_2D_MULTISAMPLE_ARRAY - | glow::SAMPLER_2D_RECT - | glow::SAMPLER_2D_RECT_SHADOW - | glow::SAMPLER_3D - | glow::SAMPLER_CUBE - | glow::SAMPLER_CUBE_MAP_ARRAY - | glow::SAMPLER_CUBE_MAP_ARRAY_SHADOW - | glow::SAMPLER_CUBE_SHADOW => true, - _ => false, - } -} - -pub(super) fn is_image(glsl_uniform_type: u32) -> bool { - match glsl_uniform_type { - glow::INT_IMAGE_1D - | glow::INT_IMAGE_1D_ARRAY - | glow::INT_IMAGE_2D - | glow::INT_IMAGE_2D_ARRAY - | glow::INT_IMAGE_2D_MULTISAMPLE - | glow::INT_IMAGE_2D_MULTISAMPLE_ARRAY - | glow::INT_IMAGE_2D_RECT - | glow::INT_IMAGE_3D - | glow::INT_IMAGE_CUBE - | glow::INT_IMAGE_CUBE_MAP_ARRAY - | glow::UNSIGNED_INT_IMAGE_1D - | glow::UNSIGNED_INT_IMAGE_1D_ARRAY - | glow::UNSIGNED_INT_IMAGE_2D - | glow::UNSIGNED_INT_IMAGE_2D_ARRAY - | glow::UNSIGNED_INT_IMAGE_2D_MULTISAMPLE - | glow::UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY - | glow::UNSIGNED_INT_IMAGE_2D_RECT - | glow::UNSIGNED_INT_IMAGE_3D - | glow::UNSIGNED_INT_IMAGE_CUBE - | glow::UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY - | glow::IMAGE_1D - | glow::IMAGE_1D_ARRAY - | glow::IMAGE_2D - | glow::IMAGE_2D_ARRAY - | glow::IMAGE_2D_MULTISAMPLE - | glow::IMAGE_2D_MULTISAMPLE_ARRAY - | glow::IMAGE_2D_RECT - | glow::IMAGE_3D - | glow::IMAGE_CUBE - | glow::IMAGE_CUBE_MAP_ARRAY => true, - _ => false, - } -} - -pub(super) fn is_atomic_counter(glsl_uniform_type: u32) -> bool { - glsl_uniform_type == glow::UNSIGNED_INT_ATOMIC_COUNTER -} - -pub(super) fn is_opaque_type(glsl_uniform_type: u32) -> bool { - is_sampler(glsl_uniform_type) - || is_image(glsl_uniform_type) - || is_atomic_counter(glsl_uniform_type) -} - -pub(super) fn uniform_byte_size(glsl_uniform_type: u32) -> u32 { - match glsl_uniform_type { - glow::FLOAT | glow::INT => 4, - glow::FLOAT_VEC2 | glow::INT_VEC2 => 8, - glow::FLOAT_VEC3 | glow::INT_VEC3 => 12, - glow::FLOAT_VEC4 | glow::INT_VEC4 => 16, - glow::FLOAT_MAT2 => 16, - glow::FLOAT_MAT3 => 36, - glow::FLOAT_MAT4 => 64, - _ => panic!("Unsupported uniform datatype! {glsl_uniform_type:#X}"), +pub(super) fn is_layered_target(target: u32) -> bool { + match target { + glow::TEXTURE_2D | glow::TEXTURE_CUBE_MAP => false, + glow::TEXTURE_2D_ARRAY | glow::TEXTURE_CUBE_MAP_ARRAY | glow::TEXTURE_3D => true, + _ => unreachable!(), } } diff --git a/wgpu-hal/src/gles/device.rs b/wgpu-hal/src/gles/device.rs index 26e47c9708..d0abe2c169 100644 --- a/wgpu-hal/src/gles/device.rs +++ b/wgpu-hal/src/gles/device.rs @@ -1,14 +1,15 @@ -use super::conv; +use super::{conv, PrivateCapabilities}; use crate::auxil::map_naga_stage; use glow::HasContext; use std::{ + cmp::max, convert::TryInto, ptr, sync::{Arc, Mutex}, }; use arrayvec::ArrayVec; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(native)] use std::mem; use std::sync::atomic::Ordering; @@ -22,15 +23,19 @@ struct CompilationContext<'a> { layout: &'a super::PipelineLayout, sampler_map: &'a mut super::SamplerBindMap, name_binding_map: &'a mut NameBindingMap, + push_constant_items: &'a mut Vec, multiview: Option, } impl CompilationContext<'_> { fn consume_reflection( self, + gl: &glow::Context, module: &naga::Module, ep_info: &naga::valid::FunctionInfo, reflection_info: naga::back::glsl::ReflectionInfo, + naga_stage: naga::ShaderStage, + program: glow::Program, ) { for (handle, var) in module.global_variables.iter() { if ep_info[handle].is_empty() { @@ -49,7 +54,7 @@ impl CompilationContext<'_> { Some(name) => name.clone(), None => continue, }; - log::debug!( + log::trace!( "Rebind buffer: {:?} -> {}, register={:?}, slot={}", var.name.as_ref(), &name, @@ -83,6 +88,22 @@ impl CompilationContext<'_> { self.sampler_map[texture_linear_index as usize] = Some(sampler_linear_index); } } + + for (name, location) in reflection_info.varying { + match naga_stage { + naga::ShaderStage::Vertex => { + assert_eq!(location.index, 0); + unsafe { gl.bind_attrib_location(program, location.location, &name) } + } + naga::ShaderStage::Fragment => { + assert_eq!(location.index, 0); + unsafe { gl.bind_frag_data_location(program, location.location, &name) } + } + naga::ShaderStage::Compute => {} + } + } + + *self.push_constant_items = reflection_info.push_constant_items; } } @@ -94,19 +115,17 @@ impl super::Device { /// - If `drop_guard` is [`None`], wgpu-hal will take ownership of the texture. If `drop_guard` is /// [`Some`], the texture must be valid until the drop implementation /// of the drop guard is called. - #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] + #[cfg(any(native, Emscripten))] pub unsafe fn texture_from_raw( &self, name: std::num::NonZeroU32, desc: &crate::TextureDescriptor, drop_guard: Option, ) -> super::Texture { - let (target, _, is_cubemap) = super::Texture::get_info_from_desc(desc); - super::Texture { inner: super::TextureInner::Texture { raw: glow::NativeTexture(name), - target, + target: super::Texture::get_info_from_desc(desc), }, drop_guard, mip_level_count: desc.mip_level_count, @@ -114,7 +133,6 @@ impl super::Device { format: desc.format, format_desc: self.shared.describe_texture_format(desc.format), copy_size: desc.copy_extent(), - is_cubemap, } } @@ -125,7 +143,7 @@ impl super::Device { /// - If `drop_guard` is [`None`], wgpu-hal will take ownership of the renderbuffer. If `drop_guard` is /// [`Some`], the renderbuffer must be valid until the drop implementation /// of the drop guard is called. - #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] + #[cfg(any(native, Emscripten))] pub unsafe fn texture_from_raw_renderbuffer( &self, name: std::num::NonZeroU32, @@ -142,7 +160,6 @@ impl super::Device { format: desc.format, format_desc: self.shared.describe_texture_format(desc.format), copy_size: desc.copy_extent(), - is_cubemap: false, } } @@ -159,7 +176,7 @@ impl super::Device { }; let raw = unsafe { gl.create_shader(target) }.unwrap(); - #[cfg(not(target_arch = "wasm32"))] + #[cfg(native)] if gl.supports_debug() { //TODO: remove all transmutes from `object_label` // https://github.com/grovesNL/glow/issues/186 @@ -170,7 +187,7 @@ impl super::Device { unsafe { gl.shader_source(raw, shader) }; unsafe { gl.compile_shader(raw) }; - log::info!("\tCompiled shader {:?}", raw); + log::debug!("\tCompiled shader {:?}", raw); let compiled_ok = unsafe { gl.get_shader_compile_status(raw) }; let msg = unsafe { gl.get_shader_info_log(raw) }; @@ -180,6 +197,8 @@ impl super::Device { } Ok(raw) } else { + log::error!("\tShader compilation failed: {}", msg); + unsafe { gl.delete_shader(raw) }; Err(crate::PipelineError::Linkage( map_naga_stage(naga_stage), msg, @@ -192,6 +211,7 @@ impl super::Device { naga_stage: naga::ShaderStage, stage: &crate::ProgrammableStage, context: CompilationContext, + program: glow::Program, ) -> Result { use naga::back::glsl; let pipeline_options = glsl::PipelineOptions { @@ -209,9 +229,9 @@ impl super::Device { .ok_or(crate::PipelineError::EntryPoint(naga_stage))?; use naga::proc::BoundsCheckPolicy; - // The image bounds checks require the TEXTURE_LEVELS feature available in GL core 1.3+. + // The image bounds checks require the TEXTURE_LEVELS feature available in GL core 4.3+. let version = gl.version(); - let image_check = if !version.is_embedded && (version.major, version.minor) >= (1, 3) { + let image_check = if !version.is_embedded && (version.major, version.minor) >= (4, 3) { BoundsCheckPolicy::ReadZeroSkipWrite } else { BoundsCheckPolicy::Unchecked @@ -248,9 +268,12 @@ impl super::Device { log::debug!("Naga generated shader:\n{}", output); context.consume_reflection( + gl, &shader.module, shader.info.get_entry_point(entry_point_index), reflection_info, + naga_stage, + program, ); unsafe { Self::compile_shader(gl, &output, naga_stage, stage.module.label.as_deref()) } @@ -259,7 +282,7 @@ impl super::Device { unsafe fn create_pipeline<'a>( &self, gl: &glow::Context, - shaders: ArrayVec, 3>, + shaders: ArrayVec, { crate::MAX_CONCURRENT_SHADER_STAGES }>, layout: &super::PipelineLayout, #[cfg_attr(target_arch = "wasm32", allow(unused))] label: Option<&str>, multiview: Option, @@ -276,10 +299,6 @@ impl super::Device { entry_point: stage.entry_point.to_owned(), }); } - let glsl_version = match self.shared.shading_language_version { - naga::back::glsl::Version::Embedded { version, .. } => version, - naga::back::glsl::Version::Desktop(_) => unreachable!(), - }; let mut guard = self .shared .program_cache @@ -299,7 +318,7 @@ impl super::Device { layout, label, multiview, - glsl_version, + self.shared.shading_language_version, self.shared.private_caps, ) }) @@ -311,43 +330,53 @@ impl super::Device { unsafe fn create_program<'a>( gl: &glow::Context, - shaders: ArrayVec, 3>, + shaders: ArrayVec, { crate::MAX_CONCURRENT_SHADER_STAGES }>, layout: &super::PipelineLayout, #[cfg_attr(target_arch = "wasm32", allow(unused))] label: Option<&str>, multiview: Option, - glsl_version: u16, - private_caps: super::PrivateCapabilities, + glsl_version: naga::back::glsl::Version, + private_caps: PrivateCapabilities, ) -> Result, crate::PipelineError> { + let glsl_version = match glsl_version { + naga::back::glsl::Version::Embedded { version, .. } => format!("{version} es"), + naga::back::glsl::Version::Desktop(version) => format!("{version}"), + }; let program = unsafe { gl.create_program() }.unwrap(); - #[cfg(not(target_arch = "wasm32"))] + #[cfg(native)] if let Some(label) = label { - if gl.supports_debug() { + if private_caps.contains(PrivateCapabilities::DEBUG_FNS) { let name = unsafe { mem::transmute(program) }; unsafe { gl.object_label(glow::PROGRAM, name, Some(label)) }; } } let mut name_binding_map = NameBindingMap::default(); + let mut push_constant_items = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); let mut sampler_map = [None; super::MAX_TEXTURE_SLOTS]; let mut has_stages = wgt::ShaderStages::empty(); - let mut shaders_to_delete = arrayvec::ArrayVec::<_, 3>::new(); + let mut shaders_to_delete = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); - for (naga_stage, stage) in shaders { + for &(naga_stage, stage) in &shaders { has_stages |= map_naga_stage(naga_stage); + let pc_item = { + push_constant_items.push(Vec::new()); + push_constant_items.last_mut().unwrap() + }; let context = CompilationContext { layout, sampler_map: &mut sampler_map, name_binding_map: &mut name_binding_map, + push_constant_items: pc_item, multiview, }; - let shader = Self::create_shader(gl, naga_stage, stage, context)?; + let shader = Self::create_shader(gl, naga_stage, stage, context, program)?; shaders_to_delete.push(shader); } // Create empty fragment shader if only vertex shader is present if has_stages == wgt::ShaderStages::VERTEX { - let shader_src = format!("#version {glsl_version} es \n void main(void) {{}}",); + let shader_src = format!("#version {glsl_version}\n void main(void) {{}}",); log::info!("Only vertex shader is present. Creating an empty fragment shader",); let shader = unsafe { Self::compile_shader( @@ -369,7 +398,7 @@ impl super::Device { unsafe { gl.delete_shader(shader) }; } - log::info!("\tLinked program {:?}", program); + log::debug!("\tLinked program {:?}", program); let linked_ok = unsafe { gl.get_program_link_status(program) }; let msg = unsafe { gl.get_program_info_log(program) }; @@ -389,6 +418,7 @@ impl super::Device { match register { super::BindingRegister::UniformBuffers => { let index = unsafe { gl.get_uniform_block_index(program, name) }.unwrap(); + log::trace!("\tBinding slot {slot} to block index {index}"); unsafe { gl.uniform_block_binding(program, index, slot as _) }; } super::BindingRegister::StorageBuffers => { @@ -409,41 +439,46 @@ impl super::Device { } } - let mut uniforms: [super::UniformDesc; super::MAX_PUSH_CONSTANTS] = - [None; super::MAX_PUSH_CONSTANTS].map(|_: Option<()>| Default::default()); - let count = unsafe { gl.get_active_uniforms(program) }; - let mut offset = 0; - - for uniform in 0..count { - let glow::ActiveUniform { utype, name, .. } = - unsafe { gl.get_active_uniform(program, uniform) }.unwrap(); - - if conv::is_opaque_type(utype) { - continue; - } - - if let Some(location) = unsafe { gl.get_uniform_location(program, &name) } { - if uniforms[offset / 4].location.is_some() { - panic!("Offset already occupied") + let mut uniforms = ArrayVec::new(); + + for (stage_idx, stage_items) in push_constant_items.into_iter().enumerate() { + for item in stage_items { + let naga_module = &shaders[stage_idx].1.module.naga.module; + let type_inner = &naga_module.types[item.ty].inner; + + let location = unsafe { gl.get_uniform_location(program, &item.access_path) }; + + log::trace!( + "push constant item: name={}, ty={:?}, offset={}, location={:?}", + item.access_path, + type_inner, + item.offset, + location, + ); + + if let Some(location) = location { + uniforms.push(super::PushConstantDesc { + location, + offset: item.offset, + size_bytes: type_inner.size(naga_module.to_ctx()), + ty: type_inner.clone(), + }); } - - // `size` will always be 1 so we need to guess the real size from the type - let uniform_size = conv::uniform_byte_size(utype); - - uniforms[offset / 4] = super::UniformDesc { - location: Some(location), - size: uniform_size, - utype, - }; - - offset += uniform_size as usize; } } + let first_instance_location = if has_stages.contains(wgt::ShaderStages::VERTEX) { + // If this returns none (the uniform isn't active), that's fine, we just won't set it. + unsafe { gl.get_uniform_location(program, naga::back::glsl::FIRST_INSTANCE_BINDING) } + } else { + None + }; + Ok(Arc::new(super::PipelineInner { program, sampler_map, - uniforms, + first_instance_location, + push_constant_descs: uniforms, })) } } @@ -556,9 +591,13 @@ impl crate::Device for super::Device { } //TODO: do we need `glow::MAP_UNSYNCHRONIZED_BIT`? - #[cfg(not(target_arch = "wasm32"))] + #[cfg(native)] if let Some(label) = desc.label { - if gl.supports_debug() { + if self + .shared + .private_caps + .contains(PrivateCapabilities::DEBUG_FNS) + { let name = unsafe { mem::transmute(raw) }; unsafe { gl.object_label(glow::BUFFER, name, Some(label)) }; } @@ -668,7 +707,7 @@ impl crate::Device for super::Device { | crate::TextureUses::DEPTH_STENCIL_READ; let format_desc = self.shared.describe_texture_format(desc.format); - let (inner, is_cubemap) = if render_usage.contains(desc.usage) + let inner = if render_usage.contains(desc.usage) && desc.dimension == wgt::TextureDimension::D2 && desc.size.depth_or_array_layers == 1 { @@ -695,23 +734,27 @@ impl crate::Device for super::Device { }; } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(native)] if let Some(label) = desc.label { - if gl.supports_debug() { + if self + .shared + .private_caps + .contains(PrivateCapabilities::DEBUG_FNS) + { let name = unsafe { mem::transmute(raw) }; unsafe { gl.object_label(glow::RENDERBUFFER, name, Some(label)) }; } } unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; - (super::TextureInner::Renderbuffer { raw }, false) + super::TextureInner::Renderbuffer { raw } } else { let raw = unsafe { gl.create_texture().unwrap() }; - let (target, is_3d, is_cubemap) = super::Texture::get_info_from_desc(desc); + let target = super::Texture::get_info_from_desc(desc); unsafe { gl.bind_texture(target, Some(raw)) }; //Note: this has to be done before defining the storage! - match desc.format.sample_type(None) { + match desc.format.sample_type(None, Some(self.shared.features)) { Some( wgt::TextureSampleType::Float { filterable: false } | wgt::TextureSampleType::Uint @@ -728,16 +771,62 @@ impl crate::Device for super::Device { _ => {} } - if is_3d { + if conv::is_layered_target(target) { unsafe { - gl.tex_storage_3d( - target, - desc.mip_level_count as i32, - format_desc.internal, - desc.size.width as i32, - desc.size.height as i32, - desc.size.depth_or_array_layers as i32, - ) + if self + .shared + .private_caps + .contains(PrivateCapabilities::TEXTURE_STORAGE) + { + gl.tex_storage_3d( + target, + desc.mip_level_count as i32, + format_desc.internal, + desc.size.width as i32, + desc.size.height as i32, + desc.size.depth_or_array_layers as i32, + ) + } else if target == glow::TEXTURE_3D { + let mut width = desc.size.width; + let mut height = desc.size.width; + let mut depth = desc.size.depth_or_array_layers; + for i in 0..desc.mip_level_count { + gl.tex_image_3d( + target, + i as i32, + format_desc.internal as i32, + width as i32, + height as i32, + depth as i32, + 0, + format_desc.external, + format_desc.data_type, + None, + ); + width = max(1, width / 2); + height = max(1, height / 2); + depth = max(1, depth / 2); + } + } else { + let mut width = desc.size.width; + let mut height = desc.size.width; + for i in 0..desc.mip_level_count { + gl.tex_image_3d( + target, + i as i32, + format_desc.internal as i32, + width as i32, + height as i32, + desc.size.depth_or_array_layers as i32, + 0, + format_desc.external, + format_desc.data_type, + None, + ); + width = max(1, width / 2); + height = max(1, height / 2); + } + } }; } else if desc.sample_count > 1 { unsafe { @@ -752,26 +841,81 @@ impl crate::Device for super::Device { }; } else { unsafe { - gl.tex_storage_2d( - target, - desc.mip_level_count as i32, - format_desc.internal, - desc.size.width as i32, - desc.size.height as i32, - ) + if self + .shared + .private_caps + .contains(PrivateCapabilities::TEXTURE_STORAGE) + { + gl.tex_storage_2d( + target, + desc.mip_level_count as i32, + format_desc.internal, + desc.size.width as i32, + desc.size.height as i32, + ) + } else if target == glow::TEXTURE_CUBE_MAP { + let mut width = desc.size.width; + let mut height = desc.size.width; + for i in 0..desc.mip_level_count { + for face in [ + glow::TEXTURE_CUBE_MAP_POSITIVE_X, + glow::TEXTURE_CUBE_MAP_NEGATIVE_X, + glow::TEXTURE_CUBE_MAP_POSITIVE_Y, + glow::TEXTURE_CUBE_MAP_NEGATIVE_Y, + glow::TEXTURE_CUBE_MAP_POSITIVE_Z, + glow::TEXTURE_CUBE_MAP_NEGATIVE_Z, + ] { + gl.tex_image_2d( + face, + i as i32, + format_desc.internal as i32, + width as i32, + height as i32, + 0, + format_desc.external, + format_desc.data_type, + None, + ); + } + width = max(1, width / 2); + height = max(1, height / 2); + } + } else { + let mut width = desc.size.width; + let mut height = desc.size.width; + for i in 0..desc.mip_level_count { + gl.tex_image_2d( + target, + i as i32, + format_desc.internal as i32, + width as i32, + height as i32, + 0, + format_desc.external, + format_desc.data_type, + None, + ); + width = max(1, width / 2); + height = max(1, height / 2); + } + } }; } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(native)] if let Some(label) = desc.label { - if gl.supports_debug() { + if self + .shared + .private_caps + .contains(PrivateCapabilities::DEBUG_FNS) + { let name = unsafe { mem::transmute(raw) }; unsafe { gl.object_label(glow::TEXTURE, name, Some(label)) }; } } unsafe { gl.bind_texture(target, None) }; - (super::TextureInner::Texture { raw, target }, is_cubemap) + super::TextureInner::Texture { raw, target } }; Ok(super::Texture { @@ -782,7 +926,6 @@ impl crate::Device for super::Device { format: desc.format, format_desc, copy_size: desc.copy_extent(), - is_cubemap, }) } unsafe fn destroy_texture(&self, texture: super::Texture) { @@ -796,7 +939,7 @@ impl crate::Device for super::Device { super::TextureInner::Texture { raw, .. } => { unsafe { gl.delete_texture(raw) }; } - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] super::TextureInner::ExternalFramebuffer { .. } => {} } } @@ -902,9 +1045,13 @@ impl crate::Device for super::Device { }; } - #[cfg(not(target_arch = "wasm32"))] + #[cfg(native)] if let Some(label) = desc.label { - if gl.supports_debug() { + if self + .shared + .private_caps + .contains(PrivateCapabilities::DEBUG_FNS) + { let name = unsafe { mem::transmute(raw) }; unsafe { gl.object_label(glow::SAMPLER, name, Some(label)) }; } @@ -959,6 +1106,12 @@ impl crate::Device for super::Device { .private_caps .contains(super::PrivateCapabilities::SHADER_TEXTURE_SHADOW_LOD), ); + writer_flags.set( + glsl::WriterFlags::DRAW_PARAMETERS, + self.shared + .private_caps + .contains(super::PrivateCapabilities::FULLY_FEATURED_INSTANCING), + ); // We always force point size to be written and it will be ignored by the driver if it's not a point list primitive. // https://github.com/gfx-rs/wgpu/pull/3440/files#r1095726950 writer_flags.set(glsl::WriterFlags::FORCE_POINT_SIZE, true); @@ -1043,8 +1196,8 @@ impl crate::Device for super::Device { } wgt::BindingType::Texture { .. } => { let view = desc.textures[entry.resource_index as usize].view; - if view.mip_levels.start != 0 || view.array_layers.start != 0 { - log::error!("Unable to create a sampled texture binding for non-zero mipmap level or array layer.\n{}", + if view.array_layers.start != 0 { + log::error!("Unable to create a sampled texture binding for non-zero array layer.\n{}", "This is an implementation problem of wgpu-hal/gles backend.") } let (raw, target) = view.inner.as_native(); @@ -1052,6 +1205,7 @@ impl crate::Device for super::Device { raw, target, aspects: view.aspects, + mip_levels: view.mip_levels.clone(), } } wgt::BindingType::StorageTexture { @@ -1221,23 +1375,18 @@ impl crate::Device for super::Device { desc: &wgt::QuerySetDescriptor, ) -> Result { let gl = &self.shared.context.lock(); - let mut temp_string = String::new(); let mut queries = Vec::with_capacity(desc.count as usize); - for i in 0..desc.count { + for _ in 0..desc.count { let query = unsafe { gl.create_query() }.map_err(|_| crate::DeviceError::OutOfMemory)?; - #[cfg(not(target_arch = "wasm32"))] - if gl.supports_debug() { - use std::fmt::Write; - - if let Some(label) = desc.label { - temp_string.clear(); - let _ = write!(temp_string, "{label}[{i}]"); - let name = unsafe { mem::transmute(query) }; - unsafe { gl.object_label(glow::QUERY, name, Some(&temp_string)) }; - } - } + + // We aren't really able to, in general, label queries. + // + // We could take a timestamp here to "initialize" the query, + // but that's a bit of a hack, and we don't want to insert + // random timestamps into the command stream of we don't have to. + queries.push(query); } @@ -1245,6 +1394,7 @@ impl crate::Device for super::Device { queries: queries.into_boxed_slice(), target: match desc.ty { wgt::QueryType::Occlusion => glow::ANY_SAMPLES_PASSED_CONSERVATIVE, + wgt::QueryType::Timestamp => glow::TIMESTAMP, _ => unimplemented!(), }, }) @@ -1282,36 +1432,36 @@ impl crate::Device for super::Device { ) -> Result { if fence.last_completed < wait_value { let gl = &self.shared.context.lock(); - let timeout_ns = if cfg!(target_arch = "wasm32") { + let timeout_ns = if cfg!(any(webgl, Emscripten)) { 0 } else { (timeout_ms as u64 * 1_000_000).min(!0u32 as u64) }; - let &(_, sync) = fence + if let Some(&(_, sync)) = fence .pending .iter() .find(|&&(value, _)| value >= wait_value) - .unwrap(); - match unsafe { - gl.client_wait_sync(sync, glow::SYNC_FLUSH_COMMANDS_BIT, timeout_ns as i32) - } { - // for some reason firefox returns WAIT_FAILED, to investigate - #[cfg(target_arch = "wasm32")] - glow::WAIT_FAILED => { - log::warn!("wait failed!"); - Ok(false) - } - glow::TIMEOUT_EXPIRED => Ok(false), - glow::CONDITION_SATISFIED | glow::ALREADY_SIGNALED => Ok(true), - _ => Err(crate::DeviceError::Lost), + { + return match unsafe { + gl.client_wait_sync(sync, glow::SYNC_FLUSH_COMMANDS_BIT, timeout_ns as i32) + } { + // for some reason firefox returns WAIT_FAILED, to investigate + #[cfg(any(webgl, Emscripten))] + glow::WAIT_FAILED => { + log::warn!("wait failed!"); + Ok(false) + } + glow::TIMEOUT_EXPIRED => Ok(false), + glow::CONDITION_SATISFIED | glow::ALREADY_SIGNALED => Ok(true), + _ => Err(crate::DeviceError::Lost), + }; } - } else { - Ok(true) } + Ok(true) } unsafe fn start_capture(&self) -> bool { - #[cfg(all(not(target_arch = "wasm32"), feature = "renderdoc"))] + #[cfg(all(native, feature = "renderdoc"))] return unsafe { self.render_doc .start_frame_capture(self.shared.context.raw_context(), ptr::null_mut()) @@ -1320,7 +1470,7 @@ impl crate::Device for super::Device { false } unsafe fn stop_capture(&self) { - #[cfg(all(not(target_arch = "wasm32"), feature = "renderdoc"))] + #[cfg(all(native, feature = "renderdoc"))] unsafe { self.render_doc .end_frame_capture(ptr::null_mut(), ptr::null_mut()) @@ -1347,15 +1497,7 @@ impl crate::Device for super::Device { unsafe fn destroy_acceleration_structure(&self, _acceleration_structure: ()) {} } -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for super::Device {} -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for super::Device {} diff --git a/wgpu-hal/src/gles/egl.rs b/wgpu-hal/src/gles/egl.rs index d6d3d621f9..ef0ff4b6f3 100644 --- a/wgpu-hal/src/gles/egl.rs +++ b/wgpu-hal/src/gles/egl.rs @@ -1,7 +1,7 @@ use glow::HasContext; -use parking_lot::{Mutex, MutexGuard}; +use parking_lot::{Mutex, MutexGuard, RwLock}; -use std::{ffi, os::raw, ptr, sync::Arc, time::Duration}; +use std::{ffi, os::raw, ptr, rc::Rc, sync::Arc, time::Duration}; /// The amount of time to wait while trying to obtain a lock to the adapter context const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 1; @@ -28,10 +28,10 @@ type WlDisplayConnectFun = type WlDisplayDisconnectFun = unsafe extern "system" fn(display: *const raw::c_void); -#[cfg(not(target_os = "emscripten"))] +#[cfg(not(Emscripten))] type EglInstance = khronos_egl::DynamicInstance; -#[cfg(target_os = "emscripten")] +#[cfg(Emscripten)] type EglInstance = khronos_egl::Instance; type WlEglWindowCreateFun = unsafe extern "system" fn( @@ -159,7 +159,7 @@ impl Drop for DisplayOwner { } fn open_x_display() -> Option { - log::info!("Loading X11 library to get the current display"); + log::debug!("Loading X11 library to get the current display"); unsafe { let library = libloading::Library::new("libX11.so").ok()?; let func: libloading::Symbol = library.get(b"XOpenDisplay").unwrap(); @@ -185,7 +185,7 @@ fn test_wayland_display() -> Option { /* We try to connect and disconnect here to simply ensure there * is an active wayland display available. */ - log::info!("Loading Wayland library to get the current display"); + log::debug!("Loading Wayland library to get the current display"); let library = unsafe { let client_library = find_library(&["libwayland-client.so.0", "libwayland-client.so"])?; let wl_display_connect: libloading::Symbol = @@ -243,7 +243,7 @@ fn choose_config( let mut attributes = Vec::with_capacity(9); for tier_max in (0..tiers.len()).rev() { let name = tiers[tier_max].0; - log::info!("\tTrying {}", name); + log::debug!("\tTrying {}", name); attributes.clear(); for &(_, tier_attr) in tiers[..=tier_max].iter() { @@ -289,55 +289,6 @@ fn choose_config( ))) } -fn gl_debug_message_callback(source: u32, gltype: u32, id: u32, severity: u32, message: &str) { - let source_str = match source { - glow::DEBUG_SOURCE_API => "API", - glow::DEBUG_SOURCE_WINDOW_SYSTEM => "Window System", - glow::DEBUG_SOURCE_SHADER_COMPILER => "ShaderCompiler", - glow::DEBUG_SOURCE_THIRD_PARTY => "Third Party", - glow::DEBUG_SOURCE_APPLICATION => "Application", - glow::DEBUG_SOURCE_OTHER => "Other", - _ => unreachable!(), - }; - - let log_severity = match severity { - glow::DEBUG_SEVERITY_HIGH => log::Level::Error, - glow::DEBUG_SEVERITY_MEDIUM => log::Level::Warn, - glow::DEBUG_SEVERITY_LOW => log::Level::Info, - glow::DEBUG_SEVERITY_NOTIFICATION => log::Level::Trace, - _ => unreachable!(), - }; - - let type_str = match gltype { - glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior", - glow::DEBUG_TYPE_ERROR => "Error", - glow::DEBUG_TYPE_MARKER => "Marker", - glow::DEBUG_TYPE_OTHER => "Other", - glow::DEBUG_TYPE_PERFORMANCE => "Performance", - glow::DEBUG_TYPE_POP_GROUP => "Pop Group", - glow::DEBUG_TYPE_PORTABILITY => "Portability", - glow::DEBUG_TYPE_PUSH_GROUP => "Push Group", - glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior", - _ => unreachable!(), - }; - - let _ = std::panic::catch_unwind(|| { - log::log!( - log_severity, - "GLES: [{}/{}] ID {} : {}", - source_str, - type_str, - id, - message - ); - }); - - if cfg!(debug_assertions) && log_severity == log::Level::Error { - // Set canary and continue - crate::VALIDATION_CANARY.set(); - } -} - #[derive(Clone, Debug)] struct EglContext { instance: Arc, @@ -483,9 +434,9 @@ struct Inner { version: (i32, i32), supports_native_window: bool, config: khronos_egl::Config, - #[cfg_attr(target_os = "emscripten", allow(dead_code))] + #[cfg_attr(Emscripten, allow(dead_code))] wl_display: Option<*mut raw::c_void>, - #[cfg_attr(target_os = "emscripten", allow(dead_code))] + #[cfg_attr(Emscripten, allow(dead_code))] force_gles_minor_version: wgt::Gles3MinorVersion, /// Method by which the framebuffer should support srgb srgb_kind: SrgbFrameBufferKind, @@ -493,7 +444,7 @@ struct Inner { impl Inner { fn create( - flags: crate::InstanceFlags, + flags: wgt::InstanceFlags, egl: Arc, display: khronos_egl::Display, force_gles_minor_version: wgt::Gles3MinorVersion, @@ -511,17 +462,17 @@ impl Inner { .query_string(Some(display), khronos_egl::EXTENSIONS) .unwrap() .to_string_lossy(); - log::info!("Display vendor {:?}, version {:?}", vendor, version,); + log::debug!("Display vendor {:?}, version {:?}", vendor, version,); log::debug!( "Display extensions: {:#?}", display_extensions.split_whitespace().collect::>() ); let srgb_kind = if version >= (1, 5) { - log::info!("\tEGL surface: +srgb"); + log::debug!("\tEGL surface: +srgb"); SrgbFrameBufferKind::Core } else if display_extensions.contains("EGL_KHR_gl_colorspace") { - log::info!("\tEGL surface: +srgb khr"); + log::debug!("\tEGL surface: +srgb khr"); SrgbFrameBufferKind::Khr } else { log::warn!("\tEGL surface: -srgb"); @@ -567,16 +518,16 @@ impl Inner { }); } - if flags.contains(crate::InstanceFlags::DEBUG) { + if flags.contains(wgt::InstanceFlags::DEBUG) { if version >= (1, 5) { - log::info!("\tEGL context: +debug"); + log::debug!("\tEGL context: +debug"); context_attributes.push(khronos_egl::CONTEXT_OPENGL_DEBUG); context_attributes.push(khronos_egl::TRUE as _); } else if supports_khr_context { - log::info!("\tEGL context: +debug KHR"); + log::debug!("\tEGL context: +debug KHR"); khr_context_flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; } else { - log::info!("\tEGL context: -debug"); + log::debug!("\tEGL context: -debug"); } } if needs_robustness { @@ -584,11 +535,11 @@ impl Inner { // (regardless of whether the extension is supported!). // In fact, Angle does precisely that awful behavior, so we don't try it there. if version >= (1, 5) && !display_extensions.contains("EGL_ANGLE_") { - log::info!("\tEGL context: +robust access"); + log::debug!("\tEGL context: +robust access"); context_attributes.push(khronos_egl::CONTEXT_OPENGL_ROBUST_ACCESS); context_attributes.push(khronos_egl::TRUE as _); } else if display_extensions.contains("EGL_EXT_create_context_robustness") { - log::info!("\tEGL context: +robust access EXT"); + log::debug!("\tEGL context: +robust access EXT"); context_attributes.push(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT); context_attributes.push(khronos_egl::TRUE as _); } else { @@ -618,9 +569,9 @@ impl Inner { // and creating dummy pbuffer surface if not. let pbuffer = if version >= (1, 5) || display_extensions.contains("EGL_KHR_surfaceless_context") - || cfg!(target_os = "emscripten") + || cfg!(Emscripten) { - log::info!("\tEGL context: +surfaceless"); + log::debug!("\tEGL context: +surfaceless"); None } else { let attributes = [ @@ -683,13 +634,13 @@ enum WindowKind { #[derive(Clone, Debug)] struct WindowSystemInterface { - display_owner: Option>, + display_owner: Option>, kind: WindowKind, } pub struct Instance { wsi: WindowSystemInterface, - flags: crate::InstanceFlags, + flags: wgt::InstanceFlags, inner: Mutex, } @@ -723,11 +674,12 @@ unsafe impl Sync for Instance {} impl crate::Instance for Instance { unsafe fn init(desc: &crate::InstanceDescriptor) -> Result { - #[cfg(target_os = "emscripten")] + profiling::scope!("Init OpenGL (EGL) Backend"); + #[cfg(Emscripten)] let egl_result: Result = Ok(khronos_egl::Instance::new(khronos_egl::Static)); - #[cfg(not(target_os = "emscripten"))] + #[cfg(not(Emscripten))] let egl_result = if cfg!(windows) { unsafe { khronos_egl::DynamicInstance::::load_required_from_filename( @@ -780,73 +732,78 @@ impl crate::Instance for Instance { None }; - #[cfg(not(target_os = "emscripten"))] + #[cfg(not(Emscripten))] let egl1_5 = egl.upcast::(); - #[cfg(target_os = "emscripten")] + #[cfg(Emscripten)] let egl1_5: Option<&Arc> = Some(&egl); let (display, display_owner, wsi_kind) = if let (Some(library), Some(egl)) = (wayland_library, egl1_5) { log::info!("Using Wayland platform"); let display_attributes = [khronos_egl::ATTRIB_NONE]; - let display = egl - .get_platform_display( + let display = unsafe { + egl.get_platform_display( EGL_PLATFORM_WAYLAND_KHR, khronos_egl::DEFAULT_DISPLAY, &display_attributes, ) - .unwrap(); - (display, Some(Arc::new(library)), WindowKind::Wayland) + } + .unwrap(); + (display, Some(Rc::new(library)), WindowKind::Wayland) } else if let (Some(display_owner), Some(egl)) = (x11_display_library, egl1_5) { log::info!("Using X11 platform"); let display_attributes = [khronos_egl::ATTRIB_NONE]; - let display = egl - .get_platform_display( + let display = unsafe { + egl.get_platform_display( EGL_PLATFORM_X11_KHR, display_owner.display.as_ptr(), &display_attributes, ) - .unwrap(); - (display, Some(Arc::new(display_owner)), WindowKind::X11) + } + .unwrap(); + (display, Some(Rc::new(display_owner)), WindowKind::X11) } else if let (Some(display_owner), Some(egl)) = (angle_x11_display_library, egl1_5) { log::info!("Using Angle platform with X11"); let display_attributes = [ EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE as khronos_egl::Attrib, EGL_PLATFORM_X11_KHR as khronos_egl::Attrib, EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED as khronos_egl::Attrib, - usize::from(desc.flags.contains(crate::InstanceFlags::VALIDATION)), + usize::from(desc.flags.contains(wgt::InstanceFlags::VALIDATION)), khronos_egl::ATTRIB_NONE, ]; - let display = egl - .get_platform_display( + let display = unsafe { + egl.get_platform_display( EGL_PLATFORM_ANGLE_ANGLE, display_owner.display.as_ptr(), &display_attributes, ) - .unwrap(); - (display, Some(Arc::new(display_owner)), WindowKind::AngleX11) + } + .unwrap(); + (display, Some(Rc::new(display_owner)), WindowKind::AngleX11) } else if client_ext_str.contains("EGL_MESA_platform_surfaceless") { - log::info!("No windowing system present. Using surfaceless platform"); + log::warn!("No windowing system present. Using surfaceless platform"); let egl = egl1_5.expect("Failed to get EGL 1.5 for surfaceless"); - let display = egl - .get_platform_display( + let display = unsafe { + egl.get_platform_display( EGL_PLATFORM_SURFACELESS_MESA, std::ptr::null_mut(), &[khronos_egl::ATTRIB_NONE], ) - .unwrap(); + } + .unwrap(); + (display, None, WindowKind::Unknown) } else { - log::info!("EGL_MESA_platform_surfaceless not available. Using default platform"); - let display = egl.get_display(khronos_egl::DEFAULT_DISPLAY).unwrap(); + log::warn!("EGL_MESA_platform_surfaceless not available. Using default platform"); + let display = unsafe { egl.get_display(khronos_egl::DEFAULT_DISPLAY) }.unwrap(); (display, None, WindowKind::Unknown) }; - if desc.flags.contains(crate::InstanceFlags::VALIDATION) + if desc.flags.contains(wgt::InstanceFlags::VALIDATION) && client_ext_str.contains("EGL_KHR_debug") { - log::info!("Enabling EGL debug output"); + log::debug!("Enabling EGL debug output"); let function: EglDebugMessageControlFun = { let addr = egl.get_proc_address("eglDebugMessageControlKHR").unwrap(); unsafe { std::mem::transmute(addr) } @@ -885,10 +842,7 @@ impl crate::Instance for Instance { ) -> Result { use raw_window_handle::RawWindowHandle as Rwh; - #[cfg_attr( - any(target_os = "android", target_os = "emscripten"), - allow(unused_mut) - )] + #[cfg_attr(any(target_os = "android", Emscripten), allow(unused_mut))] let mut inner = self.inner.lock(); match (window_handle, display_handle) { @@ -909,7 +863,7 @@ impl crate::Instance for Instance { .unwrap(); let ret = unsafe { - ANativeWindow_setBuffersGeometry(handle.a_native_window, 0, 0, format) + ANativeWindow_setBuffersGeometry(handle.a_native_window.as_ptr(), 0, 0, format) }; if ret != 0 { @@ -918,11 +872,11 @@ impl crate::Instance for Instance { ))); } } - #[cfg(not(target_os = "emscripten"))] + #[cfg(not(Emscripten))] (Rwh::Wayland(_), raw_window_handle::RawDisplayHandle::Wayland(display_handle)) => { if inner .wl_display - .map(|ptr| ptr != display_handle.display) + .map(|ptr| ptr != display_handle.display.as_ptr()) .unwrap_or(true) { /* Wayland displays are not sharable between surfaces so if the @@ -936,17 +890,19 @@ impl crate::Instance for Instance { use std::ops::DerefMut; let display_attributes = [khronos_egl::ATTRIB_NONE]; - let display = inner - .egl - .instance - .upcast::() - .unwrap() - .get_platform_display( - EGL_PLATFORM_WAYLAND_KHR, - display_handle.display, - &display_attributes, - ) - .unwrap(); + let display = unsafe { + inner + .egl + .instance + .upcast::() + .unwrap() + .get_platform_display( + EGL_PLATFORM_WAYLAND_KHR, + display_handle.display.as_ptr(), + &display_attributes, + ) + } + .unwrap(); let new_inner = Inner::create( self.flags, @@ -956,12 +912,12 @@ impl crate::Instance for Instance { )?; let old_inner = std::mem::replace(inner.deref_mut(), new_inner); - inner.wl_display = Some(display_handle.display); + inner.wl_display = Some(display_handle.display.as_ptr()); drop(old_inner); } } - #[cfg(target_os = "emscripten")] + #[cfg(Emscripten)] (Rwh::Web(_), _) => {} other => { return Err(crate::InstanceError::new(format!( @@ -978,7 +934,7 @@ impl crate::Instance for Instance { config: inner.config, presentable: inner.supports_native_window, raw_window_handle: window_handle, - swapchain: None, + swapchain: RwLock::new(None), srgb_kind: inner.srgb_kind, }) } @@ -988,7 +944,7 @@ impl crate::Instance for Instance { let inner = self.inner.lock(); inner.egl.make_current(); - let gl = unsafe { + let mut gl = unsafe { glow::Context::from_loader_function(|name| { inner .egl @@ -998,16 +954,16 @@ impl crate::Instance for Instance { }) }; - if self.flags.contains(crate::InstanceFlags::DEBUG) && gl.supports_debug() { - log::info!("Max label length: {}", unsafe { + if self.flags.contains(wgt::InstanceFlags::DEBUG) && gl.supports_debug() { + log::debug!("Max label length: {}", unsafe { gl.get_parameter_i32(glow::MAX_LABEL_LENGTH) }); } - if self.flags.contains(crate::InstanceFlags::VALIDATION) && gl.supports_debug() { - log::info!("Enabling GLES debug output"); + if self.flags.contains(wgt::InstanceFlags::VALIDATION) && gl.supports_debug() { + log::debug!("Enabling GLES debug output"); unsafe { gl.enable(glow::DEBUG_OUTPUT) }; - unsafe { gl.debug_message_callback(gl_debug_message_callback) }; + unsafe { gl.debug_message_callback(super::gl_debug_message_callback) }; } inner.egl.unmake_current(); @@ -1076,7 +1032,7 @@ pub struct Surface { config: khronos_egl::Config, pub(super) presentable: bool, raw_window_handle: raw_window_handle::RawWindowHandle, - swapchain: Option, + swapchain: RwLock>, srgb_kind: SrgbFrameBufferKind, } @@ -1085,11 +1041,13 @@ unsafe impl Sync for Surface {} impl Surface { pub(super) unsafe fn present( - &mut self, + &self, _suf_texture: super::Texture, - gl: &glow::Context, + context: &AdapterContext, ) -> Result<(), crate::SurfaceError> { - let sc = self.swapchain.as_ref().unwrap(); + let gl = unsafe { context.get_without_egl_lock() }; + let swapchain = self.swapchain.read(); + let sc = swapchain.as_ref().unwrap(); self.egl .instance @@ -1147,11 +1105,11 @@ impl Surface { } unsafe fn unconfigure_impl( - &mut self, + &self, device: &super::Device, ) -> Option<(khronos_egl::Surface, Option<*mut raw::c_void>)> { let gl = &device.shared.context.lock(); - match self.swapchain.take() { + match self.swapchain.write().take() { Some(sc) => { unsafe { gl.delete_renderbuffer(sc.renderbuffer) }; unsafe { gl.delete_framebuffer(sc.framebuffer) }; @@ -1171,7 +1129,7 @@ impl Surface { impl crate::Surface for Surface { unsafe fn configure( - &mut self, + &self, device: &super::Device, config: &crate::SurfaceConfiguration, ) -> Result<(), crate::SurfaceError> { @@ -1196,30 +1154,35 @@ impl crate::Surface for Surface { &mut temp_xcb_handle as *mut _ as *mut std::ffi::c_void } (WindowKind::AngleX11, Rwh::Xcb(handle)) => { - handle.window as *mut std::ffi::c_void + handle.window.get() as *mut std::ffi::c_void + } + (WindowKind::Unknown, Rwh::AndroidNdk(handle)) => { + handle.a_native_window.as_ptr() } - (WindowKind::Unknown, Rwh::AndroidNdk(handle)) => handle.a_native_window, (WindowKind::Wayland, Rwh::Wayland(handle)) => { let library = &self.wsi.display_owner.as_ref().unwrap().library; let wl_egl_window_create: libloading::Symbol = unsafe { library.get(b"wl_egl_window_create") }.unwrap(); - let window = unsafe { wl_egl_window_create(handle.surface, 640, 480) } - as *mut _ as *mut std::ffi::c_void; + let window = + unsafe { wl_egl_window_create(handle.surface.as_ptr(), 640, 480) } + as *mut _; wl_window = Some(window); window } - #[cfg(target_os = "emscripten")] + #[cfg(Emscripten)] (WindowKind::Unknown, Rwh::Web(handle)) => handle.id as *mut std::ffi::c_void, - (WindowKind::Unknown, Rwh::Win32(handle)) => handle.hwnd, + (WindowKind::Unknown, Rwh::Win32(handle)) => { + handle.hwnd.get() as *mut std::ffi::c_void + } (WindowKind::Unknown, Rwh::AppKit(handle)) => { #[cfg(not(target_os = "macos"))] - let window_ptr = handle.ns_view; + let window_ptr = handle.ns_view.as_ptr(); #[cfg(target_os = "macos")] let window_ptr = { use objc::{msg_send, runtime::Object, sel, sel_impl}; // ns_view always have a layer and don't need to verify that it exists. let layer: *mut Object = - msg_send![handle.ns_view as *mut Object, layer]; + msg_send![handle.ns_view.as_ptr() as *mut Object, layer]; layer as *mut ffi::c_void }; window_ptr @@ -1263,10 +1226,10 @@ impl crate::Surface for Surface { } attributes.push(khronos_egl::ATTRIB_NONE as i32); - #[cfg(not(target_os = "emscripten"))] + #[cfg(not(Emscripten))] let egl1_5 = self.egl.instance.upcast::(); - #[cfg(target_os = "emscripten")] + #[cfg(Emscripten)] let egl1_5: Option<&Arc> = Some(&self.egl.instance); // Careful, we can still be in 1.4 version even if `upcast` succeeds @@ -1276,12 +1239,14 @@ impl crate::Surface for Surface { .into_iter() .map(|v| v as usize) .collect::>(); - egl.create_platform_window_surface( - self.egl.display, - self.config, - native_window_ptr, - &attributes_usize, - ) + unsafe { + egl.create_platform_window_surface( + self.egl.display, + self.config, + native_window_ptr, + &attributes_usize, + ) + } } _ => unsafe { self.egl.instance.create_window_surface( @@ -1349,7 +1314,8 @@ impl crate::Surface for Surface { unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; - self.swapchain = Some(Swapchain { + let mut swapchain = self.swapchain.write(); + *swapchain = Some(Swapchain { surface, wl_window, renderbuffer, @@ -1363,7 +1329,7 @@ impl crate::Surface for Surface { Ok(()) } - unsafe fn unconfigure(&mut self, device: &super::Device) { + unsafe fn unconfigure(&self, device: &super::Device) { if let Some((surface, wl_window)) = unsafe { self.unconfigure_impl(device) } { self.egl .instance @@ -1384,10 +1350,11 @@ impl crate::Surface for Surface { } unsafe fn acquire_texture( - &mut self, + &self, _timeout_ms: Option, //TODO ) -> Result>, crate::SurfaceError> { - let sc = self.swapchain.as_ref().unwrap(); + let swapchain = self.swapchain.read(); + let sc = swapchain.as_ref().unwrap(); let texture = super::Texture { inner: super::TextureInner::Renderbuffer { raw: sc.renderbuffer, @@ -1402,12 +1369,11 @@ impl crate::Surface for Surface { height: sc.extent.height, depth: 1, }, - is_cubemap: false, }; Ok(Some(crate::AcquiredSurfaceTexture { texture, suboptimal: false, })) } - unsafe fn discard_texture(&mut self, _texture: super::Texture) {} + unsafe fn discard_texture(&self, _texture: super::Texture) {} } diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index df82a05e63..701b13fb57 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -34,7 +34,7 @@ will be minimal, if any. Generally, vertex buffers are marked as dirty and lazily bound on draw. -GLES3 doesn't support "base instance" semantics. However, it's easy to support, +GLES3 doesn't support `first_instance` semantics. However, it's easy to support, since we are forced to do late binding anyway. We just adjust the offsets into the vertex data. @@ -51,18 +51,45 @@ from the vertex data itself. This mostly matches WebGPU, however there is a catc `stride` needs to be specified with the data, not as a part of the layout. To address this, we invalidate the vertex buffers based on: - - whether or not `start_instance` is used + - whether or not `first_instance` is used - stride has changed +## Handling of `base_vertex`, `first_instance`, and `first_vertex` + +Between indirect, the lack of `first_instance` semantics, and the availability of `gl_BaseInstance` +in shaders, getting buffers and builtins to work correctly is a bit tricky. + +We never emulate `base_vertex` and gl_VertexID behaves as `@builtin(vertex_index)` does, so we +never need to do anything about that. + +We always advertise support for `VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW`. + +### GL 4.2+ with ARB_shader_draw_parameters + +- `@builtin(instance_index)` translates to `gl_InstanceID + gl_BaseInstance` +- We bind instance buffers without any offset emulation. +- We advertise support for the `INDIRECT_FIRST_INSTANCE` feature. + +While we can theoretically have a card with 4.2+ support but without ARB_shader_draw_parameters, +we don't bother with that combination. + +### GLES & GL 4.1 + +- `@builtin(instance_index)` translates to `gl_InstanceID + naga_vs_first_instance` +- We bind instance buffers with offset emulation. +- We _do not_ advertise support for `INDIRECT_FIRST_INSTANCE` and cpu-side pretend the `first_instance` is 0 on indirect calls. + */ ///cbindgen:ignore -#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] +#[cfg(not(any(windows, webgl)))] mod egl; -#[cfg(target_os = "emscripten")] +#[cfg(Emscripten)] mod emscripten; -#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] +#[cfg(webgl)] mod web; +#[cfg(windows)] +mod wgl; mod adapter; mod command; @@ -72,26 +99,31 @@ mod queue; use crate::{CopyExtent, TextureDescriptor}; -#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] +#[cfg(not(any(windows, webgl)))] pub use self::egl::{AdapterContext, AdapterContextLock}; -#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] +#[cfg(not(any(windows, webgl)))] use self::egl::{Instance, Surface}; -#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] +#[cfg(webgl)] pub use self::web::AdapterContext; -#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] +#[cfg(webgl)] use self::web::{Instance, Surface}; +#[cfg(windows)] +use self::wgl::AdapterContext; +#[cfg(windows)] +use self::wgl::{Instance, Surface}; + use arrayvec::ArrayVec; use glow::HasContext; use naga::FastHashMap; use parking_lot::Mutex; -use std::sync::atomic::AtomicU32; +use std::sync::atomic::{AtomicU32, AtomicU8}; use std::{fmt, ops::Range, sync::Arc}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Api; //Note: we can support more samplers if not every one of them is used at a time, @@ -101,6 +133,8 @@ const MAX_SAMPLERS: usize = 16; const MAX_VERTEX_ATTRIBUTES: usize = 16; const ZERO_BUFFER_SIZE: usize = 256 << 10; const MAX_PUSH_CONSTANTS: usize = 64; +// We have to account for each push constant may need to be set for every shader. +const MAX_PUSH_CONSTANT_COMMANDS: usize = MAX_PUSH_CONSTANTS * crate::MAX_CONCURRENT_SHADER_STAGES; impl crate::Api for Api { type Instance = Instance; @@ -147,16 +181,26 @@ bitflags::bitflags! { /// Indicates that buffers used as `GL_ELEMENT_ARRAY_BUFFER` may be created / initialized / used /// as other targets, if not present they must not be mixed with other targets. const INDEX_BUFFER_ROLE_CHANGE = 1 << 5; - /// Indicates that the device supports disabling draw buffers - const CAN_DISABLE_DRAW_BUFFER = 1 << 6; /// Supports `glGetBufferSubData` const GET_BUFFER_SUB_DATA = 1 << 7; /// Supports `f16` color buffers const COLOR_BUFFER_HALF_FLOAT = 1 << 8; /// Supports `f11/f10` and `f32` color buffers const COLOR_BUFFER_FLOAT = 1 << 9; - /// Supports linear flitering `f32` textures. - const TEXTURE_FLOAT_LINEAR = 1 << 10; + /// Supports query buffer objects. + const QUERY_BUFFERS = 1 << 11; + /// Supports 64 bit queries via `glGetQueryObjectui64v` + const QUERY_64BIT = 1 << 12; + /// Supports `glTexStorage2D`, etc. + const TEXTURE_STORAGE = 1 << 13; + /// Supports `push_debug_group`, `pop_debug_group` and `debug_message_insert`. + const DEBUG_FNS = 1 << 14; + /// Supports framebuffer invalidation. + const INVALIDATE_FRAMEBUFFER = 1 << 15; + /// Indicates support for `glDrawElementsInstancedBaseVertexBaseInstance` and `ARB_shader_draw_parameters` + /// + /// When this is true, instance offset emulation via vertex buffer rebinding and a shader uniform will be disabled. + const FULLY_FEATURED_INSTANCING = 1 << 16; } } @@ -204,9 +248,9 @@ struct AdapterShared { features: wgt::Features, workarounds: Workarounds, shading_language_version: naga::back::glsl::Version, - max_texture_size: u32, next_shader_id: AtomicU32, program_cache: Mutex, + es: bool, } pub struct Adapter { @@ -216,7 +260,7 @@ pub struct Adapter { pub struct Device { shared: Arc, main_vao: glow::VertexArray, - #[cfg(all(not(target_arch = "wasm32"), feature = "renderdoc"))] + #[cfg(all(native, feature = "renderdoc"))] render_doc: crate::auxil::renderdoc::RenderDoc, } @@ -233,9 +277,9 @@ pub struct Queue { /// Keep a reasonably large buffer filled with zeroes, so that we can implement `ClearBuffer` of /// zeroes by copying from it. zero_buffer: glow::Buffer, - temp_query_results: Vec, - draw_buffer_count: u8, - current_index_buffer: Option, + temp_query_results: Mutex>, + draw_buffer_count: AtomicU8, + current_index_buffer: Mutex>, } #[derive(Clone, Debug)] @@ -247,17 +291,9 @@ pub struct Buffer { data: Option>>>, } -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for Buffer {} -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for Buffer {} #[derive(Clone, Debug)] @@ -270,23 +306,15 @@ pub enum TextureInner { raw: glow::Texture, target: BindTarget, }, - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] ExternalFramebuffer { inner: web_sys::WebGlFramebuffer, }, } -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for TextureInner {} -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for TextureInner {} impl TextureInner { @@ -296,7 +324,7 @@ impl TextureInner { panic!("Unexpected renderbuffer"); } Self::Texture { raw, target } => (raw, target), - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] Self::ExternalFramebuffer { .. } => panic!("Unexpected external framebuffer"), } } @@ -312,7 +340,6 @@ pub struct Texture { #[allow(unused)] pub format_desc: TextureFormatDesc, pub copy_size: CopyExtent, - pub is_cubemap: bool, } impl Texture { @@ -333,24 +360,23 @@ impl Texture { height: 0, depth: 0, }, - is_cubemap: false, } } /// Returns the `target`, whether the image is 3d and whether the image is a cubemap. - fn get_info_from_desc(desc: &TextureDescriptor) -> (u32, bool, bool) { + fn get_info_from_desc(desc: &TextureDescriptor) -> u32 { match desc.dimension { - wgt::TextureDimension::D1 => (glow::TEXTURE_2D, false, false), + wgt::TextureDimension::D1 => glow::TEXTURE_2D, wgt::TextureDimension::D2 => { // HACK: detect a cube map; forces cube compatible textures to be cube textures match (desc.is_cube_compatible(), desc.size.depth_or_array_layers) { - (false, 1) => (glow::TEXTURE_2D, false, false), - (false, _) => (glow::TEXTURE_2D_ARRAY, true, false), - (true, 6) => (glow::TEXTURE_CUBE_MAP, false, true), - (true, _) => (glow::TEXTURE_CUBE_MAP_ARRAY, true, true), + (false, 1) => glow::TEXTURE_2D, + (false, _) => glow::TEXTURE_2D_ARRAY, + (true, 6) => glow::TEXTURE_CUBE_MAP, + (true, _) => glow::TEXTURE_CUBE_MAP_ARRAY, } } - wgt::TextureDimension::D3 => (glow::TEXTURE_3D, true, false), + wgt::TextureDimension::D3 => glow::TEXTURE_3D, } } } @@ -369,10 +395,12 @@ pub struct Sampler { raw: glow::Sampler, } +#[derive(Debug)] pub struct BindGroupLayout { entries: Arc<[wgt::BindGroupLayoutEntry]>, } +#[derive(Debug)] struct BindGroupLayoutInfo { entries: Arc<[wgt::BindGroupLayoutEntry]>, /// Mapping of resources, indexed by `binding`, into the whole layout space. @@ -383,6 +411,7 @@ struct BindGroupLayoutInfo { binding_to_slot: Box<[u8]>, } +#[derive(Debug)] pub struct PipelineLayout { group_infos: Box<[BindGroupLayoutInfo]>, naga_options: naga::back::glsl::Options, @@ -414,7 +443,8 @@ enum RawBinding { raw: glow::Texture, target: BindTarget, aspects: crate::FormatAspects, - //TODO: mip levels, array layers + mip_levels: Range, + //TODO: array layers }, Image(ImageBinding), Sampler(glow::Sampler), @@ -470,34 +500,29 @@ struct VertexBufferDesc { stride: u32, } -#[derive(Clone, Debug, Default)] -struct UniformDesc { - location: Option, - size: u32, - utype: u32, +#[derive(Clone, Debug)] +struct PushConstantDesc { + location: glow::UniformLocation, + ty: naga::TypeInner, + offset: u32, + size_bytes: u32, } -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] -unsafe impl Sync for UniformDesc {} -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] -unsafe impl Send for UniformDesc {} +#[cfg(send_sync)] +unsafe impl Sync for PushConstantDesc {} +#[cfg(send_sync)] +unsafe impl Send for PushConstantDesc {} /// For each texture in the pipeline layout, store the index of the only /// sampler (in this layout) that the texture is used with. type SamplerBindMap = [Option; MAX_TEXTURE_SLOTS]; +#[derive(Debug)] struct PipelineInner { program: glow::Program, sampler_map: SamplerBindMap, - uniforms: [UniformDesc; MAX_PUSH_CONSTANTS], + first_instance_location: Option, + push_constant_descs: ArrayVec, } #[derive(Clone, Debug)] @@ -540,6 +565,7 @@ struct ProgramCacheKey { type ProgramCache = FastHashMap, crate::PipelineError>>; +#[derive(Debug)] pub struct RenderPipeline { inner: Arc, primitive: wgt::PrimitiveState, @@ -552,34 +578,19 @@ pub struct RenderPipeline { alpha_to_coverage_enabled: bool, } -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for RenderPipeline {} -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for RenderPipeline {} +#[derive(Debug)] pub struct ComputePipeline { inner: Arc, } -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for ComputePipeline {} -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for ComputePipeline {} #[derive(Debug)] @@ -675,7 +686,7 @@ impl Default for StencilSide { } } -#[derive(Clone, Default)] +#[derive(Debug, Clone, Default)] struct StencilState { front: StencilSide, back: StencilSide, @@ -686,6 +697,7 @@ struct PrimitiveState { front_face: u32, cull_face: u32, unclipped_depth: bool, + polygon_mode: u32, } type InvalidatedAttachments = ArrayVec; @@ -694,9 +706,11 @@ type InvalidatedAttachments = ArrayVec, }, DrawIndexed { topology: u32, @@ -704,18 +718,22 @@ enum Command { index_count: u32, index_offset: wgt::BufferAddress, base_vertex: i32, + first_instance: u32, instance_count: u32, + first_instance_location: Option, }, DrawIndirect { topology: u32, indirect_buf: glow::Buffer, indirect_offset: wgt::BufferAddress, + first_instance_location: Option, }, DrawIndexedIndirect { topology: u32, index_type: u32, indirect_buf: glow::Buffer, indirect_offset: wgt::BufferAddress, + first_instance_location: Option, }, Dispatch([u32; 3]), DispatchIndirect { @@ -734,7 +752,7 @@ enum Command { dst_target: BindTarget, copy: crate::BufferCopy, }, - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] CopyExternalImageToTexture { src: wgt::ImageCopyExternalImage, dst: glow::Texture, @@ -749,7 +767,6 @@ enum Command { dst: glow::Texture, dst_target: BindTarget, copy: crate::TextureCopy, - dst_is_cubemap: bool, }, CopyBufferToTexture { src: Buffer, @@ -772,6 +789,7 @@ enum Command { SetIndexBuffer(glow::Buffer), BeginQuery(glow::Query, BindTarget), EndQuery(BindTarget), + TimestampQuery(glow::Query), CopyQueryResults { query_range: Range, dst: Buffer, @@ -859,6 +877,7 @@ enum Command { texture: glow::Texture, target: BindTarget, aspects: crate::FormatAspects, + mip_levels: Range, }, BindImage { slot: u32, @@ -868,7 +887,7 @@ enum Command { PushDebugGroup(Range), PopDebugGroup, SetPushConstants { - uniform: UniformDesc, + uniform: PushConstantDesc, /// Offset from the start of the `data_bytes` offset: u32, }, @@ -892,6 +911,11 @@ impl fmt::Debug for CommandBuffer { } } +#[cfg(send_sync)] +unsafe impl Sync for CommandBuffer {} +#[cfg(send_sync)] +unsafe impl Send for CommandBuffer {} + //TODO: we would have something like `Arc` // here and in the command buffers. So that everything grows // inside the encoder and stays there until `reset_all`. @@ -909,3 +933,58 @@ impl fmt::Debug for CommandEncoder { .finish() } } + +#[cfg(send_sync)] +unsafe impl Sync for CommandEncoder {} +#[cfg(send_sync)] +unsafe impl Send for CommandEncoder {} + +#[cfg(not(webgl))] +fn gl_debug_message_callback(source: u32, gltype: u32, id: u32, severity: u32, message: &str) { + let source_str = match source { + glow::DEBUG_SOURCE_API => "API", + glow::DEBUG_SOURCE_WINDOW_SYSTEM => "Window System", + glow::DEBUG_SOURCE_SHADER_COMPILER => "ShaderCompiler", + glow::DEBUG_SOURCE_THIRD_PARTY => "Third Party", + glow::DEBUG_SOURCE_APPLICATION => "Application", + glow::DEBUG_SOURCE_OTHER => "Other", + _ => unreachable!(), + }; + + let log_severity = match severity { + glow::DEBUG_SEVERITY_HIGH => log::Level::Error, + glow::DEBUG_SEVERITY_MEDIUM => log::Level::Warn, + glow::DEBUG_SEVERITY_LOW => log::Level::Info, + glow::DEBUG_SEVERITY_NOTIFICATION => log::Level::Trace, + _ => unreachable!(), + }; + + let type_str = match gltype { + glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior", + glow::DEBUG_TYPE_ERROR => "Error", + glow::DEBUG_TYPE_MARKER => "Marker", + glow::DEBUG_TYPE_OTHER => "Other", + glow::DEBUG_TYPE_PERFORMANCE => "Performance", + glow::DEBUG_TYPE_POP_GROUP => "Pop Group", + glow::DEBUG_TYPE_PORTABILITY => "Portability", + glow::DEBUG_TYPE_PUSH_GROUP => "Push Group", + glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior", + _ => unreachable!(), + }; + + let _ = std::panic::catch_unwind(|| { + log::log!( + log_severity, + "GLES: [{}/{}] ID {} : {}", + source_str, + type_str, + id, + message + ); + }); + + if cfg!(debug_assertions) && log_severity == log::Level::Error { + // Set canary and continue + crate::VALIDATION_CANARY.add(message.to_string()); + } +} diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index 6eacfdd88a..5a4deb8e1a 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -1,29 +1,39 @@ -use super::Command as C; +use super::{conv::is_layered_target, Command as C, PrivateCapabilities}; use arrayvec::ArrayVec; use glow::HasContext; -use std::{mem, slice, sync::Arc}; +use std::{ + mem, slice, + sync::{atomic::Ordering, Arc}, +}; -#[cfg(not(target_arch = "wasm32"))] const DEBUG_ID: u32 = 0; -const CUBEMAP_FACES: [u32; 6] = [ - glow::TEXTURE_CUBE_MAP_POSITIVE_X, - glow::TEXTURE_CUBE_MAP_NEGATIVE_X, - glow::TEXTURE_CUBE_MAP_POSITIVE_Y, - glow::TEXTURE_CUBE_MAP_NEGATIVE_Y, - glow::TEXTURE_CUBE_MAP_POSITIVE_Z, - glow::TEXTURE_CUBE_MAP_NEGATIVE_Z, -]; - -#[cfg(not(target_arch = "wasm32"))] fn extract_marker<'a>(data: &'a [u8], range: &std::ops::Range) -> &'a str { std::str::from_utf8(&data[range.start as usize..range.end as usize]).unwrap() } -fn is_layered_target(target: super::BindTarget) -> bool { +fn get_2d_target(target: u32, array_layer: u32) -> u32 { + const CUBEMAP_FACES: [u32; 6] = [ + glow::TEXTURE_CUBE_MAP_POSITIVE_X, + glow::TEXTURE_CUBE_MAP_NEGATIVE_X, + glow::TEXTURE_CUBE_MAP_POSITIVE_Y, + glow::TEXTURE_CUBE_MAP_NEGATIVE_Y, + glow::TEXTURE_CUBE_MAP_POSITIVE_Z, + glow::TEXTURE_CUBE_MAP_NEGATIVE_Z, + ]; + match target { - glow::TEXTURE_2D_ARRAY | glow::TEXTURE_3D | glow::TEXTURE_CUBE_MAP_ARRAY => true, - _ => false, + glow::TEXTURE_2D => target, + glow::TEXTURE_CUBE_MAP => CUBEMAP_FACES[array_layer as usize], + _ => unreachable!(), + } +} + +fn get_z_offset(target: u32, base: &crate::TextureCopyBase) -> u32 { + match target { + glow::TEXTURE_2D_ARRAY | glow::TEXTURE_CUBE_MAP_ARRAY => base.array_layer, + glow::TEXTURE_3D => base.origin.z, + _ => unreachable!(), } } @@ -48,20 +58,17 @@ impl super::Queue { unsafe { gl.draw_buffers(&[glow::COLOR_ATTACHMENT0 + draw_buffer]) }; unsafe { gl.draw_arrays(glow::TRIANGLES, 0, 3) }; - if self.draw_buffer_count != 0 { + let draw_buffer_count = self.draw_buffer_count.load(Ordering::Relaxed); + if draw_buffer_count != 0 { // Reset the draw buffers to what they were before the clear - let indices = (0..self.draw_buffer_count as u32) + let indices = (0..draw_buffer_count as u32) .map(|i| glow::COLOR_ATTACHMENT0 + i) .collect::>(); unsafe { gl.draw_buffers(&indices) }; } - #[cfg(not(target_arch = "wasm32"))] - for draw_buffer in 0..self.draw_buffer_count as u32 { - unsafe { gl.disable_draw_buffer(glow::BLEND, draw_buffer) }; - } } - unsafe fn reset_state(&mut self, gl: &glow::Context) { + unsafe fn reset_state(&self, gl: &glow::Context) { unsafe { gl.use_program(None) }; unsafe { gl.bind_framebuffer(glow::FRAMEBUFFER, None) }; unsafe { gl.disable(glow::DEPTH_TEST) }; @@ -76,7 +83,8 @@ impl super::Queue { } unsafe { gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None) }; - self.current_index_buffer = None; + let mut current_index_buffer = self.current_index_buffer.lock(); + *current_index_buffer = None; } unsafe fn set_attachment( @@ -101,7 +109,7 @@ impl super::Queue { super::TextureInner::Texture { raw, target } => { let num_layers = view.array_layers.end - view.array_layers.start; if num_layers > 1 { - #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + #[cfg(webgl)] unsafe { gl.framebuffer_texture_multiview_ovr( fbo_target, @@ -122,29 +130,20 @@ impl super::Queue { view.array_layers.start as i32, ) }; - } else if target == glow::TEXTURE_CUBE_MAP { - unsafe { - gl.framebuffer_texture_2d( - fbo_target, - attachment, - CUBEMAP_FACES[view.array_layers.start as usize], - Some(raw), - view.mip_levels.start as i32, - ) - }; } else { unsafe { + assert_eq!(view.mip_levels.len(), 1); gl.framebuffer_texture_2d( fbo_target, attachment, - target, + get_2d_target(target, view.array_layers.start), Some(raw), view.mip_levels.start as i32, ) }; } } - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] super::TextureInner::ExternalFramebuffer { ref inner } => unsafe { gl.bind_external_framebuffer(glow::FRAMEBUFFER, inner); }, @@ -152,7 +151,7 @@ impl super::Queue { } unsafe fn process( - &mut self, + &self, gl: &glow::Context, command: &C, #[cfg_attr(target_arch = "wasm32", allow(unused))] data_bytes: &[u8], @@ -161,20 +160,43 @@ impl super::Queue { match *command { C::Draw { topology, - start_vertex, + first_vertex, vertex_count, instance_count, + first_instance, + ref first_instance_location, } => { - // Don't use `gl.draw_arrays` for `instance_count == 1`. - // Angle has a bug where it doesn't consider the instance divisor when `DYNAMIC_DRAW` is used in `draw_arrays`. - // See https://github.com/gfx-rs/wgpu/issues/3578 - unsafe { - gl.draw_arrays_instanced( - topology, - start_vertex as i32, - vertex_count as i32, - instance_count as i32, - ) + let supports_full_instancing = self + .shared + .private_caps + .contains(PrivateCapabilities::FULLY_FEATURED_INSTANCING); + + if supports_full_instancing { + unsafe { + gl.draw_arrays_instanced_base_instance( + topology, + first_vertex as i32, + vertex_count as i32, + instance_count as i32, + first_instance, + ) + } + } else { + unsafe { + gl.uniform_1_u32(first_instance_location.as_ref(), first_instance); + } + + // Don't use `gl.draw_arrays` for `instance_count == 1`. + // Angle has a bug where it doesn't consider the instance divisor when `DYNAMIC_DRAW` is used in `draw_arrays`. + // See https://github.com/gfx-rs/wgpu/issues/3578 + unsafe { + gl.draw_arrays_instanced( + topology, + first_vertex as i32, + vertex_count as i32, + instance_count as i32, + ) + } }; } C::DrawIndexed { @@ -183,38 +205,75 @@ impl super::Queue { index_count, index_offset, base_vertex, + first_instance, instance_count, + ref first_instance_location, } => { match base_vertex { - // Don't use `gl.draw_elements`/`gl.draw_elements_base_vertex` for `instance_count == 1`. - // Angle has a bug where it doesn't consider the instance divisor when `DYNAMIC_DRAW` is used in `gl.draw_elements`/`gl.draw_elements_base_vertex`. - // See https://github.com/gfx-rs/wgpu/issues/3578 - 0 => unsafe { - gl.draw_elements_instanced( - topology, - index_count as i32, - index_type, - index_offset as i32, - instance_count as i32, - ) - }, - _ => unsafe { - gl.draw_elements_instanced_base_vertex( - topology, - index_count as _, - index_type, - index_offset as i32, - instance_count as i32, - base_vertex, - ) - }, + 0 => { + unsafe { + gl.uniform_1_u32(first_instance_location.as_ref(), first_instance) + }; + + unsafe { + // Don't use `gl.draw_elements`/`gl.draw_elements_base_vertex` for `instance_count == 1`. + // Angle has a bug where it doesn't consider the instance divisor when `DYNAMIC_DRAW` is used in `gl.draw_elements`/`gl.draw_elements_base_vertex`. + // See https://github.com/gfx-rs/wgpu/issues/3578 + gl.draw_elements_instanced( + topology, + index_count as i32, + index_type, + index_offset as i32, + instance_count as i32, + ) + } + } + _ => { + let supports_full_instancing = self + .shared + .private_caps + .contains(PrivateCapabilities::FULLY_FEATURED_INSTANCING); + + if supports_full_instancing { + unsafe { + gl.draw_elements_instanced_base_vertex_base_instance( + topology, + index_count as i32, + index_type, + index_offset as i32, + instance_count as i32, + base_vertex, + first_instance, + ) + } + } else { + unsafe { + gl.uniform_1_u32(first_instance_location.as_ref(), first_instance) + }; + + // If we've gotten here, wgpu-core has already validated that this function exists via the DownlevelFlags::BASE_VERTEX feature. + unsafe { + gl.draw_elements_instanced_base_vertex( + topology, + index_count as _, + index_type, + index_offset as i32, + instance_count as i32, + base_vertex, + ) + } + } + } } } C::DrawIndirect { topology, indirect_buf, indirect_offset, + ref first_instance_location, } => { + unsafe { gl.uniform_1_u32(first_instance_location.as_ref(), 0) }; + unsafe { gl.bind_buffer(glow::DRAW_INDIRECT_BUFFER, Some(indirect_buf)) }; unsafe { gl.draw_arrays_indirect_offset(topology, indirect_offset as i32) }; } @@ -223,7 +282,10 @@ impl super::Queue { index_type, indirect_buf, indirect_offset, + ref first_instance_location, } => { + unsafe { gl.uniform_1_u32(first_instance_location.as_ref(), 0) }; + unsafe { gl.bind_buffer(glow::DRAW_INDIRECT_BUFFER, Some(indirect_buf)) }; unsafe { gl.draw_elements_indirect_offset(topology, index_type, indirect_offset as i32) @@ -361,13 +423,16 @@ impl super::Queue { unsafe { gl.bind_buffer(copy_src_target, None) }; if is_index_buffer_only_element_dst { unsafe { - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, self.current_index_buffer) + gl.bind_buffer( + glow::ELEMENT_ARRAY_BUFFER, + *self.current_index_buffer.lock(), + ) }; } else { unsafe { gl.bind_buffer(copy_dst_target, None) }; } } - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] C::CopyExternalImageToTexture { ref src, dst, @@ -393,12 +458,7 @@ impl super::Queue { unsafe { gl.bind_texture(dst_target, Some(dst)) }; let format_desc = self.shared.describe_texture_format(dst_format); if is_layered_target(dst_target) { - let z_offset = - if let glow::TEXTURE_2D_ARRAY | glow::TEXTURE_CUBE_MAP_ARRAY = dst_target { - copy.dst_base.array_layer as i32 - } else { - copy.dst_base.origin.z as i32 - }; + let z_offset = get_z_offset(dst_target, ©.dst_base); match src.source { wgt::ExternalImageSource::ImageBitmap(ref b) => unsafe { @@ -407,7 +467,7 @@ impl super::Queue { copy.dst_base.mip_level as i32, copy.dst_base.origin.x as i32, copy.dst_base.origin.y as i32, - z_offset, + z_offset as i32, copy.size.width as i32, copy.size.height as i32, copy.size.depth as i32, @@ -422,7 +482,7 @@ impl super::Queue { copy.dst_base.mip_level as i32, copy.dst_base.origin.x as i32, copy.dst_base.origin.y as i32, - z_offset, + z_offset as i32, copy.size.width as i32, copy.size.height as i32, copy.size.depth as i32, @@ -437,7 +497,7 @@ impl super::Queue { copy.dst_base.mip_level as i32, copy.dst_base.origin.x as i32, copy.dst_base.origin.y as i32, - z_offset, + z_offset as i32, copy.size.width as i32, copy.size.height as i32, copy.size.depth as i32, @@ -449,11 +509,7 @@ impl super::Queue { wgt::ExternalImageSource::OffscreenCanvas(_) => unreachable!(), } } else { - let dst_target = if let glow::TEXTURE_CUBE_MAP = dst_target { - CUBEMAP_FACES[copy.dst_base.array_layer as usize] - } else { - dst_target - }; + let dst_target = get_2d_target(dst_target, copy.dst_base.array_layer); match src.source { wgt::ExternalImageSource::ImageBitmap(ref b) => unsafe { @@ -513,7 +569,6 @@ impl super::Queue { src_target, dst, dst_target, - dst_is_cubemap, ref copy, } => { //TODO: handle 3D copies @@ -542,33 +597,14 @@ impl super::Queue { } unsafe { gl.bind_texture(dst_target, Some(dst)) }; - if dst_is_cubemap { - unsafe { - gl.copy_tex_sub_image_2d( - CUBEMAP_FACES[copy.dst_base.array_layer as usize], - copy.dst_base.mip_level as i32, - copy.dst_base.origin.x as i32, - copy.dst_base.origin.y as i32, - copy.src_base.origin.x as i32, - copy.src_base.origin.y as i32, - copy.size.width as i32, - copy.size.height as i32, - ) - }; - } else if is_layered_target(dst_target) { + if is_layered_target(dst_target) { unsafe { gl.copy_tex_sub_image_3d( dst_target, copy.dst_base.mip_level as i32, copy.dst_base.origin.x as i32, copy.dst_base.origin.y as i32, - if let glow::TEXTURE_2D_ARRAY | glow::TEXTURE_CUBE_MAP_ARRAY = - dst_target - { - copy.dst_base.array_layer as i32 - } else { - copy.dst_base.origin.z as i32 - }, + get_z_offset(dst_target, ©.dst_base) as i32, copy.src_base.origin.x as i32, copy.src_base.origin.y as i32, copy.size.width as i32, @@ -578,7 +614,7 @@ impl super::Queue { } else { unsafe { gl.copy_tex_sub_image_2d( - dst_target, + get_2d_target(dst_target, copy.dst_base.array_layer), copy.dst_base.mip_level as i32, copy.dst_base.origin.x as i32, copy.dst_base.origin.y as i32, @@ -599,7 +635,7 @@ impl super::Queue { ref copy, } => { let (block_width, block_height) = dst_format.block_dimensions(); - let block_size = dst_format.block_size(None).unwrap(); + let block_size = dst_format.block_copy_size(None).unwrap(); let format_desc = self.shared.describe_texture_format(dst_format); let row_texels = copy .buffer_layout @@ -636,13 +672,7 @@ impl super::Queue { copy.texture_base.mip_level as i32, copy.texture_base.origin.x as i32, copy.texture_base.origin.y as i32, - if let glow::TEXTURE_2D_ARRAY | glow::TEXTURE_CUBE_MAP_ARRAY = - dst_target - { - copy.texture_base.array_layer as i32 - } else { - copy.texture_base.origin.z as i32 - }, + get_z_offset(dst_target, ©.texture_base) as i32, copy.size.width as i32, copy.size.height as i32, copy.size.depth as i32, @@ -654,11 +684,7 @@ impl super::Queue { } else { unsafe { gl.tex_sub_image_2d( - if let glow::TEXTURE_CUBE_MAP = dst_target { - CUBEMAP_FACES[copy.texture_base.array_layer as usize] - } else { - dst_target - }, + get_2d_target(dst_target, copy.texture_base.array_layer), copy.texture_base.mip_level as i32, copy.texture_base.origin.x as i32, copy.texture_base.origin.y as i32, @@ -712,13 +738,7 @@ impl super::Queue { copy.texture_base.mip_level as i32, copy.texture_base.origin.x as i32, copy.texture_base.origin.y as i32, - if let glow::TEXTURE_2D_ARRAY | glow::TEXTURE_CUBE_MAP_ARRAY = - dst_target - { - copy.texture_base.array_layer as i32 - } else { - copy.texture_base.origin.z as i32 - }, + get_z_offset(dst_target, ©.texture_base) as i32, copy.size.width as i32, copy.size.height as i32, copy.size.depth as i32, @@ -729,11 +749,7 @@ impl super::Queue { } else { unsafe { gl.compressed_tex_sub_image_2d( - if let glow::TEXTURE_CUBE_MAP = dst_target { - CUBEMAP_FACES[copy.texture_base.array_layer as usize] - } else { - dst_target - }, + get_2d_target(dst_target, copy.texture_base.array_layer), copy.texture_base.mip_level as i32, copy.texture_base.origin.x as i32, copy.texture_base.origin.y as i32, @@ -757,7 +773,7 @@ impl super::Queue { dst_target: _, ref copy, } => { - let block_size = src_format.block_size(None).unwrap(); + let block_size = src_format.block_copy_size(None).unwrap(); if src_format.is_compressed() { log::error!("Not implemented yet: compressed texture copy to buffer"); return; @@ -854,7 +870,8 @@ impl super::Queue { } C::SetIndexBuffer(buffer) => { unsafe { gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(buffer)) }; - self.current_index_buffer = Some(buffer); + let mut current_index_buffer = self.current_index_buffer.lock(); + *current_index_buffer = Some(buffer); } C::BeginQuery(query, target) => { unsafe { gl.begin_query(target, query) }; @@ -862,34 +879,107 @@ impl super::Queue { C::EndQuery(target) => { unsafe { gl.end_query(target) }; } + C::TimestampQuery(query) => { + unsafe { gl.query_counter(query, glow::TIMESTAMP) }; + } C::CopyQueryResults { ref query_range, ref dst, dst_target, dst_offset, } => { - self.temp_query_results.clear(); - for &query in queries[query_range.start as usize..query_range.end as usize].iter() { - let result = unsafe { gl.get_query_parameter_u32(query, glow::QUERY_RESULT) }; - self.temp_query_results.push(result as u64); - } - let query_data = unsafe { - slice::from_raw_parts( - self.temp_query_results.as_ptr() as *const u8, - self.temp_query_results.len() * mem::size_of::(), - ) - }; - match dst.raw { - Some(buffer) => { - unsafe { gl.bind_buffer(dst_target, Some(buffer)) }; + if self + .shared + .private_caps + .contains(PrivateCapabilities::QUERY_BUFFERS) + && dst.raw.is_some() + { + unsafe { + // We're assuming that the only relevant queries are 8 byte timestamps or + // occlusion tests. + let query_size = 8; + + let query_range_size = query_size * query_range.len(); + + let buffer = gl.create_buffer().ok(); + gl.bind_buffer(glow::QUERY_BUFFER, buffer); + gl.buffer_data_size( + glow::QUERY_BUFFER, + query_range_size as _, + glow::STREAM_COPY, + ); + + for (i, &query) in queries + [query_range.start as usize..query_range.end as usize] + .iter() + .enumerate() + { + gl.get_query_parameter_u64_with_offset( + query, + glow::QUERY_RESULT, + query_size * i, + ) + } + gl.bind_buffer(dst_target, dst.raw); + gl.copy_buffer_sub_data( + glow::QUERY_BUFFER, + dst_target, + 0, + dst_offset as _, + query_range_size as _, + ); + if let Some(buffer) = buffer { + gl.delete_buffer(buffer) + } + } + } else { + let mut temp_query_results = self.temp_query_results.lock(); + temp_query_results.clear(); + for &query in + queries[query_range.start as usize..query_range.end as usize].iter() + { + let mut result: u64 = 0; unsafe { - gl.buffer_sub_data_u8_slice(dst_target, dst_offset as i32, query_data) + if self + .shared + .private_caps + .contains(PrivateCapabilities::QUERY_64BIT) + { + let result: *mut u64 = &mut result; + gl.get_query_parameter_u64_with_offset( + query, + glow::QUERY_RESULT, + result as usize, + ) + } else { + result = + gl.get_query_parameter_u32(query, glow::QUERY_RESULT) as u64; + } }; + temp_query_results.push(result); } - None => { - let data = &mut dst.data.as_ref().unwrap().lock().unwrap(); - let len = query_data.len().min(data.len()); - data[..len].copy_from_slice(&query_data[..len]); + let query_data = unsafe { + slice::from_raw_parts( + temp_query_results.as_ptr() as *const u8, + temp_query_results.len() * mem::size_of::(), + ) + }; + match dst.raw { + Some(buffer) => { + unsafe { gl.bind_buffer(dst_target, Some(buffer)) }; + unsafe { + gl.buffer_sub_data_u8_slice( + dst_target, + dst_offset as i32, + query_data, + ) + }; + } + None => { + let data = &mut dst.data.as_ref().unwrap().lock().unwrap(); + let len = query_data.len().min(data.len()); + data[..len].copy_from_slice(&query_data[..len]); + } } } } @@ -962,24 +1052,20 @@ impl super::Queue { unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, Some(self.draw_fbo)) }; } C::InvalidateAttachments(ref list) => { - unsafe { gl.invalidate_framebuffer(glow::DRAW_FRAMEBUFFER, list) }; + if self + .shared + .private_caps + .contains(PrivateCapabilities::INVALIDATE_FRAMEBUFFER) + { + unsafe { gl.invalidate_framebuffer(glow::DRAW_FRAMEBUFFER, list) }; + } } C::SetDrawColorBuffers(count) => { - self.draw_buffer_count = count; + self.draw_buffer_count.store(count, Ordering::Relaxed); let indices = (0..count as u32) .map(|i| glow::COLOR_ATTACHMENT0 + i) .collect::>(); unsafe { gl.draw_buffers(&indices) }; - - if self - .shared - .private_caps - .contains(super::PrivateCapabilities::CAN_DISABLE_DRAW_BUFFER) - { - for draw_buffer in 0..count as u32 { - unsafe { gl.disable_draw_buffer(glow::BLEND, draw_buffer) }; - } - } } C::ClearColorF { draw_buffer, @@ -994,7 +1080,13 @@ impl super::Queue { { unsafe { self.perform_shader_clear(gl, draw_buffer, *color) }; } else { - unsafe { gl.clear_buffer_f32_slice(glow::COLOR, draw_buffer, color) }; + // Prefer `clear` as `clear_buffer` functions have issues on Sandy Bridge + // on Windows. + unsafe { + gl.draw_buffers(&[glow::COLOR_ATTACHMENT0 + draw_buffer]); + gl.clear_color(color[0], color[1], color[2], color[3]); + gl.clear(glow::COLOR_BUFFER_BIT); + } } } C::ClearColorU(draw_buffer, ref color) => { @@ -1004,20 +1096,29 @@ impl super::Queue { unsafe { gl.clear_buffer_i32_slice(glow::COLOR, draw_buffer, color) }; } C::ClearDepth(depth) => { - unsafe { gl.clear_buffer_f32_slice(glow::DEPTH, 0, &[depth]) }; + // Prefer `clear` as `clear_buffer` functions have issues on Sandy Bridge + // on Windows. + unsafe { + gl.clear_depth_f32(depth); + gl.clear(glow::DEPTH_BUFFER_BIT); + } } C::ClearStencil(value) => { - unsafe { gl.clear_buffer_i32_slice(glow::STENCIL, 0, &[value as i32]) }; + // Prefer `clear` as `clear_buffer` functions have issues on Sandy Bridge + // on Windows. + unsafe { + gl.clear_stencil(value as i32); + gl.clear(glow::STENCIL_BUFFER_BIT); + } } C::ClearDepthAndStencil(depth, stencil_value) => { + // Prefer `clear` as `clear_buffer` functions have issues on Sandy Bridge + // on Windows. unsafe { - gl.clear_buffer_depth_stencil( - glow::DEPTH_STENCIL, - 0, - depth, - stencil_value as i32, - ) - }; + gl.clear_depth_f32(depth); + gl.clear_stencil(stencil_value as i32); + gl.clear(glow::DEPTH_BUFFER_BIT | glow::STENCIL_BUFFER_BIT); + } } C::BufferBarrier(raw, usage) => { let mut flags = 0; @@ -1229,6 +1330,10 @@ impl super::Queue { unsafe { gl.disable(glow::DEPTH_CLAMP) }; } } + // POLYGON_MODE_LINE also implies POLYGON_MODE_POINT + if self.features.contains(wgt::Features::POLYGON_MODE_LINE) { + unsafe { gl.polygon_mode(glow::FRONT_AND_BACK, state.polygon_mode) }; + } } C::SetBlendConstant(c) => { unsafe { gl.blend_color(c[0], c[1], c[2], c[3]) }; @@ -1249,7 +1354,7 @@ impl super::Queue { ) }; if let Some(ref blend) = *blend { - unsafe { gl.enable_draw_buffer(index, glow::BLEND) }; + unsafe { gl.enable_draw_buffer(glow::BLEND, index) }; if blend.color != blend.alpha { unsafe { gl.blend_equation_separate_draw_buffer( @@ -1273,12 +1378,8 @@ impl super::Queue { gl.blend_func_draw_buffer(index, blend.color.src, blend.color.dst) }; } - } else if self - .shared - .private_caps - .contains(super::PrivateCapabilities::CAN_DISABLE_DRAW_BUFFER) - { - unsafe { gl.disable_draw_buffer(index, glow::BLEND) }; + } else { + unsafe { gl.disable_draw_buffer(glow::BLEND, index) }; } } else { unsafe { @@ -1332,10 +1433,22 @@ impl super::Queue { texture, target, aspects, + ref mip_levels, } => { unsafe { gl.active_texture(glow::TEXTURE0 + slot) }; unsafe { gl.bind_texture(target, Some(texture)) }; + unsafe { + gl.tex_parameter_i32(target, glow::TEXTURE_BASE_LEVEL, mip_levels.start as i32) + }; + unsafe { + gl.tex_parameter_i32( + target, + glow::TEXTURE_MAX_LEVEL, + (mip_levels.end - 1) as i32, + ) + }; + let version = gl.version(); let is_min_es_3_1 = version.is_embedded && (version.major, version.minor) >= (3, 1); let is_min_4_3 = !version.is_embedded && (version.major, version.minor) >= (4, 3); @@ -1369,98 +1482,262 @@ impl super::Queue { ) }; } - #[cfg(not(target_arch = "wasm32"))] C::InsertDebugMarker(ref range) => { let marker = extract_marker(data_bytes, range); unsafe { - gl.debug_message_insert( - glow::DEBUG_SOURCE_APPLICATION, - glow::DEBUG_TYPE_MARKER, - DEBUG_ID, - glow::DEBUG_SEVERITY_NOTIFICATION, - marker, - ) + if self + .shared + .private_caps + .contains(PrivateCapabilities::DEBUG_FNS) + { + gl.debug_message_insert( + glow::DEBUG_SOURCE_APPLICATION, + glow::DEBUG_TYPE_MARKER, + DEBUG_ID, + glow::DEBUG_SEVERITY_NOTIFICATION, + marker, + ) + } }; } - #[cfg(target_arch = "wasm32")] - C::InsertDebugMarker(_) => (), - #[cfg_attr(target_arch = "wasm32", allow(unused))] C::PushDebugGroup(ref range) => { - #[cfg(not(target_arch = "wasm32"))] let marker = extract_marker(data_bytes, range); - #[cfg(not(target_arch = "wasm32"))] unsafe { - gl.push_debug_group(glow::DEBUG_SOURCE_APPLICATION, DEBUG_ID, marker) + if self + .shared + .private_caps + .contains(PrivateCapabilities::DEBUG_FNS) + { + gl.push_debug_group(glow::DEBUG_SOURCE_APPLICATION, DEBUG_ID, marker) + } }; } C::PopDebugGroup => { - #[cfg(not(target_arch = "wasm32"))] unsafe { - gl.pop_debug_group() + if self + .shared + .private_caps + .contains(PrivateCapabilities::DEBUG_FNS) + { + gl.pop_debug_group() + } }; } C::SetPushConstants { ref uniform, offset, } => { - fn get_data(data: &[u8], offset: u32) -> &[T] { - let raw = &data[(offset as usize)..]; - unsafe { - slice::from_raw_parts( - raw.as_ptr() as *const _, - raw.len() / mem::size_of::(), - ) - } + // T must be POD + // + // This function is absolutely sketchy and we really should be using bytemuck. + unsafe fn get_data(data: &[u8], offset: u32) -> &[T; COUNT] { + let data_required = mem::size_of::() * COUNT; + + let raw = &data[(offset as usize)..][..data_required]; + + debug_assert_eq!(data_required, raw.len()); + + let slice: &[T] = + unsafe { slice::from_raw_parts(raw.as_ptr() as *const _, COUNT) }; + + slice.try_into().unwrap() } - let location = uniform.location.as_ref(); + let location = Some(&uniform.location); - match uniform.utype { - glow::FLOAT => { - let data = get_data::(data_bytes, offset)[0]; + match uniform.ty { + // + // --- Float 1-4 Component --- + // + naga::TypeInner::Scalar(naga::Scalar::F32) => { + let data = unsafe { get_data::(data_bytes, offset)[0] }; unsafe { gl.uniform_1_f32(location, data) }; } - glow::FLOAT_VEC2 => { - let data = get_data::<[f32; 2]>(data_bytes, offset)[0]; - unsafe { gl.uniform_2_f32_slice(location, &data) }; + naga::TypeInner::Vector { + size: naga::VectorSize::Bi, + scalar: naga::Scalar::F32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_2_f32_slice(location, data) }; } - glow::FLOAT_VEC3 => { - let data = get_data::<[f32; 3]>(data_bytes, offset)[0]; - unsafe { gl.uniform_3_f32_slice(location, &data) }; + naga::TypeInner::Vector { + size: naga::VectorSize::Tri, + scalar: naga::Scalar::F32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_3_f32_slice(location, data) }; } - glow::FLOAT_VEC4 => { - let data = get_data::<[f32; 4]>(data_bytes, offset)[0]; - unsafe { gl.uniform_4_f32_slice(location, &data) }; + naga::TypeInner::Vector { + size: naga::VectorSize::Quad, + scalar: naga::Scalar::F32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_4_f32_slice(location, data) }; } - glow::INT => { - let data = get_data::(data_bytes, offset)[0]; + + // + // --- Int 1-4 Component --- + // + naga::TypeInner::Scalar(naga::Scalar::I32) => { + let data = unsafe { get_data::(data_bytes, offset)[0] }; unsafe { gl.uniform_1_i32(location, data) }; } - glow::INT_VEC2 => { - let data = get_data::<[i32; 2]>(data_bytes, offset)[0]; - unsafe { gl.uniform_2_i32_slice(location, &data) }; + naga::TypeInner::Vector { + size: naga::VectorSize::Bi, + scalar: naga::Scalar::I32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_2_i32_slice(location, data) }; } - glow::INT_VEC3 => { - let data = get_data::<[i32; 3]>(data_bytes, offset)[0]; - unsafe { gl.uniform_3_i32_slice(location, &data) }; + naga::TypeInner::Vector { + size: naga::VectorSize::Tri, + scalar: naga::Scalar::I32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_3_i32_slice(location, data) }; } - glow::INT_VEC4 => { - let data = get_data::<[i32; 4]>(data_bytes, offset)[0]; - unsafe { gl.uniform_4_i32_slice(location, &data) }; + naga::TypeInner::Vector { + size: naga::VectorSize::Quad, + scalar: naga::Scalar::I32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_4_i32_slice(location, data) }; } - glow::FLOAT_MAT2 => { - let data = get_data::<[f32; 4]>(data_bytes, offset)[0]; - unsafe { gl.uniform_matrix_2_f32_slice(location, false, &data) }; + + // + // --- Uint 1-4 Component --- + // + naga::TypeInner::Scalar(naga::Scalar::U32) => { + let data = unsafe { get_data::(data_bytes, offset)[0] }; + unsafe { gl.uniform_1_u32(location, data) }; + } + naga::TypeInner::Vector { + size: naga::VectorSize::Bi, + scalar: naga::Scalar::U32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_2_u32_slice(location, data) }; } - glow::FLOAT_MAT3 => { - let data = get_data::<[f32; 9]>(data_bytes, offset)[0]; - unsafe { gl.uniform_matrix_3_f32_slice(location, false, &data) }; + naga::TypeInner::Vector { + size: naga::VectorSize::Tri, + scalar: naga::Scalar::U32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_3_u32_slice(location, data) }; } - glow::FLOAT_MAT4 => { - let data = get_data::<[f32; 16]>(data_bytes, offset)[0]; - unsafe { gl.uniform_matrix_4_f32_slice(location, false, &data) }; + naga::TypeInner::Vector { + size: naga::VectorSize::Quad, + scalar: naga::Scalar::U32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_4_u32_slice(location, data) }; } - _ => panic!("Unsupported uniform datatype!"), + + // + // --- Matrix 2xR --- + // + naga::TypeInner::Matrix { + columns: naga::VectorSize::Bi, + rows: naga::VectorSize::Bi, + scalar: naga::Scalar::F32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_2_f32_slice(location, false, data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Bi, + rows: naga::VectorSize::Tri, + scalar: naga::Scalar::F32, + } => { + // repack 2 vec3s into 6 values. + let unpacked_data = unsafe { get_data::(data_bytes, offset) }; + #[rustfmt::skip] + let packed_data = [ + unpacked_data[0], unpacked_data[1], unpacked_data[2], + unpacked_data[4], unpacked_data[5], unpacked_data[6], + ]; + unsafe { gl.uniform_matrix_2x3_f32_slice(location, false, &packed_data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Bi, + rows: naga::VectorSize::Quad, + scalar: naga::Scalar::F32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_2x4_f32_slice(location, false, data) }; + } + + // + // --- Matrix 3xR --- + // + naga::TypeInner::Matrix { + columns: naga::VectorSize::Tri, + rows: naga::VectorSize::Bi, + scalar: naga::Scalar::F32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_3x2_f32_slice(location, false, data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Tri, + rows: naga::VectorSize::Tri, + scalar: naga::Scalar::F32, + } => { + // repack 3 vec3s into 9 values. + let unpacked_data = unsafe { get_data::(data_bytes, offset) }; + #[rustfmt::skip] + let packed_data = [ + unpacked_data[0], unpacked_data[1], unpacked_data[2], + unpacked_data[4], unpacked_data[5], unpacked_data[6], + unpacked_data[8], unpacked_data[9], unpacked_data[10], + ]; + unsafe { gl.uniform_matrix_3_f32_slice(location, false, &packed_data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Tri, + rows: naga::VectorSize::Quad, + scalar: naga::Scalar::F32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_3x4_f32_slice(location, false, data) }; + } + + // + // --- Matrix 4xR --- + // + naga::TypeInner::Matrix { + columns: naga::VectorSize::Quad, + rows: naga::VectorSize::Bi, + scalar: naga::Scalar::F32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_4x2_f32_slice(location, false, data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Quad, + rows: naga::VectorSize::Tri, + scalar: naga::Scalar::F32, + } => { + // repack 4 vec3s into 12 values. + let unpacked_data = unsafe { get_data::(data_bytes, offset) }; + #[rustfmt::skip] + let packed_data = [ + unpacked_data[0], unpacked_data[1], unpacked_data[2], + unpacked_data[4], unpacked_data[5], unpacked_data[6], + unpacked_data[8], unpacked_data[9], unpacked_data[10], + unpacked_data[12], unpacked_data[13], unpacked_data[14], + ]; + unsafe { gl.uniform_matrix_4x3_f32_slice(location, false, &packed_data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Quad, + rows: naga::VectorSize::Quad, + scalar: naga::Scalar::F32, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_4_f32_slice(location, false, data) }; + } + _ => panic!("Unsupported uniform datatype: {:?}!", uniform.ty), } } } @@ -1469,7 +1746,7 @@ impl super::Queue { impl crate::Queue for super::Queue { unsafe fn submit( - &mut self, + &self, command_buffers: &[&super::CommandBuffer], signal_fence: Option<(&mut super::Fence, crate::FenceValue)>, ) -> Result<(), crate::DeviceError> { @@ -1481,17 +1758,26 @@ impl crate::Queue for super::Queue { // this at the beginning of the loop in case something outside of wgpu modified // this state prior to commit. unsafe { self.reset_state(gl) }; - #[cfg(not(target_arch = "wasm32"))] if let Some(ref label) = cmd_buf.label { - unsafe { gl.push_debug_group(glow::DEBUG_SOURCE_APPLICATION, DEBUG_ID, label) }; + if self + .shared + .private_caps + .contains(PrivateCapabilities::DEBUG_FNS) + { + unsafe { gl.push_debug_group(glow::DEBUG_SOURCE_APPLICATION, DEBUG_ID, label) }; + } } for command in cmd_buf.commands.iter() { unsafe { self.process(gl, command, &cmd_buf.data_bytes, &cmd_buf.queries) }; } - #[cfg(not(target_arch = "wasm32"))] - if cmd_buf.label.is_some() { + if cmd_buf.label.is_some() + && self + .shared + .private_caps + .contains(PrivateCapabilities::DEBUG_FNS) + { unsafe { gl.pop_debug_group() }; } } @@ -1507,17 +1793,11 @@ impl crate::Queue for super::Queue { } unsafe fn present( - &mut self, - surface: &mut super::Surface, + &self, + surface: &super::Surface, texture: super::Texture, ) -> Result<(), crate::SurfaceError> { - #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] - let gl = unsafe { &self.shared.context.get_without_egl_lock() }; - - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - let gl = &self.shared.context.glow_context; - - unsafe { surface.present(texture, gl) } + unsafe { surface.present(texture, &self.shared.context) } } unsafe fn get_timestamp_period(&self) -> f32 { @@ -1525,15 +1805,7 @@ impl crate::Queue for super::Queue { } } -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for super::Queue {} -#[cfg(all( - target_arch = "wasm32", - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for super::Queue {} diff --git a/wgpu-hal/src/gles/shaders/clear.frag b/wgpu-hal/src/gles/shaders/clear.frag index 7766c12d9f..1d0e414b28 100644 --- a/wgpu-hal/src/gles/shaders/clear.frag +++ b/wgpu-hal/src/gles/shaders/clear.frag @@ -1,5 +1,3 @@ -#version 300 es -precision lowp float; uniform vec4 color; //Hack: Some WebGL implementations don't find "color" otherwise. uniform vec4 color_workaround; diff --git a/wgpu-hal/src/gles/shaders/clear.vert b/wgpu-hal/src/gles/shaders/clear.vert index ac655e7f31..341b4e5f06 100644 --- a/wgpu-hal/src/gles/shaders/clear.vert +++ b/wgpu-hal/src/gles/shaders/clear.vert @@ -1,7 +1,5 @@ -#version 300 es -precision lowp float; // A triangle that fills the whole screen -const vec2[3] TRIANGLE_POS = vec2[]( +vec2[3] TRIANGLE_POS = vec2[]( vec2( 0.0, -3.0), vec2(-3.0, 1.0), vec2( 3.0, 1.0) diff --git a/wgpu-hal/src/gles/web.rs b/wgpu-hal/src/gles/web.rs index 13bce85f84..797d6f91d7 100644 --- a/wgpu-hal/src/gles/web.rs +++ b/wgpu-hal/src/gles/web.rs @@ -1,6 +1,6 @@ use glow::HasContext; -use parking_lot::Mutex; -use wasm_bindgen::JsCast; +use parking_lot::{Mutex, RwLock}; +use wasm_bindgen::{JsCast, JsValue}; use super::TextureFormatDesc; @@ -55,7 +55,7 @@ impl Instance { fn create_surface_from_context( &self, canvas: Canvas, - context_result: Result, wasm_bindgen::JsValue>, + context_result: Result, JsValue>, ) -> Result { let context_object: js_sys::Object = match context_result { Ok(Some(context)) => context, @@ -92,9 +92,9 @@ impl Instance { Ok(Surface { canvas, webgl2_context, - srgb_present_program: None, - swapchain: None, - texture: None, + srgb_present_program: Mutex::new(None), + swapchain: RwLock::new(None), + texture: Mutex::new(None), presentable: true, }) } @@ -111,19 +111,14 @@ impl Instance { } } -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for Instance {} -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for Instance {} impl crate::Instance for Instance { unsafe fn init(_desc: &crate::InstanceDescriptor) -> Result { + profiling::scope!("Init OpenGL (WebGL) Backend"); Ok(Instance { webgl2_context: Mutex::new(None), }) @@ -146,22 +141,33 @@ impl crate::Instance for Instance { _display_handle: raw_window_handle::RawDisplayHandle, window_handle: raw_window_handle::RawWindowHandle, ) -> Result { - if let raw_window_handle::RawWindowHandle::Web(handle) = window_handle { - let canvas: web_sys::HtmlCanvasElement = web_sys::window() + let canvas: web_sys::HtmlCanvasElement = match window_handle { + raw_window_handle::RawWindowHandle::Web(handle) => web_sys::window() .and_then(|win| win.document()) .expect("Cannot get document") .query_selector(&format!("canvas[data-raw-handle=\"{}\"]", handle.id)) .expect("Cannot query for canvas") .expect("Canvas is not found") .dyn_into() - .expect("Failed to downcast to canvas type"); + .expect("Failed to downcast to canvas type"), + raw_window_handle::RawWindowHandle::WebCanvas(handle) => { + let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; + value.clone().unchecked_into() + } + raw_window_handle::RawWindowHandle::WebOffscreenCanvas(handle) => { + let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; + let canvas: web_sys::OffscreenCanvas = value.clone().unchecked_into(); - self.create_surface_from_canvas(canvas) - } else { - Err(crate::InstanceError::new(format!( - "window handle {window_handle:?} is not a web handle" - ))) - } + return self.create_surface_from_offscreen_canvas(canvas); + } + _ => { + return Err(crate::InstanceError::new(format!( + "window handle {window_handle:?} is not a web handle" + ))) + } + }; + + self.create_surface_from_canvas(canvas) } unsafe fn destroy_surface(&self, surface: Surface) { @@ -175,25 +181,32 @@ impl crate::Instance for Instance { } } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Surface { canvas: Canvas, webgl2_context: web_sys::WebGl2RenderingContext, - pub(super) swapchain: Option, - texture: Option, + pub(super) swapchain: RwLock>, + texture: Mutex>, pub(super) presentable: bool, - srgb_present_program: Option, + srgb_present_program: Mutex>, } -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +impl Clone for Surface { + fn clone(&self) -> Self { + Self { + canvas: self.canvas.clone(), + webgl2_context: self.webgl2_context.clone(), + swapchain: RwLock::new(self.swapchain.read().clone()), + texture: Mutex::new(*self.texture.lock()), + presentable: self.presentable, + srgb_present_program: Mutex::new(*self.srgb_present_program.lock()), + } + } +} + +#[cfg(send_sync)] unsafe impl Sync for Surface {} -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for Surface {} #[derive(Clone, Debug)] @@ -213,11 +226,13 @@ pub struct Swapchain { impl Surface { pub(super) unsafe fn present( - &mut self, + &self, _suf_texture: super::Texture, - gl: &glow::Context, + context: &AdapterContext, ) -> Result<(), crate::SurfaceError> { - let swapchain = self.swapchain.as_ref().ok_or(crate::SurfaceError::Other( + let gl = &context.glow_context; + let swapchain = self.swapchain.read(); + let swapchain = swapchain.as_ref().ok_or(crate::SurfaceError::Other( "need to configure surface before presenting", ))?; @@ -234,8 +249,8 @@ impl Surface { unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) }; unsafe { gl.bind_sampler(0, None) }; unsafe { gl.active_texture(glow::TEXTURE0) }; - unsafe { gl.bind_texture(glow::TEXTURE_2D, self.texture) }; - unsafe { gl.use_program(self.srgb_present_program) }; + unsafe { gl.bind_texture(glow::TEXTURE_2D, *self.texture.lock()) }; + unsafe { gl.use_program(*self.srgb_present_program.lock()) }; unsafe { gl.disable(glow::DEPTH_TEST) }; unsafe { gl.disable(glow::STENCIL_TEST) }; unsafe { gl.disable(glow::SCISSOR_TEST) }; @@ -296,7 +311,7 @@ impl Surface { impl crate::Surface for Surface { unsafe fn configure( - &mut self, + &self, device: &super::Device, config: &crate::SurfaceConfiguration, ) -> Result<(), crate::SurfaceError> { @@ -313,94 +328,107 @@ impl crate::Surface for Surface { let gl = &device.shared.context.lock(); - if let Some(swapchain) = self.swapchain.take() { - // delete all frame buffers already allocated - unsafe { gl.delete_framebuffer(swapchain.framebuffer) }; + { + let mut swapchain = self.swapchain.write(); + if let Some(swapchain) = swapchain.take() { + // delete all frame buffers already allocated + unsafe { gl.delete_framebuffer(swapchain.framebuffer) }; + } } - - if self.srgb_present_program.is_none() && config.format.is_srgb() { - self.srgb_present_program = Some(unsafe { Self::create_srgb_present_program(gl) }); + { + let mut srgb_present_program = self.srgb_present_program.lock(); + if srgb_present_program.is_none() && config.format.is_srgb() { + *srgb_present_program = Some(unsafe { Self::create_srgb_present_program(gl) }); + } } + { + let mut texture = self.texture.lock(); + if let Some(texture) = texture.take() { + unsafe { gl.delete_texture(texture) }; + } - if let Some(texture) = self.texture.take() { - unsafe { gl.delete_texture(texture) }; - } + *texture = Some(unsafe { gl.create_texture() }.map_err(|error| { + log::error!("Internal swapchain texture creation failed: {error}"); + crate::DeviceError::OutOfMemory + })?); - self.texture = Some(unsafe { gl.create_texture() }.map_err(|error| { - log::error!("Internal swapchain texture creation failed: {error}"); - crate::DeviceError::OutOfMemory - })?); - - let desc = device.shared.describe_texture_format(config.format); - unsafe { gl.bind_texture(glow::TEXTURE_2D, self.texture) }; - unsafe { - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MIN_FILTER, - glow::NEAREST as _, - ) - }; - unsafe { - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MAG_FILTER, - glow::NEAREST as _, - ) - }; - unsafe { - gl.tex_storage_2d( - glow::TEXTURE_2D, - 1, - desc.internal, - config.extent.width as i32, - config.extent.height as i32, - ) - }; + let desc = device.shared.describe_texture_format(config.format); + unsafe { gl.bind_texture(glow::TEXTURE_2D, *texture) }; + unsafe { + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::NEAREST as _, + ) + }; + unsafe { + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::NEAREST as _, + ) + }; + unsafe { + gl.tex_storage_2d( + glow::TEXTURE_2D, + 1, + desc.internal, + config.extent.width as i32, + config.extent.height as i32, + ) + }; - let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| { - log::error!("Internal swapchain framebuffer creation failed: {error}"); - crate::DeviceError::OutOfMemory - })?; - unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) }; - unsafe { - gl.framebuffer_texture_2d( - glow::READ_FRAMEBUFFER, - glow::COLOR_ATTACHMENT0, - glow::TEXTURE_2D, - self.texture, - 0, - ) - }; - unsafe { gl.bind_texture(glow::TEXTURE_2D, None) }; + let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| { + log::error!("Internal swapchain framebuffer creation failed: {error}"); + crate::DeviceError::OutOfMemory + })?; + unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) }; + unsafe { + gl.framebuffer_texture_2d( + glow::READ_FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, + glow::TEXTURE_2D, + *texture, + 0, + ) + }; + unsafe { gl.bind_texture(glow::TEXTURE_2D, None) }; + + let mut swapchain = self.swapchain.write(); + *swapchain = Some(Swapchain { + extent: config.extent, + // channel: config.format.base_format().1, + format: config.format, + format_desc: desc, + framebuffer, + }); + } - self.swapchain = Some(Swapchain { - extent: config.extent, - // channel: config.format.base_format().1, - format: config.format, - format_desc: desc, - framebuffer, - }); Ok(()) } - unsafe fn unconfigure(&mut self, device: &super::Device) { + unsafe fn unconfigure(&self, device: &super::Device) { let gl = device.shared.context.lock(); - if let Some(swapchain) = self.swapchain.take() { - unsafe { gl.delete_framebuffer(swapchain.framebuffer) }; + { + let mut swapchain = self.swapchain.write(); + if let Some(swapchain) = swapchain.take() { + unsafe { gl.delete_framebuffer(swapchain.framebuffer) }; + } } - if let Some(renderbuffer) = self.texture.take() { + if let Some(renderbuffer) = self.texture.lock().take() { unsafe { gl.delete_texture(renderbuffer) }; } } unsafe fn acquire_texture( - &mut self, + &self, _timeout_ms: Option, //TODO ) -> Result>, crate::SurfaceError> { - let sc = self.swapchain.as_ref().unwrap(); + let swapchain = self.swapchain.read(); + let sc = swapchain.as_ref().unwrap(); let texture = super::Texture { inner: super::TextureInner::Texture { - raw: self.texture.unwrap(), + raw: self.texture.lock().unwrap(), target: glow::TEXTURE_2D, }, drop_guard: None, @@ -413,7 +441,6 @@ impl crate::Surface for Surface { height: sc.extent.height, depth: 1, }, - is_cubemap: false, }; Ok(Some(crate::AcquiredSurfaceTexture { texture, @@ -421,5 +448,5 @@ impl crate::Surface for Surface { })) } - unsafe fn discard_texture(&mut self, _texture: super::Texture) {} + unsafe fn discard_texture(&self, _texture: super::Texture) {} } diff --git a/wgpu-hal/src/gles/wgl.rs b/wgpu-hal/src/gles/wgl.rs new file mode 100644 index 0000000000..a09a50330d --- /dev/null +++ b/wgpu-hal/src/gles/wgl.rs @@ -0,0 +1,809 @@ +use glow::HasContext; +use glutin_wgl_sys::wgl_extra::{ + Wgl, CONTEXT_CORE_PROFILE_BIT_ARB, CONTEXT_DEBUG_BIT_ARB, CONTEXT_FLAGS_ARB, + CONTEXT_PROFILE_MASK_ARB, +}; +use once_cell::sync::Lazy; +use parking_lot::{Mutex, MutexGuard, RwLock}; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; +use std::{ + collections::HashSet, + ffi::{c_void, CStr, CString}, + io::Error, + mem, + os::raw::c_int, + ptr, + sync::{ + mpsc::{sync_channel, SyncSender}, + Arc, + }, + thread, + time::Duration, +}; +use wgt::InstanceFlags; +use winapi::{ + shared::{ + minwindef::{FALSE, HMODULE, LPARAM, LRESULT, UINT, WPARAM}, + windef::{HDC, HGLRC, HWND}, + }, + um::{ + libloaderapi::{GetModuleHandleA, GetProcAddress, LoadLibraryA}, + wingdi::{ + wglCreateContext, wglDeleteContext, wglGetCurrentContext, wglGetProcAddress, + wglMakeCurrent, ChoosePixelFormat, DescribePixelFormat, GetPixelFormat, SetPixelFormat, + SwapBuffers, PFD_DOUBLEBUFFER, PFD_DRAW_TO_WINDOW, PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, + PIXELFORMATDESCRIPTOR, + }, + winuser::{ + CreateWindowExA, DefWindowProcA, DestroyWindow, GetDC, RegisterClassExA, ReleaseDC, + CS_OWNDC, WNDCLASSEXA, + }, + }, +}; + +/// The amount of time to wait while trying to obtain a lock to the adapter context +const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 1; + +/// A wrapper around a `[`glow::Context`]` and the required WGL context that uses locking to +/// guarantee exclusive access when shared with multiple threads. +pub struct AdapterContext { + inner: Arc>, +} + +unsafe impl Sync for AdapterContext {} +unsafe impl Send for AdapterContext {} + +impl AdapterContext { + pub fn is_owned(&self) -> bool { + true + } + + pub fn raw_context(&self) -> *mut c_void { + self.inner.lock().context.context as *mut _ + } + + /// Obtain a lock to the WGL context and get handle to the [`glow::Context`] that can be used to + /// do rendering. + #[track_caller] + pub fn lock(&self) -> AdapterContextLock<'_> { + let inner = self + .inner + // Don't lock forever. If it takes longer than 1 second to get the lock we've got a + // deadlock and should panic to show where we got stuck + .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) + .expect("Could not lock adapter context. This is most-likely a deadlock."); + + inner.context.make_current(inner.device.dc).unwrap(); + + AdapterContextLock { inner } + } +} + +/// A guard containing a lock to an [`AdapterContext`] +pub struct AdapterContextLock<'a> { + inner: MutexGuard<'a, Inner>, +} + +impl<'a> std::ops::Deref for AdapterContextLock<'a> { + type Target = glow::Context; + + fn deref(&self) -> &Self::Target { + &self.inner.gl + } +} + +impl<'a> Drop for AdapterContextLock<'a> { + fn drop(&mut self) { + self.inner.context.unmake_current().unwrap(); + } +} + +struct WglContext { + context: HGLRC, +} + +impl WglContext { + fn make_current(&self, device: HDC) -> Result<(), Error> { + if unsafe { wglMakeCurrent(device, self.context) } == FALSE { + Err(Error::last_os_error()) + } else { + Ok(()) + } + } + + fn unmake_current(&self) -> Result<(), Error> { + if unsafe { wglGetCurrentContext().is_null() } { + return Ok(()); + } + if unsafe { wglMakeCurrent(ptr::null_mut(), ptr::null_mut()) } == FALSE { + Err(Error::last_os_error()) + } else { + Ok(()) + } + } +} + +impl Drop for WglContext { + fn drop(&mut self) { + unsafe { + if wglDeleteContext(self.context) == FALSE { + log::error!("failed to delete WGL context {}", Error::last_os_error()); + } + }; + } +} + +unsafe impl Send for WglContext {} +unsafe impl Sync for WglContext {} + +struct Inner { + gl: glow::Context, + device: InstanceDevice, + context: WglContext, +} + +pub struct Instance { + srgb_capable: bool, + inner: Arc>, +} + +unsafe impl Send for Instance {} +unsafe impl Sync for Instance {} + +fn load_gl_func(name: &str, module: Option) -> *const c_void { + let addr = CString::new(name.as_bytes()).unwrap(); + let mut ptr = unsafe { wglGetProcAddress(addr.as_ptr()) }; + if ptr.is_null() { + if let Some(module) = module { + ptr = unsafe { GetProcAddress(module, addr.as_ptr()) }; + } + } + ptr.cast() +} + +fn extensions(extra: &Wgl, dc: HDC) -> HashSet { + if extra.GetExtensionsStringARB.is_loaded() { + unsafe { CStr::from_ptr(extra.GetExtensionsStringARB(dc as *const _)) } + .to_str() + .unwrap_or("") + } else { + "" + } + .split(' ') + .map(|s| s.to_owned()) + .collect() +} + +unsafe fn setup_pixel_format(dc: HDC) -> Result<(), crate::InstanceError> { + let mut format: PIXELFORMATDESCRIPTOR = unsafe { mem::zeroed() }; + format.nVersion = 1; + format.nSize = mem::size_of_val(&format) as u16; + format.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + format.iPixelType = PFD_TYPE_RGBA; + format.cColorBits = 8; + + let index = unsafe { ChoosePixelFormat(dc, &format) }; + if index == 0 { + return Err(crate::InstanceError::with_source( + String::from("unable to choose pixel format"), + Error::last_os_error(), + )); + } + + let current = unsafe { GetPixelFormat(dc) }; + + if index != current && unsafe { SetPixelFormat(dc, index, &format) } == FALSE { + return Err(crate::InstanceError::with_source( + String::from("unable to set pixel format"), + Error::last_os_error(), + )); + } + + let index = unsafe { GetPixelFormat(dc) }; + if index == 0 { + return Err(crate::InstanceError::with_source( + String::from("unable to get pixel format index"), + Error::last_os_error(), + )); + } + if unsafe { DescribePixelFormat(dc, index, mem::size_of_val(&format) as UINT, &mut format) } + == 0 + { + return Err(crate::InstanceError::with_source( + String::from("unable to read pixel format"), + Error::last_os_error(), + )); + } + + if format.dwFlags & PFD_SUPPORT_OPENGL == 0 || format.iPixelType != PFD_TYPE_RGBA { + return Err(crate::InstanceError::new(String::from( + "unsuitable pixel format", + ))); + } + Ok(()) +} + +fn create_global_window_class() -> Result { + let instance = unsafe { GetModuleHandleA(ptr::null()) }; + if instance.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to get executable instance"), + Error::last_os_error(), + )); + } + + // Use the address of `UNIQUE` as part of the window class name to ensure different + // `wgpu` versions use different names. + static UNIQUE: Mutex = Mutex::new(0); + let class_addr: *const _ = &UNIQUE; + let name = format!("wgpu Device Class {:x}\0", class_addr as usize); + let name = CString::from_vec_with_nul(name.into_bytes()).unwrap(); + + // Use a wrapper function for compatibility with `windows-rs`. + unsafe extern "system" fn wnd_proc( + window: HWND, + msg: UINT, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT { + unsafe { DefWindowProcA(window, msg, wparam, lparam) } + } + + let window_class = WNDCLASSEXA { + cbSize: mem::size_of::() as u32, + style: CS_OWNDC, + lpfnWndProc: Some(wnd_proc), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: instance, + hIcon: ptr::null_mut(), + hCursor: ptr::null_mut(), + hbrBackground: ptr::null_mut(), + lpszMenuName: ptr::null_mut(), + lpszClassName: name.as_ptr(), + hIconSm: ptr::null_mut(), + }; + + let atom = unsafe { RegisterClassExA(&window_class) }; + + if atom == 0 { + return Err(crate::InstanceError::with_source( + String::from("unable to register window class"), + Error::last_os_error(), + )); + } + + // We intentionally leak the window class as we only need one per process. + + Ok(name) +} + +fn get_global_window_class() -> Result { + static GLOBAL: Lazy> = + Lazy::new(create_global_window_class); + GLOBAL.clone() +} + +struct InstanceDevice { + dc: HDC, + + /// This is used to keep the thread owning `dc` alive until this struct is dropped. + _tx: SyncSender<()>, +} + +fn create_instance_device() -> Result { + #[derive(Clone, Copy)] + struct SendDc(HDC); + unsafe impl Sync for SendDc {} + unsafe impl Send for SendDc {} + + struct Window { + window: HWND, + } + impl Drop for Window { + fn drop(&mut self) { + unsafe { + if DestroyWindow(self.window) == FALSE { + log::error!("failed to destroy window {}", Error::last_os_error()); + } + }; + } + } + struct DeviceContextHandle { + dc: HDC, + window: HWND, + } + impl Drop for DeviceContextHandle { + fn drop(&mut self) { + unsafe { + ReleaseDC(self.window, self.dc); + }; + } + } + + let window_class = get_global_window_class()?; + + let (drop_tx, drop_rx) = sync_channel(0); + let (setup_tx, setup_rx) = sync_channel(0); + + // We spawn a thread which owns the hidden window for this instance. + thread::Builder::new() + .stack_size(256 * 1024) + .name("wgpu-hal WGL Instance Thread".to_owned()) + .spawn(move || { + let setup = (|| { + let instance = unsafe { GetModuleHandleA(ptr::null()) }; + if instance.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to get executable instance"), + Error::last_os_error(), + )); + } + + // Create a hidden window since we don't pass `WS_VISIBLE`. + let window = unsafe { + CreateWindowExA( + 0, + window_class.as_ptr(), + window_class.as_ptr(), + 0, + 0, + 0, + 1, + 1, + ptr::null_mut(), + ptr::null_mut(), + instance, + ptr::null_mut(), + ) + }; + if window.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create hidden instance window"), + Error::last_os_error(), + )); + } + let window = Window { window }; + + let dc = unsafe { GetDC(window.window) }; + if dc.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create memory device"), + Error::last_os_error(), + )); + } + let dc = DeviceContextHandle { + dc, + window: window.window, + }; + unsafe { setup_pixel_format(dc.dc)? }; + + Ok((window, dc)) + })(); + + match setup { + Ok((_window, dc)) => { + setup_tx.send(Ok(SendDc(dc.dc))).unwrap(); + // Wait for the shutdown event to free the window and device context handle. + drop_rx.recv().ok(); + } + Err(err) => { + setup_tx.send(Err(err)).unwrap(); + } + } + }) + .map_err(|e| { + crate::InstanceError::with_source(String::from("unable to create instance thread"), e) + })?; + + let dc = setup_rx.recv().unwrap()?.0; + + Ok(InstanceDevice { dc, _tx: drop_tx }) +} + +impl crate::Instance for Instance { + unsafe fn init(desc: &crate::InstanceDescriptor) -> Result { + profiling::scope!("Init OpenGL (WGL) Backend"); + let opengl_module = unsafe { LoadLibraryA("opengl32.dll\0".as_ptr() as *const _) }; + if opengl_module.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to load the OpenGL library"), + Error::last_os_error(), + )); + } + + let device = create_instance_device()?; + let dc = device.dc; + + let context = unsafe { wglCreateContext(dc) }; + if context.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create initial OpenGL context"), + Error::last_os_error(), + )); + } + let context = WglContext { context }; + context.make_current(dc).map_err(|e| { + crate::InstanceError::with_source( + String::from("unable to set initial OpenGL context as current"), + e, + ) + })?; + + let extra = Wgl::load_with(|name| load_gl_func(name, None)); + let extentions = extensions(&extra, dc); + + let can_use_profile = extentions.contains("WGL_ARB_create_context_profile") + && extra.CreateContextAttribsARB.is_loaded(); + + let context = if can_use_profile { + let attributes = [ + CONTEXT_PROFILE_MASK_ARB as c_int, + CONTEXT_CORE_PROFILE_BIT_ARB as c_int, + CONTEXT_FLAGS_ARB as c_int, + if desc.flags.contains(InstanceFlags::DEBUG) { + CONTEXT_DEBUG_BIT_ARB as c_int + } else { + 0 + }, + 0, // End of list + ]; + let context = unsafe { + extra.CreateContextAttribsARB(dc as *const _, ptr::null(), attributes.as_ptr()) + }; + if context.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create OpenGL context"), + Error::last_os_error(), + )); + } + WglContext { + context: context as *mut _, + } + } else { + context + }; + + context.make_current(dc).map_err(|e| { + crate::InstanceError::with_source( + String::from("unable to set OpenGL context as current"), + e, + ) + })?; + + let mut gl = unsafe { + glow::Context::from_loader_function(|name| load_gl_func(name, Some(opengl_module))) + }; + + let extra = Wgl::load_with(|name| load_gl_func(name, None)); + let extentions = extensions(&extra, dc); + + let srgb_capable = extentions.contains("WGL_EXT_framebuffer_sRGB") + || extentions.contains("WGL_ARB_framebuffer_sRGB") + || gl + .supported_extensions() + .contains("GL_ARB_framebuffer_sRGB"); + + if srgb_capable { + unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) }; + } + + if desc.flags.contains(InstanceFlags::VALIDATION) && gl.supports_debug() { + log::debug!("Enabling GL debug output"); + unsafe { gl.enable(glow::DEBUG_OUTPUT) }; + unsafe { gl.debug_message_callback(super::gl_debug_message_callback) }; + } + + context.unmake_current().map_err(|e| { + crate::InstanceError::with_source( + String::from("unable to unset the current WGL context"), + e, + ) + })?; + + Ok(Instance { + inner: Arc::new(Mutex::new(Inner { + device, + gl, + context, + })), + srgb_capable, + }) + } + + #[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))] + unsafe fn create_surface( + &self, + _display_handle: RawDisplayHandle, + window_handle: RawWindowHandle, + ) -> Result { + let window = if let RawWindowHandle::Win32(handle) = window_handle { + handle + } else { + return Err(crate::InstanceError::new(format!( + "unsupported window: {window_handle:?}" + ))); + }; + Ok(Surface { + window: window.hwnd.get() as *mut _, + presentable: true, + swapchain: RwLock::new(None), + srgb_capable: self.srgb_capable, + }) + } + unsafe fn destroy_surface(&self, _surface: Surface) {} + + unsafe fn enumerate_adapters(&self) -> Vec> { + unsafe { + super::Adapter::expose(AdapterContext { + inner: self.inner.clone(), + }) + } + .into_iter() + .collect() + } +} + +struct DeviceContextHandle { + device: HDC, + window: HWND, +} + +impl Drop for DeviceContextHandle { + fn drop(&mut self) { + unsafe { + ReleaseDC(self.window, self.device); + }; + } +} + +pub struct Swapchain { + framebuffer: glow::Framebuffer, + renderbuffer: glow::Renderbuffer, + + /// Extent because the window lies + extent: wgt::Extent3d, + + format: wgt::TextureFormat, + format_desc: super::TextureFormatDesc, + #[allow(unused)] + sample_type: wgt::TextureSampleType, +} + +pub struct Surface { + window: HWND, + pub(super) presentable: bool, + swapchain: RwLock>, + srgb_capable: bool, +} + +unsafe impl Send for Surface {} +unsafe impl Sync for Surface {} + +impl Surface { + pub(super) unsafe fn present( + &self, + _suf_texture: super::Texture, + context: &AdapterContext, + ) -> Result<(), crate::SurfaceError> { + let swapchain = self.swapchain.read(); + let sc = swapchain.as_ref().unwrap(); + let dc = unsafe { GetDC(self.window) }; + if dc.is_null() { + log::error!( + "unable to get the device context from window: {}", + Error::last_os_error() + ); + return Err(crate::SurfaceError::Other( + "unable to get the device context from window", + )); + } + let dc = DeviceContextHandle { + device: dc, + window: self.window, + }; + + let inner = context.inner.lock(); + + if let Err(e) = inner.context.make_current(dc.device) { + log::error!("unable to make the OpenGL context current for surface: {e}",); + return Err(crate::SurfaceError::Other( + "unable to make the OpenGL context current for surface", + )); + } + + let gl = &inner.gl; + + unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) }; + unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer)) }; + + if self.srgb_capable { + // Disable sRGB conversions for `glBlitFramebuffer` as behavior does diverge between + // drivers and formats otherwise and we want to ensure no sRGB conversions happen. + unsafe { gl.disable(glow::FRAMEBUFFER_SRGB) }; + } + + // Note the Y-flipping here. GL's presentation is not flipped, + // but main rendering is. Therefore, we Y-flip the output positions + // in the shader, and also this blit. + unsafe { + gl.blit_framebuffer( + 0, + sc.extent.height as i32, + sc.extent.width as i32, + 0, + 0, + 0, + sc.extent.width as i32, + sc.extent.height as i32, + glow::COLOR_BUFFER_BIT, + glow::NEAREST, + ) + }; + + if self.srgb_capable { + unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) }; + } + + unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; + unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; + + if unsafe { SwapBuffers(dc.device) } == FALSE { + log::error!("unable to swap buffers: {}", Error::last_os_error()); + return Err(crate::SurfaceError::Other("unable to swap buffers")); + } + + Ok(()) + } + + pub fn supports_srgb(&self) -> bool { + self.srgb_capable + } +} + +impl crate::Surface for Surface { + unsafe fn configure( + &self, + device: &super::Device, + config: &crate::SurfaceConfiguration, + ) -> Result<(), crate::SurfaceError> { + // Remove the old configuration. + unsafe { self.unconfigure(device) }; + + let dc = unsafe { GetDC(self.window) }; + if dc.is_null() { + log::error!( + "unable to get the device context from window: {}", + Error::last_os_error() + ); + return Err(crate::SurfaceError::Other( + "unable to get the device context from window", + )); + } + let dc = DeviceContextHandle { + device: dc, + window: self.window, + }; + + if let Err(e) = unsafe { setup_pixel_format(dc.device) } { + log::error!("unable to setup surface pixel format: {e}",); + return Err(crate::SurfaceError::Other( + "unable to setup surface pixel format", + )); + } + + let format_desc = device.shared.describe_texture_format(config.format); + let inner = &device.shared.context.inner.lock(); + + if let Err(e) = inner.context.make_current(dc.device) { + log::error!("unable to make the OpenGL context current for surface: {e}",); + return Err(crate::SurfaceError::Other( + "unable to make the OpenGL context current for surface", + )); + } + + let gl = &inner.gl; + let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| { + log::error!("Internal swapchain renderbuffer creation failed: {error}"); + crate::DeviceError::OutOfMemory + })?; + unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) }; + unsafe { + gl.renderbuffer_storage( + glow::RENDERBUFFER, + format_desc.internal, + config.extent.width as _, + config.extent.height as _, + ) + }; + + let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| { + log::error!("Internal swapchain framebuffer creation failed: {error}"); + crate::DeviceError::OutOfMemory + })?; + unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) }; + unsafe { + gl.framebuffer_renderbuffer( + glow::READ_FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, + glow::RENDERBUFFER, + Some(renderbuffer), + ) + }; + unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; + unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; + + // Setup presentation mode + let extra = Wgl::load_with(|name| load_gl_func(name, None)); + let extentions = extensions(&extra, dc.device); + if !(extentions.contains("WGL_EXT_swap_control") && extra.SwapIntervalEXT.is_loaded()) { + log::error!("WGL_EXT_swap_control is unsupported"); + return Err(crate::SurfaceError::Other( + "WGL_EXT_swap_control is unsupported", + )); + } + + let vsync = match config.present_mode { + wgt::PresentMode::Immediate => false, + wgt::PresentMode::Fifo => true, + _ => { + log::error!("unsupported present mode: {:?}", config.present_mode); + return Err(crate::SurfaceError::Other("unsupported present mode")); + } + }; + + if unsafe { extra.SwapIntervalEXT(if vsync { 1 } else { 0 }) } == FALSE { + log::error!("unable to set swap interval: {}", Error::last_os_error()); + return Err(crate::SurfaceError::Other("unable to set swap interval")); + } + + self.swapchain.write().replace(Swapchain { + renderbuffer, + framebuffer, + extent: config.extent, + format: config.format, + format_desc, + sample_type: wgt::TextureSampleType::Float { filterable: false }, + }); + + Ok(()) + } + + unsafe fn unconfigure(&self, device: &super::Device) { + let gl = &device.shared.context.lock(); + if let Some(sc) = self.swapchain.write().take() { + unsafe { + gl.delete_renderbuffer(sc.renderbuffer); + gl.delete_framebuffer(sc.framebuffer) + }; + } + } + + unsafe fn acquire_texture( + &self, + _timeout_ms: Option, + ) -> Result>, crate::SurfaceError> { + let swapchain = self.swapchain.read(); + let sc = swapchain.as_ref().unwrap(); + let texture = super::Texture { + inner: super::TextureInner::Renderbuffer { + raw: sc.renderbuffer, + }, + drop_guard: None, + array_layer_count: 1, + mip_level_count: 1, + format: sc.format, + format_desc: sc.format_desc.clone(), + copy_size: crate::CopyExtent { + width: sc.extent.width, + height: sc.extent.height, + depth: 1, + }, + }; + Ok(Some(crate::AcquiredSurfaceTexture { + texture, + suboptimal: false, + })) + } + unsafe fn discard_texture(&self, _texture: super::Texture) {} +} diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index c73642cc2c..7bd6eb77bb 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -11,7 +11,7 @@ * General design direction is to follow the majority by the following weights: * - wgpu-core: 1.5 * - primary backends (Vulkan/Metal/DX12): 1.0 each - * - secondary backends (DX11/GLES): 0.5 each + * - secondary backend (GLES): 0.5 */ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] @@ -51,36 +51,31 @@ clippy::pattern_type_mismatch, )] -/// DirectX11 API internals. -#[cfg(all(feature = "dx11", windows))] -pub mod dx11; /// DirectX12 API internals. -#[cfg(all(feature = "dx12", windows))] +#[cfg(dx12)] pub mod dx12; /// A dummy API implementation. pub mod empty; /// GLES API internals. -#[cfg(feature = "gles")] +#[cfg(gles)] pub mod gles; /// Metal API internals. -#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] +#[cfg(metal)] pub mod metal; /// Vulkan API internals. -#[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] +#[cfg(vulkan)] pub mod vulkan; pub mod auxil; pub mod api { - #[cfg(all(feature = "dx11", windows))] - pub use super::dx11::Api as Dx11; - #[cfg(all(feature = "dx12", windows))] + #[cfg(dx12)] pub use super::dx12::Api as Dx12; pub use super::empty::Api as Empty; - #[cfg(feature = "gles")] + #[cfg(gles)] pub use super::gles::Api as Gles; - #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + #[cfg(metal)] pub use super::metal::Api as Metal; - #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + #[cfg(vulkan)] pub use super::vulkan::Api as Vulkan; } @@ -90,13 +85,17 @@ use std::{ num::NonZeroU32, ops::{Range, RangeInclusive}, ptr::NonNull, - sync::{atomic::AtomicBool, Arc}, + sync::Arc, }; use bitflags::bitflags; +use parking_lot::Mutex; use thiserror::Error; -use wgt::{WasmNotSend, WasmNotSync}; +use wgt::WasmNotSendSync; +// - Vertex + Fragment +// - Compute +pub const MAX_CONCURRENT_SHADER_STAGES: usize = 2; pub const MAX_ANISOTROPY: u8 = 16; pub const MAX_BIND_GROUPS: usize = 8; pub const MAX_VERTEX_BUFFERS: usize = 16; @@ -189,7 +188,7 @@ impl InstanceError { } } -pub trait Api: Clone + Sized { +pub trait Api: Clone + fmt::Debug + Sized { type Instance: Instance; type Surface: Surface; type Adapter: Adapter; @@ -197,27 +196,27 @@ pub trait Api: Clone + Sized { type Queue: Queue; type CommandEncoder: CommandEncoder; - type CommandBuffer: WasmNotSend + WasmNotSync + fmt::Debug; + type CommandBuffer: WasmNotSendSync + fmt::Debug; - type Buffer: fmt::Debug + WasmNotSend + WasmNotSync + 'static; - type Texture: fmt::Debug + WasmNotSend + WasmNotSync + 'static; - type SurfaceTexture: fmt::Debug + WasmNotSend + WasmNotSync + Borrow; - type TextureView: fmt::Debug + WasmNotSend + WasmNotSync; - type Sampler: fmt::Debug + WasmNotSend + WasmNotSync; - type QuerySet: fmt::Debug + WasmNotSend + WasmNotSync; - type Fence: fmt::Debug + WasmNotSend + WasmNotSync; + type Buffer: fmt::Debug + WasmNotSendSync + 'static; + type Texture: fmt::Debug + WasmNotSendSync + 'static; + type SurfaceTexture: fmt::Debug + WasmNotSendSync + Borrow; + type TextureView: fmt::Debug + WasmNotSendSync; + type Sampler: fmt::Debug + WasmNotSendSync; + type QuerySet: fmt::Debug + WasmNotSendSync; + type Fence: fmt::Debug + WasmNotSendSync; - type BindGroupLayout: WasmNotSend + WasmNotSync; - type BindGroup: fmt::Debug + WasmNotSend + WasmNotSync; - type PipelineLayout: WasmNotSend + WasmNotSync; - type ShaderModule: fmt::Debug + WasmNotSend + WasmNotSync; - type RenderPipeline: WasmNotSend + WasmNotSync; - type ComputePipeline: WasmNotSend + WasmNotSync; + type BindGroupLayout: fmt::Debug + WasmNotSendSync; + type BindGroup: fmt::Debug + WasmNotSendSync; + type PipelineLayout: fmt::Debug + WasmNotSendSync; + type ShaderModule: fmt::Debug + WasmNotSendSync; + type RenderPipeline: fmt::Debug + WasmNotSendSync; + type ComputePipeline: fmt::Debug + WasmNotSendSync; - type AccelerationStructure: fmt::Debug + WasmNotSend + WasmNotSync + 'static; + type AccelerationStructure: fmt::Debug + WasmNotSendSync + 'static; } -pub trait Instance: Sized + WasmNotSend + WasmNotSync { +pub trait Instance: Sized + WasmNotSendSync { unsafe fn init(desc: &InstanceDescriptor) -> Result; unsafe fn create_surface( &self, @@ -228,14 +227,30 @@ pub trait Instance: Sized + WasmNotSend + WasmNotSync { unsafe fn enumerate_adapters(&self) -> Vec>; } -pub trait Surface: WasmNotSend + WasmNotSync { +pub trait Surface: WasmNotSendSync { + /// Configures the surface to use the given device. + /// + /// # Safety + /// + /// - All gpu work that uses the surface must have been completed. + /// - All [`AcquiredSurfaceTexture`]s must have been destroyed. + /// - All [`Api::TextureView`]s derived from the [`AcquiredSurfaceTexture`]s must have been destroyed. + /// - All surfaces created using other devices must have been unconfigured before this call. unsafe fn configure( - &mut self, + &self, device: &A::Device, config: &SurfaceConfiguration, ) -> Result<(), SurfaceError>; - unsafe fn unconfigure(&mut self, device: &A::Device); + /// Unconfigures the surface on the given device. + /// + /// # Safety + /// + /// - All gpu work that uses the surface must have been completed. + /// - All [`AcquiredSurfaceTexture`]s must have been destroyed. + /// - All [`Api::TextureView`]s derived from the [`AcquiredSurfaceTexture`]s must have been destroyed. + /// - The surface must have been configured on the given device. + unsafe fn unconfigure(&self, device: &A::Device); /// Returns the next texture to be presented by the swapchain for drawing /// @@ -248,13 +263,13 @@ pub trait Surface: WasmNotSend + WasmNotSync { /// /// Returns `None` on timing out. unsafe fn acquire_texture( - &mut self, + &self, timeout: Option, ) -> Result>, SurfaceError>; - unsafe fn discard_texture(&mut self, texture: A::SurfaceTexture); + unsafe fn discard_texture(&self, texture: A::SurfaceTexture); } -pub trait Adapter: WasmNotSend + WasmNotSync { +pub trait Adapter: WasmNotSendSync { unsafe fn open( &self, features: wgt::Features, @@ -278,7 +293,7 @@ pub trait Adapter: WasmNotSend + WasmNotSync { unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp; } -pub trait Device: WasmNotSend + WasmNotSync { +pub trait Device: WasmNotSendSync { /// Exit connection to this logical device. unsafe fn exit(self, queue: A::Queue); /// Creates a new buffer. @@ -391,7 +406,7 @@ pub trait Device: WasmNotSend + WasmNotSync { ); } -pub trait Queue: WasmNotSend + WasmNotSync { +pub trait Queue: WasmNotSendSync { /// Submits the command buffers for execution on GPU. /// /// Valid usage: @@ -399,13 +414,13 @@ pub trait Queue: WasmNotSend + WasmNotSync { /// that are associated with this queue. /// - all of the command buffers had `CommadBuffer::finish()` called. unsafe fn submit( - &mut self, + &self, command_buffers: &[&A::CommandBuffer], signal_fence: Option<(&mut A::Fence, FenceValue)>, ) -> Result<(), DeviceError>; unsafe fn present( - &mut self, - surface: &mut A::Surface, + &self, + surface: &A::Surface, texture: A::SurfaceTexture, ) -> Result<(), SurfaceError>; unsafe fn get_timestamp_period(&self) -> f32; @@ -415,7 +430,7 @@ pub trait Queue: WasmNotSend + WasmNotSync { /// Serves as a parent for all the encoded command buffers. /// Works in bursts of action: one or more command buffers are recorded, /// then submitted to a queue, and then it needs to be `reset_all()`. -pub trait CommandEncoder: WasmNotSend + WasmNotSync + fmt::Debug { +pub trait CommandEncoder: WasmNotSendSync + fmt::Debug { /// Begin encoding a new command buffer. unsafe fn begin_encoding(&mut self, label: Label) -> Result<(), DeviceError>; /// Discard currently recorded list, if any. @@ -448,7 +463,7 @@ pub trait CommandEncoder: WasmNotSend + WasmNotSync + fmt::Debug { /// Works with a single array layer. /// Note: `dst` current usage has to be `TextureUses::COPY_DST`. /// Note: the copy extent is in physical size (rounded to the block size) - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(webgl)] unsafe fn copy_external_image_to_texture( &mut self, src: &wgt::ImageCopyExternalImage, @@ -503,11 +518,19 @@ pub trait CommandEncoder: WasmNotSend + WasmNotSync + fmt::Debug { dynamic_offsets: &[wgt::DynamicOffset], ); + /// Sets a range in push constant data. + /// + /// IMPORTANT: while the data is passed as words, the offset is in bytes! + /// + /// # Safety + /// + /// - `offset_bytes` must be a multiple of 4. + /// - The range of push constants written must be valid for the pipeline layout at draw time. unsafe fn set_push_constants( &mut self, layout: &A::PipelineLayout, stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ); @@ -557,17 +580,17 @@ pub trait CommandEncoder: WasmNotSend + WasmNotSync + fmt::Debug { unsafe fn draw( &mut self, - start_vertex: u32, + first_vertex: u32, vertex_count: u32, - start_instance: u32, + first_instance: u32, instance_count: u32, ); unsafe fn draw_indexed( &mut self, - start_index: u32, + first_index: u32, index_count: u32, base_vertex: i32, - start_instance: u32, + first_instance: u32, instance_count: u32, ); unsafe fn draw_indirect( @@ -631,23 +654,12 @@ pub trait CommandEncoder: WasmNotSend + WasmNotSync + fmt::Debug { ); } -bitflags!( - /// Instance initialization flags. - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - pub struct InstanceFlags: u32 { - /// Generate debug information in shaders and objects. - const DEBUG = 1 << 0; - /// Enable validation, if possible. - const VALIDATION = 1 << 1; - } -); - bitflags!( /// Pipeline layout creation flags. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PipelineLayoutFlags: u32 { - /// Include support for base vertex/instance drawing. - const BASE_VERTEX_INSTANCE = 1 << 0; + /// Include support for `first_vertex` / `first_instance` drawing. + const FIRST_VERTEX_INSTANCE = 1 << 0; /// Include support for num work groups builtin. const NUM_WORK_GROUPS = 1 << 1; } @@ -713,6 +725,11 @@ bitflags!( const COLOR = 1 << 0; const DEPTH = 1 << 1; const STENCIL = 1 << 2; + const PLANE_0 = 1 << 3; + const PLANE_1 = 1 << 4; + const PLANE_2 = 1 << 5; + + const DEPTH_STENCIL = Self::DEPTH.bits() | Self::STENCIL.bits(); } ); @@ -722,6 +739,9 @@ impl FormatAspects { wgt::TextureAspect::All => Self::all(), wgt::TextureAspect::DepthOnly => Self::DEPTH, wgt::TextureAspect::StencilOnly => Self::STENCIL, + wgt::TextureAspect::Plane0 => Self::PLANE_0, + wgt::TextureAspect::Plane1 => Self::PLANE_1, + wgt::TextureAspect::Plane2 => Self::PLANE_2, }; Self::from(format) & aspect_mask } @@ -736,6 +756,9 @@ impl FormatAspects { Self::COLOR => wgt::TextureAspect::All, Self::DEPTH => wgt::TextureAspect::DepthOnly, Self::STENCIL => wgt::TextureAspect::StencilOnly, + Self::PLANE_0 => wgt::TextureAspect::Plane0, + Self::PLANE_1 => wgt::TextureAspect::Plane1, + Self::PLANE_2 => wgt::TextureAspect::Plane2, _ => unreachable!(), } } @@ -749,8 +772,9 @@ impl From for FormatAspects { | wgt::TextureFormat::Depth32Float | wgt::TextureFormat::Depth24Plus => Self::DEPTH, wgt::TextureFormat::Depth32FloatStencil8 | wgt::TextureFormat::Depth24PlusStencil8 => { - Self::DEPTH | Self::STENCIL + Self::DEPTH_STENCIL } + wgt::TextureFormat::NV12 => Self::PLANE_0 | Self::PLANE_1, _ => Self::COLOR, } } @@ -860,7 +884,7 @@ bitflags::bitflags! { #[derive(Clone, Debug)] pub struct InstanceDescriptor<'a> { pub name: &'a str, - pub flags: InstanceFlags, + pub flags: wgt::InstanceFlags, pub dx12_shader_compiler: wgt::Dx12Compiler, pub gles_minor_version: wgt::Gles3MinorVersion, } @@ -907,11 +931,6 @@ pub struct SurfaceCapabilities { /// Current extent of the surface, if known. pub current_extent: Option, - /// Range of supported extents. - /// - /// `current_extent` must be inside this range. - pub extents: RangeInclusive, - /// Supported texture usage flags. /// /// Must have at least `TextureUses::COLOR_TARGET` @@ -1135,6 +1154,8 @@ pub struct NagaShader { pub module: Cow<'static, naga::Module>, /// Analysis information of the module. pub info: naga::valid::ModuleInfo, + /// Source codes for debug + pub debug_source: Option, } // Custom implementation avoids the need to generate Debug impl code @@ -1157,6 +1178,12 @@ pub struct ShaderModuleDescriptor<'a> { pub runtime_checks: bool, } +#[derive(Debug, Clone)] +pub struct DebugSource { + pub file_name: Cow<'static, str>, + pub source_code: Cow<'static, str>, +} + /// Describes a programmable pipeline stage. #[derive(Debug)] pub struct ProgrammableStage<'a, A: Api> { @@ -1403,8 +1430,11 @@ pub struct ComputePassDescriptor<'a, A: Api> { pub timestamp_writes: Option>, } -/// Stores if any API validation error has occurred in this process -/// since it was last reset. +/// Stores the text of any validation errors that have occurred since +/// the last call to `get_and_reset`. +/// +/// Each value is a validation error and a message associated with it, +/// or `None` if the error has no message from the api. /// /// This is used for internal wgpu testing only and _must not_ be used /// as a way to check for errors. @@ -1415,24 +1445,24 @@ pub struct ComputePassDescriptor<'a, A: Api> { /// This prevents the issue of one validation error terminating the /// entire process. pub static VALIDATION_CANARY: ValidationCanary = ValidationCanary { - inner: AtomicBool::new(false), + inner: Mutex::new(Vec::new()), }; /// Flag for internal testing. pub struct ValidationCanary { - inner: AtomicBool, + inner: Mutex>, } impl ValidationCanary { #[allow(dead_code)] // in some configurations this function is dead - fn set(&self) { - self.inner.store(true, std::sync::atomic::Ordering::SeqCst); + fn add(&self, msg: String) { + self.inner.lock().push(msg); } - /// Returns true if any API validation error has occurred in this process + /// Returns any API validation errors that hav occurred in this process /// since the last call to this function. - pub fn get_and_reset(&self) -> bool { - self.inner.swap(false, std::sync::atomic::Ordering::SeqCst) + pub fn get_and_reset(&self) -> Vec { + self.inner.lock().drain(..).collect() } } @@ -1553,8 +1583,8 @@ pub struct AccelerationStructureTriangleTransform<'a, A: Api> { pub offset: u32, } -pub type AccelerationStructureBuildFlags = wgt::AccelerationStructureFlags; -pub type AccelerationStructureGeometryFlags = wgt::AccelerationStructureGeometryFlags; +pub use wgt::AccelerationStructureFlags as AccelerationStructureBuildFlags; +pub use wgt::AccelerationStructureGeometryFlags; bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index bc90954b35..3d8f6f3e57 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -5,6 +5,8 @@ use wgt::{AstcBlock, AstcChannel}; use std::{sync::Arc, thread}; +use super::TimestampQuerySupport; + const MAX_COMMAND_BUFFERS: u64 = 2048; unsafe impl Send for super::Adapter {} @@ -164,6 +166,11 @@ impl crate::Adapter for super::Adapter { flags.set(Tfc::STORAGE, pc.format_rgba8_srgb_all); flags } + Tf::Rgb10a2Uint => { + let mut flags = Tfc::COLOR_ATTACHMENT | msaa_count; + flags.set(Tfc::STORAGE, pc.format_rgb10a2_uint_write); + flags + } Tf::Rgb10a2Unorm => { let mut flags = all_caps; flags.set(Tfc::STORAGE, pc.format_rgb10a2_unorm_all); @@ -225,6 +232,7 @@ impl crate::Adapter for super::Adapter { } flags } + Tf::NV12 => return Tfc::empty(), Tf::Rgb9e5Ufloat => { if pc.msaa_apple3 { all_caps @@ -331,16 +339,9 @@ impl crate::Adapter for super::Adapter { ], current_extent, - extents: wgt::Extent3d { - width: 4, - height: 4, - depth_or_array_layers: 1, - }..=wgt::Extent3d { - width: pc.max_texture_size as u32, - height: pc.max_texture_size as u32, - depth_or_array_layers: 1, - }, - usage: crate::TextureUses::COLOR_TARGET | crate::TextureUses::COPY_DST, //TODO: expose more + usage: crate::TextureUses::COLOR_TARGET + | crate::TextureUses::COPY_SRC + | crate::TextureUses::COPY_DST, }) } @@ -397,7 +398,7 @@ const RGB10A2UNORM_ALL: &[MTLFeatureSet] = &[ MTLFeatureSet::macOS_GPUFamily1_v1, ]; -const RGB10A2UINT_COLOR_WRITE: &[MTLFeatureSet] = &[ +const RGB10A2UINT_WRITE: &[MTLFeatureSet] = &[ MTLFeatureSet::iOS_GPUFamily3_v1, MTLFeatureSet::tvOS_GPUFamily2_v1, MTLFeatureSet::macOS_GPUFamily1_v1, @@ -420,17 +421,17 @@ const BGR10A2_ALL: &[MTLFeatureSet] = &[ MTLFeatureSet::macOS_GPUFamily2_v1, ]; -const BASE_INSTANCE_SUPPORT: &[MTLFeatureSet] = &[ +/// "Indirect draw & dispatch arguments" in the Metal feature set tables +const INDIRECT_DRAW_DISPATCH_SUPPORT: &[MTLFeatureSet] = &[ MTLFeatureSet::iOS_GPUFamily3_v1, MTLFeatureSet::tvOS_GPUFamily2_v1, MTLFeatureSet::macOS_GPUFamily1_v1, ]; -const BASE_VERTEX_INSTANCE_SUPPORT: &[MTLFeatureSet] = &[ - MTLFeatureSet::iOS_GPUFamily3_v1, - MTLFeatureSet::tvOS_GPUFamily2_v1, - MTLFeatureSet::macOS_GPUFamily1_v1, -]; +/// "Base vertex/instance drawing" in the Metal feature set tables +/// +/// in our terms, `base_vertex` and `first_instance` must be 0 +const BASE_VERTEX_FIRST_INSTANCE_SUPPORT: &[MTLFeatureSet] = INDIRECT_DRAW_DISPATCH_SUPPORT; const TEXTURE_CUBE_ARRAY_SUPPORT: &[MTLFeatureSet] = &[ MTLFeatureSet::iOS_GPUFamily4_v1, @@ -536,6 +537,26 @@ impl super::PrivateCapabilities { MTLReadWriteTextureTier::TierNone }; + let mut timestamp_query_support = TimestampQuerySupport::empty(); + if version.at_least((11, 0), (14, 0), os_is_mac) + && device.supports_counter_sampling(metal::MTLCounterSamplingPoint::AtStageBoundary) + { + // If we don't support at stage boundary, don't support anything else. + timestamp_query_support.insert(TimestampQuerySupport::STAGE_BOUNDARIES); + + if device.supports_counter_sampling(metal::MTLCounterSamplingPoint::AtDrawBoundary) { + timestamp_query_support.insert(TimestampQuerySupport::ON_RENDER_ENCODER); + } + if device.supports_counter_sampling(metal::MTLCounterSamplingPoint::AtDispatchBoundary) + { + timestamp_query_support.insert(TimestampQuerySupport::ON_COMPUTE_ENCODER); + } + if device.supports_counter_sampling(metal::MTLCounterSamplingPoint::AtBlitBoundary) { + timestamp_query_support.insert(TimestampQuerySupport::ON_BLIT_ENCODER); + } + // `TimestampQuerySupport::INSIDE_WGPU_PASSES` emerges from the other flags. + } + Self { family_check, msl_version: if os_is_xr || version.at_least((12, 0), (15, 0), os_is_mac) { @@ -573,8 +594,11 @@ impl super::PrivateCapabilities { MUTABLE_COMPARISON_SAMPLER_SUPPORT, ), sampler_clamp_to_border: Self::supports_any(device, SAMPLER_CLAMP_TO_BORDER_SUPPORT), - base_instance: Self::supports_any(device, BASE_INSTANCE_SUPPORT), - base_vertex_instance_drawing: Self::supports_any(device, BASE_VERTEX_INSTANCE_SUPPORT), + indirect_draw_dispatch: Self::supports_any(device, INDIRECT_DRAW_DISPATCH_SUPPORT), + base_vertex_first_instance_drawing: Self::supports_any( + device, + BASE_VERTEX_FIRST_INSTANCE_SUPPORT, + ), dual_source_blending: Self::supports_any(device, DUAL_SOURCE_BLEND_SUPPORT), low_power: !os_is_mac || device.is_low_power(), headless: os_is_mac && device.is_headless(), @@ -582,6 +606,9 @@ impl super::PrivateCapabilities { function_specialization: Self::supports_any(device, FUNCTION_SPECIALIZATION_SUPPORT), depth_clip_mode: Self::supports_any(device, DEPTH_CLIP_MODE), texture_cube_array: Self::supports_any(device, TEXTURE_CUBE_ARRAY_SUPPORT), + supports_float_filtering: os_is_mac + || (version.at_least((11, 0), (14, 0), os_is_mac) + && device.supports_32bit_float_filtering()), format_depth24_stencil8: os_is_mac && device.d24_s8_supported(), format_depth32_stencil8_filter: os_is_mac, format_depth32_stencil8_none: !os_is_mac, @@ -614,8 +641,7 @@ impl super::PrivateCapabilities { format_rgba8_srgb_no_write: !Self::supports_any(device, RGBA8_SRGB), format_rgb10a2_unorm_all: Self::supports_any(device, RGB10A2UNORM_ALL), format_rgb10a2_unorm_no_write: !Self::supports_any(device, RGB10A2UNORM_ALL), - format_rgb10a2_uint_color: !Self::supports_any(device, RGB10A2UINT_COLOR_WRITE), - format_rgb10a2_uint_color_write: Self::supports_any(device, RGB10A2UINT_COLOR_WRITE), + format_rgb10a2_uint_write: Self::supports_any(device, RGB10A2UINT_WRITE), format_rg11b10_all: Self::supports_any(device, RG11B10FLOAT_ALL), format_rg11b10_no_write: !Self::supports_any(device, RG11B10FLOAT_ALL), format_rgb9e5_all: Self::supports_any(device, RGB9E5FLOAT_ALL), @@ -649,7 +675,7 @@ impl super::PrivateCapabilities { format_bgr10a2_all: Self::supports_any(device, BGR10A2_ALL), format_bgr10a2_no_write: !Self::supports_any(device, BGR10A2_ALL), max_buffers_per_stage: 31, - max_vertex_buffers: 31, + max_vertex_buffers: 31.min(crate::MAX_VERTEX_BUFFERS as u32), max_textures_per_stage: if os_is_mac || (family_check && device.supports_family(MTLGPUFamily::Apple6)) { @@ -773,13 +799,7 @@ impl super::PrivateCapabilities { } else { None }, - support_timestamp_query: version.at_least((11, 0), (14, 0), os_is_mac) - && device - .supports_counter_sampling(metal::MTLCounterSamplingPoint::AtStageBoundary), - support_timestamp_query_in_passes: version.at_least((11, 0), (14, 0), os_is_mac) - && device.supports_counter_sampling(metal::MTLCounterSamplingPoint::AtDrawBoundary) - && device - .supports_counter_sampling(metal::MTLCounterSamplingPoint::AtDispatchBoundary), + timestamp_query_support, } } @@ -795,7 +815,6 @@ impl super::PrivateCapabilities { use wgt::Features as F; let mut features = F::empty() - | F::INDIRECT_FIRST_INSTANCE | F::MAPPABLE_PRIMARY_BUFFERS | F::VERTEX_WRITABLE_STORAGE | F::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES @@ -805,14 +824,27 @@ impl super::PrivateCapabilities { | F::TEXTURE_FORMAT_16BIT_NORM | F::SHADER_F16 | F::DEPTH32FLOAT_STENCIL8 - | F::MULTI_DRAW_INDIRECT; - - features.set(F::TIMESTAMP_QUERY, self.support_timestamp_query); - // TODO: Not yet implemented. - // features.set( - // F::TIMESTAMP_QUERY_INSIDE_PASSES, - // self.support_timestamp_query_in_passes, - // ); + | F::BGRA8UNORM_STORAGE; + + features.set(F::FLOAT32_FILTERABLE, self.supports_float_filtering); + features.set( + F::INDIRECT_FIRST_INSTANCE | F::MULTI_DRAW_INDIRECT, + self.indirect_draw_dispatch, + ); + features.set( + F::TIMESTAMP_QUERY, + self.timestamp_query_support + .contains(TimestampQuerySupport::STAGE_BOUNDARIES), + ); + features.set( + F::TIMESTAMP_QUERY_INSIDE_PASSES, + self.timestamp_query_support + .contains(TimestampQuerySupport::INSIDE_WGPU_PASSES), + ); + features.set( + F::DUAL_SOURCE_BLENDING, + self.msl_version >= MTLLanguageVersion::V1_2 && self.dual_source_blending, + ); features.set(F::TEXTURE_COMPRESSION_ASTC, self.format_astc); features.set(F::TEXTURE_COMPRESSION_ASTC_HDR, self.format_astc_hdr); features.set(F::TEXTURE_COMPRESSION_BC, self.format_bc); @@ -847,6 +879,7 @@ impl super::PrivateCapabilities { features.set(F::ADDRESS_MODE_CLAMP_TO_ZERO, true); features.set(F::RG11B10UFLOAT_RENDERABLE, self.format_rg11b10_all); + features.set(F::SHADER_UNUSED_VERTEX_OUTPUT, true); features } @@ -861,11 +894,20 @@ impl super::PrivateCapabilities { wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES, self.texture_cube_array, ); - //TODO: separate the mutable comparisons from immutable ones + // TODO: separate the mutable comparisons from immutable ones downlevel.flags.set( wgt::DownlevelFlags::COMPARISON_SAMPLERS, self.mutable_comparison_samplers, ); + downlevel.flags.set( + wgt::DownlevelFlags::INDIRECT_EXECUTION, + self.indirect_draw_dispatch, + ); + // TODO: add another flag for `first_instance` + downlevel.flags.set( + wgt::DownlevelFlags::BASE_VERTEX, + self.base_vertex_first_instance_drawing, + ); downlevel .flags .set(wgt::DownlevelFlags::ANISOTROPIC_FILTERING, true); @@ -946,6 +988,7 @@ impl super::PrivateCapabilities { Tf::Bgra8Unorm => BGRA8Unorm, Tf::Rgba8Uint => RGBA8Uint, Tf::Rgba8Sint => RGBA8Sint, + Tf::Rgb10a2Uint => RGB10A2Uint, Tf::Rgb10a2Unorm => RGB10A2Unorm, Tf::Rg11b10Float => RG11B10Float, Tf::Rg32Uint => RG32Uint, @@ -977,6 +1020,7 @@ impl super::PrivateCapabilities { Depth32Float_Stencil8 } } + Tf::NV12 => unreachable!(), Tf::Rgb9e5Ufloat => RGB9E5Float, Tf::Bc1RgbaUnorm => BC1_RGBA, Tf::Bc1RgbaUnormSrgb => BC1_RGBA_sRGB, diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index 35edf746c5..6f1a0d9c2f 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -1,4 +1,4 @@ -use super::{conv, AsNative}; +use super::{conv, AsNative, TimestampQuerySupport}; use crate::CommandEncoder as _; use std::{borrow::Cow, mem, ops::Range}; @@ -18,6 +18,7 @@ impl Default for super::CommandState { storage_buffer_length_map: Default::default(), work_group_memory_sizes: Vec::new(), push_constants: Vec::new(), + pending_timer_queries: Vec::new(), } } } @@ -26,10 +27,85 @@ impl super::CommandEncoder { fn enter_blit(&mut self) -> &metal::BlitCommandEncoderRef { if self.state.blit.is_none() { debug_assert!(self.state.render.is_none() && self.state.compute.is_none()); + let cmd_buf = self.raw_cmd_buf.as_ref().unwrap(); + + // Take care of pending timer queries. + // If we can't use `sample_counters_in_buffer` we have to create a dummy blit encoder! + // + // There is a known bug in Metal where blit encoders won't write timestamps if they don't have a blit operation. + // See https://github.com/gpuweb/gpuweb/issues/2046#issuecomment-1205793680 & https://source.chromium.org/chromium/chromium/src/+/006c4eb70c96229834bbaf271290f40418144cd3:third_party/dawn/src/dawn/native/metal/BackendMTL.mm;l=350 + // + // To make things worse: + // * what counts as a blit operation is a bit unclear, experimenting seemed to indicate that resolve_counters doesn't count. + // * in some cases (when?) using `set_start_of_encoder_sample_index` doesn't work, so we have to use `set_end_of_encoder_sample_index` instead + // + // All this means that pretty much the only *reliable* thing as of writing is to: + // * create a dummy blit encoder using set_end_of_encoder_sample_index + // * do a dummy write that is known to be not optimized out. + // * close the encoder since we used set_end_of_encoder_sample_index and don't want to get any extra stuff in there. + // * create another encoder for whatever we actually had in mind. + let supports_sample_counters_in_buffer = self + .shared + .private_caps + .timestamp_query_support + .contains(TimestampQuerySupport::ON_BLIT_ENCODER); + + if !self.state.pending_timer_queries.is_empty() && !supports_sample_counters_in_buffer { + objc::rc::autoreleasepool(|| { + let descriptor = metal::BlitPassDescriptor::new(); + let mut last_query = None; + for (i, (set, index)) in self.state.pending_timer_queries.drain(..).enumerate() + { + let sba_descriptor = descriptor + .sample_buffer_attachments() + .object_at(i as _) + .unwrap(); + sba_descriptor + .set_sample_buffer(set.counter_sample_buffer.as_ref().unwrap()); + + // Here be dragons: + // As mentioned above, for some reasons using the start of the encoder won't yield any results sometimes! + sba_descriptor + .set_start_of_encoder_sample_index(metal::COUNTER_DONT_SAMPLE); + sba_descriptor.set_end_of_encoder_sample_index(index as _); + + last_query = Some((set, index)); + } + let encoder = cmd_buf.blit_command_encoder_with_descriptor(descriptor); + + // As explained above, we need to do some write: + // Conveniently, we have a buffer with every query set, that we can use for this for a dummy write, + // since we know that it is going to be overwritten again on timer resolve and HAL doesn't define its state before that. + let raw_range = metal::NSRange { + location: last_query.as_ref().unwrap().1 as u64 * crate::QUERY_SIZE, + length: 1, + }; + encoder.fill_buffer( + &last_query.as_ref().unwrap().0.raw_buffer, + raw_range, + 255, // Don't write 0, so it's easier to identify if something went wrong. + ); + + encoder.end_encoding(); + }); + } + objc::rc::autoreleasepool(|| { - let cmd_buf = self.raw_cmd_buf.as_ref().unwrap(); self.state.blit = Some(cmd_buf.new_blit_command_encoder().to_owned()); }); + + let encoder = self.state.blit.as_ref().unwrap(); + + // UNTESTED: + // If the above described issue with empty blit encoder applies to `sample_counters_in_buffer` as well, we should use the same workaround instead! + for (set, index) in self.state.pending_timer_queries.drain(..) { + debug_assert!(supports_sample_counters_in_buffer); + encoder.sample_counters_in_buffer( + set.counter_sample_buffer.as_ref().unwrap(), + index as _, + true, + ) + } } self.state.blit.as_ref().unwrap() } @@ -40,7 +116,7 @@ impl super::CommandEncoder { } } - fn enter_any(&mut self) -> Option<&metal::CommandEncoderRef> { + fn active_encoder(&mut self) -> Option<&metal::CommandEncoderRef> { if let Some(ref encoder) = self.state.render { Some(encoder) } else if let Some(ref encoder) = self.state.compute { @@ -127,9 +203,17 @@ impl crate::CommandEncoder for super::CommandEncoder { } unsafe fn end_encoding(&mut self) -> Result { + // Handle pending timer query if any. + if !self.state.pending_timer_queries.is_empty() { + self.leave_blit(); + self.enter_blit(); + } + self.leave_blit(); debug_assert!(self.state.render.is_none()); debug_assert!(self.state.compute.is_none()); + debug_assert!(self.state.pending_timer_queries.is_empty()); + Ok(super::CommandBuffer { raw: self.raw_cmd_buf.take().unwrap(), }) @@ -322,16 +406,43 @@ impl crate::CommandEncoder for super::CommandEncoder { _ => {} } } - unsafe fn write_timestamp(&mut self, _set: &super::QuerySet, _index: u32) { - // TODO: If MTLCounterSamplingPoint::AtDrawBoundary/AtBlitBoundary/AtDispatchBoundary is supported, - // we don't need to insert a new encoder, but can instead use respective current one. - //let encoder = self.enter_any().unwrap_or_else(|| self.enter_blit()); + unsafe fn write_timestamp(&mut self, set: &super::QuerySet, index: u32) { + let support = self.shared.private_caps.timestamp_query_support; + debug_assert!( + support.contains(TimestampQuerySupport::STAGE_BOUNDARIES), + "Timestamp queries are not supported" + ); + let sample_buffer = set.counter_sample_buffer.as_ref().unwrap(); + let with_barrier = true; + + // Try to use an existing encoder for timestamp query if possible. + // This works only if it's supported for the active encoder. + if let (true, Some(encoder)) = ( + support.contains(TimestampQuerySupport::ON_BLIT_ENCODER), + self.state.blit.as_ref(), + ) { + encoder.sample_counters_in_buffer(sample_buffer, index as _, with_barrier); + } else if let (true, Some(encoder)) = ( + support.contains(TimestampQuerySupport::ON_RENDER_ENCODER), + self.state.render.as_ref(), + ) { + encoder.sample_counters_in_buffer(sample_buffer, index as _, with_barrier); + } else if let (true, Some(encoder)) = ( + support.contains(TimestampQuerySupport::ON_COMPUTE_ENCODER), + self.state.compute.as_ref(), + ) { + encoder.sample_counters_in_buffer(sample_buffer, index as _, with_barrier); + } else { + // If we're here it means we either have no encoder open, or it's not supported to sample within them. + // If this happens with render/compute open, this is an invalid usage! + debug_assert!(self.state.render.is_none() && self.state.compute.is_none()); - // TODO: Otherwise, we need to create a new blit command encoder with a descriptor that inserts the timestamps. - // Note that as of writing creating a new encoder is not exposed by the metal crate. - // https://developer.apple.com/documentation/metal/mtlcommandbuffer/3564431-makeblitcommandencoder + // But otherwise it means we'll put defer this to the next created encoder. + self.state.pending_timer_queries.push((set.clone(), index)); - // TODO: Enable respective test in `examples/timestamp-queries/src/tests.rs`. + // Ensure we didn't already have a blit open. + self.leave_blit(); + }; } unsafe fn reset_queries(&mut self, set: &super::QuerySet, range: Range) { @@ -342,6 +453,7 @@ impl crate::CommandEncoder for super::CommandEncoder { }; encoder.fill_buffer(&set.raw_buffer, raw_range, 0); } + unsafe fn copy_query_results( &mut self, set: &super::QuerySet, @@ -454,8 +566,29 @@ impl crate::CommandEncoder for super::CommandEncoder { } } + let mut sba_index = 0; + let mut next_sba_descriptor = || { + let sba_descriptor = descriptor + .sample_buffer_attachments() + .object_at(sba_index) + .unwrap(); + + sba_descriptor.set_end_of_vertex_sample_index(metal::COUNTER_DONT_SAMPLE); + sba_descriptor.set_start_of_fragment_sample_index(metal::COUNTER_DONT_SAMPLE); + + sba_index += 1; + sba_descriptor + }; + + for (set, index) in self.state.pending_timer_queries.drain(..) { + let sba_descriptor = next_sba_descriptor(); + sba_descriptor.set_sample_buffer(set.counter_sample_buffer.as_ref().unwrap()); + sba_descriptor.set_start_of_vertex_sample_index(index as _); + sba_descriptor.set_end_of_fragment_sample_index(metal::COUNTER_DONT_SAMPLE); + } + if let Some(ref timestamp_writes) = desc.timestamp_writes { - let sba_descriptor = descriptor.sample_buffer_attachments().object_at(0).unwrap(); + let sba_descriptor = next_sba_descriptor(); sba_descriptor.set_sample_buffer( timestamp_writes .query_set @@ -464,12 +597,16 @@ impl crate::CommandEncoder for super::CommandEncoder { .unwrap(), ); - if let Some(start_index) = timestamp_writes.beginning_of_pass_write_index { - sba_descriptor.set_start_of_vertex_sample_index(start_index as _); - } - if let Some(end_index) = timestamp_writes.end_of_pass_write_index { - sba_descriptor.set_end_of_fragment_sample_index(end_index as _); - } + sba_descriptor.set_start_of_vertex_sample_index( + timestamp_writes + .beginning_of_pass_write_index + .map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _), + ); + sba_descriptor.set_end_of_fragment_sample_index( + timestamp_writes + .end_of_pass_write_index + .map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _), + ); } if let Some(occlusion_query_set) = desc.occlusion_query_set { @@ -661,17 +798,17 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, layout: &super::PipelineLayout, stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ) { let state_pc = &mut self.state.push_constants; if state_pc.len() < layout.total_push_constants as usize { state_pc.resize(layout.total_push_constants as usize, 0); } - assert_eq!(offset as usize % WORD_SIZE, 0); + debug_assert_eq!(offset_bytes as usize % WORD_SIZE, 0); - let offset = offset as usize / WORD_SIZE; - state_pc[offset..offset + data.len()].copy_from_slice(data); + let offset_words = offset_bytes as usize / WORD_SIZE; + state_pc[offset_words..offset_words + data.len()].copy_from_slice(data); if stages.contains(wgt::ShaderStages::COMPUTE) { self.state.compute.as_ref().unwrap().set_bytes( @@ -697,19 +834,19 @@ impl crate::CommandEncoder for super::CommandEncoder { } unsafe fn insert_debug_marker(&mut self, label: &str) { - if let Some(encoder) = self.enter_any() { + if let Some(encoder) = self.active_encoder() { encoder.insert_debug_signpost(label); } } unsafe fn begin_debug_marker(&mut self, group_label: &str) { - if let Some(encoder) = self.enter_any() { + if let Some(encoder) = self.active_encoder() { encoder.push_debug_group(group_label); } else if let Some(ref buf) = self.raw_cmd_buf { buf.push_debug_group(group_label); } } unsafe fn end_debug_marker(&mut self) { - if let Some(encoder) = self.enter_any() { + if let Some(encoder) = self.active_encoder() { encoder.pop_debug_group(); } else if let Some(ref buf) = self.raw_cmd_buf { buf.pop_debug_group(); @@ -828,31 +965,31 @@ impl crate::CommandEncoder for super::CommandEncoder { unsafe fn draw( &mut self, - start_vertex: u32, + first_vertex: u32, vertex_count: u32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { let encoder = self.state.render.as_ref().unwrap(); - if start_instance != 0 { + if first_instance != 0 { encoder.draw_primitives_instanced_base_instance( self.state.raw_primitive_type, - start_vertex as _, + first_vertex as _, vertex_count as _, instance_count as _, - start_instance as _, + first_instance as _, ); } else if instance_count != 1 { encoder.draw_primitives_instanced( self.state.raw_primitive_type, - start_vertex as _, + first_vertex as _, vertex_count as _, instance_count as _, ); } else { encoder.draw_primitives( self.state.raw_primitive_type, - start_vertex as _, + first_vertex as _, vertex_count as _, ); } @@ -860,16 +997,16 @@ impl crate::CommandEncoder for super::CommandEncoder { unsafe fn draw_indexed( &mut self, - start_index: u32, + first_index: u32, index_count: u32, base_vertex: i32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { let encoder = self.state.render.as_ref().unwrap(); let index = self.state.index.as_ref().unwrap(); - let offset = index.offset + index.stride * start_index as wgt::BufferAddress; - if base_vertex != 0 || start_instance != 0 { + let offset = index.offset + index.stride * first_index as wgt::BufferAddress; + if base_vertex != 0 || first_instance != 0 { encoder.draw_indexed_primitives_instanced_base_instance( self.state.raw_primitive_type, index_count as _, @@ -878,7 +1015,7 @@ impl crate::CommandEncoder for super::CommandEncoder { offset, instance_count as _, base_vertex as _, - start_instance as _, + first_instance as _, ); } else if instance_count != 1 { encoder.draw_indexed_primitives_instanced( @@ -967,30 +1104,55 @@ impl crate::CommandEncoder for super::CommandEncoder { let raw = self.raw_cmd_buf.as_ref().unwrap(); objc::rc::autoreleasepool(|| { - let descriptor = metal::ComputePassDescriptor::new(); + // TimeStamp Queries and ComputePassDescriptor were both introduced in Metal 2.3 (macOS 11, iOS 14) + // and we currently only need ComputePassDescriptor for timestamp queries + let encoder = if self.shared.private_caps.timestamp_query_support.is_empty() { + raw.new_compute_command_encoder() + } else { + let descriptor = metal::ComputePassDescriptor::new(); + + let mut sba_index = 0; + let mut next_sba_descriptor = || { + let sba_descriptor = descriptor + .sample_buffer_attachments() + .object_at(sba_index) + .unwrap(); + sba_index += 1; + sba_descriptor + }; + + for (set, index) in self.state.pending_timer_queries.drain(..) { + let sba_descriptor = next_sba_descriptor(); + sba_descriptor.set_sample_buffer(set.counter_sample_buffer.as_ref().unwrap()); + sba_descriptor.set_start_of_encoder_sample_index(index as _); + sba_descriptor.set_end_of_encoder_sample_index(metal::COUNTER_DONT_SAMPLE); + } - if let Some(timestamp_writes) = desc.timestamp_writes.as_ref() { - let sba_descriptor = descriptor - .sample_buffer_attachments() - .object_at(0 as _) - .unwrap(); - sba_descriptor.set_sample_buffer( - timestamp_writes - .query_set - .counter_sample_buffer - .as_ref() - .unwrap(), - ); + if let Some(timestamp_writes) = desc.timestamp_writes.as_ref() { + let sba_descriptor = next_sba_descriptor(); + sba_descriptor.set_sample_buffer( + timestamp_writes + .query_set + .counter_sample_buffer + .as_ref() + .unwrap(), + ); - if let Some(start_index) = timestamp_writes.beginning_of_pass_write_index { - sba_descriptor.set_start_of_encoder_sample_index(start_index as _); - } - if let Some(end_index) = timestamp_writes.end_of_pass_write_index { - sba_descriptor.set_end_of_encoder_sample_index(end_index as _); + sba_descriptor.set_start_of_encoder_sample_index( + timestamp_writes + .beginning_of_pass_write_index + .map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _), + ); + sba_descriptor.set_end_of_encoder_sample_index( + timestamp_writes + .end_of_pass_write_index + .map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _), + ); } - } - let encoder = raw.compute_command_encoder_with_descriptor(descriptor); + raw.compute_command_encoder_with_descriptor(descriptor) + }; + if let Some(label) = desc.label { encoder.set_label(label); } diff --git a/wgpu-hal/src/metal/conv.rs b/wgpu-hal/src/metal/conv.rs index a1ceb287ab..8f6439b50b 100644 --- a/wgpu-hal/src/metal/conv.rs +++ b/wgpu-hal/src/metal/conv.rs @@ -152,13 +152,11 @@ pub fn map_blend_factor(factor: wgt::BlendFactor) -> metal::MTLBlendFactor { Bf::OneMinusDstAlpha => OneMinusDestinationAlpha, Bf::Constant => BlendColor, Bf::OneMinusConstant => OneMinusBlendColor, - //Bf::ConstantAlpha => BlendAlpha, - //Bf::OneMinusConstantAlpha => OneMinusBlendAlpha, Bf::SrcAlphaSaturated => SourceAlphaSaturated, - //Bf::Src1 => Source1Color, - //Bf::OneMinusSrc1 => OneMinusSource1Color, - //Bf::Src1Alpha => Source1Alpha, - //Bf::OneMinusSrc1Alpha => OneMinusSource1Alpha, + Bf::Src1 => Source1Color, + Bf::OneMinusSrc1 => OneMinusSource1Color, + Bf::Src1Alpha => Source1Alpha, + Bf::OneMinusSrc1Alpha => OneMinusSource1Alpha, } } diff --git a/wgpu-hal/src/metal/device.rs b/wgpu-hal/src/metal/device.rs index 6a387dd57b..d7fd06c8f3 100644 --- a/wgpu-hal/src/metal/device.rs +++ b/wgpu-hal/src/metal/device.rs @@ -396,7 +396,7 @@ impl crate::Device for super::Device { conv::map_texture_view_dimension(desc.dimension) }; - let aspects = crate::FormatAspects::new(desc.format, desc.range.aspect); + let aspects = crate::FormatAspects::new(texture.format, desc.range.aspect); let raw_format = self .shared diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index 078c776153..39589115e7 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -33,10 +33,11 @@ use std::{ }; use arrayvec::ArrayVec; +use bitflags::bitflags; use metal::foreign_types::ForeignTypeRef as _; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Api; type ResourceIndex = u32; @@ -81,7 +82,9 @@ impl Instance { impl crate::Instance for Instance { unsafe fn init(_desc: &crate::InstanceDescriptor) -> Result { - //TODO: enable `METAL_DEVICE_WRAPPER_TYPE` environment based on the flags? + profiling::scope!("Init Metal Backend"); + // We do not enable metal validation based on the validation flags as it affects the entire + // process. Instead, we enable the validation inside the test harness itself in tests/src/native.rs. Ok(Instance { managed_metal_layer_delegate: surface::HalManagedMetalLayerDelegate::new(), }) @@ -96,11 +99,14 @@ impl crate::Instance for Instance { #[cfg(target_os = "ios")] raw_window_handle::RawWindowHandle::UiKit(handle) => { let _ = &self.managed_metal_layer_delegate; - Ok(unsafe { Surface::from_view(handle.ui_view, None) }) + Ok(unsafe { Surface::from_view(handle.ui_view.as_ptr(), None) }) } #[cfg(target_os = "macos")] raw_window_handle::RawWindowHandle::AppKit(handle) => Ok(unsafe { - Surface::from_view(handle.ns_view, Some(&self.managed_metal_layer_delegate)) + Surface::from_view( + handle.ns_view.as_ptr(), + Some(&self.managed_metal_layer_delegate), + ) }), _ => Err(crate::InstanceError::new(format!( "window handle {window_handle:?} is not a Metal-compatible handle" @@ -145,6 +151,24 @@ impl crate::Instance for Instance { } } +bitflags!( + /// Similar to `MTLCounterSamplingPoint`, but a bit higher abstracted for our purposes. + #[derive(Debug, Copy, Clone)] + pub struct TimestampQuerySupport: u32 { + /// On creating Metal encoders. + const STAGE_BOUNDARIES = 1 << 1; + /// Within existing draw encoders. + const ON_RENDER_ENCODER = Self::STAGE_BOUNDARIES.bits() | (1 << 2); + /// Within existing dispatch encoders. + const ON_COMPUTE_ENCODER = Self::STAGE_BOUNDARIES.bits() | (1 << 3); + /// Within existing blit encoders. + const ON_BLIT_ENCODER = Self::STAGE_BOUNDARIES.bits() | (1 << 4); + + /// Within any wgpu render/compute pass. + const INSIDE_WGPU_PASSES = Self::ON_RENDER_ENCODER.bits() | Self::ON_COMPUTE_ENCODER.bits(); + } +); + #[allow(dead_code)] #[derive(Clone, Debug)] struct PrivateCapabilities { @@ -160,8 +184,8 @@ struct PrivateCapabilities { shared_textures: bool, mutable_comparison_samplers: bool, sampler_clamp_to_border: bool, - base_instance: bool, - base_vertex_instance_drawing: bool, + indirect_draw_dispatch: bool, + base_vertex_first_instance_drawing: bool, dual_source_blending: bool, low_power: bool, headless: bool, @@ -169,6 +193,7 @@ struct PrivateCapabilities { function_specialization: bool, depth_clip_mode: bool, texture_cube_array: bool, + supports_float_filtering: bool, format_depth24_stencil8: bool, format_depth32_stencil8_filter: bool, format_depth32_stencil8_none: bool, @@ -191,8 +216,7 @@ struct PrivateCapabilities { format_rgba8_srgb_no_write: bool, format_rgb10a2_unorm_all: bool, format_rgb10a2_unorm_no_write: bool, - format_rgb10a2_uint_color: bool, - format_rgb10a2_uint_color_write: bool, + format_rgb10a2_uint_write: bool, format_rg11b10_all: bool, format_rg11b10_no_write: bool, format_rgb9e5_all: bool, @@ -241,8 +265,7 @@ struct PrivateCapabilities { supports_preserve_invariance: bool, supports_shader_primitive_index: bool, has_unified_memory: Option, - support_timestamp_query: bool, - support_timestamp_query_in_passes: bool, + timestamp_query_support: TimestampQuerySupport, } #[derive(Clone, Debug)] @@ -314,8 +337,8 @@ pub struct Device { pub struct Surface { view: Option>, render_layer: Mutex, - swapchain_format: Option, - extent: wgt::Extent3d, + swapchain_format: RwLock>, + extent: RwLock, main_thread_id: thread::ThreadId, // Useful for UI-intensive applications that are sensitive to // window resizing. @@ -343,7 +366,7 @@ unsafe impl Sync for SurfaceTexture {} impl crate::Queue for Queue { unsafe fn submit( - &mut self, + &self, command_buffers: &[&CommandBuffer], signal_fence: Option<(&mut Fence, crate::FenceValue)>, ) -> Result<(), crate::DeviceError> { @@ -390,8 +413,8 @@ impl crate::Queue for Queue { Ok(()) } unsafe fn present( - &mut self, - _surface: &mut Surface, + &self, + _surface: &Surface, texture: SurfaceTexture, ) -> Result<(), crate::SurfaceError> { let queue = &self.raw.lock(); @@ -675,6 +698,7 @@ impl PipelineStageInfo { } } +#[derive(Debug)] pub struct RenderPipeline { raw: metal::RenderPipelineState, #[allow(dead_code)] @@ -694,6 +718,7 @@ pub struct RenderPipeline { unsafe impl Send for RenderPipeline {} unsafe impl Sync for RenderPipeline {} +#[derive(Debug)] pub struct ComputePipeline { raw: metal::ComputePipelineState, #[allow(dead_code)] @@ -706,7 +731,7 @@ pub struct ComputePipeline { unsafe impl Send for ComputePipeline {} unsafe impl Sync for ComputePipeline {} -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct QuerySet { raw_buffer: metal::Buffer, //Metal has a custom buffer for counters. @@ -789,6 +814,9 @@ struct CommandState { work_group_memory_sizes: Vec, push_constants: Vec, + + /// Timer query that should be executed when the next pass starts. + pending_timer_queries: Vec<(QuerySet, u32)>, } pub struct CommandEncoder { diff --git a/wgpu-hal/src/metal/surface.rs b/wgpu-hal/src/metal/surface.rs index 3b25e55729..e54a176da5 100644 --- a/wgpu-hal/src/metal/surface.rs +++ b/wgpu-hal/src/metal/surface.rs @@ -14,7 +14,7 @@ use objc::{ runtime::{Class, Object, Sel, BOOL, NO, YES}, sel, sel_impl, }; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; #[cfg(target_os = "macos")] #[cfg_attr(feature = "link", link(name = "QuartzCore", kind = "framework"))] @@ -63,8 +63,8 @@ impl super::Surface { Self { view, render_layer: Mutex::new(layer), - swapchain_format: None, - extent: wgt::Extent3d::default(), + swapchain_format: RwLock::new(None), + extent: RwLock::new(wgt::Extent3d::default()), main_thread_id: thread::current().id(), present_with_transaction: false, } @@ -171,15 +171,15 @@ impl super::Surface { impl crate::Surface for super::Surface { unsafe fn configure( - &mut self, + &self, device: &super::Device, config: &crate::SurfaceConfiguration, ) -> Result<(), crate::SurfaceError> { - log::info!("build swapchain {:?}", config); + log::debug!("build swapchain {:?}", config); let caps = &device.shared.private_caps; - self.swapchain_format = Some(config.format); - self.extent = config.extent; + *self.swapchain_format.write() = Some(config.format); + *self.extent.write() = config.extent; let render_layer = self.render_layer.lock(); let framebuffer_only = config.usage == crate::TextureUses::COLOR_TARGET; @@ -233,12 +233,12 @@ impl crate::Surface for super::Surface { Ok(()) } - unsafe fn unconfigure(&mut self, _device: &super::Device) { - self.swapchain_format = None; + unsafe fn unconfigure(&self, _device: &super::Device) { + *self.swapchain_format.write() = None; } unsafe fn acquire_texture( - &mut self, + &self, _timeout_ms: Option, //TODO ) -> Result>, crate::SurfaceError> { let render_layer = self.render_layer.lock(); @@ -251,16 +251,18 @@ impl crate::Surface for super::Surface { None => return Ok(None), }; + let swapchain_format = self.swapchain_format.read().unwrap(); + let extent = self.extent.read(); let suf_texture = super::SurfaceTexture { texture: super::Texture { raw: texture, - format: self.swapchain_format.unwrap(), + format: swapchain_format, raw_type: metal::MTLTextureType::D2, array_layers: 1, mip_levels: 1, copy_size: crate::CopyExtent { - width: self.extent.width, - height: self.extent.height, + width: extent.width, + height: extent.height, depth: 1, }, }, @@ -274,5 +276,5 @@ impl crate::Surface for super::Surface { })) } - unsafe fn discard_texture(&mut self, _texture: super::SurfaceTexture) {} + unsafe fn discard_texture(&self, _texture: super::SurfaceTexture) {} } diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index e70dfca65b..21b44dae0a 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -3,7 +3,11 @@ use super::conv; use ash::{extensions::khr, vk}; use parking_lot::Mutex; -use std::{collections::BTreeMap, ffi::CStr, sync::Arc}; +use std::{ + collections::BTreeMap, + ffi::CStr, + sync::{atomic::AtomicIsize, Arc}, +}; fn depth_stencil_required_flags() -> vk::FormatFeatureFlags { vk::FormatFeatureFlags::SAMPLED_IMAGE | vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT @@ -26,6 +30,7 @@ pub struct PhysicalDeviceFeatures { image_robustness: Option, robustness2: Option, multiview: Option, + sampler_ycbcr_conversion: Option, astc_hdr: Option, shader_float16: Option<( vk::PhysicalDeviceShaderFloat16Int8Features, @@ -90,7 +95,7 @@ impl PhysicalDeviceFeatures { /// /// `requested_features` should be the same as what was used to generate `enabled_extensions`. fn from_extensions_and_requested_features( - effective_api_version: u32, + device_api_version: u32, enabled_extensions: &[&'static CStr], requested_features: wgt::Features, downlevel_flags: wgt::DownlevelFlags, @@ -189,6 +194,7 @@ impl PhysicalDeviceFeatures { //.shader_resource_residency(requested_features.contains(wgt::Features::SHADER_RESOURCE_RESIDENCY)) .geometry_shader(requested_features.contains(wgt::Features::SHADER_PRIMITIVE_INDEX)) .depth_clamp(requested_features.contains(wgt::Features::DEPTH_CLIP_CONTROL)) + .dual_src_blend(requested_features.contains(wgt::Features::DUAL_SOURCE_BLENDING)) .build(), descriptor_indexing: if requested_features.intersects(indexing_features()) { Some( @@ -211,7 +217,7 @@ impl PhysicalDeviceFeatures { } else { None }, - imageless_framebuffer: if effective_api_version >= vk::API_VERSION_1_2 + imageless_framebuffer: if device_api_version >= vk::API_VERSION_1_2 || enabled_extensions.contains(&vk::KhrImagelessFramebufferFn::name()) { Some( @@ -222,7 +228,7 @@ impl PhysicalDeviceFeatures { } else { None }, - timeline_semaphore: if effective_api_version >= vk::API_VERSION_1_2 + timeline_semaphore: if device_api_version >= vk::API_VERSION_1_2 || enabled_extensions.contains(&vk::KhrTimelineSemaphoreFn::name()) { Some( @@ -233,7 +239,7 @@ impl PhysicalDeviceFeatures { } else { None }, - image_robustness: if effective_api_version >= vk::API_VERSION_1_3 + image_robustness: if device_api_version >= vk::API_VERSION_1_3 || enabled_extensions.contains(&vk::ExtImageRobustnessFn::name()) { Some( @@ -257,7 +263,7 @@ impl PhysicalDeviceFeatures { } else { None }, - multiview: if effective_api_version >= vk::API_VERSION_1_1 + multiview: if device_api_version >= vk::API_VERSION_1_1 || enabled_extensions.contains(&vk::KhrMultiviewFn::name()) { Some( @@ -268,6 +274,17 @@ impl PhysicalDeviceFeatures { } else { None }, + sampler_ycbcr_conversion: if device_api_version >= vk::API_VERSION_1_1 + || enabled_extensions.contains(&vk::KhrSamplerYcbcrConversionFn::name()) + { + Some( + vk::PhysicalDeviceSamplerYcbcrConversionFeatures::builder() + // .sampler_ycbcr_conversion(requested_features.contains(wgt::Features::TEXTURE_FORMAT_NV12)) + .build(), + ) + } else { + None + }, astc_hdr: if enabled_extensions.contains(&vk::ExtTextureCompressionAstcHdrFn::name()) { Some( vk::PhysicalDeviceTextureCompressionASTCHDRFeaturesEXT::builder() @@ -321,7 +338,7 @@ impl PhysicalDeviceFeatures { } else { None }, - zero_initialize_workgroup_memory: if effective_api_version >= vk::API_VERSION_1_3 + zero_initialize_workgroup_memory: if device_api_version >= vk::API_VERSION_1_3 || enabled_extensions.contains(&vk::KhrZeroInitializeWorkgroupMemoryFn::name()) { Some( @@ -368,7 +385,9 @@ impl PhysicalDeviceFeatures { | Df::UNRESTRICTED_INDEX_BUFFER | Df::INDIRECT_EXECUTION | Df::VIEW_FORMATS - | Df::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES; + | Df::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES + | Df::NONBLOCKING_QUERY_RESOLVE + | Df::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW; dl_flags.set( Df::SURFACE_VIEW_FORMATS, @@ -503,6 +522,7 @@ impl PhysicalDeviceFeatures { } features.set(F::DEPTH_CLIP_CONTROL, self.core.depth_clamp != 0); + features.set(F::DUAL_SOURCE_BLENDING, self.core.dual_src_blend != 0); if let Some(ref multiview) = self.multiview { features.set(F::MULTIVIEW, multiview.multiview != 0); @@ -575,6 +595,35 @@ impl PhysicalDeviceFeatures { | vk::FormatFeatureFlags::COLOR_ATTACHMENT_BLEND, ); features.set(F::RG11B10UFLOAT_RENDERABLE, rg11b10ufloat_renderable); + features.set(F::SHADER_UNUSED_VERTEX_OUTPUT, true); + + features.set( + F::BGRA8UNORM_STORAGE, + supports_bgra8unorm_storage(instance, phd, caps.device_api_version), + ); + + features.set( + F::FLOAT32_FILTERABLE, + is_float32_filterable_supported(instance, phd), + ); + + if let Some(ref _sampler_ycbcr_conversion) = self.sampler_ycbcr_conversion { + features.set( + F::TEXTURE_FORMAT_NV12, + supports_format( + instance, + phd, + vk::Format::G8_B8R8_2PLANE_420_UNORM, + vk::ImageTiling::OPTIMAL, + vk::FormatFeatureFlags::SAMPLED_IMAGE + | vk::FormatFeatureFlags::TRANSFER_SRC + | vk::FormatFeatureFlags::TRANSFER_DST, + ) && !caps + .driver + .map(|driver| driver.driver_id == vk::DriverId::MOLTENVK) + .unwrap_or_default(), + ); + } (features, dl_flags) } @@ -598,20 +647,12 @@ pub struct PhysicalDeviceCapabilities { descriptor_indexing: Option, acceleration_structure: Option, driver: Option, - /// The effective driver api version supported by the physical device. - /// - /// The Vulkan specification states the following in the documentation for VkPhysicalDeviceProperties: - /// > The value of apiVersion may be different than the version returned by vkEnumerateInstanceVersion; - /// > either higher or lower. In such cases, the application must not use functionality that exceeds - /// > the version of Vulkan associated with a given object. + /// The device API version. /// - /// For example, a Vulkan 1.1 instance cannot use functionality added in Vulkan 1.2 even if the physical - /// device supports Vulkan 1.2. + /// Which is the version of Vulkan supported for device-level functionality. /// - /// This means that assuming that the apiVersion provided by VkPhysicalDeviceProperties is the actual - /// version we can use is incorrect. Instead the effective version is the lower of the instance version - /// and physical device version. - effective_api_version: u32, + /// It is associated with a `VkPhysicalDevice` and its children. + device_api_version: u32, } // This is safe because the structs have `p_next: *mut c_void`, which we null out/never read. @@ -640,7 +681,7 @@ impl PhysicalDeviceCapabilities { // Require `VK_KHR_swapchain` extensions.push(vk::KhrSwapchainFn::name()); - if self.effective_api_version < vk::API_VERSION_1_1 { + if self.device_api_version < vk::API_VERSION_1_1 { // Require either `VK_KHR_maintenance1` or `VK_AMD_negative_viewport_height` if self.supports_extension(vk::KhrMaintenance1Fn::name()) { extensions.push(vk::KhrMaintenance1Fn::name()); @@ -666,9 +707,14 @@ impl PhysicalDeviceCapabilities { if requested_features.contains(wgt::Features::MULTIVIEW) { extensions.push(vk::KhrMultiviewFn::name()); } + + // Require `VK_KHR_sampler_ycbcr_conversion` if the associated feature was requested + if requested_features.contains(wgt::Features::TEXTURE_FORMAT_NV12) { + extensions.push(vk::KhrSamplerYcbcrConversionFn::name()); + } } - if self.effective_api_version < vk::API_VERSION_1_2 { + if self.device_api_version < vk::API_VERSION_1_2 { // Optional `VK_KHR_image_format_list` if self.supports_extension(vk::KhrImageFormatListFn::name()) { extensions.push(vk::KhrImageFormatListFn::name()); @@ -678,7 +724,7 @@ impl PhysicalDeviceCapabilities { if self.supports_extension(vk::KhrImagelessFramebufferFn::name()) { extensions.push(vk::KhrImagelessFramebufferFn::name()); // Require `VK_KHR_maintenance2` due to it being a dependency - if self.effective_api_version < vk::API_VERSION_1_1 { + if self.device_api_version < vk::API_VERSION_1_1 { extensions.push(vk::KhrMaintenance2Fn::name()); } } @@ -702,7 +748,7 @@ impl PhysicalDeviceCapabilities { if requested_features.contains(wgt::Features::SHADER_F16) { extensions.push(vk::KhrShaderFloat16Int8Fn::name()); // `VK_KHR_16bit_storage` requires `VK_KHR_storage_buffer_storage_class`, however we require that one already - if self.effective_api_version < vk::API_VERSION_1_1 { + if self.device_api_version < vk::API_VERSION_1_1 { extensions.push(vk::Khr16bitStorageFn::name()); } } @@ -711,7 +757,7 @@ impl PhysicalDeviceCapabilities { //extensions.push(vk::ExtSamplerFilterMinmaxFn::name()); } - if self.effective_api_version < vk::API_VERSION_1_3 { + if self.device_api_version < vk::API_VERSION_1_3 { // Optional `VK_EXT_image_robustness` if self.supports_extension(vk::ExtImageRobustnessFn::name()) { extensions.push(vk::ExtImageRobustnessFn::name()); @@ -848,22 +894,25 @@ impl super::InstanceShared { let mut capabilities = PhysicalDeviceCapabilities::default(); capabilities.supported_extensions = unsafe { self.raw.enumerate_device_extension_properties(phd).unwrap() }; - capabilities.properties = if let Some(ref get_device_properties) = - self.get_physical_device_properties - { + capabilities.properties = unsafe { self.raw.get_physical_device_properties(phd) }; + capabilities.device_api_version = capabilities.properties.api_version; + + if let Some(ref get_device_properties) = self.get_physical_device_properties { // Get these now to avoid borrowing conflicts later - let supports_descriptor_indexing = self.driver_api_version >= vk::API_VERSION_1_2 + let supports_maintenance3 = capabilities.device_api_version >= vk::API_VERSION_1_1 + || capabilities.supports_extension(vk::KhrMaintenance3Fn::name()); + let supports_descriptor_indexing = capabilities.device_api_version + >= vk::API_VERSION_1_2 || capabilities.supports_extension(vk::ExtDescriptorIndexingFn::name()); - let supports_driver_properties = self.driver_api_version >= vk::API_VERSION_1_2 + let supports_driver_properties = capabilities.device_api_version + >= vk::API_VERSION_1_2 || capabilities.supports_extension(vk::KhrDriverPropertiesFn::name()); let supports_acceleration_structure = capabilities.supports_extension(vk::KhrAccelerationStructureFn::name()); let mut builder = vk::PhysicalDeviceProperties2KHR::builder(); - if self.driver_api_version >= vk::API_VERSION_1_1 - || capabilities.supports_extension(vk::KhrMaintenance3Fn::name()) - { + if supports_maintenance3 { capabilities.maintenance_3 = Some(vk::PhysicalDeviceMaintenance3Properties::default()); builder = builder.push_next(capabilities.maintenance_3.as_mut().unwrap()); @@ -894,15 +943,18 @@ impl super::InstanceShared { unsafe { get_device_properties.get_physical_device_properties2(phd, &mut properties2); } - properties2.properties - } else { - unsafe { self.raw.get_physical_device_properties(phd) } - }; - // Set the effective api version - capabilities.effective_api_version = self - .driver_api_version - .min(capabilities.properties.api_version); + if is_intel_igpu_outdated_for_robustness2( + capabilities.properties, + capabilities.driver, + ) { + use crate::auxil::cstr_from_bytes_until_nul; + capabilities.supported_extensions.retain(|&x| { + cstr_from_bytes_until_nul(&x.extension_name) + != Some(vk::ExtRobustness2Fn::name()) + }); + } + }; capabilities }; @@ -913,7 +965,7 @@ impl super::InstanceShared { let mut builder = vk::PhysicalDeviceFeatures2KHR::builder().features(core); // `VK_KHR_multiview` is promoted to 1.1 - if capabilities.effective_api_version >= vk::API_VERSION_1_1 + if capabilities.device_api_version >= vk::API_VERSION_1_1 || capabilities.supports_extension(vk::KhrMultiviewFn::name()) { let next = features @@ -922,6 +974,16 @@ impl super::InstanceShared { builder = builder.push_next(next); } + // `VK_KHR_sampler_ycbcr_conversion` is promoted to 1.1 + if capabilities.device_api_version >= vk::API_VERSION_1_1 + || capabilities.supports_extension(vk::KhrSamplerYcbcrConversionFn::name()) + { + let next = features + .sampler_ycbcr_conversion + .insert(vk::PhysicalDeviceSamplerYcbcrConversionFeatures::default()); + builder = builder.push_next(next); + } + if capabilities.supports_extension(vk::ExtDescriptorIndexingFn::name()) { let next = features .descriptor_indexing @@ -981,7 +1043,7 @@ impl super::InstanceShared { } // `VK_KHR_zero_initialize_workgroup_memory` is promoted to 1.3 - if capabilities.effective_api_version >= vk::API_VERSION_1_3 + if capabilities.device_api_version >= vk::API_VERSION_1_3 || capabilities.supports_extension(vk::KhrZeroInitializeWorkgroupMemoryFn::name()) { let next = features @@ -1055,22 +1117,38 @@ impl super::Instance { phd_features.to_wgpu(&self.shared.raw, phd, &phd_capabilities); let mut workarounds = super::Workarounds::empty(); { - // see https://github.com/gfx-rs/gfx/issues/1930 - let _is_windows_intel_dual_src_bug = cfg!(windows) - && phd_capabilities.properties.vendor_id == db::intel::VENDOR - && (phd_capabilities.properties.device_id & db::intel::DEVICE_KABY_LAKE_MASK - == db::intel::DEVICE_KABY_LAKE_MASK - || phd_capabilities.properties.device_id & db::intel::DEVICE_SKY_LAKE_MASK - == db::intel::DEVICE_SKY_LAKE_MASK); // TODO: only enable for particular devices workarounds |= super::Workarounds::SEPARATE_ENTRY_POINTS; workarounds.set( super::Workarounds::EMPTY_RESOLVE_ATTACHMENT_LISTS, phd_capabilities.properties.vendor_id == db::qualcomm::VENDOR, ); + workarounds.set( + super::Workarounds::FORCE_FILL_BUFFER_WITH_SIZE_GREATER_4096_ALIGNED_OFFSET_16, + phd_capabilities.properties.vendor_id == db::nvidia::VENDOR, + ); }; - if phd_capabilities.effective_api_version == vk::API_VERSION_1_0 + if let Some(driver) = phd_capabilities.driver { + if driver.conformance_version.major == 0 { + if driver.driver_id == ash::vk::DriverId::MOLTENVK { + log::debug!("Adapter is not Vulkan compliant, but is MoltenVK, continuing"); + } else if self + .shared + .flags + .contains(wgt::InstanceFlags::ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER) + { + log::warn!("Adapter is not Vulkan compliant: {}", info.name); + } else { + log::warn!( + "Adapter is not Vulkan compliant, hiding adapter: {}", + info.name + ); + return None; + } + } + } + if phd_capabilities.device_api_version == vk::API_VERSION_1_0 && !phd_capabilities.supports_extension(vk::KhrStorageBufferStorageClassFn::name()) { log::warn!( @@ -1081,7 +1159,7 @@ impl super::Instance { } if !phd_capabilities.supports_extension(vk::AmdNegativeViewportHeightFn::name()) && !phd_capabilities.supports_extension(vk::KhrMaintenance1Fn::name()) - && phd_capabilities.effective_api_version < vk::API_VERSION_1_1 + && phd_capabilities.device_api_version < vk::API_VERSION_1_1 { log::warn!( "viewport Y-flip is not supported, hiding adapter: {}", @@ -1102,7 +1180,7 @@ impl super::Instance { } let private_caps = super::PrivateCapabilities { - flip_y_requires_shift: phd_capabilities.effective_api_version >= vk::API_VERSION_1_1 + flip_y_requires_shift: phd_capabilities.device_api_version >= vk::API_VERSION_1_1 || phd_capabilities.supports_extension(vk::KhrMaintenance1Fn::name()), imageless_framebuffers: match phd_features.imageless_framebuffer { Some(features) => features.imageless_framebuffer == vk::TRUE, @@ -1110,7 +1188,7 @@ impl super::Instance { .imageless_framebuffer .map_or(false, |ext| ext.imageless_framebuffer != 0), }, - image_view_usage: phd_capabilities.effective_api_version >= vk::API_VERSION_1_1 + image_view_usage: phd_capabilities.device_api_version >= vk::API_VERSION_1_1 || phd_capabilities.supports_extension(vk::KhrMaintenance2Fn::name()), timeline_semaphores: match phd_features.timeline_semaphore { Some(features) => features.timeline_semaphore == vk::TRUE, @@ -1164,6 +1242,8 @@ impl super::Instance { .map_or(false, |ext| { ext.shader_zero_initialize_workgroup_memory == vk::TRUE }), + image_format_list: phd_capabilities.device_api_version >= vk::API_VERSION_1_2 + || phd_capabilities.supports_extension(vk::KhrImageFormatListFn::name()), }; let capabilities = crate::Capabilities { limits: phd_capabilities.to_wgpu_limits(), @@ -1237,7 +1317,7 @@ impl super::Adapter { features: wgt::Features, ) -> PhysicalDeviceFeatures { PhysicalDeviceFeatures::from_extensions_and_requested_features( - self.phd_capabilities.effective_api_version, + self.phd_capabilities.device_api_version, enabled_extensions, features, self.downlevel_flags, @@ -1291,7 +1371,7 @@ impl super::Adapter { &self.instance.raw, &raw_device, ))) - } else if self.phd_capabilities.effective_api_version >= vk::API_VERSION_1_2 { + } else if self.phd_capabilities.device_api_version >= vk::API_VERSION_1_2 { Some(super::ExtensionFn::Promoted) } else { None @@ -1316,6 +1396,8 @@ impl super::Adapter { let naga_options = { use naga::back::spv; + // The following capabilities are always available + // see https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap52.html#spirvenv-capabilities let mut capabilities = vec![ spv::Capability::Shader, spv::Capability::Matrix, @@ -1323,15 +1405,23 @@ impl super::Adapter { spv::Capability::Image1D, spv::Capability::ImageQuery, spv::Capability::DerivativeControl, - spv::Capability::SampledCubeArray, - spv::Capability::SampleRateShading, - //Note: this is requested always, no matter what the actual - // adapter supports. It's not the responsibility of SPV-out - // translation to handle the storage support for formats. spv::Capability::StorageImageExtendedFormats, - //TODO: fill out the rest ]; + if self + .downlevel_flags + .contains(wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES) + { + capabilities.push(spv::Capability::SampledCubeArray); + } + + if self + .downlevel_flags + .contains(wgt::DownlevelFlags::MULTISAMPLED_SHADING) + { + capabilities.push(spv::Capability::SampleRateShading); + } + if features.contains(wgt::Features::MULTIVIEW) { capabilities.push(spv::Capability::MultiView); } @@ -1346,11 +1436,18 @@ impl super::Adapter { ) { capabilities.push(spv::Capability::ShaderNonUniform); } + if features.contains(wgt::Features::BGRA8UNORM_STORAGE) { + capabilities.push(spv::Capability::StorageImageWriteWithoutFormat); + } + + if features.contains(wgt::Features::RAY_QUERY) { + capabilities.push(spv::Capability::RayQueryKHR); + } let mut flags = spv::WriterFlags::empty(); flags.set( spv::WriterFlags::DEBUG, - self.instance.flags.contains(crate::InstanceFlags::DEBUG), + self.instance.flags.contains(wgt::InstanceFlags::DEBUG), ); flags.set( spv::WriterFlags::LABEL_VARYINGS, @@ -1440,7 +1537,7 @@ impl super::Adapter { device: Arc::clone(&shared), family_index, relay_semaphores, - relay_index: None, + relay_index: AtomicIsize::new(-1), }; let mem_allocator = { @@ -1617,8 +1714,18 @@ impl crate::Adapter for super::Adapter { .framebuffer_stencil_sample_counts .min(limits.sampled_image_stencil_sample_counts) } else { - match format.sample_type(None).unwrap() { - wgt::TextureSampleType::Float { filterable: _ } => limits + let first_aspect = format_aspect + .iter() + .next() + .expect("All texture should at least one aspect") + .map(); + + // We should never get depth or stencil out of this, due to the above. + assert_ne!(first_aspect, wgt::TextureAspect::DepthOnly); + assert_ne!(first_aspect, wgt::TextureAspect::StencilOnly); + + match format.sample_type(Some(first_aspect), None).unwrap() { + wgt::TextureSampleType::Float { .. } => limits .framebuffer_color_sample_counts .min(limits.sampled_image_color_sample_counts), wgt::TextureSampleType::Sint | wgt::TextureSampleType::Uint => { @@ -1708,18 +1815,6 @@ impl crate::Adapter for super::Adapter { None }; - let min_extent = wgt::Extent3d { - width: caps.min_image_extent.width, - height: caps.min_image_extent.height, - depth_or_array_layers: 1, - }; - - let max_extent = wgt::Extent3d { - width: caps.max_image_extent.width, - height: caps.max_image_extent.height, - depth_or_array_layers: caps.max_image_array_layers, - }; - let raw_present_modes = { profiling::scope!("vkGetPhysicalDeviceSurfacePresentModesKHR"); match unsafe { @@ -1758,7 +1853,6 @@ impl crate::Adapter for super::Adapter { formats, swap_chain_sizes: caps.min_image_count..=max_image_count, current_extent, - extents: min_extent..=max_extent, usage: conv::map_vk_image_usage(caps.supported_usage_flags), present_modes: raw_present_modes .into_iter() @@ -1822,6 +1916,21 @@ fn is_format_16bit_norm_supported(instance: &ash::Instance, phd: vk::PhysicalDev r16unorm && r16snorm && rg16unorm && rg16snorm && rgba16unorm && rgba16snorm } +fn is_float32_filterable_supported(instance: &ash::Instance, phd: vk::PhysicalDevice) -> bool { + let tiling = vk::ImageTiling::OPTIMAL; + let features = vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR; + let r_float = supports_format(instance, phd, vk::Format::R32_SFLOAT, tiling, features); + let rg_float = supports_format(instance, phd, vk::Format::R32G32_SFLOAT, tiling, features); + let rgba_float = supports_format( + instance, + phd, + vk::Format::R32G32B32A32_SFLOAT, + tiling, + features, + ); + r_float && rg_float && rgba_float +} + fn supports_format( instance: &ash::Instance, phd: vk::PhysicalDevice, @@ -1836,3 +1945,61 @@ fn supports_format( _ => false, } } + +fn supports_bgra8unorm_storage( + instance: &ash::Instance, + phd: vk::PhysicalDevice, + device_api_version: u32, +) -> bool { + // See https://github.com/KhronosGroup/Vulkan-Docs/issues/2027#issuecomment-1380608011 + + // This check gates the function call and structures used below. + // TODO: check for (`VK_KHR_get_physical_device_properties2` or VK1.1) and (`VK_KHR_format_feature_flags2` or VK1.3). + // Right now we only check for VK1.3. + if device_api_version < vk::API_VERSION_1_3 { + return false; + } + + unsafe { + let mut properties3 = vk::FormatProperties3::default(); + let mut properties2 = vk::FormatProperties2::builder().push_next(&mut properties3); + + instance.get_physical_device_format_properties2( + phd, + vk::Format::B8G8R8A8_UNORM, + &mut properties2, + ); + + let features2 = properties2.format_properties.optimal_tiling_features; + let features3 = properties3.optimal_tiling_features; + + features2.contains(vk::FormatFeatureFlags::STORAGE_IMAGE) + && features3.contains(vk::FormatFeatureFlags2::STORAGE_WRITE_WITHOUT_FORMAT) + } +} + +// For https://github.com/gfx-rs/wgpu/issues/4599 +// Intel iGPUs with outdated drivers can break rendering if `VK_EXT_robustness2` is used. +// Driver version 31.0.101.2115 works, but there's probably an earlier functional version. +fn is_intel_igpu_outdated_for_robustness2( + props: vk::PhysicalDeviceProperties, + driver: Option, +) -> bool { + const DRIVER_VERSION_WORKING: u32 = (101 << 14) | 2115; // X.X.101.2115 + + let is_outdated = props.vendor_id == crate::auxil::db::intel::VENDOR + && props.device_type == vk::PhysicalDeviceType::INTEGRATED_GPU + && props.driver_version < DRIVER_VERSION_WORKING + && driver + .map(|driver| driver.driver_id == vk::DriverId::INTEL_PROPRIETARY_WINDOWS) + .unwrap_or_default(); + + if is_outdated { + log::warn!( + "Disabling robustBufferAccess2 and robustImageAccess2: IntegratedGpu Intel Driver is outdated. Found with version 0x{:X}, less than the known good version 0x{:X} (31.0.101.2115)", + props.driver_version, + DRIVER_VERSION_WORKING + ); + } + is_outdated +} diff --git a/wgpu-hal/src/vulkan/command.rs b/wgpu-hal/src/vulkan/command.rs index 09059175f0..239133bb54 100644 --- a/wgpu-hal/src/vulkan/command.rs +++ b/wgpu-hal/src/vulkan/command.rs @@ -23,7 +23,7 @@ impl super::Texture { buffer_offset: r.buffer_layout.offset, buffer_row_length: r.buffer_layout.bytes_per_row.map_or(0, |bpr| { let block_size = format - .block_size(Some(r.texture_base.aspect.map())) + .block_copy_size(Some(r.texture_base.aspect.map())) .unwrap(); block_width * (bpr / block_size) }), @@ -212,15 +212,44 @@ impl crate::CommandEncoder for super::CommandEncoder { } unsafe fn clear_buffer(&mut self, buffer: &super::Buffer, range: crate::MemoryRange) { - unsafe { - self.device.raw.cmd_fill_buffer( - self.active, - buffer.raw, - range.start, - range.end - range.start, - 0, - ) - }; + let range_size = range.end - range.start; + if self.device.workarounds.contains( + super::Workarounds::FORCE_FILL_BUFFER_WITH_SIZE_GREATER_4096_ALIGNED_OFFSET_16, + ) && range_size >= 4096 + && range.start % 16 != 0 + { + let rounded_start = wgt::math::align_to(range.start, 16); + let prefix_size = rounded_start - range.start; + + unsafe { + self.device.raw.cmd_fill_buffer( + self.active, + buffer.raw, + range.start, + prefix_size, + 0, + ) + }; + + // This will never be zero, as rounding can only add up to 12 bytes, and the total size is 4096. + let suffix_size = range.end - rounded_start; + + unsafe { + self.device.raw.cmd_fill_buffer( + self.active, + buffer.raw, + rounded_start, + suffix_size, + 0, + ) + }; + } else { + unsafe { + self.device + .raw + .cmd_fill_buffer(self.active, buffer.raw, range.start, range_size, 0) + }; + } } unsafe fn copy_buffer_to_buffer( @@ -394,10 +423,12 @@ impl crate::CommandEncoder for super::CommandEncoder { const CAPACITY_INNER: usize = 1; let descriptor_count = descriptor_count as usize; - let ray_tracing_functions = match self.device.extension_fns.ray_tracing { - Some(ref functions) => functions, - None => panic!("Feature `RAY_TRACING` not enabled"), - }; + let ray_tracing_functions = self + .device + .extension_fns + .ray_tracing + .as_ref() + .expect("Feature `RAY_TRACING` not enabled"); let get_device_address = |buffer: Option<&super::Buffer>| unsafe { match buffer { @@ -589,39 +620,11 @@ impl crate::CommandEncoder for super::CommandEncoder { ranges_ptrs.push(&ranges_storage[i]); } - // let mut geometry_infos = - // Vec::::with_capacity(descriptors.len()); - - // let mut ranges_vec = - // Vec::<&[vk::AccelerationStructureBuildRangeInfoKHR]>::with_capacity(descriptors.len()); - - // let mut ranges_storage = - // Vec::>::with_capacity(descriptors.len()); - - // for desc in descriptors { - // let (ranges, geometry_info) = prepare_geometry_info_and_ranges(desc); - // geometry_infos.push(geometry_info); - // ranges_storage.push(ranges); - - // } - - // for i in 0..descriptors.len() { - // ranges_vec.push(&ranges_storage[i]); - // } - - // let (ranges, geometry_info) = prepare_geometry_info_and_ranges(descriptors[0]); - unsafe { ray_tracing_functions .acceleration_structure .cmd_build_acceleration_structures(self.active, &geometry_infos, &ranges_ptrs); } - - // unsafe { - // ray_tracing_functions - // .acceleration_structure - // .cmd_build_acceleration_structures(self.active, &geometry_infos, &ranges_vec); - // } } unsafe fn place_acceleration_structure_barrier( @@ -834,7 +837,7 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, layout: &super::PipelineLayout, stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ) { unsafe { @@ -842,7 +845,7 @@ impl crate::CommandEncoder for super::CommandEncoder { self.active, layout.raw, conv::map_shader_stage(stages), - offset, + offset_bytes, slice::from_raw_parts(data.as_ptr() as _, data.len() * 4), ) }; @@ -956,9 +959,9 @@ impl crate::CommandEncoder for super::CommandEncoder { unsafe fn draw( &mut self, - start_vertex: u32, + first_vertex: u32, vertex_count: u32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { unsafe { @@ -966,17 +969,17 @@ impl crate::CommandEncoder for super::CommandEncoder { self.active, vertex_count, instance_count, - start_vertex, - start_instance, + first_vertex, + first_instance, ) }; } unsafe fn draw_indexed( &mut self, - start_index: u32, + first_index: u32, index_count: u32, base_vertex: i32, - start_instance: u32, + first_instance: u32, instance_count: u32, ) { unsafe { @@ -984,9 +987,9 @@ impl crate::CommandEncoder for super::CommandEncoder { self.active, index_count, instance_count, - start_index, + first_index, base_vertex, - start_instance, + first_instance, ) }; } diff --git a/wgpu-hal/src/vulkan/conv.rs b/wgpu-hal/src/vulkan/conv.rs index 76efbf841e..15fe5de3c4 100644 --- a/wgpu-hal/src/vulkan/conv.rs +++ b/wgpu-hal/src/vulkan/conv.rs @@ -34,6 +34,7 @@ impl super::PrivateCapabilities { Tf::Bgra8Unorm => F::B8G8R8A8_UNORM, Tf::Rgba8Uint => F::R8G8B8A8_UINT, Tf::Rgba8Sint => F::R8G8B8A8_SINT, + Tf::Rgb10a2Uint => F::A2B10G10R10_UINT_PACK32, Tf::Rgb10a2Unorm => F::A2B10G10R10_UNORM_PACK32, Tf::Rg11b10Float => F::B10G11R11_UFLOAT_PACK32, Tf::Rg32Uint => F::R32G32_UINT, @@ -73,6 +74,7 @@ impl super::PrivateCapabilities { } } Tf::Depth16Unorm => F::D16_UNORM, + Tf::NV12 => F::G8_B8R8_2PLANE_420_UNORM, Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32, Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK, Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK, @@ -198,7 +200,7 @@ impl crate::ColorAttachment<'_, super::Api> { .view .attachment .view_format - .sample_type(None) + .sample_type(None, None) .unwrap() { wgt::TextureSampleType::Float { .. } => vk::ClearColorValue { @@ -220,7 +222,7 @@ pub fn derive_image_layout( format: wgt::TextureFormat, ) -> vk::ImageLayout { // Note: depth textures are always sampled with RODS layout - let is_color = crate::FormatAspects::from(format).contains(crate::FormatAspects::COLOR); + let is_color = !format.is_depth_stencil_format(); match usage { crate::TextureUses::UNINITIALIZED => vk::ImageLayout::UNDEFINED, crate::TextureUses::COPY_SRC => vk::ImageLayout::TRANSFER_SRC_OPTIMAL, @@ -411,6 +413,15 @@ pub fn map_aspects(aspects: crate::FormatAspects) -> vk::ImageAspectFlags { if aspects.contains(crate::FormatAspects::STENCIL) { flags |= vk::ImageAspectFlags::STENCIL; } + if aspects.contains(crate::FormatAspects::PLANE_0) { + flags |= vk::ImageAspectFlags::PLANE_0; + } + if aspects.contains(crate::FormatAspects::PLANE_1) { + flags |= vk::ImageAspectFlags::PLANE_1; + } + if aspects.contains(crate::FormatAspects::PLANE_2) { + flags |= vk::ImageAspectFlags::PLANE_2; + } flags } @@ -450,8 +461,7 @@ pub fn map_vk_present_mode(mode: vk::PresentModeKHR) -> Option } else if mode == vk::PresentModeKHR::FIFO { Some(wgt::PresentMode::Fifo) } else if mode == vk::PresentModeKHR::FIFO_RELAXED { - //Some(wgt::PresentMode::Relaxed) - None + Some(wgt::PresentMode::FifoRelaxed) } else { log::warn!("Unrecognized present mode {:?}", mode); None @@ -812,6 +822,10 @@ fn map_blend_factor(factor: wgt::BlendFactor) -> vk::BlendFactor { Bf::SrcAlphaSaturated => vk::BlendFactor::SRC_ALPHA_SATURATE, Bf::Constant => vk::BlendFactor::CONSTANT_COLOR, Bf::OneMinusConstant => vk::BlendFactor::ONE_MINUS_CONSTANT_COLOR, + Bf::Src1 => vk::BlendFactor::SRC1_COLOR, + Bf::OneMinusSrc1 => vk::BlendFactor::ONE_MINUS_SRC1_COLOR, + Bf::Src1Alpha => vk::BlendFactor::SRC1_ALPHA, + Bf::OneMinusSrc1Alpha => vk::BlendFactor::ONE_MINUS_SRC1_ALPHA, } } diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index aa79dd236e..a37017a9e6 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -539,7 +539,7 @@ struct CompiledStage { impl super::Device { pub(super) unsafe fn create_swapchain( &self, - surface: &mut super::Surface, + surface: &super::Surface, config: &crate::SurfaceConfiguration, provided_old_swapchain: Option, ) -> Result { @@ -667,6 +667,9 @@ impl super::Device { vk::ImageCreateFlags::MUTABLE_FORMAT | vk::ImageCreateFlags::EXTENDED_USAGE; view_formats.push(desc.format) } + if desc.format.is_multi_planar_format() { + raw_flags |= vk::ImageCreateFlags::MUTABLE_FORMAT; + } super::Texture { raw: vk_image, @@ -723,7 +726,9 @@ impl super::Device { entry_point: stage.entry_point.to_string(), shader_stage: naga_stage, }; - let needs_temp_options = !runtime_checks || !binding_map.is_empty(); + let needs_temp_options = !runtime_checks + || !binding_map.is_empty() + || naga_shader.debug_source.is_some(); let mut temp_options; let options = if needs_temp_options { temp_options = self.naga_options.clone(); @@ -739,6 +744,14 @@ impl super::Device { if !binding_map.is_empty() { temp_options.binding_map = binding_map.clone(); } + + if let Some(ref debug) = naga_shader.debug_source { + temp_options.debug_info = Some(naga::back::spv::DebugInfo { + source_code: &debug.source_code, + file_name: debug.file_name.as_ref().as_ref(), + }) + } + &temp_options } else { &self.naga_options @@ -979,11 +992,7 @@ impl crate::Device for super::Device { wgt_view_formats = desc.view_formats.clone(); wgt_view_formats.push(desc.format); - if self.shared_instance().driver_api_version >= vk::API_VERSION_1_2 - || self - .enabled_device_extensions() - .contains(&vk::KhrImageFormatListFn::name()) - { + if self.shared.private_caps.image_format_list { vk_view_formats = desc .view_formats .iter() @@ -992,6 +1001,9 @@ impl crate::Device for super::Device { vk_view_formats.push(original_format) } } + if desc.format.is_multi_planar_format() { + raw_flags |= vk::ImageCreateFlags::MUTABLE_FORMAT; + } let mut vk_info = vk::ImageCreateInfo::builder() .flags(raw_flags) @@ -1065,7 +1077,7 @@ impl crate::Device for super::Device { texture: &super::Texture, desc: &crate::TextureViewDescriptor, ) -> Result { - let subresource_range = conv::map_subresource_range(&desc.range, desc.format); + let subresource_range = conv::map_subresource_range(&desc.range, texture.format); let mut vk_info = vk::ImageViewCreateInfo::builder() .flags(vk::ImageViewCreateFlags::empty()) .image(texture.raw) @@ -1152,7 +1164,7 @@ impl crate::Device for super::Device { } if desc.anisotropy_clamp != 1 { - // We only enable anisotropy if it is supported, and wgpu-hal interface guarentees + // We only enable anisotropy if it is supported, and wgpu-hal interface guarantees // the clamp is in the range [1, 16] which is always supported if anisotropy is. vk_info = vk_info .anisotropy_enable(true) @@ -1571,6 +1583,14 @@ impl crate::Device for super::Device { }); } let mut naga_options = self.naga_options.clone(); + naga_options.debug_info = + naga_shader + .debug_source + .as_ref() + .map(|d| naga::back::spv::DebugInfo { + source_code: d.source_code.as_ref(), + file_name: d.file_name.as_ref().as_ref(), + }); if !desc.runtime_checks { naga_options.bounds_check_policies = naga::proc::BoundsCheckPolicies { index: naga::proc::BoundsCheckPolicy::Unchecked, @@ -1628,7 +1648,7 @@ impl crate::Device for super::Device { multiview: desc.multiview, ..Default::default() }; - let mut stages = ArrayVec::<_, 2>::new(); + let mut stages = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); let mut vertex_buffers = Vec::with_capacity(desc.vertex_buffers.len()); let mut vertex_attributes = Vec::new(); @@ -2071,10 +2091,12 @@ impl crate::Device for super::Device { ) -> crate::AccelerationStructureBuildSizes { const CAPACITY: usize = 8; - let ray_tracing_functions = match self.shared.extension_fns.ray_tracing { - Some(ref functions) => functions, - None => panic!("Feature `RAY_TRACING` not enabled"), - }; + let ray_tracing_functions = self + .shared + .extension_fns + .ray_tracing + .as_ref() + .expect("Feature `RAY_TRACING` not enabled"); let (geometries, primitive_counts) = match *desc.entries { crate::AccelerationStructureEntries::Instances(ref instances) => { @@ -2182,10 +2204,12 @@ impl crate::Device for super::Device { &self, acceleration_structure: &super::AccelerationStructure, ) -> wgt::BufferAddress { - let ray_tracing_functions = match self.shared.extension_fns.ray_tracing { - Some(ref functions) => functions, - None => panic!("Feature `RAY_TRACING` not enabled"), - }; + let ray_tracing_functions = self + .shared + .extension_fns + .ray_tracing + .as_ref() + .expect("Feature `RAY_TRACING` not enabled"); unsafe { ray_tracing_functions @@ -2201,10 +2225,12 @@ impl crate::Device for super::Device { &self, desc: &crate::AccelerationStructureDescriptor, ) -> Result { - let ray_tracing_functions = match self.shared.extension_fns.ray_tracing { - Some(ref functions) => functions, - None => panic!("Feature `RAY_TRACING` not enabled"), - }; + let ray_tracing_functions = self + .shared + .extension_fns + .ray_tracing + .as_ref() + .expect("Feature `RAY_TRACING` not enabled"); let vk_buffer_info = vk::BufferCreateInfo::builder() .size(desc.size) @@ -2264,10 +2290,12 @@ impl crate::Device for super::Device { &self, acceleration_structure: super::AccelerationStructure, ) { - let ray_tracing_functions = match self.shared.extension_fns.ray_tracing { - Some(ref functions) => functions, - None => panic!("Feature `RAY_TRACING` not enabled"), - }; + let ray_tracing_functions = self + .shared + .extension_fns + .ray_tracing + .as_ref() + .expect("Feature `RAY_TRACING` not enabled"); unsafe { ray_tracing_functions diff --git a/wgpu-hal/src/vulkan/instance.rs b/wgpu-hal/src/vulkan/instance.rs index 18b141a070..179842c1e5 100644 --- a/wgpu-hal/src/vulkan/instance.rs +++ b/wgpu-hal/src/vulkan/instance.rs @@ -1,6 +1,7 @@ use std::{ ffi::{c_void, CStr, CString}, slice, + str::FromStr, sync::Arc, thread, }; @@ -9,6 +10,7 @@ use ash::{ extensions::{ext, khr}, vk, }; +use parking_lot::RwLock; unsafe extern "system" fn debug_utils_messenger_callback( message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, @@ -40,10 +42,11 @@ unsafe extern "system" fn debug_utils_messenger_callback( } } - // Silence Vulkan Validation error "VUID-VkSwapchainCreateInfoKHR-imageExtent-01274" - // - it's a false positive due to the inherent racy-ness of surface resizing - const VUID_VKSWAPCHAINCREATEINFOKHR_IMAGEEXTENT_01274: i32 = 0x7cd0911d; - if cd.message_id_number == VUID_VKSWAPCHAINCREATEINFOKHR_IMAGEEXTENT_01274 { + // Silence Vulkan Validation error "VUID-VkSwapchainCreateInfoKHR-pNext-07781" + // This happens when a surface is configured with a size outside the allowed extent. + // It's s false positive due to the inherent racy-ness of surface resizing. + const VUID_VKSWAPCHAINCREATEINFOKHR_PNEXT_07781: i32 = 0x4c8929c1; + if cd.message_id_number == VUID_VKSWAPCHAINCREATEINFOKHR_PNEXT_07781 { return vk::FALSE; } @@ -145,17 +148,33 @@ unsafe extern "system" fn debug_utils_messenger_callback( if cfg!(debug_assertions) && level == log::Level::Error { // Set canary and continue - crate::VALIDATION_CANARY.set(); + crate::VALIDATION_CANARY.add(message.to_string()); } vk::FALSE } +impl super::DebugUtilsCreateInfo { + fn to_vk_create_info(&self) -> vk::DebugUtilsMessengerCreateInfoEXTBuilder<'_> { + let user_data_ptr: *const super::DebugUtilsMessengerUserData = &*self.callback_data; + vk::DebugUtilsMessengerCreateInfoEXT::builder() + .message_severity(self.severity) + .message_type(self.message_type) + .user_data(user_data_ptr as *mut _) + .pfn_user_callback(Some(debug_utils_messenger_callback)) + } +} + impl super::Swapchain { + /// # Safety + /// + /// - The device must have been made idle before calling this function. unsafe fn release_resources(self, device: &ash::Device) -> Self { profiling::scope!("Swapchain::release_resources"); { profiling::scope!("vkDeviceWaitIdle"); + // We need to also wait until all presentation work is done. Because there is no way to portably wait until + // the presentation work is done, we are forced to wait until the device is idle. let _ = unsafe { device.device_wait_idle() }; }; unsafe { device.destroy_fence(self.fence, None) }; @@ -172,8 +191,8 @@ impl super::InstanceShared { &self.raw } - pub fn driver_api_version(&self) -> u32 { - self.driver_api_version + pub fn instance_api_version(&self) -> u32 { + self.instance_api_version } pub fn extensions(&self) -> &[&'static CStr] { @@ -186,19 +205,34 @@ impl super::Instance { &self.shared } - pub fn required_extensions( + /// Return the instance extension names wgpu would like to enable. + /// + /// Return a vector of the names of instance extensions actually available + /// on `entry` that wgpu would like to enable. + /// + /// The `instance_api_version` argument should be the instance's Vulkan API + /// version, as obtained from `vkEnumerateInstanceVersion`. This is the same + /// space of values as the `VK_API_VERSION` constants. + /// + /// Note that wgpu can function without many of these extensions (for + /// example, `VK_KHR_wayland_surface` is certainly not going to be available + /// everywhere), but if one of these extensions is available at all, wgpu + /// assumes that it has been enabled. + pub fn desired_extensions( entry: &ash::Entry, - _driver_api_version: u32, - flags: crate::InstanceFlags, + _instance_api_version: u32, + flags: wgt::InstanceFlags, ) -> Result, crate::InstanceError> { - let instance_extensions = entry - .enumerate_instance_extension_properties(None) - .map_err(|e| { - crate::InstanceError::with_source( - String::from("enumerate_instance_extension_properties() failed"), - e, - ) - })?; + let instance_extensions = { + profiling::scope!("vkEnumerateInstanceExtensionProperties"); + entry.enumerate_instance_extension_properties(None) + }; + let instance_extensions = instance_extensions.map_err(|e| { + crate::InstanceError::with_source( + String::from("enumerate_instance_extension_properties() failed"), + e, + ) + })?; // Check our extensions against the available extensions let mut extensions: Vec<&'static CStr> = Vec::new(); @@ -233,7 +267,7 @@ impl super::Instance { extensions.push(ash::vk::KhrPortabilityEnumerationFn::name()); } - if flags.contains(crate::InstanceFlags::DEBUG) { + if flags.contains(wgt::InstanceFlags::DEBUG) { // VK_EXT_debug_utils extensions.push(ext::DebugUtils::name()); } @@ -254,7 +288,7 @@ impl super::Instance { }) { true } else { - log::info!("Unable to find extension: {}", ext.to_string_lossy()); + log::warn!("Unable to find extension: {}", ext.to_string_lossy()); false } }); @@ -264,9 +298,9 @@ impl super::Instance { /// # Safety /// /// - `raw_instance` must be created from `entry` - /// - `raw_instance` must be created respecting `driver_api_version`, `extensions` and `flags` - /// - `extensions` must be a superset of `required_extensions()` and must be created from the - /// same entry, driver_api_version and flags. + /// - `raw_instance` must be created respecting `instance_api_version`, `extensions` and `flags` + /// - `extensions` must be a superset of `desired_extensions()` and must be created from the + /// same entry, `instance_api_version`` and flags. /// - `android_sdk_version` is ignored and can be `0` for all platforms besides Android /// /// If `debug_utils_user_data` is `Some`, then the validation layer is @@ -275,52 +309,29 @@ impl super::Instance { pub unsafe fn from_raw( entry: ash::Entry, raw_instance: ash::Instance, - driver_api_version: u32, + instance_api_version: u32, android_sdk_version: u32, - debug_utils_user_data: Option, + debug_utils_create_info: Option, extensions: Vec<&'static CStr>, - flags: crate::InstanceFlags, + flags: wgt::InstanceFlags, has_nv_optimus: bool, drop_guard: Option, ) -> Result { - log::info!("Instance version: 0x{:x}", driver_api_version); + log::debug!("Instance version: 0x{:x}", instance_api_version); - let debug_utils = if let Some(debug_callback_user_data) = debug_utils_user_data { + let debug_utils = if let Some(debug_utils_create_info) = debug_utils_create_info { if extensions.contains(&ext::DebugUtils::name()) { log::info!("Enabling debug utils"); - // Move the callback data to the heap, to ensure it will never be - // moved. - let callback_data = Box::new(debug_callback_user_data); let extension = ext::DebugUtils::new(&entry, &raw_instance); - // having ERROR unconditionally because Vk doesn't like empty flags - let mut severity = vk::DebugUtilsMessageSeverityFlagsEXT::ERROR; - if log::max_level() >= log::LevelFilter::Debug { - severity |= vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE; - } - if log::max_level() >= log::LevelFilter::Info { - severity |= vk::DebugUtilsMessageSeverityFlagsEXT::INFO; - } - if log::max_level() >= log::LevelFilter::Warn { - severity |= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING; - } - let user_data_ptr: *const super::DebugUtilsMessengerUserData = &*callback_data; - let vk_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() - .flags(vk::DebugUtilsMessengerCreateFlagsEXT::empty()) - .message_severity(severity) - .message_type( - vk::DebugUtilsMessageTypeFlagsEXT::GENERAL - | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION - | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, - ) - .pfn_user_callback(Some(debug_utils_messenger_callback)) - .user_data(user_data_ptr as *mut _); + let vk_info = debug_utils_create_info.to_vk_create_info(); let messenger = unsafe { extension.create_debug_utils_messenger(&vk_info, None) }.unwrap(); + Some(super::DebugUtils { extension, messenger, - callback_data, + callback_data: debug_utils_create_info.callback_data, }) } else { log::info!("Debug utils not enabled: extension not listed"); @@ -336,7 +347,7 @@ impl super::Instance { let get_physical_device_properties = if extensions.contains(&khr::GetPhysicalDeviceProperties2::name()) { - log::info!("Enabling device properties2"); + log::debug!("Enabling device properties2"); Some(khr::GetPhysicalDeviceProperties2::new( &entry, &raw_instance, @@ -355,7 +366,7 @@ impl super::Instance { get_physical_device_properties, entry, has_nv_optimus, - driver_api_version, + instance_api_version, android_sdk_version, }), }) @@ -497,7 +508,7 @@ impl super::Instance { Ok(self.create_surface_from_vk_surface_khr(surface)) } - #[cfg(any(target_os = "macos", target_os = "ios"))] + #[cfg(metal)] fn create_surface_from_view( &self, view: *mut c_void, @@ -531,7 +542,7 @@ impl super::Instance { raw: surface, functor, instance: Arc::clone(&self.shared), - swapchain: None, + swapchain: RwLock::new(None), } } } @@ -539,10 +550,12 @@ impl super::Instance { impl Drop for super::InstanceShared { fn drop(&mut self) { unsafe { - if let Some(du) = self.debug_utils.take() { + // Keep du alive since destroy_instance may also log + let _du = self.debug_utils.take().map(|du| { du.extension .destroy_debug_utils_messenger(du.messenger, None); - } + du + }); if let Some(_drop_guard) = self.drop_guard.take() { self.raw.destroy_instance(None); } @@ -552,12 +565,21 @@ impl Drop for super::InstanceShared { impl crate::Instance for super::Instance { unsafe fn init(desc: &crate::InstanceDescriptor) -> Result { + profiling::scope!("Init Vulkan Backend"); use crate::auxil::cstr_from_bytes_until_nul; - let entry = unsafe { ash::Entry::load() }.map_err(|err| { + let entry = unsafe { + profiling::scope!("Load vk library"); + ash::Entry::load() + } + .map_err(|err| { crate::InstanceError::with_source(String::from("missing Vulkan entry points"), err) })?; - let driver_api_version = match entry.try_enumerate_instance_version() { + let version = { + profiling::scope!("vkEnumerateInstanceVersion"); + entry.try_enumerate_instance_version() + }; + let instance_api_version = match version { // Vulkan 1.1+ Ok(Some(version)) => version, Ok(None) => vk::API_VERSION_1_0, @@ -577,7 +599,7 @@ impl crate::Instance for super::Instance { .engine_version(2) .api_version( // Vulkan 1.0 doesn't like anything but 1.0 passed in here... - if driver_api_version < vk::API_VERSION_1_1 { + if instance_api_version < vk::API_VERSION_1_1 { vk::API_VERSION_1_0 } else { // This is the max Vulkan API version supported by `wgpu-hal`. @@ -588,14 +610,18 @@ impl crate::Instance for super::Instance { // - If any were promoted in the new API version and the behavior has changed, we must handle the new behavior in addition to the old behavior. // - If any were obsoleted in the new API version, we must implement a fallback for the new API version // - If any are non-KHR-vendored, we must ensure the new behavior is still correct (since backwards-compatibility is not guaranteed). - vk::HEADER_VERSION_COMPLETE + vk::API_VERSION_1_3 }, ); - let extensions = Self::required_extensions(&entry, driver_api_version, desc.flags)?; + let extensions = Self::desired_extensions(&entry, instance_api_version, desc.flags)?; - let instance_layers = entry.enumerate_instance_layer_properties().map_err(|e| { - log::info!("enumerate_instance_layer_properties: {:?}", e); + let instance_layers = { + profiling::scope!("vkEnumerateInstanceLayerProperties"); + entry.enumerate_instance_layer_properties() + }; + let instance_layers = instance_layers.map_err(|e| { + log::debug!("enumerate_instance_layer_properties: {:?}", e); crate::InstanceError::with_source( String::from("enumerate_instance_layer_properties() failed"), e, @@ -620,21 +646,52 @@ impl crate::Instance for super::Instance { let mut layers: Vec<&'static CStr> = Vec::new(); // Request validation layer if asked. - let mut debug_callback_user_data = None; - if desc.flags.contains(crate::InstanceFlags::VALIDATION) { + let mut debug_utils = None; + if desc.flags.intersects(wgt::InstanceFlags::VALIDATION) { let validation_layer_name = CStr::from_bytes_with_nul(b"VK_LAYER_KHRONOS_validation\0").unwrap(); if let Some(layer_properties) = find_layer(&instance_layers, validation_layer_name) { layers.push(validation_layer_name); - debug_callback_user_data = Some(super::DebugUtilsMessengerUserData { - validation_layer_description: cstr_from_bytes_until_nul( - &layer_properties.description, - ) - .unwrap() - .to_owned(), - validation_layer_spec_version: layer_properties.spec_version, - has_obs_layer, - }); + + if extensions.contains(&ext::DebugUtils::name()) { + // Put the callback data on the heap, to ensure it will never be + // moved. + let callback_data = Box::new(super::DebugUtilsMessengerUserData { + validation_layer_description: cstr_from_bytes_until_nul( + &layer_properties.description, + ) + .unwrap() + .to_owned(), + validation_layer_spec_version: layer_properties.spec_version, + has_obs_layer, + }); + + // having ERROR unconditionally because Vk doesn't like empty flags + let mut severity = vk::DebugUtilsMessageSeverityFlagsEXT::ERROR; + if log::max_level() >= log::LevelFilter::Debug { + severity |= vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE; + } + if log::max_level() >= log::LevelFilter::Info { + severity |= vk::DebugUtilsMessageSeverityFlagsEXT::INFO; + } + if log::max_level() >= log::LevelFilter::Warn { + severity |= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING; + } + + let message_type = vk::DebugUtilsMessageTypeFlagsEXT::GENERAL + | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION + | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE; + + let create_info = super::DebugUtilsCreateInfo { + severity, + message_type, + callback_data, + }; + + let vk_create_info = create_info.to_vk_create_info().build(); + + debug_utils = Some((create_info, vk_create_info)); + } } else { log::warn!( "InstanceFlags::VALIDATION requested, but unable to find layer: {}", @@ -673,24 +730,31 @@ impl crate::Instance for super::Instance { if extensions.contains(&ash::vk::KhrPortabilityEnumerationFn::name()) { flags |= vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR; } - let vk_instance = { let str_pointers = layers .iter() .chain(extensions.iter()) - .map(|&s| { + .map(|&s: &&'static _| { // Safe because `layers` and `extensions` entries have static lifetime. s.as_ptr() }) .collect::>(); - let create_info = vk::InstanceCreateInfo::builder() + let mut create_info = vk::InstanceCreateInfo::builder() .flags(flags) .application_info(&app_info) .enabled_layer_names(&str_pointers[..layers.len()]) .enabled_extension_names(&str_pointers[layers.len()..]); - unsafe { entry.create_instance(&create_info, None) }.map_err(|e| { + if let Some(&mut (_, ref mut vk_create_info)) = debug_utils.as_mut() { + create_info = create_info.push_next(vk_create_info); + } + + unsafe { + profiling::scope!("vkCreateInstance"); + entry.create_instance(&create_info, None) + } + .map_err(|e| { crate::InstanceError::with_source( String::from("Entry::create_instance() failed"), e, @@ -702,9 +766,9 @@ impl crate::Instance for super::Instance { Self::from_raw( entry, vk_instance, - driver_api_version, + instance_api_version, android_sdk_version, - debug_callback_user_data, + debug_utils.map(|(i, _)| i), extensions, desc.flags, has_nv_optimus, @@ -722,33 +786,37 @@ impl crate::Instance for super::Instance { match (window_handle, display_handle) { (Rwh::Wayland(handle), Rdh::Wayland(display)) => { - self.create_surface_from_wayland(display.display, handle.surface) + self.create_surface_from_wayland(display.display.as_ptr(), handle.surface.as_ptr()) } (Rwh::Xlib(handle), Rdh::Xlib(display)) => { - self.create_surface_from_xlib(display.display as *mut _, handle.window) + let display = display.display.expect("Display pointer is not set."); + self.create_surface_from_xlib(display.as_ptr() as *mut *const c_void, handle.window) } (Rwh::Xcb(handle), Rdh::Xcb(display)) => { - self.create_surface_from_xcb(display.connection, handle.window) + let connection = display.connection.expect("Pointer to X-Server is not set."); + self.create_surface_from_xcb(connection.as_ptr(), handle.window.get()) + } + (Rwh::AndroidNdk(handle), _) => { + self.create_surface_android(handle.a_native_window.as_ptr()) } - (Rwh::AndroidNdk(handle), _) => self.create_surface_android(handle.a_native_window), #[cfg(windows)] (Rwh::Win32(handle), _) => { use winapi::um::libloaderapi::GetModuleHandleW; let hinstance = unsafe { GetModuleHandleW(std::ptr::null()) }; - self.create_surface_from_hwnd(hinstance as *mut _, handle.hwnd) + self.create_surface_from_hwnd(hinstance as *mut _, handle.hwnd.get() as *mut _) } - #[cfg(target_os = "macos")] + #[cfg(all(target_os = "macos", feature = "metal"))] (Rwh::AppKit(handle), _) if self.shared.extensions.contains(&ext::MetalSurface::name()) => { - self.create_surface_from_view(handle.ns_view) + self.create_surface_from_view(handle.ns_view.as_ptr()) } - #[cfg(target_os = "ios")] + #[cfg(all(target_os = "ios", feature = "metal"))] (Rwh::UiKit(handle), _) if self.shared.extensions.contains(&ext::MetalSurface::name()) => { - self.create_surface_from_view(handle.ui_view) + self.create_surface_from_view(handle.ui_view.as_ptr()) } (_, _) => Err(crate::InstanceError::new(format!( "window handle {window_handle:?} is not a Vulkan-compatible handle" @@ -786,13 +854,27 @@ impl crate::Instance for super::Instance { if exposed.info.device_type == wgt::DeviceType::IntegratedGpu && exposed.info.vendor == db::intel::VENDOR { - // See https://gitlab.freedesktop.org/mesa/mesa/-/issues/4688 - log::warn!( - "Disabling presentation on '{}' (id {:?}) because of NV Optimus (on Linux)", - exposed.info.name, - exposed.adapter.raw - ); - exposed.adapter.private_caps.can_present = false; + // Check if mesa driver and version less than 21.2 + if let Some(version) = exposed.info.driver_info.split_once("Mesa ").map(|s| { + let mut components = s.1.split('.'); + let major = components.next().and_then(|s| u8::from_str(s).ok()); + let minor = components.next().and_then(|s| u8::from_str(s).ok()); + if let (Some(major), Some(minor)) = (major, minor) { + (major, minor) + } else { + (0, 0) + } + }) { + if version < (21, 2) { + // See https://gitlab.freedesktop.org/mesa/mesa/-/issues/4688 + log::warn!( + "Disabling presentation on '{}' (id {:?}) due to NV Optimus and Intel Mesa < v21.2", + exposed.info.name, + exposed.adapter.raw + ); + exposed.adapter.private_caps.can_present = false; + } + } } } } @@ -803,33 +885,36 @@ impl crate::Instance for super::Instance { impl crate::Surface for super::Surface { unsafe fn configure( - &mut self, + &self, device: &super::Device, config: &crate::SurfaceConfiguration, ) -> Result<(), crate::SurfaceError> { - let old = self - .swapchain + // Safety: `configure`'s contract guarantees there are no resources derived from the swapchain in use. + let mut swap_chain = self.swapchain.write(); + let old = swap_chain .take() .map(|sc| unsafe { sc.release_resources(&device.shared.raw) }); let swapchain = unsafe { device.create_swapchain(self, config, old)? }; - self.swapchain = Some(swapchain); + *swap_chain = Some(swapchain); Ok(()) } - unsafe fn unconfigure(&mut self, device: &super::Device) { - if let Some(sc) = self.swapchain.take() { + unsafe fn unconfigure(&self, device: &super::Device) { + if let Some(sc) = self.swapchain.write().take() { + // Safety: `unconfigure`'s contract guarantees there are no resources derived from the swapchain in use. let swapchain = unsafe { sc.release_resources(&device.shared.raw) }; unsafe { swapchain.functor.destroy_swapchain(swapchain.raw, None) }; } } unsafe fn acquire_texture( - &mut self, + &self, timeout: Option, ) -> Result>, crate::SurfaceError> { - let sc = self.swapchain.as_mut().unwrap(); + let mut swapchain = self.swapchain.write(); + let sc = swapchain.as_mut().unwrap(); let mut timeout_ns = match timeout { Some(duration) => duration.as_nanos() as u64, @@ -916,5 +1001,5 @@ impl crate::Surface for super::Surface { })) } - unsafe fn discard_texture(&mut self, _texture: super::SurfaceTexture) {} + unsafe fn discard_texture(&self, _texture: super::SurfaceTexture) {} } diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index 837bd15cec..45deda5d5b 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -31,19 +31,28 @@ mod conv; mod device; mod instance; -use std::{borrow::Borrow, ffi::CStr, fmt, num::NonZeroU32, sync::Arc}; +use std::{ + borrow::Borrow, + ffi::CStr, + fmt, + num::NonZeroU32, + sync::{ + atomic::{AtomicIsize, Ordering}, + Arc, + }, +}; use arrayvec::ArrayVec; use ash::{ extensions::{ext, khr}, vk, }; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; const MILLIS_TO_NANOS: u64 = 1_000_000; const MAX_TOTAL_ATTACHMENTS: usize = crate::MAX_COLOR_ATTACHMENTS * 2 + 1; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Api; impl crate::Api for Api { @@ -86,6 +95,12 @@ struct DebugUtils { callback_data: Box, } +pub struct DebugUtilsCreateInfo { + severity: vk::DebugUtilsMessageSeverityFlagsEXT, + message_type: vk::DebugUtilsMessageTypeFlagsEXT, + callback_data: Box, +} + /// User data needed by `instance::debug_utils_messenger_callback`. /// /// When we create the [`vk::DebugUtilsMessengerEXT`], the `pUserData` @@ -107,13 +122,19 @@ pub struct InstanceShared { raw: ash::Instance, extensions: Vec<&'static CStr>, drop_guard: Option, - flags: crate::InstanceFlags, + flags: wgt::InstanceFlags, debug_utils: Option, get_physical_device_properties: Option, entry: ash::Entry, has_nv_optimus: bool, android_sdk_version: u32, - driver_api_version: u32, + /// The instance API version. + /// + /// Which is the version of Vulkan supported for instance-level functionality. + /// + /// It is associated with a `VkInstance` and its children, + /// except for a `VkPhysicalDevice` and its children. + instance_api_version: u32, } pub struct Instance { @@ -135,7 +156,7 @@ pub struct Surface { raw: vk::SurfaceKHR, functor: khr::Surface, instance: Arc, - swapchain: Option, + swapchain: RwLock>, } #[derive(Debug)] @@ -203,6 +224,7 @@ struct PrivateCapabilities { robust_buffer_access2: bool, robust_image_access2: bool, zero_initialize_workgroup_memory: bool, + image_format_list: bool, } bitflags::bitflags!( @@ -214,6 +236,28 @@ bitflags::bitflags!( /// Qualcomm OOMs when there are zero color attachments but a non-null pointer /// to a subpass resolve attachment array. This nulls out that pointer in that case. const EMPTY_RESOLVE_ATTACHMENT_LISTS = 0x2; + /// If the following code returns false, then nvidia will end up filling the wrong range. + /// + /// ```skip + /// fn nvidia_succeeds() -> bool { + /// # let (copy_length, start_offset) = (0, 0); + /// if copy_length >= 4096 { + /// if start_offset % 16 != 0 { + /// if copy_length == 4096 { + /// return true; + /// } + /// if copy_length % 16 == 0 { + /// return false; + /// } + /// } + /// } + /// true + /// } + /// ``` + /// + /// As such, we need to make sure all calls to vkCmdFillBuffer are aligned to 16 bytes + /// if they cover a range of 4096 bytes or more. + const FORCE_FILL_BUFFER_WITH_SIZE_GREATER_4096_ALIGNED_OFFSET_16 = 0x4; } ); @@ -312,7 +356,7 @@ pub struct Queue { /// It would be correct to use a single semaphore there, but /// [Intel hangs in `anv_queue_finish`](https://gitlab.freedesktop.org/mesa/mesa/-/issues/5508). relay_semaphores: [vk::Semaphore; 2], - relay_index: Option, + relay_index: AtomicIsize, } #[derive(Debug)] @@ -539,7 +583,7 @@ impl Fence { impl crate::Queue for Queue { unsafe fn submit( - &mut self, + &self, command_buffers: &[&CommandBuffer], signal_fence: Option<(&mut Fence, crate::FenceValue)>, ) -> Result<(), crate::DeviceError> { @@ -584,16 +628,17 @@ impl crate::Queue for Queue { } let wait_stage_mask = [vk::PipelineStageFlags::TOP_OF_PIPE]; - let sem_index = match self.relay_index { - Some(old_index) => { - vk_info = vk_info - .wait_semaphores(&self.relay_semaphores[old_index..old_index + 1]) - .wait_dst_stage_mask(&wait_stage_mask); - (old_index + 1) % self.relay_semaphores.len() - } - None => 0, + let old_index = self.relay_index.load(Ordering::Relaxed); + let sem_index = if old_index >= 0 { + vk_info = vk_info + .wait_semaphores(&self.relay_semaphores[old_index as usize..old_index as usize + 1]) + .wait_dst_stage_mask(&wait_stage_mask); + (old_index as usize + 1) % self.relay_semaphores.len() + } else { + 0 }; - self.relay_index = Some(sem_index); + self.relay_index + .store(sem_index as isize, Ordering::Relaxed); signal_semaphores[0] = self.relay_semaphores[sem_index]; let signal_count = if signal_semaphores[1] == vk::Semaphore::null() { @@ -613,11 +658,12 @@ impl crate::Queue for Queue { } unsafe fn present( - &mut self, - surface: &mut Surface, + &self, + surface: &Surface, texture: SurfaceTexture, ) -> Result<(), crate::SurfaceError> { - let ssc = surface.swapchain.as_ref().unwrap(); + let mut swapchain = surface.swapchain.write(); + let ssc = swapchain.as_mut().unwrap(); let swapchains = [ssc.raw]; let image_indices = [texture.index]; @@ -625,8 +671,11 @@ impl crate::Queue for Queue { .swapchains(&swapchains) .image_indices(&image_indices); - if let Some(old_index) = self.relay_index.take() { - vk_info = vk_info.wait_semaphores(&self.relay_semaphores[old_index..old_index + 1]); + let old_index = self.relay_index.swap(-1, Ordering::Relaxed); + if old_index >= 0 { + vk_info = vk_info.wait_semaphores( + &self.relay_semaphores[old_index as usize..old_index as usize + 1], + ); } let suboptimal = { diff --git a/wgpu-info/README.md b/wgpu-info/README.md index 7857553b7a..8bfdd7d210 100644 --- a/wgpu-info/README.md +++ b/wgpu-info/README.md @@ -14,6 +14,6 @@ cargo run --bin wgpu-info #### Running Test on many Adapters -When called with any amount of arguments it will interpret all of the arguments as a command to run. It will run this command N different times, one for every combination of adapter and backend on the system. +When called with any amount of arguments, it will interpret all of the arguments as a command to run. It will run this command N different times, one for every combination of adapter and backend on the system. For every command invocation, it will set `WGPU_ADAPTER_NAME` to the name of the adapter name and `WGPU_BACKEND` to the name of the backend. This is used as the primary means of testing across many adapters. diff --git a/wgpu-info/src/cli.rs b/wgpu-info/src/cli.rs index 1e81e9f0f8..b7fe25a593 100644 --- a/wgpu-info/src/cli.rs +++ b/wgpu-info/src/cli.rs @@ -2,12 +2,23 @@ use std::{io, process::exit}; use anyhow::Context; +use crate::human::PrintingVerbosity; + const HELP: &str = "\ -Usage: wgpu-info [--input ] [--output ] [--json] +Usage: wgpu-info [--input ] [--output ] [-q/-v/-vv/--json] + +Information Density: + These settings have no effect on the JSON output. The highest verbosity + provided will be used if multiple are passed. + + -q Quiet mode, only print the names and backends of the adapters. + [default] Print the adapter info. + -v Additionally print all features, limits, and downlevel capabilities. + -vv Additionally print all texture capabilities and flags. Options: -h, --help Print this help message. - -i, --input Source to read JSON report from. (\"-\" reads from stdin) + -i, --input Read a json report to make it human readable. (\"-\" reads from stdin) -o, --output Destination to write output to. (\"-\" writes to stdout) -j, --json Output JSON information instead of human-readable text. "; @@ -31,6 +42,23 @@ pub fn main() -> anyhow::Result<()> { let output_path: Option = args.opt_value_from_str(["-o", "--output"]).unwrap(); let json = args.contains(["-j", "--json"]); + let verbosity = if args.contains("-vv") { + PrintingVerbosity::InformationFeaturesLimitsTexture + } else if args.contains("-v") { + PrintingVerbosity::InformationFeaturesLimits + } else if args.contains("-q") { + PrintingVerbosity::NameOnly + } else { + PrintingVerbosity::Information + }; + + // Binary OR is intentional, we want all flags to be consumed every iteration. + while args.contains("-vv") | args.contains("-v") | args.contains("-q") { + eprintln!( + "Warning: More than one verbosity flag was passed. Using the most verbose option." + ); + } + let remaining = args.finish(); if !remaining.is_empty() { eprint!("Unknown argument(s): "); @@ -82,7 +110,7 @@ pub fn main() -> anyhow::Result<()> { .into_json(output) .with_context(|| format!("Failed to write to output: {output_name}"))?; } else { - crate::human::print_adapters(&mut output, &report) + crate::human::print_adapters(&mut output, &report, verbosity) .with_context(|| format!("Failed to write to output: {output_name}"))?; } diff --git a/wgpu-info/src/human.rs b/wgpu-info/src/human.rs index 11d88d955c..9cc4c27f84 100644 --- a/wgpu-info/src/human.rs +++ b/wgpu-info/src/human.rs @@ -53,9 +53,29 @@ trait FlagsExt: Flags { impl FlagsExt for T where T: Flags {} +fn print_empty_string(input: &str) -> &str { + if input.is_empty() { + "" + } else { + input + } +} + +#[derive(Debug, Clone, Copy)] +pub enum PrintingVerbosity { + /// Corresponds to the `-q` flag + NameOnly, + /// Corresponds to no flag. + Information, + /// Corresponds to the `-v` flag + InformationFeaturesLimits, + /// Corresponds to the `-vv` flag + InformationFeaturesLimitsTexture, +} + // Lets keep these print statements on one line #[rustfmt::skip] -fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize) -> io::Result<()> { +fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize, verbosity: PrintingVerbosity) -> io::Result<()> { let AdapterReport { info, features, @@ -69,15 +89,24 @@ fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize // Adapter Info // ////////////////// + if matches!(verbosity, PrintingVerbosity::NameOnly) { + writeln!(output, "Adapter {idx}: {} ({:?})", info.name, info.backend)?; + return Ok(()); + } + writeln!(output, "Adapter {idx}:")?; - writeln!(output, "\t Backend: {:?}", info.backend)?; - writeln!(output, "\t Name: {:?}", info.name)?; - writeln!(output, "\t VendorID: {:?}", info.vendor)?; - writeln!(output, "\t DeviceID: {:?}", info.device)?; - writeln!(output, "\t Type: {:?}", info.device_type)?; - writeln!(output, "\t Driver: {:?}", info.driver)?; - writeln!(output, "\tDriverInfo: {:?}", info.driver_info)?; - writeln!(output, "\t Compliant: {:?}", downlevel.is_webgpu_compliant())?; + writeln!(output, "\t Backend: {:?}", info.backend)?; + writeln!(output, "\t Name: {}", info.name)?; + writeln!(output, "\t VendorID: {:#X?}", info.vendor)?; + writeln!(output, "\t DeviceID: {:#X?}", info.device)?; + writeln!(output, "\t Type: {:?}", info.device_type)?; + writeln!(output, "\t Driver: {}", print_empty_string(&info.driver))?; + writeln!(output, "\t DriverInfo: {}", print_empty_string(&info.driver_info))?; + writeln!(output, "\tWebGPU Compliant: {:?}", downlevel.is_webgpu_compliant())?; + + if matches!(verbosity, PrintingVerbosity::Information) { + return Ok(()); + } ////////////// // Features // @@ -166,16 +195,19 @@ fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize writeln!(output, "\tDownlevel Properties:")?; let wgpu::DownlevelCapabilities { - shader_model, + shader_model: _, limits: _, flags, } = downlevel; - writeln!(output, "\t\t Shader Model: {shader_model:?}")?; let max_downlevel_flag_width = wgpu::DownlevelFlags::max_debug_print_width(); for bit in wgpu::DownlevelFlags::all().iter() { writeln!(output, "\t\t{:>width$}: {}", bit.name(), flags.contains(bit), width = max_downlevel_flag_width)?; }; + if matches!(verbosity, PrintingVerbosity::InformationFeaturesLimits) { + return Ok(()); + } + //////////////////// // Texture Usages // //////////////////// @@ -237,9 +269,13 @@ fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize Ok(()) } -pub fn print_adapters(output: &mut impl io::Write, report: &GpuReport) -> io::Result<()> { +pub fn print_adapters( + output: &mut impl io::Write, + report: &GpuReport, + verbosity: PrintingVerbosity, +) -> io::Result<()> { for (idx, adapter) in report.devices.iter().enumerate() { - print_adapter(output, adapter, idx)?; + print_adapter(output, adapter, idx, verbosity)?; } Ok(()) } diff --git a/wgpu-info/src/report.rs b/wgpu-info/src/report.rs index 656e96ee87..5e27fa7c0e 100644 --- a/wgpu-info/src/report.rs +++ b/wgpu-info/src/report.rs @@ -7,6 +7,9 @@ use wgpu::{ use crate::texture; +/// Report specifying the capabilities of the GPUs on the system. +/// +/// Must be synchronized with the definition on tests/src/report.rs. #[derive(Deserialize, Serialize)] pub struct GpuReport { pub devices: Vec, @@ -14,7 +17,12 @@ pub struct GpuReport { impl GpuReport { pub fn generate() -> Self { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::util::backend_bits_from_env().unwrap_or_default(), + flags: wgpu::InstanceFlags::debugging().with_env(), + dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(), + gles_minor_version: wgpu::util::gles_minor_version_from_env().unwrap_or_default(), + }); let adapters = instance.enumerate_adapters(wgpu::Backends::all()); let mut devices = Vec::with_capacity(adapters.len()); @@ -48,6 +56,9 @@ impl GpuReport { } } +/// A single report of the capabilities of an Adapter. +/// +/// Must be synchronized with the definition on tests/src/report.rs. #[derive(Deserialize, Serialize)] pub struct AdapterReport { pub info: AdapterInfo, diff --git a/wgpu-info/src/texture.rs b/wgpu-info/src/texture.rs index 5bafa67a59..b6f79c0482 100644 --- a/wgpu-info/src/texture.rs +++ b/wgpu-info/src/texture.rs @@ -1,6 +1,6 @@ // Lets keep these on one line #[rustfmt::skip] -pub const TEXTURE_FORMAT_LIST: [wgpu::TextureFormat; 114] = [ +pub const TEXTURE_FORMAT_LIST: [wgpu::TextureFormat; 119] = [ wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::R8Snorm, wgpu::TextureFormat::R8Uint, @@ -29,6 +29,7 @@ pub const TEXTURE_FORMAT_LIST: [wgpu::TextureFormat; 114] = [ wgpu::TextureFormat::Rgba8Sint, wgpu::TextureFormat::Bgra8Unorm, wgpu::TextureFormat::Bgra8UnormSrgb, + wgpu::TextureFormat::Rgb10a2Uint, wgpu::TextureFormat::Rgb10a2Unorm, wgpu::TextureFormat::Rg11b10Float, wgpu::TextureFormat::Rg32Uint, @@ -49,6 +50,10 @@ pub const TEXTURE_FORMAT_LIST: [wgpu::TextureFormat; 114] = [ wgpu::TextureFormat::Depth24Plus, wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureFormat::Rgb9e5Ufloat, + wgpu::TextureFormat::Rgb10a2Uint, + wgpu::TextureFormat::Rgb10a2Unorm, + wgpu::TextureFormat::Rg11b10Float, + wgpu::TextureFormat::NV12, wgpu::TextureFormat::Bc1RgbaUnorm, wgpu::TextureFormat::Bc1RgbaUnormSrgb, wgpu::TextureFormat::Bc2RgbaUnorm, diff --git a/wgpu-macros/Cargo.toml b/wgpu-macros/Cargo.toml new file mode 100644 index 0000000000..b06df02cce --- /dev/null +++ b/wgpu-macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wgpu-macros" +version.workspace = true +authors.workspace = true +edition.workspace = true +description = "Macros for wgpu" +homepage.workspace = true +repository.workspace = true +keywords.workspace = true +license.workspace = true +exclude = ["Cargo.lock"] +publish = false + +[lib] +proc-macro = true + +[dependencies] +heck = "0.4" +quote = "1" +syn = { version = "2", features = ["full"] } diff --git a/wgpu-macros/src/lib.rs b/wgpu-macros/src/lib.rs new file mode 100644 index 0000000000..0b0812507f --- /dev/null +++ b/wgpu-macros/src/lib.rs @@ -0,0 +1,44 @@ +use heck::ToSnakeCase; +use proc_macro::TokenStream; +use quote::quote; +use syn::Ident; + +/// Creates a test that will run on all gpus on a given system. +/// +/// Apply this macro to a static variable with a type that can be converted to a `GpuTestConfiguration`. +#[proc_macro_attribute] +pub fn gpu_test(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input_static = syn::parse_macro_input!(item as syn::ItemStatic); + let expr = &input_static.expr; + let ident = &input_static.ident; + let ident_str = ident.to_string(); + let ident_lower = ident_str.to_snake_case(); + + let register_test_name = Ident::new(&format!("{}_initializer", ident_lower), ident.span()); + let test_name_webgl = Ident::new(&format!("{}_webgl", ident_lower), ident.span()); + + quote! { + #[cfg(not(target_arch = "wasm32"))] + #[::wgpu_test::ctor] + fn #register_test_name() { + struct S; + + ::wgpu_test::native::TEST_LIST.lock().push( + // Allow any type that can be converted to a GpuTestConfiguration + ::wgpu_test::GpuTestConfiguration::from(#expr).name_from_init_function_typename::(#ident_lower) + ) + } + + #[cfg(target_arch = "wasm32")] + #[wasm_bindgen_test::wasm_bindgen_test] + async fn #test_name_webgl() { + struct S; + + // Allow any type that can be converted to a GpuTestConfiguration + let test_config = ::wgpu_test::GpuTestConfiguration::from(#expr).name_from_init_function_typename::(#ident_lower); + + ::wgpu_test::execute_test(test_config, None, 0).await; + } + } + .into() +} diff --git a/wgpu-types/Cargo.toml b/wgpu-types/Cargo.toml index 4ef59398d0..1b306d0672 100644 --- a/wgpu-types/Cargo.toml +++ b/wgpu-types/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "wgpu-types" -version = "0.17.0" -authors = ["wgpu developers"] +version = "0.18.0" +authors = ["gfx-rs developers"] edition = "2021" description = "WebGPU types" homepage = "https://wgpu.rs/" @@ -9,6 +9,12 @@ repository = "https://github.com/gfx-rs/wgpu" keywords = ["graphics"] license = "MIT OR Apache-2.0" +# Override the workspace's `rust-version` key. Firefox uses `cargo vendor` to +# copy the crates it actually uses out of the workspace, so it's meaningful for +# them to have less restrictive MSRVs individually than the workspace as a +# whole, if their code permits. See `../README.md` for details. +rust-version = "1.70" + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] @@ -32,8 +38,8 @@ bitflags = "2" serde = { version = "1", features = ["serde_derive"], optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -js-sys = "0.3.64" -web-sys = { version = "0.3.64", features = [ +js-sys = "0.3.66" +web-sys = { version = "0.3.66", features = [ "ImageBitmap", "HtmlVideoElement", "HtmlCanvasElement", @@ -42,4 +48,4 @@ web-sys = { version = "0.3.64", features = [ [dev-dependencies] serde = { version = "1", features = ["serde_derive"] } -serde_json = "1.0.105" +serde_json = "1.0.111" diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index e0057678fe..c9aca690e5 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -102,12 +102,10 @@ pub enum Backend { Metal = 2, /// Direct3D-12 (Windows) Dx12 = 3, - /// Direct3D-11 (Windows) - Dx11 = 4, /// OpenGL ES-3 (Linux, Android) - Gl = 5, + Gl = 4, /// WebGPU in the browser - BrowserWebGpu = 6, + BrowserWebGpu = 5, } impl Backend { @@ -118,7 +116,6 @@ impl Backend { Backend::Vulkan => "vulkan", Backend::Metal => "metal", Backend::Dx12 => "dx12", - Backend::Dx11 => "dx11", Backend::Gl => "gl", Backend::BrowserWebGpu => "webgpu", } @@ -158,9 +155,12 @@ bitflags::bitflags! { const METAL = 1 << Backend::Metal as u32; /// Supported on Windows 10 const DX12 = 1 << Backend::Dx12 as u32; - /// Supported on Windows 7+ - const DX11 = 1 << Backend::Dx11 as u32; - /// Supported when targeting the web through webassembly + /// Supported when targeting the web through webassembly with the `webgpu` feature enabled. + /// + /// The WebGPU backend is special in several ways: + /// It is not not implemented by `wgpu_core` and instead by the higher level `wgpu` crate. + /// Whether WebGPU is targeted is decided upon the creation of the `wgpu::Instance`, + /// *not* upon adapter creation. See `wgpu::Instance::new`. const BROWSER_WEBGPU = 1 << Backend::BrowserWebGpu as u32; /// All the apis that wgpu offers first tier of support for. /// @@ -172,8 +172,14 @@ bitflags::bitflags! { /// All the apis that wgpu offers second tier of support for. These may /// be unsupported/still experimental. /// - /// OpenGL + DX11 - const SECONDARY = Self::GL.bits() | Self::DX11.bits(); + /// OpenGL + const SECONDARY = Self::GL.bits(); + } +} + +impl Default for Backends { + fn default() -> Self { + Self::all() } } @@ -270,16 +276,25 @@ bitflags::bitflags! { /// Supported Platforms: /// - Vulkan /// - DX12 - /// - Metal - TODO: Not yet supported on command encoder. + /// - Metal /// /// This is a web and native feature. const TIMESTAMP_QUERY = 1 << 1; - /// Allows non-zero value for the "first instance" in indirect draw calls. + /// Allows non-zero value for the `first_instance` member in indirect draw calls. + /// + /// If this feature is not enabled, and the `first_instance` member is non-zero, the behavior may be: + /// - The draw call is ignored. + /// - The draw call is executed as if the `first_instance` is zero. + /// - The draw call is executed with the correct `first_instance` value. /// /// Supported Platforms: /// - Vulkan (mostly) /// - DX12 - /// - Metal + /// - Metal on Apple3+ or Mac1+ + /// - OpenGL (Desktop 4.2+ with ARB_shader_draw_parameters only) + /// + /// Not Supported: + /// - OpenGL ES / WebGL /// /// This is a web and native feature. const INDIRECT_FIRST_INSTANCE = 1 << 2; @@ -309,10 +324,31 @@ bitflags::bitflags! { // // ? const FORMATS_TIER_1 = 1 << 14; (https://github.com/gpuweb/gpuweb/issues/3837) // ? const RW_STORAGE_TEXTURE_TIER_1 = 1 << 15; (https://github.com/gpuweb/gpuweb/issues/3838) - // TODO const BGRA8UNORM_STORAGE = 1 << 16; + + /// Allows the [`wgpu::TextureUsages::STORAGE_BINDING`] usage on textures with format [`TextureFormat::Bgra8unorm`] + /// + /// Supported Platforms: + /// - Vulkan + /// - DX12 + /// - Metal + /// + /// This is a web and native feature. + const BGRA8UNORM_STORAGE = 1 << 16; + // ? const NORM16_FILTERABLE = 1 << 17; (https://github.com/gpuweb/gpuweb/issues/3839) // ? const NORM16_RESOLVE = 1 << 18; (https://github.com/gpuweb/gpuweb/issues/3839) - // TODO const FLOAT32_FILTERABLE = 1 << 19; + + /// Allows textures with formats "r32float", "rg32float", and "rgba32float" to be filterable. + /// + /// Supported Platforms: + /// - Vulkan (mainly on Desktop GPUs) + /// - DX12 + /// - Metal on macOS or Apple9+ GPUs, optional on iOS/iPadOS with Apple7/8 GPUs + /// - GL with one of `GL_ARB_color_buffer_float`/`GL_EXT_color_buffer_float`/`OES_texture_float_linear` + /// + /// This is a web and native feature. + const FLOAT32_FILTERABLE = 1 << 19; + // ? const FLOAT32_BLENDABLE = 1 << 20; (https://github.com/gpuweb/gpuweb/issues/3556) // ? const 32BIT_FORMAT_MULTISAMPLE = 1 << 21; (https://github.com/gpuweb/gpuweb/issues/3844) // ? const 32BIT_FORMAT_RESOLVE = 1 << 22; (https://github.com/gpuweb/gpuweb/issues/3844) @@ -371,7 +407,7 @@ bitflags::bitflags! { /// Compressed textures sacrifice some quality in exchange for significantly reduced /// bandwidth usage. /// - /// Support for this feature guarantees availability of [`TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING`] for ASTC formats. + /// Support for this feature guarantees availability of [`TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING`] for ASTC formats with Unorm/UnormSrgb channel type. /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] may enable additional usages. /// /// Supported Platforms: @@ -409,7 +445,7 @@ bitflags::bitflags! { /// Compressed textures sacrifice some quality in exchange for significantly reduced /// bandwidth usage. /// - /// Support for this feature guarantees availability of [`TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING`] for BCn formats. + /// Support for this feature guarantees availability of [`TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING`] for ASTC formats with the HDR channel type. /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] may enable additional usages. /// /// Supported Platforms: @@ -458,10 +494,9 @@ bitflags::bitflags! { /// Supported platforms: /// - Vulkan /// - DX12 + /// - Metal (AMD & Intel, not Apple GPUs) /// - /// This is currently unimplemented on Metal. - /// When implemented, it will be supported on Metal on AMD and Intel GPUs, but not Apple GPUs. - /// (This is a common limitation of tile-based rasterization GPUs) + /// This is generally not available on tile-based rasterization GPUs. /// /// This is a native only feature with a [proposal](https://github.com/gpuweb/gpuweb/blob/0008bd30da2366af88180b511a5d0d0c1dffbc36/proposals/timestamp-query-inside-passes.md) for the web. const TIMESTAMP_QUERY_INSIDE_PASSES = 1 << 33; @@ -598,7 +633,7 @@ bitflags::bitflags! { /// Supported platforms: /// - DX12 /// - Vulkan - /// - Metal (Emulated on top of `draw_indirect` and `draw_indexed_indirect`) + /// - Metal on Apple3+ or Mac1+ (Emulated on top of `draw_indirect` and `draw_indexed_indirect`) /// /// This is a native only feature. /// @@ -630,7 +665,6 @@ bitflags::bitflags! { /// - DX12 /// - Vulkan /// - Metal - /// - DX11 (emulated with uniforms) /// - OpenGL (emulated with uniforms) /// /// This is a native only feature. @@ -646,7 +680,6 @@ bitflags::bitflags! { /// - DX12 /// - Vulkan /// - Metal - /// - DX11 /// - OpenGL /// /// This is a native only feature. @@ -658,7 +691,6 @@ bitflags::bitflags! { /// - DX12 /// - Vulkan /// - Metal (macOS 10.12+ only) - /// - DX11 /// - OpenGL /// /// This is a native only feature. @@ -737,18 +769,41 @@ bitflags::bitflags! { /// /// This is a native only feature. const VERTEX_ATTRIBUTE_64BIT = 1 << 53; + /// Allows vertex shaders to have outputs which are not consumed + /// by the fragment shader. + /// + /// Supported platforms: + /// - Vulkan + /// - Metal + /// - OpenGL + const SHADER_UNUSED_VERTEX_OUTPUT = 1 << 54; + /// Allows for creation of textures of format [`TextureFormat::NV12`] + /// + /// Supported platforms: + /// - DX12 + /// - Vulkan + /// + /// This is a native only feature. + const TEXTURE_FORMAT_NV12 = 1 << 55; /// Allows for the creation of ray-tracing acceleration structures. /// /// Supported platforms: /// - Vulkan /// /// This is a native-only feature. - const RAY_TRACING_ACCELERATION_STRUCTURE = 1 << 54; + const RAY_TRACING_ACCELERATION_STRUCTURE = 1 << 56; - // 55..59 available + // 57 available // Shader: + /// Allows for the creation of ray-tracing queries within shaders. + /// + /// Supported platforms: + /// - Vulkan + /// + /// This is a native-only feature. + const RAY_QUERY = 1 << 58; /// Enables 64-bit floating point types in SPIR-V shaders. /// /// Note: even when supported by GPU hardware, 64-bit floating point operations are @@ -774,7 +829,6 @@ bitflags::bitflags! { /// /// Supported platforms: /// - Vulkan - /// - DX11 (feature level 10+) /// - DX12 /// - Metal (some) /// - OpenGL (some) @@ -788,15 +842,17 @@ bitflags::bitflags! { /// /// This is a native only feature. const SHADER_EARLY_DEPTH_TEST = 1 << 62; - /// Allows for the creation of ray-tracing queries within shaders. + /// Allows two outputs from a shader to be used for blending. + /// Note that dual-source blending doesn't support multiple render targets. /// - /// Supported platforms: - /// - Vulkan + /// For more info see the OpenGL ES extension GL_EXT_blend_func_extended. /// - /// This is a native-only feature. - const RAY_QUERY = 1 << 63; - - // 64 available + /// Supported platforms: + /// - OpenGL ES (with GL_EXT_blend_func_extended) + /// - Metal (with MSL 1.2+) + /// - Vulkan (with dualSrcBlend) + /// - DX12 + const DUAL_SOURCE_BLENDING = 1 << 63; } } @@ -814,6 +870,91 @@ impl Features { } } +bitflags::bitflags! { + /// Instance debugging flags. + /// + /// These are not part of the webgpu standard. + /// + /// Defaults to enabling debugging-related flags if the build configuration has `debug_assertions`. + #[repr(transparent)] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct InstanceFlags: u32 { + /// Generate debug information in shaders and objects. + const DEBUG = 1 << 0; + /// Enable validation, if possible. + const VALIDATION = 1 << 1; + /// Don't pass labels to wgpu-hal. + const DISCARD_HAL_LABELS = 1 << 2; + /// Whether wgpu should expose adapters that run on top of non-compliant adapters. + /// + /// Turning this on might mean that some of the functionality provided by the wgpu + /// adapter/device is not working or is broken. It could be that all the functionality + /// wgpu currently exposes works but we can't tell for sure since we have no additional + /// transparency into what is working and what is not on the underlying adapter. + /// + /// This mainly applies to a Vulkan driver's compliance version. If the major compliance version + /// is `0`, then the driver is ignored. This flag allows that driver to be enabled for testing. + const ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER = 1 << 3; + } +} + +impl Default for InstanceFlags { + fn default() -> Self { + Self::from_build_config() + } +} + +impl InstanceFlags { + /// Enable debugging and validation flags. + pub fn debugging() -> Self { + InstanceFlags::DEBUG | InstanceFlags::VALIDATION + } + + /// Infer good defaults from the build type + /// + /// Returns the default flags and add debugging flags if the build configuration has `debug_assertions`. + pub fn from_build_config() -> Self { + if cfg!(debug_assertions) { + return InstanceFlags::debugging(); + } + + InstanceFlags::empty() + } + + /// Returns this set of flags, affected by environment variables. + /// + /// The presence of an environment variable implies that the corresponding flag should be set + /// unless the value is "0" in which case the flag is unset. If the environment variable is + /// not present, then the flag is unaffected. + /// + /// For example `let flags = InstanceFlags::debugging().with_env();` with `WGPU_VALIDATION=0` + /// does not contain `InstanceFlags::VALIDATION`. + /// + /// The environment variables are named after the flags prefixed with "WGPU_". For example: + /// - WGPU_DEBUG + /// - WGPU_VALIDATION + pub fn with_env(mut self) -> Self { + fn env(key: &str) -> Option { + std::env::var(key).ok().map(|s| match s.as_str() { + "0" => false, + _ => true, + }) + } + + if let Some(bit) = env("WGPU_VALIDATION") { + self.set(Self::VALIDATION, bit); + } + if let Some(bit) = env("WGPU_DEBUG") { + self.set(Self::DEBUG, bit); + } + if let Some(bit) = env("WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER") { + self.set(Self::ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER, bit); + } + + self + } +} + /// Represents the sets of limits an adapter/device supports. /// /// We provide three different defaults. @@ -942,7 +1083,7 @@ pub struct Limits { /// - Vulkan: 128-256 bytes /// - DX12: 256 bytes /// - Metal: 4096 bytes - /// - DX11 & OpenGL don't natively support push constants, and are emulated with uniforms, + /// - OpenGL doesn't natively support push constants, and are emulated with uniforms, /// so this number is less useful but likely 256. pub max_push_constant_size: u32, @@ -1092,7 +1233,7 @@ impl Limits { /// max_push_constant_size: 0, /// min_uniform_buffer_offset_alignment: 256, /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 60, + /// max_inter_stage_shader_components: 31, /// max_compute_workgroup_storage_size: 0, // + /// max_compute_invocations_per_workgroup: 0, // + /// max_compute_workgroup_size_x: 0, // + @@ -1118,6 +1259,9 @@ impl Limits { max_compute_workgroup_size_z: 0, max_compute_workgroups_per_dimension: 0, + // Value supported by Intel Celeron B830 on Windows (OpenGL 3.1) + max_inter_stage_shader_components: 31, + // Most of the values should be the same as the downlevel defaults ..Self::downlevel_defaults() } @@ -1284,15 +1428,24 @@ bitflags::bitflags! { pub struct DownlevelFlags: u32 { /// The device supports compiling and using compute shaders. /// - /// DX11 on FL10 level hardware, WebGL2, and GLES3.0 devices do not support compute. + /// WebGL2, and GLES3.0 devices do not support compute. const COMPUTE_SHADERS = 1 << 0; /// Supports binding storage buffers and textures to fragment shaders. const FRAGMENT_WRITABLE_STORAGE = 1 << 1; /// Supports indirect drawing and dispatching. /// - /// DX11 on FL10 level hardware, WebGL2, and GLES 3.0 devices do not support indirect. + /// WebGL2, GLES 3.0, and Metal on Apple1/Apple2 GPUs do not support indirect. const INDIRECT_EXECUTION = 1 << 2; - /// Supports non-zero `base_vertex` parameter to indexed draw calls. + /// Supports non-zero `base_vertex` parameter to direct indexed draw calls. + /// + /// Indirect calls, if supported, always support non-zero `base_vertex`. + /// + /// Supported by: + /// - Vulkan + /// - DX12 + /// - Metal on Apple3+ or Mac1+ + /// - OpenGL 3.2+ + /// - OpenGL ES 3.2 const BASE_VERTEX = 1 << 3; /// Supports reading from a depth/stencil texture while using it as a read-only /// depth/stencil attachment. @@ -1374,6 +1527,51 @@ bitflags::bitflags! { /// /// The GLES/WebGL and Vulkan on Android doesn't support this. const SURFACE_VIEW_FORMATS = 1 << 21; + + /// If this is true, calls to `CommandEncoder::resolve_query_set` will be performed on the queue timeline. + /// + /// If this is false, calls to `CommandEncoder::resolve_query_set` will be performed on the device (i.e. cpu) timeline + /// and will block that timeline until the query has data. You may work around this limitation by waiting until the submit + /// whose queries you are resolving is fully finished (through use of `queue.on_submitted_work_done`) and only + /// then submitting the resolve_query_set command. The queries will be guarenteed finished, so will not block. + /// + /// Supported by: + /// - Vulkan, + /// - DX12 + /// - Metal + /// - OpenGL 4.4+ + /// + /// Not Supported by: + /// - GL ES / WebGL + const NONBLOCKING_QUERY_RESOLVE = 1 << 22; + + /// If this is true, use of `@builtin(vertex_index)` and `@builtin(instance_index)` will properly take into consideration + /// the `first_vertex` and `first_instance` parameters of indirect draw calls. + /// + /// If this is false, `@builtin(vertex_index)` and `@builtin(instance_index)` will start by counting from 0, ignoring the + /// `first_vertex` and `first_instance` parameters. + /// + /// For example, if you had a draw call like this: + /// - `first_vertex: 4,` + /// - `vertex_count: 12,` + /// + /// When this flag is present, `@builtin(vertex_index)` will start at 4 and go up to 15 (12 invocations). + /// + /// When this flag is not present, `@builtin(vertex_index)` will start at 0 and go up to 11 (12 invocations). + /// + /// This only affects the builtins in the shaders, + /// vertex buffers and instance rate vertex buffers will behave like expected with this flag disabled. + /// + /// See also [`Features::`] + /// + /// Supported By: + /// - Vulkan + /// - Metal + /// - OpenGL + /// + /// Will be implemented in the future by: + /// - DX12 ([#2471](https://github.com/gfx-rs/wgpu/issues/2471)) + const VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW = 1 << 23; } } @@ -1471,12 +1669,18 @@ pub struct AdapterInfo { pub struct DeviceDescriptor { /// Debug label for the device. pub label: L, - /// Features that the device should support. If any feature is not supported by - /// the adapter, creating a device will panic. - pub features: Features, - /// Limits that the device should support. If any limit is "better" than the limit exposed by - /// the adapter, creating a device will panic. - pub limits: Limits, + /// Specifies the features that are required by the device request. + /// The request will fail if the adapter cannot provide these features. + /// + /// Exactly the specified set of features, and no more or less, + /// will be allowed in validation of API calls on the resulting device. + pub required_features: Features, + /// Specifies the limits that are required by the device request. + /// The request will fail if the adapter cannot provide these limits. + /// + /// Exactly the specified limits, and no better or worse, + /// will be allowed in validation of API calls on the resulting device. + pub required_limits: Limits, } impl DeviceDescriptor { @@ -1484,8 +1688,8 @@ impl DeviceDescriptor { pub fn map_label(&self, fun: impl FnOnce(&L) -> K) -> DeviceDescriptor { DeviceDescriptor { label: fun(&self.label), - features: self.features, - limits: self.limits.clone(), + required_features: self.required_features, + required_limits: self.required_limits.clone(), } } } @@ -1564,6 +1768,8 @@ impl TextureViewDimension { /// /// Corresponds to [WebGPU `GPUBlendFactor`]( /// https://gpuweb.github.io/gpuweb/#enumdef-gpublendfactor). +/// Values using S1 requires [`Features::DUAL_SOURCE_BLENDING`] and can only be +/// used with the first render target. #[repr(C)] #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] #[cfg_attr(feature = "trace", derive(Serialize))] @@ -1596,6 +1802,29 @@ pub enum BlendFactor { Constant = 11, /// 1.0 - Constant OneMinusConstant = 12, + /// S1.component + Src1 = 13, + /// 1.0 - S1.component + OneMinusSrc1 = 14, + /// S1.alpha + Src1Alpha = 15, + /// 1.0 - S1.alpha + OneMinusSrc1Alpha = 16, +} + +impl BlendFactor { + /// Returns `true` if the blend factor references the second blend source. + /// + /// Note that the usage of those blend factors require [`Features::DUAL_SOURCE_BLENDING`]. + pub fn ref_second_blend_source(&self) -> bool { + match self { + BlendFactor::Src1 + | BlendFactor::OneMinusSrc1 + | BlendFactor::Src1Alpha + | BlendFactor::OneMinusSrc1Alpha => true, + _ => false, + } + } } /// Alpha blend operation. @@ -1966,6 +2195,15 @@ impl TextureFormatFeatureFlags { _ => false, } } + + /// A `Vec` of supported sample counts. + pub fn supported_sample_counts(&self) -> Vec { + let all_possible_sample_counts: [u32; 5] = [1, 2, 4, 8, 16]; + all_possible_sample_counts + .into_iter() + .filter(|&sc| self.sample_count_supported(sc)) + .collect() + } } impl_bitflags!(TextureFormatFeatureFlags); @@ -2119,6 +2357,8 @@ pub enum TextureFormat { // Packed 32 bit formats /// Packed unsigned float with 9 bits mantisa for each RGB component, then a common 5 bits exponent Rgb9e5Ufloat, + /// Red, green, blue, and alpha channels. 10 bit integer for RGB channels, 2 bit integer for alpha channel. Unsigned in shader. + Rgb10a2Uint, /// Red, green, blue, and alpha channels. 10 bit integer for RGB channels, 2 bit integer for alpha channel. [0, 1023] ([0, 3] for alpha) converted to/from float [0, 1] in shader. Rgb10a2Unorm, /// Red, green, and blue channels. 11 bit float with no sign bit for RG channels. 10 bit float with no sign bit for blue channel. Float in shader. @@ -2170,6 +2410,21 @@ pub enum TextureFormat { /// [`Features::DEPTH32FLOAT_STENCIL8`] must be enabled to use this texture format. Depth32FloatStencil8, + /// YUV 4:2:0 chroma subsampled format. + /// + /// Contains two planes: + /// - 0: Single 8 bit channel luminance. + /// - 1: Dual 8 bit channel chrominance at half width and half height. + /// + /// Valid view formats for luminance are [`TextureFormat::R8Unorm`]. + /// + /// Valid view formats for chrominance are [`TextureFormat::Rg8Unorm`]. + /// + /// Width and height must be even. + /// + /// [`Features::TEXTURE_FORMAT_NV12`] must be enabled to use this texture format. + NV12, + // Compressed textures usable with `TEXTURE_COMPRESSION_BC` feature. /// 4x4 block compressed texture. 8 bytes per block (4 bit/px). 4 color + alpha pallet. 5 bit R + 6 bit G + 5 bit B + 1 bit alpha. /// [0, 63] ([0, 1] for alpha) converted to/from float [0, 1] in shader. @@ -2379,6 +2634,7 @@ impl<'de> Deserialize<'de> for TextureFormat { "rgba8sint" => TextureFormat::Rgba8Sint, "bgra8unorm" => TextureFormat::Bgra8Unorm, "bgra8unorm-srgb" => TextureFormat::Bgra8UnormSrgb, + "rgb10a2uint" => TextureFormat::Rgb10a2Uint, "rgb10a2unorm" => TextureFormat::Rgb10a2Unorm, "rg11b10ufloat" => TextureFormat::Rg11b10Float, "rg32uint" => TextureFormat::Rg32Uint, @@ -2398,6 +2654,7 @@ impl<'de> Deserialize<'de> for TextureFormat { "depth16unorm" => TextureFormat::Depth16Unorm, "depth24plus" => TextureFormat::Depth24Plus, "depth24plus-stencil8" => TextureFormat::Depth24PlusStencil8, + "nv12" => TextureFormat::NV12, "rgb9e5ufloat" => TextureFormat::Rgb9e5Ufloat, "bc1-rgba-unorm" => TextureFormat::Bc1RgbaUnorm, "bc1-rgba-unorm-srgb" => TextureFormat::Bc1RgbaUnormSrgb, @@ -2505,6 +2762,7 @@ impl Serialize for TextureFormat { TextureFormat::Rgba8Sint => "rgba8sint", TextureFormat::Bgra8Unorm => "bgra8unorm", TextureFormat::Bgra8UnormSrgb => "bgra8unorm-srgb", + TextureFormat::Rgb10a2Uint => "rgb10a2uint", TextureFormat::Rgb10a2Unorm => "rgb10a2unorm", TextureFormat::Rg11b10Float => "rg11b10ufloat", TextureFormat::Rg32Uint => "rg32uint", @@ -2524,6 +2782,7 @@ impl Serialize for TextureFormat { TextureFormat::Depth32FloatStencil8 => "depth32float-stencil8", TextureFormat::Depth24Plus => "depth24plus", TextureFormat::Depth24PlusStencil8 => "depth24plus-stencil8", + TextureFormat::NV12 => "nv12", TextureFormat::Rgb9e5Ufloat => "rgb9e5ufloat", TextureFormat::Bc1RgbaUnorm => "bc1-rgba-unorm", TextureFormat::Bc1RgbaUnormSrgb => "bc1-rgba-unorm-srgb", @@ -2581,6 +2840,18 @@ impl Serialize for TextureFormat { } } +impl TextureAspect { + /// Returns the texture aspect for a given plane. + pub fn from_plane(plane: u32) -> Option { + Some(match plane { + 0 => Self::Plane0, + 1 => Self::Plane1, + 2 => Self::Plane2, + _ => return None, + }) + } +} + impl TextureFormat { /// Returns the aspect-specific format of the original format /// @@ -2598,7 +2869,10 @@ impl TextureFormat { ) => Some(Self::Stencil8), (Self::Depth24PlusStencil8, TextureAspect::DepthOnly) => Some(Self::Depth24Plus), (Self::Depth32FloatStencil8, TextureAspect::DepthOnly) => Some(Self::Depth32Float), - (format, TextureAspect::All) => Some(format), + (Self::NV12, TextureAspect::Plane0) => Some(Self::R8Unorm), + (Self::NV12, TextureAspect::Plane1) => Some(Self::Rg8Unorm), + // views to multi-planar formats must specify the plane + (format, TextureAspect::All) if !format.is_multi_planar_format() => Some(format), _ => None, } } @@ -2638,6 +2912,19 @@ impl TextureFormat { } } + /// Returns `true` if the format is a multi-planar format + pub fn is_multi_planar_format(&self) -> bool { + self.planes().is_some() + } + + /// Returns the number of planes a multi-planar format has. + pub fn planes(&self) -> Option { + match *self { + Self::NV12 => Some(2), + _ => None, + } + } + /// Returns `true` if the format has a color aspect pub fn has_color_aspect(&self) -> bool { !self.is_depth_stencil_format() @@ -2663,7 +2950,17 @@ impl TextureFormat { } } - /// Returns the dimension of a block of texels. + /// Returns the size multiple requirement for a texture using this format. + pub fn size_multiple_requirement(&self) -> (u32, u32) { + match *self { + Self::NV12 => (2, 2), + _ => self.block_dimensions(), + } + } + + /// Returns the dimension of a [block](https://gpuweb.github.io/gpuweb/#texel-block) of texels. + /// + /// Uncompressed formats have a block dimension of `(1, 1)`. pub fn block_dimensions(&self) -> (u32, u32) { match *self { Self::R8Unorm @@ -2695,6 +2992,7 @@ impl TextureFormat { | Self::Bgra8Unorm | Self::Bgra8UnormSrgb | Self::Rgb9e5Ufloat + | Self::Rgb10a2Uint | Self::Rgb10a2Unorm | Self::Rg11b10Float | Self::Rg32Uint @@ -2713,7 +3011,8 @@ impl TextureFormat { | Self::Depth24Plus | Self::Depth24PlusStencil8 | Self::Depth32Float - | Self::Depth32FloatStencil8 => (1, 1), + | Self::Depth32FloatStencil8 + | Self::NV12 => (1, 1), Self::Bc1RgbaUnorm | Self::Bc1RgbaUnormSrgb @@ -2793,6 +3092,7 @@ impl TextureFormat { | Self::Bgra8Unorm | Self::Bgra8UnormSrgb | Self::Rgb9e5Ufloat + | Self::Rgb10a2Uint | Self::Rgb10a2Unorm | Self::Rg11b10Float | Self::Rg32Uint @@ -2812,6 +3112,8 @@ impl TextureFormat { Self::Depth32FloatStencil8 => Features::DEPTH32FLOAT_STENCIL8, + Self::NV12 => Features::TEXTURE_FORMAT_NV12, + Self::R16Unorm | Self::R16Snorm | Self::Rg16Unorm @@ -2866,12 +3168,18 @@ impl TextureFormat { TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING; let attachment = basic | TextureUsages::RENDER_ATTACHMENT; let storage = basic | TextureUsages::STORAGE_BINDING; + let binding = TextureUsages::TEXTURE_BINDING; let all_flags = TextureUsages::all(); let rg11b10f = if device_features.contains(Features::RG11B10UFLOAT_RENDERABLE) { attachment } else { basic }; + let bgra8unorm = if device_features.contains(Features::BGRA8UNORM_STORAGE) { + attachment | TextureUsages::STORAGE_BINDING + } else { + attachment + }; #[rustfmt::skip] // lets make a nice table let ( @@ -2900,8 +3208,9 @@ impl TextureFormat { Self::Rgba8Snorm => ( noaa, storage), Self::Rgba8Uint => ( msaa, all_flags), Self::Rgba8Sint => ( msaa, all_flags), - Self::Bgra8Unorm => (msaa_resolve, attachment), + Self::Bgra8Unorm => (msaa_resolve, bgra8unorm), Self::Bgra8UnormSrgb => (msaa_resolve, attachment), + Self::Rgb10a2Uint => ( msaa, attachment), Self::Rgb10a2Unorm => (msaa_resolve, attachment), Self::Rg11b10Float => ( msaa, rg11b10f), Self::Rg32Uint => ( noaa, all_flags), @@ -2921,6 +3230,9 @@ impl TextureFormat { Self::Depth32Float => ( msaa, attachment), Self::Depth32FloatStencil8 => ( msaa, attachment), + // We only support sampling nv12 textures until we implement transfer plane data. + Self::NV12 => ( noaa, binding), + Self::R16Unorm => ( msaa, storage), Self::R16Snorm => ( msaa, storage), Self::Rg16Unorm => ( msaa, storage), @@ -2959,10 +3271,16 @@ impl TextureFormat { Self::Astc { .. } => ( noaa, basic), }; - let is_filterable = - self.sample_type(None) == Some(TextureSampleType::Float { filterable: true }); + // Get whether the format is filterable, taking features into account + let sample_type1 = self.sample_type(None, Some(device_features)); + let is_filterable = sample_type1 == Some(TextureSampleType::Float { filterable: true }); + + // Features that enable filtering don't affect blendability + let sample_type2 = self.sample_type(None, None); + let is_blendable = sample_type2 == Some(TextureSampleType::Float { filterable: true }); + flags.set(TextureFormatFeatureFlags::FILTERABLE, is_filterable); - flags.set(TextureFormatFeatureFlags::BLENDABLE, is_filterable); + flags.set(TextureFormatFeatureFlags::BLENDABLE, is_blendable); TextureFormatFeatures { allowed_usages, @@ -2970,13 +3288,22 @@ impl TextureFormat { } } - /// Returns the sample type compatible with this format and aspect + /// Returns the sample type compatible with this format and aspect. /// - /// Returns `None` only if the format is combined depth-stencil - /// and `TextureAspect::All` or no `aspect` was provided - pub fn sample_type(&self, aspect: Option) -> Option { + /// Returns `None` only if this is a combined depth-stencil format or a multi-planar format + /// and `TextureAspect::All` or no `aspect` was provided. + pub fn sample_type( + &self, + aspect: Option, + device_features: Option, + ) -> Option { let float = TextureSampleType::Float { filterable: true }; let unfilterable_float = TextureSampleType::Float { filterable: false }; + let float32_sample_type = TextureSampleType::Float { + filterable: device_features + .unwrap_or(Features::empty()) + .contains(Features::FLOAT32_FILTERABLE), + }; let depth = TextureSampleType::Depth; let uint = TextureSampleType::Uint; let sint = TextureSampleType::Sint; @@ -2997,7 +3324,7 @@ impl TextureFormat { | Self::Rgb10a2Unorm | Self::Rg11b10Float => Some(float), - Self::R32Float | Self::Rg32Float | Self::Rgba32Float => Some(unfilterable_float), + Self::R32Float | Self::Rg32Float | Self::Rgba32Float => Some(float32_sample_type), Self::R8Uint | Self::Rg8Uint @@ -3007,7 +3334,8 @@ impl TextureFormat { | Self::Rgba16Uint | Self::R32Uint | Self::Rg32Uint - | Self::Rgba32Uint => Some(uint), + | Self::Rgba32Uint + | Self::Rgb10a2Uint => Some(uint), Self::R8Sint | Self::Rg8Sint @@ -3022,9 +3350,16 @@ impl TextureFormat { Self::Stencil8 => Some(uint), Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float => Some(depth), Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => match aspect { - None | Some(TextureAspect::All) => None, Some(TextureAspect::DepthOnly) => Some(depth), Some(TextureAspect::StencilOnly) => Some(uint), + _ => None, + }, + + Self::NV12 => match aspect { + Some(TextureAspect::Plane0) | Some(TextureAspect::Plane1) => { + Some(unfilterable_float) + } + _ => None, }, Self::R16Unorm @@ -3066,14 +3401,36 @@ impl TextureFormat { } } - /// Returns the [texel block size](https://gpuweb.github.io/gpuweb/#texel-block-size) - /// of this format. + /// The number of bytes one [texel block](https://gpuweb.github.io/gpuweb/#texel-block) occupies during an image copy, if applicable. + /// + /// Known as the [texel block copy footprint](https://gpuweb.github.io/gpuweb/#texel-block-copy-footprint). + /// + /// Note that for uncompressed formats this is the same as the size of a single texel, + /// since uncompressed formats have a block size of 1x1. /// /// Returns `None` if any of the following are true: - /// - the format is combined depth-stencil and no `aspect` was provided + /// - the format is a combined depth-stencil and no `aspect` was provided + /// - the format is a multi-planar format and no `aspect` was provided /// - the format is `Depth24Plus` /// - the format is `Depth24PlusStencil8` and `aspect` is depth. + #[deprecated(since = "0.19.0", note = "Use `block_copy_size` instead.")] pub fn block_size(&self, aspect: Option) -> Option { + self.block_copy_size(aspect) + } + + /// The number of bytes one [texel block](https://gpuweb.github.io/gpuweb/#texel-block) occupies during an image copy, if applicable. + /// + /// Known as the [texel block copy footprint](https://gpuweb.github.io/gpuweb/#texel-block-copy-footprint). + /// + /// Note that for uncompressed formats this is the same as the size of a single texel, + /// since uncompressed formats have a block size of 1x1. + /// + /// Returns `None` if any of the following are true: + /// - the format is a combined depth-stencil and no `aspect` was provided + /// - the format is a multi-planar format and no `aspect` was provided + /// - the format is `Depth24Plus` + /// - the format is `Depth24PlusStencil8` and `aspect` is depth. + pub fn block_copy_size(&self, aspect: Option) -> Option { match *self { Self::R8Unorm | Self::R8Snorm | Self::R8Uint | Self::R8Sint => Some(1), @@ -3095,7 +3452,9 @@ impl TextureFormat { | Self::Rg16Sint | Self::Rg16Float => Some(4), Self::R32Uint | Self::R32Sint | Self::R32Float => Some(4), - Self::Rgb9e5Ufloat | Self::Rgb10a2Unorm | Self::Rg11b10Float => Some(4), + Self::Rgb9e5Ufloat | Self::Rgb10a2Uint | Self::Rgb10a2Unorm | Self::Rg11b10Float => { + Some(4) + } Self::Rgba16Unorm | Self::Rgba16Snorm @@ -3111,14 +3470,20 @@ impl TextureFormat { Self::Depth32Float => Some(4), Self::Depth24Plus => None, Self::Depth24PlusStencil8 => match aspect { - None | Some(TextureAspect::All) => None, Some(TextureAspect::DepthOnly) => None, Some(TextureAspect::StencilOnly) => Some(1), + _ => None, }, Self::Depth32FloatStencil8 => match aspect { - None | Some(TextureAspect::All) => None, Some(TextureAspect::DepthOnly) => Some(4), Some(TextureAspect::StencilOnly) => Some(1), + _ => None, + }, + + Self::NV12 => match aspect { + Some(TextureAspect::Plane0) => Some(1), + Some(TextureAspect::Plane1) => Some(2), + _ => None, }, Self::Bc1RgbaUnorm | Self::Bc1RgbaUnormSrgb | Self::Bc4RUnorm | Self::Bc4RSnorm => { @@ -3157,7 +3522,7 @@ impl TextureFormat { /// Returns the number of components this format has taking into account the `aspect`. /// - /// The `aspect` is only relevant for combined depth-stencil formats. + /// The `aspect` is only relevant for combined depth-stencil formats and multi-planar formats. pub fn components_with_aspect(&self, aspect: TextureAspect) -> u8 { match *self { Self::R8Unorm @@ -3203,13 +3568,19 @@ impl TextureFormat { | Self::Rgba32Float => 4, Self::Rgb9e5Ufloat | Self::Rg11b10Float => 3, - Self::Rgb10a2Unorm => 4, + Self::Rgb10a2Uint | Self::Rgb10a2Unorm => 4, Self::Stencil8 | Self::Depth16Unorm | Self::Depth24Plus | Self::Depth32Float => 1, Self::Depth24PlusStencil8 | Self::Depth32FloatStencil8 => match aspect { - TextureAspect::All => 2, TextureAspect::DepthOnly | TextureAspect::StencilOnly => 1, + _ => 2, + }, + + Self::NV12 => match aspect { + TextureAspect::Plane0 => 1, + TextureAspect::Plane1 => 2, + _ => 3, }, Self::Bc4RUnorm | Self::Bc4RSnorm => 1, @@ -3402,6 +3773,10 @@ fn texture_format_serialize() { serde_json::to_string(&TextureFormat::Bgra8UnormSrgb).unwrap(), "\"bgra8unorm-srgb\"".to_string() ); + assert_eq!( + serde_json::to_string(&TextureFormat::Rgb10a2Uint).unwrap(), + "\"rgb10a2uint\"".to_string() + ); assert_eq!( serde_json::to_string(&TextureFormat::Rgb10a2Unorm).unwrap(), "\"rgb10a2unorm\"".to_string() @@ -3694,6 +4069,10 @@ fn texture_format_deserialize() { serde_json::from_str::("\"bgra8unorm-srgb\"").unwrap(), TextureFormat::Bgra8UnormSrgb ); + assert_eq!( + serde_json::from_str::("\"rgb10a2uint\"").unwrap(), + TextureFormat::Rgb10a2Uint + ); assert_eq!( serde_json::from_str::("\"rgb10a2unorm\"").unwrap(), TextureFormat::Rgb10a2Unorm @@ -3906,10 +4285,10 @@ impl Default for ColorWrites { /// Passed to `Device::poll` to control how and if it should block. #[derive(Clone)] pub enum Maintain { - /// On native backends, block until the given submission has + /// On wgpu-core based backends, block until the given submission has /// completed execution, and any callbacks have been invoked. /// - /// On the web, this has no effect. Callbacks are invoked from the + /// On WebGPU, this has no effect. Callbacks are invoked from the /// window event loop. WaitForSubmissionIndex(T), /// Same as WaitForSubmissionIndex but waits for the most recent submission. @@ -3919,6 +4298,22 @@ pub enum Maintain { } impl Maintain { + /// Construct a wait variant + pub fn wait() -> Self { + // This function seems a little silly, but it is useful to allow + // to be split up, as + // it has meaning in that PR. + Self::Wait + } + + /// Construct a WaitForSubmissionIndex variant + pub fn wait_for(submission_index: T) -> Self { + // This function seems a little silly, but it is useful to allow + // to be split up, as + // it has meaning in that PR. + Self::WaitForSubmissionIndex(submission_index) + } + /// This maintain represents a wait of some kind. pub fn is_wait(&self) -> bool { match *self { @@ -3940,6 +4335,29 @@ impl Maintain { } } +/// Result of a maintain operation. +pub enum MaintainResult { + /// There are no active submissions in flight as of the beginning of the poll call. + /// Other submissions may have been queued on other threads at the same time. + /// + /// This implies that the given poll is complete. + SubmissionQueueEmpty, + /// More information coming soon + Ok, +} + +impl MaintainResult { + /// Returns true if the result is [`Self::SubmissionQueueEmpty`]`. + pub fn is_queue_empty(&self) -> bool { + matches!(self, Self::SubmissionQueueEmpty) + } + + /// Panics if the MaintainResult is not Ok. + pub fn panic_on_timeout(self) { + let _ = self; + } +} + /// State of the stencil operation (fixed-pipeline stage). /// /// For use in [`DepthStencilState`]. @@ -4610,7 +5028,7 @@ pub enum PresentMode { /// /// No tearing will be observed. /// - /// Supported on DX11/12 on Windows 10, NVidia on Vulkan and Wayland on Vulkan. + /// Supported on DX12 on Windows 10, NVidia on Vulkan and Wayland on Vulkan. /// /// This is traditionally called "Fast Vsync" Mailbox = 5, @@ -4622,7 +5040,7 @@ pub enum PresentMode { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "trace", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] pub enum CompositeAlphaMode { /// Chooses either `Opaque` or `Inherit` automatically,depending on the /// `alpha_mode` that the current surface can support. @@ -4728,9 +5146,9 @@ pub struct SurfaceConfiguration { /// The texture format of the swap chain. The only formats that are guaranteed are /// `Bgra8Unorm` and `Bgra8UnormSrgb` pub format: TextureFormat, - /// Width of the swap chain. Must be the same size as the surface. + /// Width of the swap chain. Must be the same size as the surface, and nonzero. pub width: u32, - /// Height of the swap chain. Must be the same size as the surface. + /// Height of the swap chain. Must be the same size as the surface, and nonzero. pub height: u32, /// Presentation mode of the swap chain. Fifo is the only mode guaranteed to be supported. /// FifoRelaxed, Immediate, and Mailbox will crash if unsupported, while AutoVsync and @@ -4904,7 +5322,7 @@ pub enum TextureDimension { /// Corresponds to [WebGPU `GPUOrigin2D`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuorigin2ddict). #[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "trace", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] @@ -4929,12 +5347,18 @@ impl Origin2d { } } +impl std::fmt::Debug for Origin2d { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self.x, self.y).fmt(f) + } +} + /// Origin of a copy to/from a texture. /// /// Corresponds to [WebGPU `GPUOrigin3D`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuorigin3ddict). #[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "trace", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] @@ -4966,12 +5390,18 @@ impl Default for Origin3d { } } +impl std::fmt::Debug for Origin3d { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self.x, self.y, self.z).fmt(f) + } +} + /// Extent of a texture related operation. /// /// Corresponds to [WebGPU `GPUExtent3D`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpuextent3ddict). #[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "trace", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] @@ -4985,6 +5415,12 @@ pub struct Extent3d { pub depth_or_array_layers: u32, } +impl std::fmt::Debug for Extent3d { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (self.width, self.height, self.depth_or_array_layers).fmt(f) + } +} + #[cfg(feature = "serde")] fn default_depth() -> u32 { 1 @@ -5309,6 +5745,12 @@ pub enum TextureAspect { StencilOnly, /// Depth. DepthOnly, + /// Plane 0. + Plane0, + /// Plane 1. + Plane1, + /// Plane 2. + Plane2, } /// How edges should be handled in texture addressing. @@ -6119,6 +6561,7 @@ impl ImageCopyTextureTagged { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[cfg_attr(feature = "trace", derive(serde::Serialize))] #[cfg_attr(feature = "replay", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ImageSubresourceRange { /// Aspect of the texture. Color textures must be [`TextureAspect::All`][TAA]. /// @@ -6334,44 +6777,84 @@ impl_bitflags!(PipelineStatisticsTypes); /// Argument buffer layout for draw_indirect commands. #[repr(C)] -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Default)] pub struct DrawIndirectArgs { /// The number of vertices to draw. pub vertex_count: u32, /// The number of instances to draw. pub instance_count: u32, - /// Offset into the vertex buffers, in vertices, to begin drawing from. + /// The Index of the first vertex to draw. pub first_vertex: u32, - /// First instance to draw. + /// The instance ID of the first instance to draw. + /// + /// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled. pub first_instance: u32, } +impl DrawIndirectArgs { + /// Returns the bytes representation of the struct, ready to be written in a buffer. + pub fn as_bytes(&self) -> &[u8] { + unsafe { + std::mem::transmute(std::slice::from_raw_parts( + self as *const _ as *const u8, + std::mem::size_of::(), + )) + } + } +} + /// Argument buffer layout for draw_indexed_indirect commands. #[repr(C)] -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Default)] pub struct DrawIndexedIndirectArgs { /// The number of indices to draw. pub index_count: u32, /// The number of instances to draw. pub instance_count: u32, - /// Offset into the index buffer, in indices, begin drawing from. + /// The first index within the index buffer. pub first_index: u32, - /// Added to each index value before indexing into the vertex buffers. + /// The value added to the vertex index before indexing into the vertex buffer. pub base_vertex: i32, - /// First instance to draw. + /// The instance ID of the first instance to draw. + /// + /// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled. pub first_instance: u32, } +impl DrawIndexedIndirectArgs { + /// Returns the bytes representation of the struct, ready to be written in a buffer. + pub fn as_bytes(&self) -> &[u8] { + unsafe { + std::mem::transmute(std::slice::from_raw_parts( + self as *const _ as *const u8, + std::mem::size_of::(), + )) + } + } +} + /// Argument buffer layout for dispatch_indirect commands. #[repr(C)] -#[derive(Clone, Copy, Debug)] +#[derive(Copy, Clone, Debug, Default)] pub struct DispatchIndirectArgs { - /// X dimension of the grid of workgroups to dispatch. - pub group_size_x: u32, - /// Y dimension of the grid of workgroups to dispatch. - pub group_size_y: u32, - /// Z dimension of the grid of workgroups to dispatch. - pub group_size_z: u32, + /// The number of work groups in X dimension. + pub x: u32, + /// The number of work groups in Y dimension. + pub y: u32, + /// The number of work groups in Z dimension. + pub z: u32, +} + +impl DispatchIndirectArgs { + /// Returns the bytes representation of the struct, ready to be written into a buffer. + pub fn as_bytes(&self) -> &[u8] { + unsafe { + std::mem::transmute(std::slice::from_raw_parts( + self as *const _ as *const u8, + std::mem::size_of::(), + )) + } + } } /// Describes how shader bound checks should be performed. @@ -6463,9 +6946,12 @@ pub enum Gles3MinorVersion { } /// Options for creating an instance. +#[derive(Debug)] pub struct InstanceDescriptor { /// Which `Backends` to enable. pub backends: Backends, + /// Flags to tune the behavior of the instance. + pub flags: InstanceFlags, /// Which DX12 shader compiler to use. pub dx12_shader_compiler: Dx12Compiler, /// Which OpenGL ES 3 minor version to request. @@ -6476,6 +6962,7 @@ impl Default for InstanceDescriptor { fn default() -> Self { Self { backends: Backends::all(), + flags: InstanceFlags::default(), dx12_shader_compiler: Dx12Compiler::default(), gles_minor_version: Gles3MinorVersion::default(), } @@ -6620,6 +7107,8 @@ pub use send_sync::*; #[doc(hidden)] mod send_sync { + pub trait WasmNotSendSync: WasmNotSend + WasmNotSync {} + impl WasmNotSendSync for T {} #[cfg(any( not(target_arch = "wasm32"), all( @@ -6686,3 +7175,15 @@ mod send_sync { )))] impl WasmNotSync for T {} } + +/// Reason for "lose the device". +/// +/// Corresponds to [WebGPU `GPUDeviceLostReason`](https://gpuweb.github.io/gpuweb/#enumdef-gpudevicelostreason). +#[repr(u8)] +#[derive(Debug, Copy, Clone)] +pub enum DeviceLostReason { + /// Triggered by driver + Unknown = 0, + /// After Device::destroy + Destroyed = 1, +} diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 7fcd5c465f..8848371b9d 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -8,6 +8,7 @@ homepage.workspace = true repository.workspace = true keywords.workspace = true license.workspace = true +readme = "../README.md" exclude = ["Cargo.lock"] [package.metadata.docs.rs] @@ -23,20 +24,76 @@ targets = [ [lib] [features] -default = ["wgsl"] -# Apply run-time checks, even in release builds. These are in addition -# to the validation carried out at public APIs in all builds. -strict_asserts = ["wgc?/strict_asserts", "wgt/strict_asserts"] +default = ["wgsl", "dx12", "metal", "webgpu"] + +#! ### Backends +# -------------------------------------------------------------------- +#! ⚠️ WIP: Not all backends can be manually configured today. +#! On Windows & Linux the Vulkan & GLES backends are always enabled. +#! See [#3514](https://github.com/gfx-rs/wgpu/issues/3514) for more details. + +## Enables the DX12 backend on Windows. +dx12 = ["wgc?/dx12"] + +## Enables the Metal backend on macOS & iOS. +metal = ["wgc?/metal"] + +## Enables the GLES backend via [ANGLE](https://github.com/google/angle) on macOS using. +angle = ["wgc?/gles"] + +## Enables the Vulkan backend on macOS & iOS. +vulkan-portability = ["wgc?/vulkan"] + +## Enables the WebGPU backend on Wasm. Disabled when targeting `emscripten`. +webgpu = [] + +## Enables the GLES backend on Wasm +## +## * ⚠️ WIP: Currently will also enable GLES dependencies on any other targets. +webgl = ["hal", "wgc/gles"] + +#! ### Shading language support +# -------------------------------------------------------------------- + +## Enable accepting SPIR-V shaders as input. spirv = ["naga/spv-in"] + +## Enable accepting GLSL shaders as input. glsl = ["naga/glsl-in"] + +## Enable accepting WGSL shaders as input. wgsl = ["wgc?/wgsl"] + +#! ### Logging & Tracing +# -------------------------------------------------------------------- +#! The following features do not have any effect on the WebGPU backend. + +## Apply run-time checks, even in release builds. These are in addition +## to the validation carried out at public APIs in all builds. +strict_asserts = ["wgc?/strict_asserts", "wgt/strict_asserts"] + +## Log all API entry points at info instead of trace level. +api_log_info = ["wgc/api_log_info"] + +## Allow writing of trace capture files. +## See [`Adapter::request_device`]. trace = ["serde", "wgc/trace"] + +## Allow deserializing of trace capture files that were written with the `trace` feature. +## To replay a trace file use the [wgpu player](https://github.com/gfx-rs/wgpu/tree/trunk/player). replay = ["serde", "wgc/replay"] -angle = ["wgc/angle"] -webgl = ["hal", "wgc"] -vulkan-portability = ["wgc/vulkan"] -expose-ids = [] -# Implement `Send` and `Sync` on Wasm. + +#! ### Other +# -------------------------------------------------------------------- + +## Implement `Send` and `Sync` on Wasm, but only if atomics are not enabled. +## +## WebGL/WebGPU objects can not be shared between threads. +## However, it can be useful to artificially mark them as `Send` and `Sync` +## anyways to make it easier to write cross-platform code. +## This is technically *very* unsafe in a multithreaded environment, +## but on a wasm binary compiled without atomics we know we are definitely +## not in a multithreaded environment. fragile-send-sync-non-atomic-wasm = [ "hal/fragile-send-sync-non-atomic-wasm", "wgc/fragile-send-sync-non-atomic-wasm", @@ -44,36 +101,38 @@ fragile-send-sync-non-atomic-wasm = [ ] # wgpu-core is always available as an optional dependency, "wgc". -# Whenever wgpu-core is selected, we want the GLES backend and raw -# window handle support. +# Whenever wgpu-core is selected, we want raw window handle support. [dependencies.wgc] optional = true workspace = true -features = ["raw-window-handle", "gles"] +features = ["raw-window-handle"] # wgpu-core is required whenever not targeting web APIs directly. -# Whenever wgpu-core is selected, we want the GLES backend and raw -# window handle support. +# Whenever wgpu-core is selected, we want raw window handle support. [target.'cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))'.dependencies.wgc] workspace = true -features = ["raw-window-handle", "gles"] +features = ["raw-window-handle"] -# We want the wgpu-core Metal backend on macOS and iOS. -# (We should consider also enabling "vulkan" for Vulkan Portability.) +# Enable `wgc` by default on macOS and iOS to allow the `metal` crate feature to +# enable the Metal backend while being no-op on other targets. [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies.wgc] workspace = true -features = ["metal"] -# We want the wgpu-core Direct3D backends on Windows. +# We want the wgpu-core Direct3D backend and OpenGL (via WGL) on Windows. [target.'cfg(windows)'.dependencies.wgc] workspace = true -features = ["dx11", "dx12"] +features = ["gles"] -# We want the wgpu-core Vulkan backend on Unix (but not Emscripten) and Windows. +# We want the wgpu-core Vulkan backend on Unix (but not emscripten, macOS, iOS) and Windows. [target.'cfg(any(windows, all(unix, not(target_os = "emscripten"), not(target_os = "ios"), not(target_os = "macos"))))'.dependencies.wgc] workspace = true features = ["vulkan"] +# We want the wgpu-core GLES backend on Unix (but not macOS, iOS). +[target.'cfg(all(unix, not(target_os = "ios"), not(target_os = "macos")))'.dependencies.wgc] +workspace = true +features = ["gles"] + [dependencies.wgt] workspace = true @@ -97,20 +156,23 @@ optional = true [dependencies] arrayvec.workspace = true +cfg-if.workspace = true log.workspace = true parking_lot.workspace = true profiling.workspace = true -raw-window-handle.workspace = true +raw-window-handle = { workspace = true, features = ["std"] } serde = { workspace = true, features = ["derive"], optional = true } smallvec.workspace = true static_assertions.workspace = true -cfg-if.workspace = true [dependencies.naga] workspace = true features = ["clone"] optional = true +[build-dependencies] +cfg_aliases.workspace = true + # used to test all the example shaders [dev-dependencies.naga] workspace = true @@ -160,7 +222,7 @@ web-sys = { workspace = true, features = [ "GpuCompilationMessageType", "GpuComputePassDescriptor", "GpuComputePassEncoder", - "GpuComputePassTimestampWrite", + "GpuComputePassTimestampWrites", "GpuComputePipeline", "GpuComputePipelineDescriptor", "GpuCullMode", diff --git a/wgpu/Makefile b/wgpu/Makefile deleted file mode 100644 index 73d1d3b15c..0000000000 --- a/wgpu/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# This Makefile generates SPIR-V shaders from GLSL shaders in the examples. - -shader_compiler = glslangValidator - -# All input shaders. -glsls = $(wildcard examples/*/*.vert examples/*/*.frag examples/*/*.comp) - -# All SPIR-V targets. -spirvs = $(addsuffix .spv,$(glsls)) - -.PHONY: default -default: $(spirvs) - -# Rule for making a SPIR-V target. -$(spirvs): %.spv: % - $(shader_compiler) -V $< -o $@ - -.PHONY: clean -clean: - rm -f $(spirvs) diff --git a/wgpu/README.md b/wgpu/README.md deleted file mode 100644 index 4f4f85a1e5..0000000000 --- a/wgpu/README.md +++ /dev/null @@ -1,60 +0,0 @@ - - -wgpu-rs is an idiomatic Rust wrapper over [wgpu-core](https://github.com/gfx-rs/wgpu). It's designed to be suitable for general purpose graphics and computation needs of Rust community. - -wgpu-rs can target both the natively supported backends and Wasm directly. - -See our [gallery](https://wgpu.rs/#showcase) and the [wiki page](https://github.com/gfx-rs/wgpu/wiki/Users) for the list of libraries and applications using `wgpu-rs`. - -## Usage - -### How to Run Examples - -All examples are located under the [examples](examples) directory. - -These examples use the default syntax for running examples, as found in the [Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html#examples) documentation. For example, to run the `cube` example: - -```bash -cargo run --bin cube -``` - -The `hello*` examples show bare-bones setup without any helper code. For `hello-compute`, pass 4 numbers separated by spaces as arguments: - -```bash -cargo run --bin hello-compute 1 2 3 4 -``` - -The following environment variables can be used to configure how the framework examples run: - -- `WGPU_BACKEND` - - Options: `vulkan`, `metal`, `dx11`, `dx12`, `gl`, `webgpu` - - If unset a default backend is chosen based on what is supported - by your system. - -- `WGPU_POWER_PREF` - - Options: `low`, `high`, `none` - - If unset power usage is not considered when choosing an adapter. - -- `WGPU_ADAPTER_NAME` - - Select a specific adapter by specifying a substring of the adapter name. - -#### Run Examples on the Web (`wasm32-unknown-unknown`) - -See [wiki article](https://github.com/gfx-rs/wgpu/wiki/Running-on-the-Web-with-WebGPU-and-WebGL). - -## Shaders - -[WGSL](https://gpuweb.github.io/gpuweb/wgsl/) is the main shading language of WebGPU. - -Users can run the [naga](https://github.com/gfx-rs/naga) binary in the following way to convert their SPIR-V shaders to WGSL: - -```bash -cargo run -- -``` - -In addition, SPIR-V can be used by enabling the `spirv` feature and GLSL can be enabled by enabling the `glsl` feature at the cost of slightly increased build times. diff --git a/wgpu/build.rs b/wgpu/build.rs new file mode 100644 index 0000000000..6f3d0f58ab --- /dev/null +++ b/wgpu/build.rs @@ -0,0 +1,15 @@ +fn main() { + cfg_aliases::cfg_aliases! { + native: { not(target_arch = "wasm32") }, + webgl: { all(target_arch = "wasm32", not(target_os = "emscripten"), feature = "webgl") }, + webgpu: { all(target_arch = "wasm32", not(target_os = "emscripten"), feature = "webgpu") }, + Emscripten: { all(target_arch = "wasm32", target_os = "emscripten") }, + wgpu_core: { any(native, webgl, emscripten) }, + send_sync: { any( + not(target_arch = "wasm32"), + all(feature = "fragile-send-sync-non-atomic-wasm", not(target_feature = "atomics")) + ) }, + dx12: { all(target_os = "windows", feature = "dx12") }, + metal: { all(any(target_os = "ios", target_os = "macos"), feature = "metal") } + } +} diff --git a/wgpu/src/backend/mod.rs b/wgpu/src/backend/mod.rs index 5680b6c5a6..02d9632efb 100644 --- a/wgpu/src/backend/mod.rs +++ b/wgpu/src/backend/mod.rs @@ -1,23 +1,9 @@ -#[cfg(all( - target_arch = "wasm32", - not(any(target_os = "emscripten", feature = "webgl")) -))] -mod web; -#[cfg(all( - target_arch = "wasm32", - not(any(target_os = "emscripten", feature = "webgl")) -))] -pub(crate) use web::Context; +#[cfg(webgpu)] +mod webgpu; +#[cfg(webgpu)] +pub(crate) use webgpu::{get_browser_gpu_property, ContextWebGpu}; -#[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" -))] -mod direct; -#[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" -))] -pub(crate) use direct::Context; +#[cfg(wgpu_core)] +mod wgpu_core; +#[cfg(wgpu_core)] +pub(crate) use wgpu_core::ContextWgpuCore; diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/webgpu.rs similarity index 88% rename from wgpu/src/backend/web.rs rename to wgpu/src/backend/webgpu.rs index 5ab9bd8463..a117260563 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/webgpu.rs @@ -7,28 +7,27 @@ use std::{ fmt, future::Future, marker::PhantomData, + num::NonZeroU64, ops::Range, pin::Pin, rc::Rc, + sync::atomic::{AtomicU64, Ordering}, task::{self, Poll}, }; use wasm_bindgen::{prelude::*, JsCast}; use crate::{ context::{downcast_ref, ObjectId, QueueWriteBuffer, Unused}, - UncapturedErrorHandler, + SurfaceTargetUnsafe, UncapturedErrorHandler, }; fn create_identified(value: T) -> (Identified, Sendable) { - cfg_if::cfg_if! { - if #[cfg(feature = "expose-ids")] { - static NEXT_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); - let id = NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - (Identified(core::num::NonZeroU64::new(id).unwrap(), PhantomData), Sendable(value)) - } else { - (Identified(PhantomData), Sendable(value)) - } - } + static NEXT_ID: AtomicU64 = AtomicU64::new(1); + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + ( + Identified(NonZeroU64::new(id).unwrap(), PhantomData), + Sendable(value), + ) } // We need to make a wrapper for some of the handle types returned by the web backend to make them @@ -39,25 +38,18 @@ fn create_identified(value: T) -> (Identified, Sendable) { // type is (for now) harmless. Eventually wasm32 will support threading, and depending on how this // is integrated (or not integrated) with values like those in webgpu, this may become unsound. -#[allow(unused_variables)] impl From for Identified { fn from(object_id: ObjectId) -> Self { - Self( - #[cfg(feature = "expose-ids")] - object_id.global_id(), - PhantomData, - ) + Self(object_id.global_id(), PhantomData) } } -#[allow(unused_variables)] impl From> for ObjectId { fn from(identified: Identified) -> Self { Self::new( // TODO: the ID isn't used, so we hardcode it to 1 for now until we rework this // API. - core::num::NonZeroU64::new(1).unwrap(), - #[cfg(feature = "expose-ids")] + NonZeroU64::new(1).unwrap(), identified.0, ) } @@ -65,48 +57,33 @@ impl From> for ObjectId { #[derive(Clone, Debug)] pub(crate) struct Sendable(T); -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for Sendable {} -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for Sendable {} #[derive(Clone, Debug)] -pub(crate) struct Identified( - #[cfg(feature = "expose-ids")] std::num::NonZeroU64, - PhantomData, -); -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +pub(crate) struct Identified(std::num::NonZeroU64, PhantomData); +#[cfg(send_sync)] unsafe impl Send for Identified {} -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Sync for Identified {} -pub(crate) struct Context(web_sys::Gpu); -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] -unsafe impl Send for Context {} -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] -unsafe impl Sync for Context {} - -impl fmt::Debug for Context { +pub(crate) struct ContextWebGpu(web_sys::Gpu); +#[cfg(send_sync)] +unsafe impl Send for ContextWebGpu {} +#[cfg(send_sync)] +unsafe impl Sync for ContextWebGpu {} +#[cfg(send_sync)] +unsafe impl Send for BufferMappedRange {} +#[cfg(send_sync)] +unsafe impl Sync for BufferMappedRange {} + +impl fmt::Debug for ContextWebGpu { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Context").field("type", &"Web").finish() + f.debug_struct("ContextWebGpu") + .field("type", &"Web") + .finish() } } @@ -139,7 +116,7 @@ pub(crate) struct MakeSendFuture { impl T, T> Future for MakeSendFuture { type Output = T; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { // This is safe because we have no Drop implementation to violate the Pin requirements and // do not provide any means of moving the inner future. unsafe { @@ -158,10 +135,7 @@ impl MakeSendFuture { } } -#[cfg(all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") -))] +#[cfg(send_sync)] unsafe impl Send for MakeSendFuture {} fn map_texture_format(texture_format: wgt::TextureFormat) -> web_sys::GpuTextureFormat { @@ -197,6 +171,9 @@ fn map_texture_format(texture_format: wgt::TextureFormat) -> web_sys::GpuTexture TextureFormat::Bgra8UnormSrgb => tf::Bgra8unormSrgb, // Packed 32-bit formats TextureFormat::Rgb9e5Ufloat => tf::Rgb9e5ufloat, + TextureFormat::Rgb10a2Uint => { + unimplemented!("Current version of web_sys is missing {texture_format:?}") + } TextureFormat::Rgb10a2Unorm => tf::Rgb10a2unorm, TextureFormat::Rg11b10Float => tf::Rg11b10ufloat, // 64-bit formats @@ -340,6 +317,18 @@ fn map_primitive_state(primitive: &wgt::PrimitiveState) -> web_sys::GpuPrimitive //TODO: //mapped.unclipped_depth(primitive.unclipped_depth); + match primitive.polygon_mode { + wgt::PolygonMode::Fill => {} + wgt::PolygonMode::Line => panic!( + "{:?} is not enabled for this backend", + wgt::Features::POLYGON_MODE_LINE + ), + wgt::PolygonMode::Point => panic!( + "{:?} is not enabled for this backend", + wgt::Features::POLYGON_MODE_POINT + ), + } + mapped } @@ -383,12 +372,14 @@ fn map_stencil_state_face(desc: &wgt::StencilFaceState) -> web_sys::GpuStencilFa } fn map_depth_stencil_state(desc: &wgt::DepthStencilState) -> web_sys::GpuDepthStencilState { - let mut mapped = web_sys::GpuDepthStencilState::new(map_texture_format(desc.format)); + let mut mapped = web_sys::GpuDepthStencilState::new( + map_compare_function(desc.depth_compare), + desc.depth_write_enabled, + map_texture_format(desc.format), + ); mapped.depth_bias(desc.bias.constant); mapped.depth_bias_clamp(desc.bias.clamp); mapped.depth_bias_slope_scale(desc.bias.slope_scale); - mapped.depth_compare(map_compare_function(desc.depth_compare)); - mapped.depth_write_enabled(desc.depth_write_enabled); mapped.stencil_back(&map_stencil_state_face(&desc.stencil.back)); mapped.stencil_front(&map_stencil_state_face(&desc.stencil.front)); mapped.stencil_read_mask(desc.stencil.read_mask); @@ -421,6 +412,15 @@ fn map_blend_factor(factor: wgt::BlendFactor) -> web_sys::GpuBlendFactor { BlendFactor::SrcAlphaSaturated => bf::SrcAlphaSaturated, BlendFactor::Constant => bf::Constant, BlendFactor::OneMinusConstant => bf::OneMinusConstant, + BlendFactor::Src1 + | BlendFactor::OneMinusSrc1 + | BlendFactor::Src1Alpha + | BlendFactor::OneMinusSrc1Alpha => { + panic!( + "{:?} is not enabled for this backend", + wgt::Features::DUAL_SOURCE_BLENDING + ) + } } } @@ -541,9 +541,10 @@ fn map_texture_view_dimension( } } -fn map_buffer_copy_view(view: crate::ImageCopyBuffer) -> web_sys::GpuImageCopyBuffer { - let buffer: &::BufferData = downcast_ref(view.buffer.data.as_ref()); - let mut mapped = web_sys::GpuImageCopyBuffer::new(&buffer.0); +fn map_buffer_copy_view(view: crate::ImageCopyBuffer<'_>) -> web_sys::GpuImageCopyBuffer { + let buffer: &::BufferData = + downcast_ref(view.buffer.data.as_ref()); + let mut mapped = web_sys::GpuImageCopyBuffer::new(&buffer.0.buffer); if let Some(bytes_per_row) = view.layout.bytes_per_row { mapped.bytes_per_row(bytes_per_row); } @@ -554,8 +555,8 @@ fn map_buffer_copy_view(view: crate::ImageCopyBuffer) -> web_sys::GpuImageCopyBu mapped } -fn map_texture_copy_view(view: crate::ImageCopyTexture) -> web_sys::GpuImageCopyTexture { - let texture: &::TextureData = +fn map_texture_copy_view(view: crate::ImageCopyTexture<'_>) -> web_sys::GpuImageCopyTexture { + let texture: &::TextureData = downcast_ref(view.texture.data.as_ref()); let mut mapped = web_sys::GpuImageCopyTexture::new(&texture.0); mapped.mip_level(view.mip_level); @@ -564,9 +565,9 @@ fn map_texture_copy_view(view: crate::ImageCopyTexture) -> web_sys::GpuImageCopy } fn map_tagged_texture_copy_view( - view: crate::ImageCopyTextureTagged, + view: crate::ImageCopyTextureTagged<'_>, ) -> web_sys::GpuImageCopyTextureTagged { - let texture: &::TextureData = + let texture: &::TextureData = downcast_ref(view.texture.data.as_ref()); let mut mapped = web_sys::GpuImageCopyTextureTagged::new(&texture.0); mapped.mip_level(view.mip_level); @@ -591,6 +592,9 @@ fn map_texture_aspect(aspect: wgt::TextureAspect) -> web_sys::GpuTextureAspect { wgt::TextureAspect::All => web_sys::GpuTextureAspect::All, wgt::TextureAspect::StencilOnly => web_sys::GpuTextureAspect::StencilOnly, wgt::TextureAspect::DepthOnly => web_sys::GpuTextureAspect::DepthOnly, + wgt::TextureAspect::Plane0 | wgt::TextureAspect::Plane1 | wgt::TextureAspect::Plane2 => { + panic!("multi-plane textures are not supported") + } } } @@ -621,11 +625,10 @@ fn map_color(color: wgt::Color) -> web_sys::GpuColorDict { web_sys::GpuColorDict::new(color.a, color.b, color.g, color.r) } -fn map_store_op(store: bool) -> web_sys::GpuStoreOp { - if store { - web_sys::GpuStoreOp::Store - } else { - web_sys::GpuStoreOp::Discard +fn map_store_op(store: crate::StoreOp) -> web_sys::GpuStoreOp { + match store { + crate::StoreOp::Store => web_sys::GpuStoreOp::Store, + crate::StoreOp::Discard => web_sys::GpuStoreOp::Discard, } } @@ -636,7 +639,7 @@ fn map_map_mode(mode: crate::MapMode) -> u32 { } } -const FEATURES_MAPPING: [(wgt::Features, web_sys::GpuFeatureName); 9] = [ +const FEATURES_MAPPING: [(wgt::Features, web_sys::GpuFeatureName); 11] = [ //TODO: update the name ( wgt::Features::DEPTH_CLIP_CONTROL, @@ -674,6 +677,14 @@ const FEATURES_MAPPING: [(wgt::Features, web_sys::GpuFeatureName); 9] = [ wgt::Features::RG11B10UFLOAT_RENDERABLE, web_sys::GpuFeatureName::Rg11b10ufloatRenderable, ), + ( + wgt::Features::BGRA8UNORM_STORAGE, + web_sys::GpuFeatureName::Bgra8unormStorage, + ), + ( + wgt::Features::FLOAT32_FILTERABLE, + web_sys::GpuFeatureName::Float32Filterable, + ), ]; fn map_wgt_features(supported_features: web_sys::GpuSupportedFeatures) -> wgt::Features { @@ -812,7 +823,9 @@ fn future_request_device( (device_id, device_data, queue_id, queue_data) }) - .map_err(|_| crate::RequestDeviceError) + .map_err(|error_value| crate::RequestDeviceError { + inner: crate::RequestDeviceErrorKind::WebGpu(error_value), + }) } fn future_pop_error_scope(result: JsFutureResult) -> Option { @@ -867,36 +880,8 @@ where *rc_callback.borrow_mut() = Some((closure_success, closure_rejected, callback)); } -impl Context { - pub fn instance_create_surface_from_canvas( - &self, - canvas: web_sys::HtmlCanvasElement, - ) -> Result< - ( - ::SurfaceId, - ::SurfaceData, - ), - crate::CreateSurfaceError, - > { - let result = canvas.get_context("webgpu"); - self.create_surface_from_context(Canvas::Canvas(canvas), result) - } - - pub fn instance_create_surface_from_offscreen_canvas( - &self, - canvas: web_sys::OffscreenCanvas, - ) -> Result< - ( - ::SurfaceId, - ::SurfaceData, - ), - crate::CreateSurfaceError, - > { - let result = canvas.get_context("webgpu"); - self.create_surface_from_context(Canvas::Offscreen(canvas), result) - } - - /// Common portion of public `instance_create_surface_from_*` functions. +impl ContextWebGpu { + /// Common portion of the internal branches of the public `instance_create_surface` function. /// /// Note: Analogous code also exists in the WebGL2 backend at /// `wgpu_hal::gles::web::Instance`. @@ -947,6 +932,15 @@ impl Context { Ok(create_identified((canvas, context))) } + + /// Get mapped buffer range directly as a `js_sys::ArrayBuffer`. + pub fn buffer_get_mapped_range_as_array_buffer( + &self, + buffer_data: &::BufferData, + sub_range: Range, + ) -> js_sys::ArrayBuffer { + buffer_data.0.get_mapped_array_buffer(sub_range) + } } // Represents the global object in the JavaScript context. @@ -970,7 +964,31 @@ pub enum Canvas { Offscreen(web_sys::OffscreenCanvas), } -impl crate::context::Context for Context { +/// Returns the browsers gpu object or `None` if the current context is neither the main thread nor a dedicated worker. +/// +/// If WebGPU is not supported, the Gpu property is `undefined` (but *not* necessarily `None`). +/// +/// See: +/// * +/// * +pub fn get_browser_gpu_property() -> Option { + let global: Global = js_sys::global().unchecked_into(); + + if !global.window().is_undefined() { + Some(global.unchecked_into::().navigator().gpu()) + } else if !global.worker().is_undefined() { + Some( + global + .unchecked_into::() + .navigator() + .gpu(), + ) + } else { + None + } +} + +impl crate::context::Context for ContextWebGpu { type AdapterId = Identified; type AdapterData = Sendable; type DeviceId = Identified; @@ -987,8 +1005,8 @@ impl crate::context::Context for Context { type TextureViewData = Sendable; type SamplerId = Identified; type SamplerData = Sendable; - type BufferId = Identified; - type BufferData = Sendable; + type BufferId = Identified; + type BufferData = Sendable; type TextureId = Identified; type TextureData = Sendable; type QuerySetId = Identified; @@ -1045,47 +1063,65 @@ impl crate::context::Context for Context { type TlasId = ObjectId; fn init(_instance_desc: wgt::InstanceDescriptor) -> Self { - let global: Global = js_sys::global().unchecked_into(); - let gpu = if !global.window().is_undefined() { - global.unchecked_into::().navigator().gpu() - } else if !global.worker().is_undefined() { - global - .unchecked_into::() - .navigator() - .gpu() - } else { + let Some(gpu) = get_browser_gpu_property() else { panic!( "Accessing the GPU is only supported on the main thread or from a dedicated worker" ); }; - Context(gpu) + ContextWebGpu(gpu) } - fn instance_create_surface( + unsafe fn instance_create_surface( &self, - _display_handle: raw_window_handle::RawDisplayHandle, - window_handle: raw_window_handle::RawWindowHandle, + target: SurfaceTargetUnsafe, ) -> Result<(Self::SurfaceId, Self::SurfaceData), crate::CreateSurfaceError> { - let canvas_attribute = match window_handle { - raw_window_handle::RawWindowHandle::Web(web_handle) => web_handle.id, - _ => panic!("expected valid handle for canvas"), - }; - let canvas_node: wasm_bindgen::JsValue = web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| { - doc.query_selector_all(&format!("[data-raw-handle=\"{canvas_attribute}\"]")) - .ok() - }) - .and_then(|nodes| nodes.get(0)) - .expect("expected to find single canvas") - .into(); - let canvas_element: web_sys::HtmlCanvasElement = canvas_node.into(); - self.instance_create_surface_from_canvas(canvas_element) + match target { + SurfaceTargetUnsafe::RawHandle { + raw_display_handle: _, + raw_window_handle, + } => { + let canvas_element: web_sys::HtmlCanvasElement = match raw_window_handle { + raw_window_handle::RawWindowHandle::Web(handle) => { + let canvas_node: wasm_bindgen::JsValue = web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| { + doc.query_selector_all(&format!( + "[data-raw-handle=\"{}\"]", + handle.id + )) + .ok() + }) + .and_then(|nodes| nodes.get(0)) + .expect("expected to find single canvas") + .into(); + canvas_node.into() + } + raw_window_handle::RawWindowHandle::WebCanvas(handle) => { + let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; + value.clone().unchecked_into() + } + raw_window_handle::RawWindowHandle::WebOffscreenCanvas(handle) => { + let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; + let canvas: web_sys::OffscreenCanvas = value.clone().unchecked_into(); + let context_result = canvas.get_context("webgpu"); + + return self.create_surface_from_context( + Canvas::Offscreen(canvas), + context_result, + ); + } + _ => panic!("expected valid handle for canvas"), + }; + + let context_result = canvas_element.get_context("webgpu"); + self.create_surface_from_context(Canvas::Canvas(canvas_element), context_result) + } + } } fn instance_request_adapter( &self, - options: &crate::RequestAdapterOptions<'_>, + options: &crate::RequestAdapterOptions<'_, '_>, ) -> Self::RequestAdapterFuture { //TODO: support this check, return `None` if the flag is not set. // It's not trivial, since we need the Future logic to have this check, @@ -1114,7 +1150,7 @@ impl crate::context::Context for Context { &self, _adapter: &Self::AdapterId, adapter_data: &Self::AdapterData, - desc: &crate::DeviceDescriptor, + desc: &crate::DeviceDescriptor<'_>, trace_dir: Option<&std::path::Path>, ) -> Self::RequestDeviceFuture { if trace_dir.is_some() { @@ -1125,7 +1161,7 @@ impl crate::context::Context for Context { // TODO: Migrate to a web_sys api. // See https://github.com/rustwasm/wasm-bindgen/issues/3587 - let limits_object = map_js_sys_limits(&desc.limits); + let limits_object = map_js_sys_limits(&desc.required_limits); js_sys::Reflect::set( &mapped_desc, @@ -1138,7 +1174,7 @@ impl crate::context::Context for Context { .iter() .copied() .flat_map(|(flag, value)| { - if desc.features.contains(flag) { + if desc.required_features.contains(flag) { Some(JsValue::from(value)) } else { None @@ -1377,7 +1413,7 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: crate::ShaderModuleDescriptor, + desc: crate::ShaderModuleDescriptor<'_>, _shader_bound_checks: wgt::ShaderBoundChecks, ) -> (Self::ShaderModuleId, Self::ShaderModuleData) { let mut descriptor = match desc.source { @@ -1463,7 +1499,7 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, _device_data: &Self::DeviceData, - _desc: &crate::ShaderModuleDescriptorSpirV, + _desc: &crate::ShaderModuleDescriptorSpirV<'_>, ) -> (Self::ShaderModuleId, Self::ShaderModuleData) { unreachable!("SPIRV_SHADER_PASSTHROUGH is not enabled for this backend") } @@ -1472,7 +1508,7 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::BindGroupLayoutDescriptor, + desc: &crate::BindGroupLayoutDescriptor<'_>, ) -> (Self::BindGroupLayoutId, Self::BindGroupLayoutData) { let mapped_bindings = desc .entries @@ -1572,7 +1608,7 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::BindGroupDescriptor, + desc: &crate::BindGroupDescriptor<'_>, ) -> (Self::BindGroupId, Self::BindGroupData) { let mapped_entries = desc .entries @@ -1584,9 +1620,10 @@ impl crate::context::Context for Context { offset, size, }) => { - let buffer: &::BufferData = + let buffer: &::BufferData = downcast_ref(buffer.data.as_ref()); - let mut mapped_buffer_binding = web_sys::GpuBufferBinding::new(&buffer.0); + let mut mapped_buffer_binding = + web_sys::GpuBufferBinding::new(&buffer.0.buffer); mapped_buffer_binding.offset(offset as f64); if let Some(s) = size { mapped_buffer_binding.size(s.get() as f64); @@ -1597,7 +1634,7 @@ impl crate::context::Context for Context { panic!("Web backend does not support arrays of buffers") } crate::BindingResource::Sampler(sampler) => { - let sampler: &::SamplerData = + let sampler: &::SamplerData = downcast_ref(sampler.data.as_ref()); JsValue::from(&sampler.0) } @@ -1605,7 +1642,7 @@ impl crate::context::Context for Context { panic!("Web backend does not support arrays of samplers") } crate::BindingResource::TextureView(texture_view) => { - let texture_view: &::TextureViewData = + let texture_view: &::TextureViewData = downcast_ref(texture_view.data.as_ref()); JsValue::from(&texture_view.0) } @@ -1621,7 +1658,7 @@ impl crate::context::Context for Context { }) .collect::(); - let bgl: &::BindGroupLayoutData = + let bgl: &::BindGroupLayoutData = downcast_ref(desc.layout.data.as_ref()); let mut mapped_desc = web_sys::GpuBindGroupDescriptor::new(&mapped_entries, &bgl.0); if let Some(label) = desc.label { @@ -1634,13 +1671,13 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::PipelineLayoutDescriptor, + desc: &crate::PipelineLayoutDescriptor<'_>, ) -> (Self::PipelineLayoutId, Self::PipelineLayoutData) { let temp_layouts = desc .bind_group_layouts .iter() .map(|bgl| { - let bgl: &::BindGroupLayoutData = + let bgl: &::BindGroupLayoutData = downcast_ref(bgl.data.as_ref()); &bgl.0 }) @@ -1656,9 +1693,9 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::RenderPipelineDescriptor, + desc: &crate::RenderPipelineDescriptor<'_>, ) -> (Self::RenderPipelineId, Self::RenderPipelineData) { - let module: &::ShaderModuleData = + let module: &::ShaderModuleData = downcast_ref(desc.vertex.module.data.as_ref()); let mut mapped_vertex_state = web_sys::GpuVertexState::new(desc.vertex.entry_point, &module.0); @@ -1695,7 +1732,7 @@ impl crate::context::Context for Context { let mut mapped_desc = web_sys::GpuRenderPipelineDescriptor::new( &match desc.layout { Some(layout) => { - let layout: &::PipelineLayoutData = + let layout: &::PipelineLayoutData = downcast_ref(layout.data.as_ref()); JsValue::from(&layout.0) } @@ -1733,7 +1770,7 @@ impl crate::context::Context for Context { None => wasm_bindgen::JsValue::null(), }) .collect::(); - let module: &::ShaderModuleData = + let module: &::ShaderModuleData = downcast_ref(frag.module.data.as_ref()); let mapped_fragment_desc = web_sys::GpuFragmentState::new(frag.entry_point, &module.0, &targets); @@ -1756,9 +1793,9 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::ComputePipelineDescriptor, + desc: &crate::ComputePipelineDescriptor<'_>, ) -> (Self::ComputePipelineId, Self::ComputePipelineData) { - let shader_module: &::ShaderModuleData = + let shader_module: &::ShaderModuleData = downcast_ref(desc.module.data.as_ref()); let mapped_compute_stage = web_sys::GpuProgrammableStage::new(desc.entry_point, &shader_module.0); @@ -1766,7 +1803,7 @@ impl crate::context::Context for Context { let mut mapped_desc = web_sys::GpuComputePipelineDescriptor::new( &match desc.layout { Some(layout) => { - let layout: &::PipelineLayoutData = + let layout: &::PipelineLayoutData = downcast_ref(layout.data.as_ref()); JsValue::from(&layout.0) } @@ -1784,7 +1821,7 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::BufferDescriptor, + desc: &crate::BufferDescriptor<'_>, ) -> (Self::BufferId, Self::BufferData) { let mut mapped_desc = web_sys::GpuBufferDescriptor::new(desc.size as f64, desc.usage.bits()); @@ -1792,14 +1829,17 @@ impl crate::context::Context for Context { if let Some(label) = desc.label { mapped_desc.label(label); } - create_identified(device_data.0.create_buffer(&mapped_desc)) + create_identified(WebBuffer::new( + device_data.0.create_buffer(&mapped_desc), + desc, + )) } fn device_create_texture( &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::TextureDescriptor, + desc: &crate::TextureDescriptor<'_>, ) -> (Self::TextureId, Self::TextureData) { let mut mapped_desc = web_sys::GpuTextureDescriptor::new( map_texture_format(desc.format), @@ -1825,7 +1865,7 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::SamplerDescriptor, + desc: &crate::SamplerDescriptor<'_>, ) -> (Self::SamplerId, Self::SamplerData) { let mut mapped_desc = web_sys::GpuSamplerDescriptor::new(); mapped_desc.address_mode_u(map_address_mode(desc.address_mode_u)); @@ -1851,7 +1891,7 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &wgt::QuerySetDescriptor, + desc: &wgt::QuerySetDescriptor>, ) -> (Self::QuerySetId, Self::QuerySetData) { let ty = match desc.ty { wgt::QueryType::Occlusion => web_sys::GpuQueryType::Occlusion, @@ -1869,7 +1909,7 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::CommandEncoderDescriptor, + desc: &crate::CommandEncoderDescriptor<'_>, ) -> (Self::CommandEncoderId, Self::CommandEncoderData) { let mut mapped_desc = web_sys::GpuCommandEncoderDescriptor::new(); if let Some(label) = desc.label { @@ -1886,7 +1926,7 @@ impl crate::context::Context for Context { &self, _device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &crate::RenderBundleEncoderDescriptor, + desc: &crate::RenderBundleEncoderDescriptor<'_>, ) -> (Self::RenderBundleEncoderId, Self::RenderBundleEncoderData) { let mapped_color_formats = desc .color_formats @@ -1913,14 +1953,42 @@ impl crate::context::Context for Context { // Device is dropped automatically } + fn device_destroy(&self, _buffer: &Self::DeviceId, device_data: &Self::DeviceData) { + device_data.0.destroy(); + } + + fn device_mark_lost( + &self, + _device: &Self::DeviceId, + _device_data: &Self::DeviceData, + _message: &str, + ) { + // TODO: figure out the GPUDevice implementation of this, including resolving + // the device.lost promise, which will require a different invocation pattern + // with a callback. + } + + fn queue_drop(&self, _queue: &Self::QueueId, _queue_data: &Self::QueueData) { + // Queue is dropped automatically + } + + fn device_set_device_lost_callback( + &self, + _device: &Self::DeviceId, + _device_data: &Self::DeviceData, + _device_lost_callback: crate::context::DeviceLostCallback, + ) { + unimplemented!(); + } + fn device_poll( &self, _device: &Self::DeviceId, _device_data: &Self::DeviceData, _maintain: crate::Maintain, - ) -> bool { + ) -> crate::MaintainResult { // Device is polled automatically - true + crate::MaintainResult::SubmissionQueueEmpty } fn device_on_uncaptured_error( @@ -1972,12 +2040,14 @@ impl crate::context::Context for Context { range: Range, callback: crate::context::BufferMapCallback, ) { - let map_promise = buffer_data.0.map_async_with_f64_and_f64( + let map_promise = buffer_data.0.buffer.map_async_with_f64_and_f64( map_map_mode(mode), range.start as f64, (range.end - range.start) as f64, ); + buffer_data.0.set_mapped_range(range); + register_then_closures(&map_promise, callback, Ok(()), Err(crate::BufferAsyncError)); } @@ -1987,9 +2057,7 @@ impl crate::context::Context for Context { buffer_data: &Self::BufferData, sub_range: Range, ) -> Box { - let array_buffer = - self.buffer_get_mapped_range_as_array_buffer(_buffer, buffer_data, sub_range); - let actual_mapping = js_sys::Uint8Array::new(&array_buffer); + let actual_mapping = buffer_data.0.get_mapped_range(sub_range); let temporary_mapping = actual_mapping.to_vec(); Box::new(BufferMappedRange { actual_mapping, @@ -1997,27 +2065,16 @@ impl crate::context::Context for Context { }) } - fn buffer_get_mapped_range_as_array_buffer( - &self, - _buffer: &Self::BufferId, - buffer_data: &Self::BufferData, - sub_range: Range, - ) -> js_sys::ArrayBuffer { - buffer_data.0.get_mapped_range_with_f64_and_f64( - sub_range.start as f64, - (sub_range.end - sub_range.start) as f64, - ) - } - fn buffer_unmap(&self, _buffer: &Self::BufferId, buffer_data: &Self::BufferData) { - buffer_data.0.unmap(); + buffer_data.0.buffer.unmap(); + buffer_data.0.mapping.borrow_mut().mapped_buffer = None; } fn texture_create_view( &self, _texture: &Self::TextureId, texture_data: &Self::TextureData, - desc: &crate::TextureViewDescriptor, + desc: &crate::TextureViewDescriptor<'_>, ) -> (Self::TextureViewId, Self::TextureViewData) { let mut mapped = web_sys::GpuTextureViewDescriptor::new(); if let Some(dim) = desc.dimension { @@ -2050,7 +2107,7 @@ impl crate::context::Context for Context { } fn buffer_destroy(&self, _buffer: &Self::BufferId, buffer_data: &Self::BufferData) { - buffer_data.0.destroy(); + buffer_data.0.buffer.destroy(); } fn buffer_drop(&self, _buffer: &Self::BufferId, _buffer_data: &Self::BufferData) { @@ -2102,7 +2159,7 @@ impl crate::context::Context for Context { _pipeline_layout: &Self::PipelineLayoutId, _pipeline_layout_data: &Self::PipelineLayoutData, ) { - // Dropped automatically + // Dropped automaticaly } fn shader_module_drop( @@ -2186,9 +2243,9 @@ impl crate::context::Context for Context { encoder_data .0 .copy_buffer_to_buffer_with_f64_and_f64_and_f64( - &source_data.0, + &source_data.0.buffer, source_offset as f64, - &destination_data.0, + &destination_data.0.buffer, destination_offset as f64, copy_size as f64, ) @@ -2198,8 +2255,8 @@ impl crate::context::Context for Context { &self, _encoder: &Self::CommandEncoderId, encoder_data: &Self::CommandEncoderData, - source: crate::ImageCopyBuffer, - destination: crate::ImageCopyTexture, + source: crate::ImageCopyBuffer<'_>, + destination: crate::ImageCopyTexture<'_>, copy_size: wgt::Extent3d, ) { encoder_data @@ -2215,8 +2272,8 @@ impl crate::context::Context for Context { &self, _encoder: &Self::CommandEncoderId, encoder_data: &Self::CommandEncoderData, - source: crate::ImageCopyTexture, - destination: crate::ImageCopyBuffer, + source: crate::ImageCopyTexture<'_>, + destination: crate::ImageCopyBuffer<'_>, copy_size: wgt::Extent3d, ) { encoder_data @@ -2232,8 +2289,8 @@ impl crate::context::Context for Context { &self, _encoder: &Self::CommandEncoderId, encoder_data: &Self::CommandEncoderData, - source: crate::ImageCopyTexture, - destination: crate::ImageCopyTexture, + source: crate::ImageCopyTexture<'_>, + destination: crate::ImageCopyTexture<'_>, copy_size: wgt::Extent3d, ) { encoder_data @@ -2249,7 +2306,7 @@ impl crate::context::Context for Context { &self, _encoder: &Self::CommandEncoderId, encoder_data: &Self::CommandEncoderData, - desc: &crate::ComputePassDescriptor, + desc: &crate::ComputePassDescriptor<'_>, ) -> (Self::ComputePassId, Self::ComputePassData) { let mut mapped_desc = web_sys::GpuComputePassDescriptor::new(); if let Some(label) = desc.label { @@ -2292,7 +2349,7 @@ impl crate::context::Context for Context { crate::LoadOp::Load => web_sys::GpuLoadOp::Load, }; - let view: &::TextureViewData = + let view: &::TextureViewData = downcast_ref(ca.view.data.as_ref()); let mut mapped_color_attachment = web_sys::GpuRenderPassColorAttachment::new( @@ -2304,7 +2361,7 @@ impl crate::context::Context for Context { mapped_color_attachment.clear_value(&cv); } if let Some(rt) = ca.resolve_target { - let resolve_target_view: &::TextureViewData = + let resolve_target_view: &::TextureViewData = downcast_ref(rt.data.as_ref()); mapped_color_attachment.resolve_target(&resolve_target_view.0); } @@ -2323,7 +2380,7 @@ impl crate::context::Context for Context { } if let Some(dsa) = &desc.depth_stencil_attachment { - let depth_stencil_attachment: &::TextureViewData = + let depth_stencil_attachment: &::TextureViewData = downcast_ref(dsa.view.data.as_ref()); let mut mapped_depth_stencil_attachment = web_sys::GpuRenderPassDepthStencilAttachment::new(&depth_stencil_attachment.0); @@ -2398,18 +2455,19 @@ impl crate::context::Context for Context { encoder_data: &Self::CommandEncoderData, buffer: &crate::Buffer, offset: wgt::BufferAddress, - size: Option, + size: Option, ) { - let buffer: &::BufferData = downcast_ref(buffer.data.as_ref()); + let buffer: &::BufferData = + downcast_ref(buffer.data.as_ref()); match size { Some(size) => encoder_data.0.clear_buffer_with_f64_and_f64( - &buffer.0, + &buffer.0.buffer, offset as f64, - size.get() as f64, + size as f64, ), None => encoder_data .0 - .clear_buffer_with_f64(&buffer.0, offset as f64), + .clear_buffer_with_f64(&buffer.0.buffer, offset as f64), } } @@ -2471,7 +2529,7 @@ impl crate::context::Context for Context { &query_set_data.0, first_query, query_count, - &destination_data.0, + &destination_data.0.buffer, destination_offset as u32, ); } @@ -2480,7 +2538,7 @@ impl crate::context::Context for Context { &self, _encoder: Self::RenderBundleEncoderId, encoder_data: Self::RenderBundleEncoderData, - desc: &crate::RenderBundleDescriptor, + desc: &crate::RenderBundleDescriptor<'_>, ) -> (Self::RenderBundleId, Self::RenderBundleData) { create_identified(match desc.label { Some(label) => { @@ -2513,7 +2571,7 @@ impl crate::context::Context for Context { queue_data .0 .write_buffer_with_f64_and_buffer_source_and_f64_and_f64( - &buffer_data.0, + &buffer_data.0.buffer, offset as f64, &js_sys::Uint8Array::from(data).buffer(), 0f64, @@ -2530,7 +2588,7 @@ impl crate::context::Context for Context { offset: wgt::BufferAddress, size: wgt::BufferSize, ) -> Option<()> { - let usage = wgt::BufferUsages::from_bits_truncate(buffer_data.0.usage()); + let usage = wgt::BufferUsages::from_bits_truncate(buffer_data.0.buffer.usage()); // TODO: actually send this down the error scope if !usage.contains(wgt::BufferUsages::COPY_DST) { log::error!("Destination buffer is missing the `COPY_DST` usage flag"); @@ -2551,8 +2609,8 @@ impl crate::context::Context for Context { ); return None; } - if write_size + offset > buffer_data.0.size() as u64 { - log::error!("copy of {}..{} would end up overrunning the bounds of the destination buffer of size {}", offset, offset + write_size, buffer_data.0.size()); + if write_size + offset > buffer_data.0.buffer.size() as u64 { + log::error!("copy of {}..{} would end up overrunning the bounds of the destination buffer of size {}", offset, offset + write_size, buffer_data.0.buffer.size()); return None; } Some(()) @@ -2597,7 +2655,7 @@ impl crate::context::Context for Context { &self, _queue: &Self::QueueId, queue_data: &Self::QueueData, - texture: crate::ImageCopyTexture, + texture: crate::ImageCopyTexture<'_>, data: &[u8], data_layout: wgt::ImageDataLayout, size: wgt::Extent3d, @@ -2634,7 +2692,7 @@ impl crate::context::Context for Context { _queue: &Self::QueueId, queue_data: &Self::QueueData, source: &wgt::ImageCopyExternalImage, - dest: crate::ImageCopyTextureTagged, + dest: crate::ImageCopyTextureTagged<'_>, size: wgt::Extent3d, ) { queue_data @@ -2702,13 +2760,13 @@ impl crate::context::Context for Context { offsets: &[wgt::DynamicOffset], ) { if offsets.is_empty() { - pass_data.0.set_bind_group(index, &bind_group_data.0); + pass_data.0.set_bind_group(index, Some(&bind_group_data.0)); } else { pass_data .0 .set_bind_group_with_u32_array_and_f64_and_dynamic_offsets_data_length( index, - &bind_group_data.0, + Some(&bind_group_data.0), offsets, 0f64, offsets.len() as u32, @@ -2806,9 +2864,10 @@ impl crate::context::Context for Context { indirect_buffer_data: &Self::BufferData, indirect_offset: wgt::BufferAddress, ) { - pass_data - .0 - .dispatch_workgroups_indirect_with_f64(&indirect_buffer_data.0, indirect_offset as f64); + pass_data.0.dispatch_workgroups_indirect_with_f64( + &indirect_buffer_data.0.buffer, + indirect_offset as f64, + ); } fn render_bundle_encoder_set_pipeline( @@ -2831,13 +2890,15 @@ impl crate::context::Context for Context { offsets: &[wgt::DynamicOffset], ) { if offsets.is_empty() { - encoder_data.0.set_bind_group(index, &bind_group_data.0); + encoder_data + .0 + .set_bind_group(index, Some(&bind_group_data.0)); } else { encoder_data .0 .set_bind_group_with_u32_array_and_f64_and_dynamic_offsets_data_length( index, - &bind_group_data.0, + Some(&bind_group_data.0), offsets, 0f64, offsets.len() as u32, @@ -2858,7 +2919,7 @@ impl crate::context::Context for Context { match size { Some(s) => { encoder_data.0.set_index_buffer_with_f64_and_f64( - &buffer_data.0, + &buffer_data.0.buffer, map_index_format(index_format), offset as f64, s.get() as f64, @@ -2866,7 +2927,7 @@ impl crate::context::Context for Context { } None => { encoder_data.0.set_index_buffer_with_f64( - &buffer_data.0, + &buffer_data.0.buffer, map_index_format(index_format), offset as f64, ); @@ -2888,15 +2949,17 @@ impl crate::context::Context for Context { Some(s) => { encoder_data.0.set_vertex_buffer_with_f64_and_f64( slot, - &buffer_data.0, + Some(&buffer_data.0.buffer), offset as f64, s.get() as f64, ); } None => { - encoder_data - .0 - .set_vertex_buffer_with_f64(slot, &buffer_data.0, offset as f64); + encoder_data.0.set_vertex_buffer_with_f64( + slot, + Some(&buffer_data.0.buffer), + offset as f64, + ); } }; } @@ -2958,7 +3021,7 @@ impl crate::context::Context for Context { ) { encoder_data .0 - .draw_indirect_with_f64(&indirect_buffer_data.0, indirect_offset as f64); + .draw_indirect_with_f64(&indirect_buffer_data.0.buffer, indirect_offset as f64); } fn render_bundle_encoder_draw_indexed_indirect( @@ -2971,7 +3034,7 @@ impl crate::context::Context for Context { ) { encoder_data .0 - .draw_indexed_indirect_with_f64(&indirect_buffer_data.0, indirect_offset as f64); + .draw_indexed_indirect_with_f64(&indirect_buffer_data.0.buffer, indirect_offset as f64); } fn render_bundle_encoder_multi_draw_indirect( @@ -3050,13 +3113,13 @@ impl crate::context::Context for Context { offsets: &[wgt::DynamicOffset], ) { if offsets.is_empty() { - pass_data.0.set_bind_group(index, &bind_group_data.0); + pass_data.0.set_bind_group(index, Some(&bind_group_data.0)); } else { pass_data .0 .set_bind_group_with_u32_array_and_f64_and_dynamic_offsets_data_length( index, - &bind_group_data.0, + Some(&bind_group_data.0), offsets, 0f64, offsets.len() as u32, @@ -3077,7 +3140,7 @@ impl crate::context::Context for Context { match size { Some(s) => { pass_data.0.set_index_buffer_with_f64_and_f64( - &buffer_data.0, + &buffer_data.0.buffer, map_index_format(index_format), offset as f64, s.get() as f64, @@ -3085,7 +3148,7 @@ impl crate::context::Context for Context { } None => { pass_data.0.set_index_buffer_with_f64( - &buffer_data.0, + &buffer_data.0.buffer, map_index_format(index_format), offset as f64, ); @@ -3107,15 +3170,17 @@ impl crate::context::Context for Context { Some(s) => { pass_data.0.set_vertex_buffer_with_f64_and_f64( slot, - &buffer_data.0, + Some(&buffer_data.0.buffer), offset as f64, s.get() as f64, ); } None => { - pass_data - .0 - .set_vertex_buffer_with_f64(slot, &buffer_data.0, offset as f64); + pass_data.0.set_vertex_buffer_with_f64( + slot, + Some(&buffer_data.0.buffer), + offset as f64, + ); } }; } @@ -3177,7 +3242,7 @@ impl crate::context::Context for Context { ) { pass_data .0 - .draw_indirect_with_f64(&indirect_buffer_data.0, indirect_offset as f64); + .draw_indirect_with_f64(&indirect_buffer_data.0.buffer, indirect_offset as f64); } fn render_pass_draw_indexed_indirect( @@ -3190,7 +3255,7 @@ impl crate::context::Context for Context { ) { pass_data .0 - .draw_indexed_indirect_with_f64(&indirect_buffer_data.0, indirect_offset as f64); + .draw_indexed_indirect_with_f64(&indirect_buffer_data.0.buffer, indirect_offset as f64); } fn render_pass_multi_draw_indirect( @@ -3373,13 +3438,11 @@ impl crate::context::Context for Context { // Not available in gecko yet } - fn render_pass_execute_bundles<'a>( + fn render_pass_execute_bundles( &self, _pass: &mut Self::RenderPassId, pass_data: &mut Self::RenderPassData, - render_bundles: Box< - dyn Iterator + 'a, - >, + render_bundles: &mut dyn Iterator, ) { let mapped = render_bundles .map(|(_, bundle_data)| &bundle_data.0) @@ -3463,6 +3526,71 @@ impl QueueWriteBuffer for WebQueueWriteBuffer { } } +/// Stores the state of a GPU buffer and a reference to its mapped `ArrayBuffer` (if any). +/// The WebGPU specification forbids calling `getMappedRange` on a `web_sys::GpuBuffer` more than +/// once, so this struct stores the initial mapped range and re-uses it, allowing for multiple `get_mapped_range` +/// calls on the Rust-side. +#[derive(Debug)] +pub struct WebBuffer { + /// The associated GPU buffer. + buffer: web_sys::GpuBuffer, + /// The mapped array buffer and mapped range. + mapping: RefCell, +} + +impl WebBuffer { + /// Creates a new web buffer for the given Javascript object and description. + fn new(buffer: web_sys::GpuBuffer, desc: &crate::BufferDescriptor<'_>) -> Self { + Self { + buffer, + mapping: RefCell::new(WebBufferMapState { + mapped_buffer: None, + range: 0..desc.size, + }), + } + } + + /// Creates a raw Javascript array buffer over the provided range. + fn get_mapped_array_buffer(&self, sub_range: Range) -> js_sys::ArrayBuffer { + self.buffer.get_mapped_range_with_f64_and_f64( + sub_range.start as f64, + (sub_range.end - sub_range.start) as f64, + ) + } + + /// Obtains a reference to the re-usable buffer mapping as a Javascript array view. + fn get_mapped_range(&self, sub_range: Range) -> js_sys::Uint8Array { + let mut mapping = self.mapping.borrow_mut(); + let range = mapping.range.clone(); + let array_buffer = mapping.mapped_buffer.get_or_insert_with(|| { + self.buffer.get_mapped_range_with_f64_and_f64( + range.start as f64, + (range.end - range.start) as f64, + ) + }); + js_sys::Uint8Array::new_with_byte_offset_and_length( + array_buffer, + (sub_range.start - range.start) as u32, + (sub_range.end - sub_range.start) as u32, + ) + } + + /// Sets the range of the buffer which is presently mapped. + fn set_mapped_range(&self, range: Range) { + self.mapping.borrow_mut().range = range; + } +} + +/// Remembers which portion of a buffer has been mapped, along with a reference +/// to the mapped portion. +#[derive(Debug)] +struct WebBufferMapState { + /// The mapped memory of the buffer. + pub mapped_buffer: Option, + /// The total range which has been mapped in the buffer overall. + pub range: Range, +} + #[derive(Debug)] pub struct BufferMappedRange { actual_mapping: js_sys::Uint8Array, diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/wgpu_core.rs similarity index 93% rename from wgpu/src/backend/direct.rs rename to wgpu/src/backend/wgpu_core.rs index 29ba5f5b53..faa7c26835 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -4,8 +4,9 @@ use crate::{ BufferDescriptor, CommandEncoderDescriptor, ComputePassDescriptor, ComputePipelineDescriptor, DownlevelCapabilities, Features, Label, Limits, LoadOp, MapMode, Operations, PipelineLayoutDescriptor, RenderBundleEncoderDescriptor, RenderPipelineDescriptor, - SamplerDescriptor, ShaderModuleDescriptor, ShaderModuleDescriptorSpirV, ShaderSource, - SurfaceStatus, TextureDescriptor, TextureViewDescriptor, UncapturedErrorHandler, + SamplerDescriptor, ShaderModuleDescriptor, ShaderModuleDescriptorSpirV, ShaderSource, StoreOp, + SurfaceStatus, SurfaceTargetUnsafe, TextureDescriptor, TextureViewDescriptor, + UncapturedErrorHandler, }; use arrayvec::ArrayVec; @@ -22,26 +23,29 @@ use std::{ sync::Arc, }; use wgc::command::{bundle_ffi::*, compute_ffi::*, render_ffi::*}; +use wgc::device::DeviceLostClosure; use wgc::id::TypedId; -use wgt::{WasmNotSend, WasmNotSync}; +use wgt::WasmNotSendSync; const LABEL: &str = "label"; -pub struct Context(wgc::global::Global); +pub struct ContextWgpuCore(wgc::global::Global); -impl Drop for Context { +impl Drop for ContextWgpuCore { fn drop(&mut self) { //nothing } } -impl fmt::Debug for Context { +impl fmt::Debug for ContextWgpuCore { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Context").field("type", &"Native").finish() + f.debug_struct("ContextWgpuCore") + .field("type", &"Native") + .finish() } } -impl Context { +impl ContextWgpuCore { pub unsafe fn from_hal_instance(hal_instance: A::Instance) -> Self { Self(unsafe { wgc::global::Global::from_hal_instance::( @@ -100,17 +104,18 @@ impl Context { &self, adapter: &wgc::id::AdapterId, hal_device: hal::OpenDevice, - desc: &crate::DeviceDescriptor, + desc: &crate::DeviceDescriptor<'_>, trace_dir: Option<&std::path::Path>, ) -> Result<(Device, Queue), crate::RequestDeviceError> { let global = &self.0; - let (device_id, error) = unsafe { + let (device_id, queue_id, error) = unsafe { global.create_device_from_hal( *adapter, hal_device, &desc.map_label(|l| l.map(Borrowed)), trace_dir, (), + (), ) }; if let Some(err) = error { @@ -120,10 +125,10 @@ impl Context { let device = Device { id: device_id, error_sink: error_sink.clone(), - features: desc.features, + features: desc.required_features, }; let queue = Queue { - id: device_id, + id: queue_id, error_sink, }; Ok((device, queue)) @@ -133,7 +138,7 @@ impl Context { &self, hal_texture: A::Texture, device: &Device, - desc: &TextureDescriptor, + desc: &TextureDescriptor<'_>, ) -> Texture { let descriptor = desc.map_label_and_view_formats(|l| l.map(Borrowed), |v| v.to_vec()); let global = &self.0; @@ -158,7 +163,7 @@ impl Context { &self, hal_buffer: A::Buffer, device: &Device, - desc: &BufferDescriptor, + desc: &BufferDescriptor<'_>, ) -> (wgc::id::BufferId, Buffer) { let global = &self.0; let (id, error) = unsafe { @@ -197,9 +202,9 @@ impl Context { } } - pub unsafe fn surface_as_hal_mut< + pub unsafe fn surface_as_hal< A: wgc::hal_api::HalApi, - F: FnOnce(Option<&mut A::Surface>) -> R, + F: FnOnce(Option<&A::Surface>) -> R, R, >( &self, @@ -208,7 +213,7 @@ impl Context { ) -> R { unsafe { self.0 - .surface_as_hal_mut::(surface.id, hal_surface_callback) + .surface_as_hal::(surface.id, hal_surface_callback) } } @@ -227,72 +232,12 @@ impl Context { self.0.generate_report() } - #[cfg(any(target_os = "ios", target_os = "macos"))] - pub unsafe fn create_surface_from_core_animation_layer( - &self, - layer: *mut std::ffi::c_void, - ) -> Surface { - let id = unsafe { self.0.instance_create_surface_metal(layer, ()) }; - Surface { - id, - configured_device: Mutex::default(), - } - } - - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - pub fn instance_create_surface_from_canvas( - &self, - canvas: web_sys::HtmlCanvasElement, - ) -> Result { - let id = self.0.create_surface_webgl_canvas(canvas, ())?; - Ok(Surface { - id, - configured_device: Mutex::default(), - }) - } - - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - pub fn instance_create_surface_from_offscreen_canvas( - &self, - canvas: web_sys::OffscreenCanvas, - ) -> Result { - let id = self.0.create_surface_webgl_offscreen_canvas(canvas, ())?; - Ok(Surface { - id, - configured_device: Mutex::default(), - }) - } - - #[cfg(target_os = "windows")] - pub unsafe fn create_surface_from_visual(&self, visual: *mut std::ffi::c_void) -> Surface { - let id = unsafe { self.0.instance_create_surface_from_visual(visual, ()) }; - Surface { - id, - configured_device: Mutex::default(), - } - } - - #[cfg(target_os = "windows")] - pub unsafe fn create_surface_from_surface_handle( - &self, - surface_handle: *mut std::ffi::c_void, - ) -> Surface { - let id = unsafe { - self.0 - .instance_create_surface_from_surface_handle(surface_handle, ()) - }; - Surface { - id, - configured_device: Mutex::default(), - } - } - fn handle_error( &self, sink_mutex: &Mutex, - cause: impl Error + WasmNotSend + WasmNotSync + 'static, + cause: impl Error + WasmNotSendSync + 'static, label_key: &'static str, - label: Label, + label: Label<'_>, string: &'static str, ) { let error = wgc::error::ContextError { @@ -324,7 +269,7 @@ impl Context { fn handle_error_nolabel( &self, sink_mutex: &Mutex, - cause: impl Error + WasmNotSend + WasmNotSync + 'static, + cause: impl Error + WasmNotSendSync + 'static, string: &'static str, ) { self.handle_error(sink_mutex, cause, "", None, string) @@ -333,7 +278,7 @@ impl Context { #[track_caller] fn handle_error_fatal( &self, - cause: impl Error + WasmNotSend + WasmNotSync + 'static, + cause: impl Error + WasmNotSendSync + 'static, operation: &'static str, ) -> ! { panic!("Error in {operation}: {f}", f = self.format_error(&cause)); @@ -359,14 +304,14 @@ impl Context { } } -fn map_buffer_copy_view(view: crate::ImageCopyBuffer) -> wgc::command::ImageCopyBuffer { +fn map_buffer_copy_view(view: crate::ImageCopyBuffer<'_>) -> wgc::command::ImageCopyBuffer { wgc::command::ImageCopyBuffer { buffer: view.buffer.id.into(), layout: view.layout, } } -fn map_texture_copy_view(view: crate::ImageCopyTexture) -> wgc::command::ImageCopyTexture { +fn map_texture_copy_view(view: crate::ImageCopyTexture<'_>) -> wgc::command::ImageCopyTexture { wgc::command::ImageCopyTexture { texture: view.texture.id.into(), mip_level: view.mip_level, @@ -380,7 +325,7 @@ fn map_texture_copy_view(view: crate::ImageCopyTexture) -> wgc::command::ImageCo allow(unused) )] fn map_texture_tagged_copy_view( - view: crate::ImageCopyTextureTagged, + view: crate::ImageCopyTextureTagged<'_>, ) -> wgc::command::ImageCopyTextureTagged { wgc::command::ImageCopyTextureTagged { texture: view.texture.id.into(), @@ -392,6 +337,13 @@ fn map_texture_tagged_copy_view( } } +fn map_store_op(op: StoreOp) -> wgc::command::StoreOp { + match op { + StoreOp::Store => wgc::command::StoreOp::Store, + StoreOp::Discard => wgc::command::StoreOp::Discard, + } +} + fn map_pass_channel( ops: Option<&Operations>, ) -> wgc::command::PassChannel { @@ -401,11 +353,7 @@ fn map_pass_channel( store, }) => wgc::command::PassChannel { load_op: wgc::command::LoadOp::Clear, - store_op: if store { - wgc::command::StoreOp::Store - } else { - wgc::command::StoreOp::Discard - }, + store_op: map_store_op(store), clear_value, read_only: false, }, @@ -414,11 +362,7 @@ fn map_pass_channel( store, }) => wgc::command::PassChannel { load_op: wgc::command::LoadOp::Load, - store_op: if store { - wgc::command::StoreOp::Store - } else { - wgc::command::StoreOp::Discard - }, + store_op: map_store_op(store), clear_value: V::default(), read_only: false, }, @@ -511,7 +455,7 @@ pub struct Tlas { // error_sink: ErrorSink, } -impl crate::Context for Context { +impl crate::Context for ContextWgpuCore { type AdapterId = wgc::id::AdapterId; type AdapterData = (); type DeviceId = wgc::id::DeviceId; @@ -589,27 +533,54 @@ impl crate::Context for Context { )) } - fn instance_create_surface( + unsafe fn instance_create_surface( &self, - display_handle: raw_window_handle::RawDisplayHandle, - window_handle: raw_window_handle::RawWindowHandle, + target: SurfaceTargetUnsafe, ) -> Result<(Self::SurfaceId, Self::SurfaceData), crate::CreateSurfaceError> { - let id = self - .0 - .instance_create_surface(display_handle, window_handle, ()); + let id = match target { + SurfaceTargetUnsafe::RawHandle { + raw_display_handle, + raw_window_handle, + } => unsafe { + self.0 + .instance_create_surface(raw_display_handle, raw_window_handle, ())? + }, + + #[cfg(metal)] + SurfaceTargetUnsafe::CoreAnimationLayer(layer) => unsafe { + self.0.instance_create_surface_metal(layer, ()) + }, + + #[cfg(dx12)] + SurfaceTargetUnsafe::CompositionVisual(visual) => unsafe { + self.0.instance_create_surface_from_visual(visual, ()) + }, + + #[cfg(dx12)] + SurfaceTargetUnsafe::SurfaceHandle(surface_handle) => unsafe { + self.0 + .instance_create_surface_from_surface_handle(surface_handle, ()) + }, + + #[cfg(dx12)] + SurfaceTargetUnsafe::SwapChainPanel(swap_chain_panel) => unsafe { + self.0 + .instance_create_surface_from_swap_chain_panel(swap_chain_panel, ()) + }, + }; Ok(( id, Surface { id, - configured_device: Mutex::new(None), + configured_device: Mutex::default(), }, )) } fn instance_request_adapter( &self, - options: &crate::RequestAdapterOptions, + options: &crate::RequestAdapterOptions<'_, '_>, ) -> Self::RequestAdapterFuture { let id = self.0.request_adapter( &wgc::instance::RequestAdapterOptions { @@ -626,28 +597,28 @@ impl crate::Context for Context { &self, adapter: &Self::AdapterId, _adapter_data: &Self::AdapterData, - desc: &crate::DeviceDescriptor, + desc: &crate::DeviceDescriptor<'_>, trace_dir: Option<&std::path::Path>, ) -> Self::RequestDeviceFuture { let global = &self.0; - let (device_id, error) = wgc::gfx_select!(*adapter => global.adapter_request_device( + let (device_id, queue_id, error) = wgc::gfx_select!(*adapter => global.adapter_request_device( *adapter, &desc.map_label(|l| l.map(Borrowed)), trace_dir, + (), () )); if let Some(err) = error { - log::error!("Error in Adapter::request_device: {}", err); - return ready(Err(crate::RequestDeviceError)); + return ready(Err(err.into())); } let error_sink = Arc::new(Mutex::new(ErrorSinkRaw::new())); let device = Device { id: device_id, error_sink: error_sink.clone(), - features: desc.features, + features: desc.required_features, }; let queue = Queue { - id: device_id, + id: queue_id, error_sink, }; ready(Ok((device_id, device, device_id, queue))) @@ -890,7 +861,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: ShaderModuleDescriptor, + desc: ShaderModuleDescriptor<'_>, shader_bound_checks: wgt::ShaderBoundChecks, ) -> (Self::ShaderModuleId, Self::ShaderModuleData) { let global = &self.0; @@ -952,7 +923,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &ShaderModuleDescriptorSpirV, + desc: &ShaderModuleDescriptorSpirV<'_>, ) -> (Self::ShaderModuleId, Self::ShaderModuleData) { let global = &self.0; let descriptor = wgc::pipeline::ShaderModuleDescriptor { @@ -980,7 +951,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &BindGroupLayoutDescriptor, + desc: &BindGroupLayoutDescriptor<'_>, ) -> (Self::BindGroupLayoutId, Self::BindGroupLayoutData) { let global = &self.0; let descriptor = wgc::binding_model::BindGroupLayoutDescriptor { @@ -1005,7 +976,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &BindGroupDescriptor, + desc: &BindGroupDescriptor<'_>, ) -> (Self::BindGroupId, Self::BindGroupData) { use wgc::binding_model as bm; @@ -1123,7 +1094,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &PipelineLayoutDescriptor, + desc: &PipelineLayoutDescriptor<'_>, ) -> (Self::PipelineLayoutId, Self::PipelineLayoutData) { // Limit is always less or equal to hal::MAX_BIND_GROUPS, so this is always right // Guards following ArrayVec @@ -1166,7 +1137,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &RenderPipelineDescriptor, + desc: &RenderPipelineDescriptor<'_>, ) -> (Self::RenderPipelineId, Self::RenderPipelineData) { use wgc::pipeline as pipe; @@ -1221,7 +1192,7 @@ impl crate::Context for Context { if let Some(cause) = error { if let wgc::pipeline::CreateRenderPipelineError::Internal { stage, ref error } = cause { log::error!("Shader translation error for stage {:?}: {}", stage, error); - log::error!("Please report it to https://github.com/gfx-rs/naga"); + log::error!("Please report it to https://github.com/gfx-rs/wgpu"); } self.handle_error( &device_data.error_sink, @@ -1237,7 +1208,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &ComputePipelineDescriptor, + desc: &ComputePipelineDescriptor<'_>, ) -> (Self::ComputePipelineId, Self::ComputePipelineData) { use wgc::pipeline as pipe; @@ -1266,12 +1237,12 @@ impl crate::Context for Context { )); if let Some(cause) = error { if let wgc::pipeline::CreateComputePipelineError::Internal(ref error) = cause { - log::warn!( + log::error!( "Shader translation error for stage {:?}: {}", wgt::ShaderStages::COMPUTE, error ); - log::warn!("Please report it to https://github.com/gfx-rs/naga"); + log::error!("Please report it to https://github.com/gfx-rs/wgpu"); } self.handle_error( &device_data.error_sink, @@ -1315,7 +1286,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &TextureDescriptor, + desc: &TextureDescriptor<'_>, ) -> (Self::TextureId, Self::TextureData) { let wgt_desc = desc.map_label_and_view_formats(|l| l.map(Borrowed), |v| v.to_vec()); let global = &self.0; @@ -1345,7 +1316,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &SamplerDescriptor, + desc: &SamplerDescriptor<'_>, ) -> (Self::SamplerId, Self::SamplerData) { let descriptor = wgc::resource::SamplerDescriptor { label: desc.label.map(Borrowed), @@ -1385,7 +1356,7 @@ impl crate::Context for Context { &self, device: &Self::DeviceId, device_data: &Self::DeviceData, - desc: &wgt::QuerySetDescriptor(hal_instance) + crate::backend::ContextWgpuCore::from_hal_instance::(hal_instance) }), } } @@ -1782,19 +1835,13 @@ impl Instance { /// - The raw instance handle returned must not be manually destroyed. /// /// [`Instance`]: hal::Api::Instance - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] pub unsafe fn as_hal(&self) -> Option<&A::Instance> { - unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .instance_as_hal::() - } + self.context + .as_any() + // If we don't have a wgpu-core instance, we don't have a hal instance either. + .downcast_ref::() + .and_then(|ctx| unsafe { ctx.instance_as_hal::() }) } /// Create an new instance of wgpu from a wgpu-core instance. @@ -1806,42 +1853,40 @@ impl Instance { /// # Safety /// /// Refer to the creation of wgpu-core Instance. - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] pub unsafe fn from_core(core_instance: wgc::instance::Instance) -> Self { Self { context: Arc::new(unsafe { - crate::backend::Context::from_core_instance(core_instance) + crate::backend::ContextWgpuCore::from_core_instance(core_instance) }), } } /// Retrieves all available [`Adapter`]s that match the given [`Backends`]. /// + /// Always returns an empty vector if the instance decided upon creation to + /// target WebGPU since adapter creation is always async on WebGPU. + /// /// # Arguments /// /// - `backends` - Backends from which to enumerate adapters. - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] - pub fn enumerate_adapters(&self, backends: Backends) -> impl ExactSizeIterator { + #[cfg(wgpu_core)] + pub fn enumerate_adapters(&self, backends: Backends) -> Vec { let context = Arc::clone(&self.context); self.context .as_any() - .downcast_ref::() - .unwrap() - .enumerate_adapters(backends) - .into_iter() - .map(move |id| crate::Adapter { - context: Arc::clone(&context), - id: ObjectId::from(id), - data: Box::new(()), + .downcast_ref::() + .map(|ctx| { + ctx.enumerate_adapters(backends) + .into_iter() + .map(move |id| crate::Adapter { + context: Arc::clone(&context), + id: ObjectId::from(id), + data: Box::new(()), + }) + .collect() }) + .unwrap_or_default() } /// Retrieves an [`Adapter`] which matches the given [`RequestAdapterOptions`]. @@ -1851,7 +1896,7 @@ impl Instance { /// If no adapters are found that suffice all the "hard" options, `None` is returned. pub fn request_adapter( &self, - options: &RequestAdapterOptions, + options: &RequestAdapterOptions<'_, '_>, ) -> impl Future> + WasmNotSend { let context = Arc::clone(&self.context); let adapter = self.context.instance_request_adapter(options); @@ -1867,11 +1912,7 @@ impl Instance { /// # Safety /// /// `hal_adapter` must be created from this instance internal handle. - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] pub unsafe fn create_adapter_from_hal( &self, hal_adapter: hal::ExposedAdapter, @@ -1880,7 +1921,7 @@ impl Instance { let id = unsafe { context .as_any() - .downcast_ref::() + .downcast_ref::() .unwrap() .create_adapter_from_hal(hal_adapter) .into() @@ -1892,186 +1933,97 @@ impl Instance { } } - /// Creates a surface from a raw window handle. - /// - /// If the specified display and window handle are not supported by any of the backends, then the surface - /// will not be supported by any adapters. + /// Creates a new surface targeting a given window/canvas/surface/etc.. /// - /// # Safety - /// - /// - `raw_window_handle` must be a valid object to create a surface upon. - /// - `raw_window_handle` must remain valid until after the returned [`Surface`] is - /// dropped. - /// - /// # Errors - /// - /// - On WebGL2: Will return an error if the browser does not support WebGL2, - /// or declines to provide GPU access (such as due to a resource shortage). - /// - /// # Panics + /// See [`SurfaceTarget`] for what targets are supported. + /// See [`Instance::create_surface`] for surface creation with unsafe target variants. /// - /// - On macOS/Metal: will panic if not called on the main thread. - /// - On web: will panic if the `raw_window_handle` does not properly refer to a - /// canvas element. - pub unsafe fn create_surface< - W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle, - >( + /// Most commonly used are window handles (or provider of windows handles) + /// which can be passed directly as they're automatically converted to [`SurfaceTarget`]. + pub fn create_surface<'window>( &self, - window: &W, - ) -> Result { - let (id, data) = DynContext::instance_create_surface( - &*self.context, - raw_window_handle::HasRawDisplayHandle::raw_display_handle(window), - raw_window_handle::HasRawWindowHandle::raw_window_handle(window), - )?; - Ok(Surface { - context: Arc::clone(&self.context), - id, - data, - config: Mutex::new(None), - }) - } + target: impl Into>, + ) -> Result, CreateSurfaceError> { + // Handle origin (i.e. window) to optionally take ownership of to make the surface outlast the window. + let handle_origin; + + let target = target.into(); + let mut surface = match target { + SurfaceTarget::Window(window) => unsafe { + let surface = self.create_surface_unsafe( + SurfaceTargetUnsafe::from_window(&window).map_err(|e| CreateSurfaceError { + inner: CreateSurfaceErrorKind::RawHandle(e), + })?, + ); + handle_origin = Some(window); + + surface + }?, + + #[cfg(any(webgpu, webgl))] + SurfaceTarget::Canvas(canvas) => { + handle_origin = None; + + let value: &wasm_bindgen::JsValue = &canvas; + let obj = std::ptr::NonNull::from(value).cast(); + let raw_window_handle = raw_window_handle::WebCanvasWindowHandle::new(obj).into(); + let raw_display_handle = raw_window_handle::WebDisplayHandle::new().into(); + + // Note that we need to call this while we still have `value` around. + // This is safe without storing canvas to `handle_origin` since the surface will create a copy internally. + unsafe { + self.create_surface_unsafe(SurfaceTargetUnsafe::RawHandle { + raw_display_handle, + raw_window_handle, + }) + }? + } - /// Creates a surface from `CoreAnimationLayer`. - /// - /// # Safety - /// - /// - layer must be a valid object to create a surface upon. - #[cfg(any(target_os = "ios", target_os = "macos"))] - pub unsafe fn create_surface_from_core_animation_layer( - &self, - layer: *mut std::ffi::c_void, - ) -> Surface { - let surface = unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .create_surface_from_core_animation_layer(layer) + #[cfg(any(webgpu, webgl))] + SurfaceTarget::OffscreenCanvas(canvas) => { + handle_origin = None; + + let value: &wasm_bindgen::JsValue = &canvas; + let obj = std::ptr::NonNull::from(value).cast(); + let raw_window_handle = + raw_window_handle::WebOffscreenCanvasWindowHandle::new(obj).into(); + let raw_display_handle = raw_window_handle::WebDisplayHandle::new().into(); + + // Note that we need to call this while we still have `value` around. + // This is safe without storing canvas to `handle_origin` since the surface will create a copy internally. + unsafe { + self.create_surface_unsafe(SurfaceTargetUnsafe::RawHandle { + raw_display_handle, + raw_window_handle, + }) + }? + } }; - Surface { - context: Arc::clone(&self.context), - id: ObjectId::from(surface.id()), - data: Box::new(surface), - config: Mutex::new(None), - } - } - /// Creates a surface from `IDCompositionVisual`. - /// - /// # Safety - /// - /// - visual must be a valid IDCompositionVisual to create a surface upon. - #[cfg(target_os = "windows")] - pub unsafe fn create_surface_from_visual(&self, visual: *mut std::ffi::c_void) -> Surface { - let surface = unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .create_surface_from_visual(visual) - }; - Surface { - context: Arc::clone(&self.context), - id: ObjectId::from(surface.id()), - data: Box::new(surface), - config: Mutex::new(None), - } - } + surface._surface = handle_origin; - /// Creates a surface from `SurfaceHandle`. - /// - /// # Safety - /// - /// - surface_handle must be a valid SurfaceHandle to create a surface upon. - #[cfg(target_os = "windows")] - pub unsafe fn create_surface_from_surface_handle( - &self, - surface_handle: *mut std::ffi::c_void, - ) -> Surface { - let surface = unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .create_surface_from_surface_handle(surface_handle) - }; - Surface { - context: Arc::clone(&self.context), - id: ObjectId::from(surface.id()), - data: Box::new(surface), - config: Mutex::new(None), - } + Ok(surface) } - /// Creates a surface from a `web_sys::HtmlCanvasElement`. + /// Creates a new surface targeting a given window/canvas/surface/etc. using an unsafe target. /// - /// The `canvas` argument must be a valid `` element to - /// create a surface upon. + /// See [`SurfaceTargetUnsafe`] for what targets are supported. + /// See [`Instance::create_surface`] for surface creation with safe target variants. /// - /// # Errors - /// - /// - On WebGL2: Will return an error if the browser does not support WebGL2, - /// or declines to provide GPU access (such as due to a resource shortage). - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - pub fn create_surface_from_canvas( - &self, - canvas: web_sys::HtmlCanvasElement, - ) -> Result { - let surface = self - .context - .as_any() - .downcast_ref::() - .unwrap() - .instance_create_surface_from_canvas(canvas)?; - - // TODO: This is ugly, a way to create things from a native context needs to be made nicer. - Ok(Surface { - context: Arc::clone(&self.context), - #[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))] - id: ObjectId::from(surface.id()), - #[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))] - data: Box::new(surface), - #[cfg(all(target_arch = "wasm32", not(feature = "webgl")))] - id: ObjectId::UNUSED, - #[cfg(all(target_arch = "wasm32", not(feature = "webgl")))] - data: Box::new(surface.1), - config: Mutex::new(None), - }) - } - - /// Creates a surface from a `web_sys::OffscreenCanvas`. - /// - /// The `canvas` argument must be a valid `OffscreenCanvas` object - /// to create a surface upon. - /// - /// # Errors + /// # Safety /// - /// - On WebGL2: Will return an error if the browser does not support WebGL2, - /// or declines to provide GPU access (such as due to a resource shortage). - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - pub fn create_surface_from_offscreen_canvas( + /// - See respective [`SurfaceTargetUnsafe`] variants for safety requirements. + pub unsafe fn create_surface_unsafe<'window>( &self, - canvas: web_sys::OffscreenCanvas, - ) -> Result { - let surface = self - .context - .as_any() - .downcast_ref::() - .unwrap() - .instance_create_surface_from_offscreen_canvas(canvas)?; + target: SurfaceTargetUnsafe, + ) -> Result, CreateSurfaceError> { + let (id, data) = unsafe { self.context.instance_create_surface(target) }?; - // TODO: This is ugly, a way to create things from a native context needs to be made nicer. Ok(Surface { context: Arc::clone(&self.context), - #[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))] - id: ObjectId::from(surface.id()), - #[cfg(any(not(target_arch = "wasm32"), feature = "webgl"))] - data: Box::new(surface), - #[cfg(all(target_arch = "wasm32", not(feature = "webgl")))] - id: ObjectId::UNUSED, - #[cfg(all(target_arch = "wasm32", not(feature = "webgl")))] - data: Box::new(surface.1), + _surface: None, + id, + data, config: Mutex::new(None), }) } @@ -2097,17 +2049,15 @@ impl Instance { } /// Generates memory report. - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] - pub fn generate_report(&self) -> wgc::global::GlobalReport { + /// + /// Returns `None` if the feature is not supported by the backend + /// which happens only when WebGPU is pre-selected by the instance creation. + #[cfg(wgpu_core)] + pub fn generate_report(&self) -> Option { self.context .as_any() - .downcast_ref::() - .unwrap() - .generate_report() + .downcast_ref::() + .map(|ctx| ctx.generate_report()) } } @@ -2130,7 +2080,7 @@ impl Adapter { /// - Adapter does not support all features wgpu requires to safely operate. pub fn request_device( &self, - desc: &DeviceDescriptor, + desc: &DeviceDescriptor<'_>, trace_path: Option<&std::path::Path>, ) -> impl Future> + WasmNotSend { let context = Arc::clone(&self.context); @@ -2172,22 +2122,20 @@ impl Adapter { /// /// - `hal_device` must be created from this adapter internal handle. /// - `desc.features` must be a subset of `hal_device` features. - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] pub unsafe fn create_device_from_hal( &self, hal_device: hal::OpenDevice, - desc: &DeviceDescriptor, + desc: &DeviceDescriptor<'_>, trace_path: Option<&std::path::Path>, ) -> Result<(Device, Queue), RequestDeviceError> { let context = Arc::clone(&self.context); unsafe { self.context .as_any() - .downcast_ref::() + .downcast_ref::() + // Part of the safety requirements is that the device was generated from the same adapter. + // Therefore, unwrap is fine here since only WgpuCoreContext based adapters have the ability to create hal devices. .unwrap() .create_device_from_hal(&self.id.into(), hal_device, desc, trace_path) } @@ -2226,26 +2174,24 @@ impl Adapter { /// - The raw handle passed to the callback must not be manually destroyed. /// /// [`A::Adapter`]: hal::Api::Adapter - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] pub unsafe fn as_hal) -> R, R>( &self, hal_adapter_callback: F, ) -> R { - unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .adapter_as_hal::(self.id.into(), hal_adapter_callback) + if let Some(ctx) = self + .context + .as_any() + .downcast_ref::() + { + unsafe { ctx.adapter_as_hal::(self.id.into(), hal_adapter_callback) } + } else { + hal_adapter_callback(None) } } /// Returns whether this adapter may present to the passed surface. - pub fn is_surface_supported(&self, surface: &Surface) -> bool { + pub fn is_surface_supported(&self, surface: &Surface<'_>) -> bool { DynContext::adapter_is_surface_supported( &*self.context, &self.id, @@ -2255,18 +2201,12 @@ impl Adapter { ) } - /// List all features that are supported with this adapter. - /// - /// Features must be explicitly requested in [`Adapter::request_device`] in order - /// to use them. + /// The features which can be used to create devices on this adapter. pub fn features(&self) -> Features { DynContext::adapter_features(&*self.context, &self.id, self.data.as_ref()) } - /// List the "best" limits that are supported by this adapter. - /// - /// Limits must be explicitly requested in [`Adapter::request_device`] to set - /// the values that you are allowed to use. + /// The best limits which can be used to create devices on this adapter. pub fn limits(&self) -> Limits { DynContext::adapter_limits(&*self.context, &self.id, self.data.as_ref()) } @@ -2321,7 +2261,7 @@ impl Adapter { } impl Device { - /// Check for resource cleanups and mapping callbacks. + /// Check for resource cleanups and mapping callbacks. Will block if [`Maintain::Wait`] is passed. /// /// Return `true` if the queue is empty, or `false` if there are more queue /// submissions still in flight. (Note that, unless access to the [`Queue`] is @@ -2329,27 +2269,27 @@ impl Device { /// the caller receives it. `Queue`s can be shared between threads, so /// other threads could submit new work at any time.) /// - /// On the web, this is a no-op. `Device`s are automatically polled. - pub fn poll(&self, maintain: Maintain) -> bool { + /// When running on WebGPU, this is a no-op. `Device`s are automatically polled. + pub fn poll(&self, maintain: Maintain) -> MaintainResult { DynContext::device_poll(&*self.context, &self.id, self.data.as_ref(), maintain) } - /// List all features that may be used with this device. + /// The features which can be used on this device. /// - /// Functions may panic if you use unsupported features. + /// No additional features can be used, even if the underlying adapter can support them. pub fn features(&self) -> Features { DynContext::device_features(&*self.context, &self.id, self.data.as_ref()) } - /// List all limits that were requested of this device. + /// The limits which can be used on this device. /// - /// If any of these limits are exceeded, functions may panic. + /// No better limits can be used, even if the underlying adapter can support them. pub fn limits(&self) -> Limits { DynContext::device_limits(&*self.context, &self.id, self.data.as_ref()) } /// Creates a shader module from either SPIR-V or WGSL source code. - pub fn create_shader_module(&self, desc: ShaderModuleDescriptor) -> ShaderModule { + pub fn create_shader_module(&self, desc: ShaderModuleDescriptor<'_>) -> ShaderModule { let (id, data) = DynContext::device_create_shader_module( &*self.context, &self.id, @@ -2376,7 +2316,7 @@ impl Device { /// This has no effect on web. pub unsafe fn create_shader_module_unchecked( &self, - desc: ShaderModuleDescriptor, + desc: ShaderModuleDescriptor<'_>, ) -> ShaderModule { let (id, data) = DynContext::device_create_shader_module( &*self.context, @@ -2402,7 +2342,7 @@ impl Device { /// See also [`include_spirv_raw!`] and [`util::make_spirv_raw`]. pub unsafe fn create_shader_module_spirv( &self, - desc: &ShaderModuleDescriptorSpirV, + desc: &ShaderModuleDescriptorSpirV<'_>, ) -> ShaderModule { let (id, data) = unsafe { DynContext::device_create_shader_module_spirv( @@ -2420,7 +2360,7 @@ impl Device { } /// Creates an empty [`CommandEncoder`]. - pub fn create_command_encoder(&self, desc: &CommandEncoderDescriptor) -> CommandEncoder { + pub fn create_command_encoder(&self, desc: &CommandEncoderDescriptor<'_>) -> CommandEncoder { let (id, data) = DynContext::device_create_command_encoder( &*self.context, &self.id, @@ -2437,8 +2377,8 @@ impl Device { /// Creates an empty [`RenderBundleEncoder`]. pub fn create_render_bundle_encoder( &self, - desc: &RenderBundleEncoderDescriptor, - ) -> RenderBundleEncoder { + desc: &RenderBundleEncoderDescriptor<'_>, + ) -> RenderBundleEncoder<'_> { let (id, data) = DynContext::device_create_render_bundle_encoder( &*self.context, &self.id, @@ -2455,7 +2395,7 @@ impl Device { } /// Creates a new [`BindGroup`]. - pub fn create_bind_group(&self, desc: &BindGroupDescriptor) -> BindGroup { + pub fn create_bind_group(&self, desc: &BindGroupDescriptor<'_>) -> BindGroup { let (id, data) = DynContext::device_create_bind_group( &*self.context, &self.id, @@ -2470,7 +2410,10 @@ impl Device { } /// Creates a [`BindGroupLayout`]. - pub fn create_bind_group_layout(&self, desc: &BindGroupLayoutDescriptor) -> BindGroupLayout { + pub fn create_bind_group_layout( + &self, + desc: &BindGroupLayoutDescriptor<'_>, + ) -> BindGroupLayout { let (id, data) = DynContext::device_create_bind_group_layout( &*self.context, &self.id, @@ -2485,7 +2428,7 @@ impl Device { } /// Creates a [`PipelineLayout`]. - pub fn create_pipeline_layout(&self, desc: &PipelineLayoutDescriptor) -> PipelineLayout { + pub fn create_pipeline_layout(&self, desc: &PipelineLayoutDescriptor<'_>) -> PipelineLayout { let (id, data) = DynContext::device_create_pipeline_layout( &*self.context, &self.id, @@ -2500,7 +2443,7 @@ impl Device { } /// Creates a [`RenderPipeline`]. - pub fn create_render_pipeline(&self, desc: &RenderPipelineDescriptor) -> RenderPipeline { + pub fn create_render_pipeline(&self, desc: &RenderPipelineDescriptor<'_>) -> RenderPipeline { let (id, data) = DynContext::device_create_render_pipeline( &*self.context, &self.id, @@ -2515,7 +2458,7 @@ impl Device { } /// Creates a [`ComputePipeline`]. - pub fn create_compute_pipeline(&self, desc: &ComputePipelineDescriptor) -> ComputePipeline { + pub fn create_compute_pipeline(&self, desc: &ComputePipelineDescriptor<'_>) -> ComputePipeline { let (id, data) = DynContext::device_create_compute_pipeline( &*self.context, &self.id, @@ -2530,7 +2473,7 @@ impl Device { } /// Creates a [`Buffer`]. - pub fn create_buffer(&self, desc: &BufferDescriptor) -> Buffer { + pub fn create_buffer(&self, desc: &BufferDescriptor<'_>) -> Buffer { let mut map_context = MapContext::new(desc.size); if desc.mapped_at_creation { map_context.initial_range = 0..desc.size; @@ -2552,7 +2495,7 @@ impl Device { /// Creates a new [`Texture`]. /// /// `desc` specifies the general format of the texture. - pub fn create_texture(&self, desc: &TextureDescriptor) -> Texture { + pub fn create_texture(&self, desc: &TextureDescriptor<'_>) -> Texture { let (id, data) = DynContext::device_create_texture(&*self.context, &self.id, self.data.as_ref(), desc); Texture { @@ -2575,20 +2518,18 @@ impl Device { /// - `hal_texture` must be created from this device internal handle /// - `hal_texture` must be created respecting `desc` /// - `hal_texture` must be initialized - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] pub unsafe fn create_texture_from_hal( &self, hal_texture: A::Texture, - desc: &TextureDescriptor, + desc: &TextureDescriptor<'_>, ) -> Texture { let texture = unsafe { self.context .as_any() - .downcast_ref::() + .downcast_ref::() + // Part of the safety requirements is that the texture was generated from the same hal device. + // Therefore, unwrap is fine here since only WgpuCoreContext has the ability to create hal textures. .unwrap() .create_texture_from_hal::( hal_texture, @@ -2616,15 +2557,11 @@ impl Device { /// - `hal_buffer` must be created from this device internal handle /// - `hal_buffer` must be created respecting `desc` /// - `hal_buffer` must be initialized - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] pub unsafe fn create_buffer_from_hal( &self, hal_buffer: A::Buffer, - desc: &BufferDescriptor, + desc: &BufferDescriptor<'_>, ) -> Buffer { let mut map_context = MapContext::new(desc.size); if desc.mapped_at_creation { @@ -2634,7 +2571,9 @@ impl Device { let (id, buffer) = unsafe { self.context .as_any() - .downcast_ref::() + .downcast_ref::() + // Part of the safety requirements is that the buffer was generated from the same hal device. + // Therefore, unwrap is fine here since only WgpuCoreContext has the ability to create hal buffers. .unwrap() .create_buffer_from_hal::( hal_buffer, @@ -2656,7 +2595,7 @@ impl Device { /// Creates a new [`Sampler`]. /// /// `desc` specifies the behavior of the sampler. - pub fn create_sampler(&self, desc: &SamplerDescriptor) -> Sampler { + pub fn create_sampler(&self, desc: &SamplerDescriptor<'_>) -> Sampler { let (id, data) = DynContext::device_create_sampler(&*self.context, &self.id, self.data.as_ref(), desc); Sampler { @@ -2667,7 +2606,7 @@ impl Device { } /// Creates a new [`QuerySet`]. - pub fn create_query_set(&self, desc: &QuerySetDescriptor) -> QuerySet { + pub fn create_query_set(&self, desc: &QuerySetDescriptor<'_>) -> QuerySet { let (id, data) = DynContext::device_create_query_set(&*self.context, &self.id, self.data.as_ref(), desc); QuerySet { @@ -2724,25 +2663,38 @@ impl Device { /// - The raw handle passed to the callback must not be manually destroyed. /// /// [`A::Device`]: hal::Api::Device - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] pub unsafe fn as_hal) -> R, R>( &self, hal_device_callback: F, - ) -> R { - unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .device_as_hal::( + ) -> Option { + self.context + .as_any() + .downcast_ref::() + .map(|ctx| unsafe { + ctx.device_as_hal::( self.data.as_ref().downcast_ref().unwrap(), hal_device_callback, ) - } + }) + } + + /// Destroy this device. + pub fn destroy(&self) { + DynContext::device_destroy(&*self.context, &self.id, self.data.as_ref()) + } + + /// Set a DeviceLostCallback on this device. + pub fn set_device_lost_callback( + &self, + callback: impl Fn(DeviceLostReason, String) + Send + 'static, + ) { + DynContext::device_set_device_lost_callback( + &*self.context, + &self.id, + self.data.as_ref(), + Box::new(callback), + ) } } @@ -2754,18 +2706,70 @@ impl Drop for Device { } } -/// Requesting a device failed. -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct RequestDeviceError; +/// Requesting a device from an [`Adapter`] failed. +#[derive(Clone, Debug)] +pub struct RequestDeviceError { + inner: RequestDeviceErrorKind, +} +#[derive(Clone, Debug)] +enum RequestDeviceErrorKind { + /// Error from [`wgpu_core`]. + // must match dependency cfg + #[cfg(wgpu_core)] + Core(wgc::instance::RequestDeviceError), + + /// Error from web API that was called by `wgpu` to request a device. + /// + /// (This is currently never used by the webgl backend, but it could be.) + #[cfg(webgpu)] + WebGpu(wasm_bindgen::JsValue), +} + +#[cfg(send_sync)] +unsafe impl Send for RequestDeviceErrorKind {} +#[cfg(send_sync)] +unsafe impl Sync for RequestDeviceErrorKind {} + +#[cfg(send_sync)] static_assertions::assert_impl_all!(RequestDeviceError: Send, Sync); impl fmt::Display for RequestDeviceError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Requesting a device failed") + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.inner { + #[cfg(wgpu_core)] + RequestDeviceErrorKind::Core(error) => error.fmt(_f), + #[cfg(webgpu)] + RequestDeviceErrorKind::WebGpu(error_js_value) => { + // wasm-bindgen provides a reasonable error stringification via `Debug` impl + write!(_f, "{error_js_value:?}") + } + #[cfg(not(any(webgpu, wgpu_core)))] + _ => unimplemented!("unknown `RequestDeviceErrorKind`"), + } + } +} + +impl error::Error for RequestDeviceError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match &self.inner { + #[cfg(wgpu_core)] + RequestDeviceErrorKind::Core(error) => error.source(), + #[cfg(webgpu)] + RequestDeviceErrorKind::WebGpu(_) => None, + #[cfg(not(any(webgpu, wgpu_core)))] + _ => unimplemented!("unknown `RequestDeviceErrorKind`"), + } } } -impl error::Error for RequestDeviceError {} +#[cfg(wgpu_core)] +impl From for RequestDeviceError { + fn from(error: wgc::instance::RequestDeviceError) -> Self { + Self { + inner: RequestDeviceErrorKind::Core(error), + } + } +} /// [`Instance::create_surface()`] or a related function failed. #[derive(Clone, Debug)] @@ -2776,30 +2780,26 @@ pub struct CreateSurfaceError { #[derive(Clone, Debug)] enum CreateSurfaceErrorKind { /// Error from [`wgpu_hal`]. - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] - // must match dependency cfg + #[cfg(wgpu_core)] Hal(hal::InstanceError), /// Error from WebGPU surface creation. #[allow(dead_code)] // may be unused depending on target and features Web(String), + + /// Error when trying to get a [`DisplayHandle`] or a [`WindowHandle`] from + /// `raw_window_handle`. + RawHandle(raw_window_handle::HandleError), } static_assertions::assert_impl_all!(CreateSurfaceError: Send, Sync); impl fmt::Display for CreateSurfaceError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.inner { - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] CreateSurfaceErrorKind::Hal(e) => e.fmt(f), CreateSurfaceErrorKind::Web(e) => e.fmt(f), + CreateSurfaceErrorKind::RawHandle(e) => e.fmt(f), } } } @@ -2807,22 +2807,15 @@ impl fmt::Display for CreateSurfaceError { impl error::Error for CreateSurfaceError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match &self.inner { - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] CreateSurfaceErrorKind::Hal(e) => e.source(), CreateSurfaceErrorKind::Web(_) => None, + CreateSurfaceErrorKind::RawHandle(e) => e.source(), } } } -#[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" -))] +#[cfg(wgpu_core)] impl From for CreateSurfaceError { fn from(e: hal::InstanceError) -> Self { Self { @@ -2953,12 +2946,12 @@ impl Drop for BufferViewMut<'_> { impl Buffer { /// Return the binding view of the entire buffer. - pub fn as_entire_binding(&self) -> BindingResource { + pub fn as_entire_binding(&self) -> BindingResource<'_> { BindingResource::Buffer(self.as_entire_buffer_binding()) } /// Return the binding view of the entire buffer. - pub fn as_entire_buffer_binding(&self) -> BufferBinding { + pub fn as_entire_buffer_binding(&self) -> BufferBinding<'_> { BufferBinding { buffer: self, offset: 0, @@ -2968,7 +2961,7 @@ impl Buffer { /// Use only a portion of this Buffer for a given operation. Choosing a range with no end /// will use the rest of the buffer. Using a totally unbounded range will use the entire buffer. - pub fn slice>(&self, bounds: S) -> BufferSlice { + pub fn slice>(&self, bounds: S) -> BufferSlice<'_> { let (offset, size) = range_to_offset_size(bounds); BufferSlice { buffer: self, @@ -3055,23 +3048,24 @@ impl<'a> BufferSlice<'a> { } /// Synchronously and immediately map a buffer for reading. If the buffer is not immediately mappable - /// through [`BufferDescriptor::mapped_at_creation`] or [`BufferSlice::map_async`], will panic. + /// through [`BufferDescriptor::mapped_at_creation`] or [`BufferSlice::map_async`], will fail. /// - /// This is useful in wasm builds when you want to pass mapped data directly to js. Unlike `get_mapped_range` - /// which unconditionally copies mapped data into the wasm heap, this function directly hands you the - /// ArrayBuffer that we mapped the data into in js. - #[cfg(all( - target_arch = "wasm32", - not(any(target_os = "emscripten", feature = "webgl")) - ))] - pub fn get_mapped_range_as_array_buffer(&self) -> js_sys::ArrayBuffer { - let end = self.buffer.map_context.lock().add(self.offset, self.size); - DynContext::buffer_get_mapped_range_as_array_buffer( - &*self.buffer.context, - &self.buffer.id, - self.buffer.data.as_ref(), - self.offset..end, - ) + /// This is useful when targeting WebGPU and you want to pass mapped data directly to js. + /// Unlike `get_mapped_range` which unconditionally copies mapped data into the wasm heap, + /// this function directly hands you the ArrayBuffer that we mapped the data into in js. + /// + /// This is only available on WebGPU, on any other backends this will return `None`. + #[cfg(webgpu)] + pub fn get_mapped_range_as_array_buffer(&self) -> Option { + self.buffer + .context + .as_any() + .downcast_ref::() + .map(|ctx| { + let buffer_data = crate::context::downcast_ref(self.buffer.data.as_ref()); + let end = self.buffer.map_context.lock().add(self.offset, self.size); + ctx.buffer_get_mapped_range_as_array_buffer(buffer_data, self.offset..end) + }) } /// Synchronously and immediately map a buffer for writing. If the buffer is not immediately mappable @@ -3107,27 +3101,26 @@ impl Texture { /// # Safety /// /// - The raw handle obtained from the hal Texture must not be manually destroyed - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] + #[cfg(wgpu_core)] pub unsafe fn as_hal)>( &self, hal_texture_callback: F, ) { let texture = self.data.as_ref().downcast_ref().unwrap(); - unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .texture_as_hal::(texture, hal_texture_callback) + + if let Some(ctx) = self + .context + .as_any() + .downcast_ref::() + { + unsafe { ctx.texture_as_hal::(texture, hal_texture_callback) } + } else { + hal_texture_callback(None) } } /// Creates a view of this texture. - pub fn create_view(&self, desc: &TextureViewDescriptor) -> TextureView { + pub fn create_view(&self, desc: &TextureViewDescriptor<'_>) -> TextureView { let (id, data) = DynContext::texture_create_view(&*self.context, &self.id, self.data.as_ref(), desc); TextureView { @@ -3143,7 +3136,7 @@ impl Texture { } /// Make an `ImageCopyTexture` representing the whole texture. - pub fn as_image_copy(&self) -> ImageCopyTexture { + pub fn as_image_copy(&self) -> ImageCopyTexture<'_> { ImageCopyTexture { texture: self, mip_level: 0, @@ -3271,7 +3264,7 @@ impl CommandEncoder { /// Begins recording of a compute pass. /// /// This function returns a [`ComputePass`] object which records a single compute pass. - pub fn begin_compute_pass(&mut self, desc: &ComputePassDescriptor) -> ComputePass { + pub fn begin_compute_pass(&mut self, desc: &ComputePassDescriptor<'_>) -> ComputePass<'_> { let id = self.id.as_ref().unwrap(); let (id, data) = DynContext::command_encoder_begin_compute_pass( &*self.context, @@ -3318,8 +3311,8 @@ impl CommandEncoder { /// Copy data from a buffer to a texture. pub fn copy_buffer_to_texture( &mut self, - source: ImageCopyBuffer, - destination: ImageCopyTexture, + source: ImageCopyBuffer<'_>, + destination: ImageCopyTexture<'_>, copy_size: Extent3d, ) { DynContext::command_encoder_copy_buffer_to_texture( @@ -3335,8 +3328,8 @@ impl CommandEncoder { /// Copy data from a texture to a buffer. pub fn copy_texture_to_buffer( &mut self, - source: ImageCopyTexture, - destination: ImageCopyBuffer, + source: ImageCopyTexture<'_>, + destination: ImageCopyBuffer<'_>, copy_size: Extent3d, ) { DynContext::command_encoder_copy_texture_to_buffer( @@ -3358,8 +3351,8 @@ impl CommandEncoder { /// - Copy would overrun either texture pub fn copy_texture_to_texture( &mut self, - source: ImageCopyTexture, - destination: ImageCopyTexture, + source: ImageCopyTexture<'_>, + destination: ImageCopyTexture<'_>, copy_size: Extent3d, ) { DynContext::command_encoder_copy_texture_to_texture( @@ -3405,7 +3398,7 @@ impl CommandEncoder { &mut self, buffer: &Buffer, offset: BufferAddress, - size: Option, + size: Option, ) { DynContext::command_encoder_clear_buffer( &*self.context, @@ -3439,34 +3432,11 @@ impl CommandEncoder { let id = self.id.as_ref().unwrap(); DynContext::command_encoder_pop_debug_group(&*self.context, id, self.data.as_ref()); } -} - -/// [`Features::TIMESTAMP_QUERY`] must be enabled on the device in order to call these functions. -impl CommandEncoder { - /// Issue a timestamp command at this point in the queue. - /// The timestamp will be written to the specified query set, at the specified index. - /// - /// Must be multiplied by [`Queue::get_timestamp_period`] to get - /// the value in nanoseconds. Absolute values have no meaning, - /// but timestamps can be subtracted to get the time it takes - /// for a string of operations to complete. - pub fn write_timestamp(&mut self, query_set: &QuerySet, query_index: u32) { - DynContext::command_encoder_write_timestamp( - &*self.context, - self.id.as_ref().unwrap(), - self.data.as_mut(), - &query_set.id, - query_set.data.as_ref(), - query_index, - ) - } -} -/// [`Features::TIMESTAMP_QUERY`] or [`Features::PIPELINE_STATISTICS_QUERY`] must be enabled on the device in order to call these functions. -impl CommandEncoder { - /// Resolve a query set, writing the results into the supplied destination buffer. + /// Resolves a query set, writing the results into the supplied destination buffer. /// - /// Queries may be between 8 and 40 bytes each. See [`PipelineStatisticsTypes`] for more information. + /// Occlusion and timestamp queries are 8 bytes each (see [`crate::QUERY_SIZE`]). For pipeline statistics queries, + /// see [`PipelineStatisticsTypes`] for more information. pub fn resolve_query_set( &mut self, query_set: &QuerySet, @@ -3489,6 +3459,27 @@ impl CommandEncoder { } } +/// [`Features::TIMESTAMP_QUERY`] must be enabled on the device in order to call these functions. +impl CommandEncoder { + /// Issue a timestamp command at this point in the queue. + /// The timestamp will be written to the specified query set, at the specified index. + /// + /// Must be multiplied by [`Queue::get_timestamp_period`] to get + /// the value in nanoseconds. Absolute values have no meaning, + /// but timestamps can be subtracted to get the time it takes + /// for a string of operations to complete. + pub fn write_timestamp(&mut self, query_set: &QuerySet, query_index: u32) { + DynContext::command_encoder_write_timestamp( + &*self.context, + self.id.as_ref().unwrap(), + self.data.as_mut(), + &query_set.id, + query_set.data.as_ref(), + query_index, + ) + } +} + impl<'a> RenderPass<'a> { /// Sets the active bind group for a given bind group index. The bind group layout /// in the active pipeline when any `draw_*()` method is called must match the layout of @@ -3637,6 +3628,35 @@ impl<'a> RenderPass<'a> { ); } + /// Inserts debug marker. + pub fn insert_debug_marker(&mut self, label: &str) { + DynContext::render_pass_insert_debug_marker( + &*self.parent.context, + &mut self.id, + self.data.as_mut(), + label, + ); + } + + /// Start record commands and group it into debug marker group. + pub fn push_debug_group(&mut self, label: &str) { + DynContext::render_pass_push_debug_group( + &*self.parent.context, + &mut self.id, + self.data.as_mut(), + label, + ); + } + + /// Stops command recording and creates debug group. + pub fn pop_debug_group(&mut self) { + DynContext::render_pass_pop_debug_group( + &*self.parent.context, + &mut self.id, + self.data.as_mut(), + ); + } + /// Draws primitives from the active vertex buffer(s). /// /// The active vertex buffer(s) can be set with [`RenderPass::set_vertex_buffer`]. @@ -3668,35 +3688,6 @@ impl<'a> RenderPass<'a> { ) } - /// Inserts debug marker. - pub fn insert_debug_marker(&mut self, label: &str) { - DynContext::render_pass_insert_debug_marker( - &*self.parent.context, - &mut self.id, - self.data.as_mut(), - label, - ); - } - - /// Start record commands and group it into debug marker group. - pub fn push_debug_group(&mut self, label: &str) { - DynContext::render_pass_push_debug_group( - &*self.parent.context, - &mut self.id, - self.data.as_mut(), - label, - ); - } - - /// Stops command recording and creates debug group. - pub fn pop_debug_group(&mut self) { - DynContext::render_pass_pop_debug_group( - &*self.parent.context, - &mut self.id, - self.data.as_mut(), - ); - } - /// Draws indexed primitives using the active index buffer and the active vertex buffers. /// /// The active index buffer can be set with [`RenderPass::set_index_buffer`] @@ -3734,12 +3725,17 @@ impl<'a> RenderPass<'a> { /// Draws primitives from the active vertex buffer(s) based on the contents of the `indirect_buffer`. /// - /// The active vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. + /// This is like calling [`RenderPass::draw`] but the contents of the call are specified in the `indirect_buffer`. + /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndirect`](crate::util::DrawIndirect). + /// Indirect drawing has some caveats depending on the features available. We are not currently able to validate + /// these and issue an error. + /// - If [`Features::INDIRECT_FIRST_INSTANCE`] is not present on the adapter, + /// [`DrawIndirect::first_instance`](crate::util::DrawIndirectArgs::first_instance) will be ignored. + /// - If [`DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW`] is not present on the adapter, + /// any use of `@builtin(vertex_index)` or `@builtin(instance_index)` in the vertex shader will have different values. /// - /// This drawing command uses the current render state, as set by preceding `set_*()` methods. - /// It is not affected by changes to the state that are performed after it is called. + /// See details on the individual flags for more information. pub fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress) { DynContext::render_pass_draw_indirect( &*self.parent.context, @@ -3754,13 +3750,17 @@ impl<'a> RenderPass<'a> { /// Draws indexed primitives using the active index buffer and the active vertex buffers, /// based on the contents of the `indirect_buffer`. /// - /// The active index buffer can be set with [`RenderPass::set_index_buffer`], while the active - /// vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. + /// This is like calling [`RenderPass::draw_indexed`] but the contents of the call are specified in the `indirect_buffer`. + /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirect`](crate::util::DrawIndexedIndirect). + /// Indirect drawing has some caveats depending on the features available. We are not currently able to validate + /// these and issue an error. + /// - If [`Features::INDIRECT_FIRST_INSTANCE`] is not present on the adapter, + /// [`DrawIndexedIndirect::first_instance`](crate::util::DrawIndexedIndirectArgs::first_instance) will be ignored. + /// - If [`DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW`] is not present on the adapter, + /// any use of `@builtin(vertex_index)` or `@builtin(instance_index)` in the vertex shader will have different values. /// - /// This drawing command uses the current render state, as set by preceding `set_*()` methods. - /// It is not affected by changes to the state that are performed after it is called. + /// See details on the individual flags for more information. pub fn draw_indexed_indirect( &mut self, indirect_buffer: &'a Buffer, @@ -3781,19 +3781,16 @@ impl<'a> RenderPass<'a> { /// /// Commands in the bundle do not inherit this render pass's current render state, and after the /// bundle has executed, the state is **cleared** (reset to defaults, not the previous state). - pub fn execute_bundles + 'a>( - &mut self, - render_bundles: I, - ) { + pub fn execute_bundles>(&mut self, render_bundles: I) { + let mut render_bundles = render_bundles + .into_iter() + .map(|rb| (&rb.id, rb.data.as_ref())); + DynContext::render_pass_execute_bundles( &*self.parent.context, &mut self.id, self.data.as_mut(), - Box::new( - render_bundles - .into_iter() - .map(|rb| (&rb.id, rb.data.as_ref())), - ), + &mut render_bundles, ) } } @@ -3805,7 +3802,7 @@ impl<'a> RenderPass<'a> { /// /// The active vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndirect`](crate::util::DrawIndirect). + /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). /// These draw structures are expected to be tightly packed. /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. @@ -3833,7 +3830,7 @@ impl<'a> RenderPass<'a> { /// The active index buffer can be set with [`RenderPass::set_index_buffer`], while the active /// vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirect`](crate::util::DrawIndexedIndirect). + /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). /// These draw structures are expected to be tightly packed. /// /// This drawing command uses the current render state, as set by preceding `set_*()` methods. @@ -3866,7 +3863,7 @@ impl<'a> RenderPass<'a> { /// /// The active vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndirect`](crate::util::DrawIndirect). + /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). /// These draw structures are expected to be tightly packed. /// /// The structure expected in `count_buffer` is the following: @@ -3912,7 +3909,7 @@ impl<'a> RenderPass<'a> { /// vertex buffers can be set with [`RenderPass::set_vertex_buffer`]. /// /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirect`](crate::util::DrawIndexedIndirect). + /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). /// /// These draw structures are expected to be tightly packed. /// @@ -4168,7 +4165,7 @@ impl<'a> ComputePass<'a> { /// Dispatches compute work operations, based on the contents of the `indirect_buffer`. /// - /// The structure expected in `indirect_buffer` must conform to [`DispatchIndirect`](crate::util::DispatchIndirect). + /// The structure expected in `indirect_buffer` must conform to [`DispatchIndirectArgs`](crate::util::DispatchIndirectArgs). pub fn dispatch_workgroups_indirect( &mut self, indirect_buffer: &'a Buffer, @@ -4268,7 +4265,7 @@ impl<'a> Drop for ComputePass<'a> { impl<'a> RenderBundleEncoder<'a> { /// Finishes recording and returns a [`RenderBundle`] that can be executed in other render passes. - pub fn finish(self, desc: &RenderBundleDescriptor) -> RenderBundle { + pub fn finish(self, desc: &RenderBundleDescriptor<'_>) -> RenderBundle { let (id, data) = DynContext::render_bundle_encoder_finish(&*self.context, self.id, self.data, desc); RenderBundle { @@ -4416,7 +4413,7 @@ impl<'a> RenderBundleEncoder<'a> { /// /// The active vertex buffers can be set with [`RenderBundleEncoder::set_vertex_buffer`]. /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndirect`](crate::util::DrawIndirect). + /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). pub fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress) { DynContext::render_bundle_encoder_draw_indirect( &*self.parent.context, @@ -4434,7 +4431,7 @@ impl<'a> RenderBundleEncoder<'a> { /// The active index buffer can be set with [`RenderBundleEncoder::set_index_buffer`], while the active /// vertex buffers can be set with [`RenderBundleEncoder::set_vertex_buffer`]. /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirect`](crate::util::DrawIndexedIndirect). + /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). pub fn draw_indexed_indirect( &mut self, indirect_buffer: &'a Buffer, @@ -4493,7 +4490,7 @@ impl<'a> RenderBundleEncoder<'a> { } } -/// A read-only view into a staging buffer. +/// A write-only view into a staging buffer. /// /// Reading into this buffer won't yield the contents of the buffer from the /// GPU and is likely to be slow. Because of this, although [`AsMut`] is @@ -4504,14 +4501,8 @@ pub struct QueueWriteBufferView<'a> { offset: BufferAddress, inner: Box, } -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] -static_assertions::assert_impl_all!(QueueWriteBufferView: Send, Sync); +#[cfg(send_sync)] +static_assertions::assert_impl_all!(QueueWriteBufferView<'_>: Send, Sync); impl Deref for QueueWriteBufferView<'_> { type Target = [u8]; @@ -4628,7 +4619,7 @@ impl Queue { /// This method fails if `size` overruns the size of `texture`, or if `data` is too short. pub fn write_texture( &self, - texture: ImageCopyTexture, + texture: ImageCopyTexture<'_>, data: &[u8], data_layout: ImageDataLayout, size: Extent3d, @@ -4645,11 +4636,11 @@ impl Queue { } /// Schedule a copy of data from `image` into `texture`. - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[cfg(any(webgpu, webgl))] pub fn copy_external_image_to_texture( &self, source: &wgt::ImageCopyExternalImage, - dest: ImageCopyTextureTagged, + dest: ImageCopyTextureTagged<'_>, size: Extent3d, ) { DynContext::queue_copy_external_image_to_texture( @@ -4667,15 +4658,15 @@ impl Queue { &self, command_buffers: I, ) -> SubmissionIndex { + let mut command_buffers = command_buffers + .into_iter() + .map(|mut comb| (comb.id.take().unwrap(), comb.data.take().unwrap())); + let (raw, data) = DynContext::queue_submit( &*self.context, &self.id, self.data.as_ref(), - Box::new( - command_buffers - .into_iter() - .map(|mut comb| (comb.id.take().unwrap(), comb.data.take().unwrap())), - ), + &mut command_buffers, ); SubmissionIndex(raw, data) @@ -4742,7 +4733,7 @@ impl Drop for SurfaceTexture { } } -impl Surface { +impl Surface<'_> { /// Returns the capabilities of the surface when used with the given adapter. /// /// Returns specified values (see [`SurfaceCapabilities`]) if surface is incompatible with the adapter. @@ -4783,6 +4774,7 @@ impl Surface { /// /// - A old [`SurfaceTexture`] is still alive referencing an old surface. /// - Texture format requested is unsupported on the surface. + /// - `config.width` or `config.height` is zero. pub fn configure(&self, device: &Device, config: &SurfaceConfiguration) { DynContext::surface_configure( &*self.context, @@ -4860,286 +4852,227 @@ impl Surface { /// # Safety /// /// - The raw handle obtained from the hal Surface must not be manually destroyed - #[cfg(any( - not(target_arch = "wasm32"), - target_os = "emscripten", - feature = "webgl" - ))] - pub unsafe fn as_hal_mut< - A: wgc::hal_api::HalApi, - F: FnOnce(Option<&mut A::Surface>) -> R, - R, - >( + #[cfg(wgpu_core)] + pub unsafe fn as_hal) -> R, R>( &mut self, hal_surface_callback: F, - ) -> R { - unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .surface_as_hal_mut::( + ) -> Option { + self.context + .as_any() + .downcast_ref::() + .map(|ctx| unsafe { + ctx.surface_as_hal::( self.data.downcast_ref().unwrap(), hal_surface_callback, ) - } + }) } } /// Opaque globally-unique identifier -#[cfg(feature = "expose-ids")] -#[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] #[repr(transparent)] -pub struct Id(::core::num::NonZeroU64, std::marker::PhantomData<*mut T>); +pub struct Id(NonZeroU64, PhantomData<*mut T>); + +impl Id { + /// For testing use only. We provide no guarentees about the actual value of the ids. + #[doc(hidden)] + pub fn inner(&self) -> u64 { + self.0.get() + } +} // SAFETY: `Id` is a bare `NonZeroU64`, the type parameter is a marker purely to avoid confusing Ids // returned for different types , so `Id` can safely implement Send and Sync. -#[cfg(feature = "expose-ids")] unsafe impl Send for Id {} // SAFETY: See the implementation for `Send`. -#[cfg(feature = "expose-ids")] unsafe impl Sync for Id {} -#[cfg(feature = "expose-ids")] impl Clone for Id { fn clone(&self) -> Self { *self } } -#[cfg(feature = "expose-ids")] impl Copy for Id {} -#[cfg(feature = "expose-ids")] impl fmt::Debug for Id { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Id").field(&self.0).finish() } } -#[cfg(feature = "expose-ids")] impl PartialEq for Id { fn eq(&self, other: &Id) -> bool { self.0 == other.0 } } -#[cfg(feature = "expose-ids")] impl Eq for Id {} -#[cfg(feature = "expose-ids")] impl std::hash::Hash for Id { fn hash(&self, state: &mut H) { self.0.hash(state) } } -#[cfg(feature = "expose-ids")] impl Adapter { /// Returns a globally-unique identifier for this `Adapter`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `Adapter`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl Device { /// Returns a globally-unique identifier for this `Device`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `Device`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl Queue { /// Returns a globally-unique identifier for this `Queue`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `Queue`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl ShaderModule { /// Returns a globally-unique identifier for this `ShaderModule`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `ShaderModule`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl BindGroupLayout { /// Returns a globally-unique identifier for this `BindGroupLayout`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `BindGroupLayout`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl BindGroup { /// Returns a globally-unique identifier for this `BindGroup`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `BindGroup`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl TextureView { /// Returns a globally-unique identifier for this `TextureView`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `TextureView`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl Sampler { /// Returns a globally-unique identifier for this `Sampler`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `Sampler`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl Buffer { /// Returns a globally-unique identifier for this `Buffer`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `Buffer`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl Texture { /// Returns a globally-unique identifier for this `Texture`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `Texture`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl QuerySet { /// Returns a globally-unique identifier for this `QuerySet`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `QuerySet`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl PipelineLayout { /// Returns a globally-unique identifier for this `PipelineLayout`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `PipelineLayout`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl RenderPipeline { /// Returns a globally-unique identifier for this `RenderPipeline`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `RenderPipeline`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl ComputePipeline { /// Returns a globally-unique identifier for this `ComputePipeline`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `ComputePipeline`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] impl RenderBundle { /// Returns a globally-unique identifier for this `RenderBundle`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `RenderBundle`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id { + Id(self.id.global_id(), PhantomData) } } -#[cfg(feature = "expose-ids")] -impl Surface { +impl Surface<'_> { /// Returns a globally-unique identifier for this `Surface`. /// /// Calling this method multiple times on the same object will always return the same value. - /// The returned value is guaranteed to be unique among all `Surface`s created from the same - /// `Instance`. - #[cfg_attr(docsrs, doc(cfg(feature = "expose-ids")))] - pub fn global_id(&self) -> Id { - Id(self.id.global_id(), std::marker::PhantomData) + /// The returned value is guaranteed to be different for all resources created from the same `Instance`. + pub fn global_id(&self) -> Id> { + Id(self.id.global_id(), PhantomData) } } @@ -5153,55 +5086,29 @@ pub enum Error { /// Out of memory error OutOfMemory { /// Lower level source of the error. - #[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - ))] + #[cfg(send_sync)] + #[cfg_attr(docsrs, doc(cfg(all())))] source: Box, /// Lower level source of the error. - #[cfg(not(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - )))] + #[cfg(not(send_sync))] + #[cfg_attr(docsrs, doc(cfg(all())))] source: Box, }, /// Validation error, signifying a bug in code or data Validation { /// Lower level source of the error. - #[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - ))] + #[cfg(send_sync)] + #[cfg_attr(docsrs, doc(cfg(all())))] source: Box, /// Lower level source of the error. - #[cfg(not(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) - )))] + #[cfg(not(send_sync))] + #[cfg_attr(docsrs, doc(cfg(all())))] source: Box, /// Description of the validation error. description: String, }, } -#[cfg(any( - not(target_arch = "wasm32"), - all( - feature = "fragile-send-sync-non-atomic-wasm", - not(target_feature = "atomics") - ) -))] +#[cfg(send_sync)] static_assertions::assert_impl_all!(Error: Send); impl error::Error for Error { @@ -5228,12 +5135,12 @@ mod send_sync { use std::any::Any; use std::fmt; - use wgt::{WasmNotSend, WasmNotSync}; + use wgt::WasmNotSendSync; - pub trait AnyWasmNotSendSync: Any + WasmNotSend + WasmNotSync { + pub trait AnyWasmNotSendSync: Any + WasmNotSendSync { fn upcast_any_ref(&self) -> &dyn Any; } - impl AnyWasmNotSendSync for T { + impl AnyWasmNotSendSync for T { #[inline] fn upcast_any_ref(&self) -> &dyn Any { self diff --git a/wgpu/src/ray_tracing.rs b/wgpu/src/ray_tracing.rs index e25e36fdcb..feb2cf959e 100644 --- a/wgpu/src/ray_tracing.rs +++ b/wgpu/src/ray_tracing.rs @@ -1,4 +1,5 @@ use std::{fmt::Debug, ops::Range, sync::Arc, thread}; +use wgt::WasmNotSendSync; use crate::{ context::{Context, DynContext, ObjectId}, @@ -27,11 +28,11 @@ static_assertions::assert_impl_all!(AccelerationStructureUpdateMode: Send, Sync) /// Descriptor to create bottom level acceleration structures. pub type CreateBlasDescriptor<'a> = wgt::CreateBlasDescriptor>; -static_assertions::assert_impl_all!(CreateBlasDescriptor: Send, Sync); +static_assertions::assert_impl_all!(CreateBlasDescriptor<'_>: Send, Sync); /// Descriptor to create top level acceleration structures. pub type CreateTlasDescriptor<'a> = wgt::CreateTlasDescriptor>; -static_assertions::assert_impl_all!(CreateTlasDescriptor: Send, Sync); +static_assertions::assert_impl_all!(CreateTlasDescriptor<'_>: Send, Sync); #[derive(Debug)] /// Definition for a triangle geometry. @@ -55,14 +56,14 @@ pub struct BlasTriangleGeometry<'a> { /// Transform buffer offset in bytes (optional, required if transform buffer is present). pub transform_buffer_offset: Option, } -static_assertions::assert_impl_all!(BlasTriangleGeometry: Send, Sync); +static_assertions::assert_impl_all!(BlasTriangleGeometry<'_>: WasmNotSendSync); /// Geometries for a bottom level acceleration structure. pub enum BlasGeometries<'a> { /// Triangle geometry variant. TriangleGeometries(Vec>), } -static_assertions::assert_impl_all!(BlasGeometries: Send, Sync); +static_assertions::assert_impl_all!(BlasGeometries<'_>: WasmNotSendSync); /// Entry for a bottom level acceleration structure build. pub struct BlasBuildEntry<'a> { @@ -71,7 +72,7 @@ pub struct BlasBuildEntry<'a> { /// Geometries. pub geometry: BlasGeometries<'a>, } -static_assertions::assert_impl_all!(BlasBuildEntry: Send, Sync); +static_assertions::assert_impl_all!(BlasBuildEntry<'_>: WasmNotSendSync); #[derive(Debug)] /// Bottom level acceleration structure. @@ -82,7 +83,7 @@ pub struct Blas { pub(crate) data: Box, pub(crate) handle: Option, } -static_assertions::assert_impl_all!(Blas: Send, Sync); +static_assertions::assert_impl_all!(Blas: WasmNotSendSync); impl Blas { /// Raw handle to the acceleration structure, used inside raw instance buffers. @@ -111,7 +112,7 @@ pub struct Tlas { pub(crate) id: ObjectId, pub(crate) data: Box, } -static_assertions::assert_impl_all!(Tlas: Send, Sync); +static_assertions::assert_impl_all!(Tlas: WasmNotSendSync); impl Tlas { /// Destroy the associated native resources as soon as possible. @@ -138,7 +139,7 @@ pub struct TlasBuildEntry<'a> { /// Number of instances in the instance buffer. pub instance_count: u32, } -static_assertions::assert_impl_all!(TlasBuildEntry: Send, Sync); +static_assertions::assert_impl_all!(TlasBuildEntry<'_>: WasmNotSendSync); /// Safe instance for a top level acceleration structure. #[derive(Debug, Clone)] @@ -181,6 +182,7 @@ pub(crate) struct DynContextTlasInstance<'a> { } /// [Context version] see `TlasInstance`. +#[allow(dead_code)] pub struct ContextTlasInstance<'a, T: Context> { pub(crate) blas_id: T::BlasId, pub(crate) transform: &'a [f32; 12], @@ -194,7 +196,7 @@ pub struct TlasPackage { pub(crate) instances: Vec>, pub(crate) lowest_unmodified: u32, } -static_assertions::assert_impl_all!(TlasPackage: Send, Sync); +static_assertions::assert_impl_all!(TlasPackage: WasmNotSendSync); impl TlasPackage { /// Construct TlasPackage consuming the Tlas (prevents modification of the Tlas without using this package). @@ -247,7 +249,7 @@ impl TlasPackage { } /// Get the binding resource for the underling acceleration structure, to be used in a - pub fn as_binding(&self) -> BindingResource { + pub fn as_binding(&self) -> BindingResource<'_> { BindingResource::AccelerationStructure(&self.tlas) } @@ -290,6 +292,7 @@ pub(crate) struct DynContextTlasPackage<'a> { } /// [Context version] see `BlasTriangleGeometry`. +#[allow(dead_code)] pub struct ContextBlasTriangleGeometry<'a, T: Context> { pub(crate) size: &'a BlasTriangleGeometrySizeDescriptor, pub(crate) vertex_buffer: T::BufferId, @@ -308,12 +311,14 @@ pub enum ContextBlasGeometries<'a, T: Context> { } /// [Context version] see `BlasBuildEntry`. +#[allow(dead_code)] pub struct ContextBlasBuildEntry<'a, T: Context> { pub(crate) blas_id: T::BlasId, pub(crate) geometries: ContextBlasGeometries<'a, T>, } /// [Context version] see `TlasBuildEntry`. +#[allow(dead_code)] pub struct ContextTlasBuildEntry { pub(crate) tlas_id: T::TlasId, pub(crate) instance_buffer_id: T::BufferId, @@ -321,6 +326,7 @@ pub struct ContextTlasBuildEntry { } /// [Context version] see `TlasPackage`. +#[allow(dead_code)] pub struct ContextTlasPackage<'a, T: Context> { pub(crate) tlas_id: T::TlasId, pub(crate) instances: Box>> + 'a>, @@ -337,15 +343,23 @@ pub trait DeviceRayTracing { /// Create a bottom level acceleration structure, used inside a top level acceleration structure for ray tracing. /// - desc: The descriptor of the acceleration structure. /// - sizes: Size descriptor limiting what can be built into the acceleration structure. - fn create_blas(&self, desc: &CreateBlasDescriptor, sizes: BlasGeometrySizeDescriptors) -> Blas; + fn create_blas( + &self, + desc: &CreateBlasDescriptor<'_>, + sizes: BlasGeometrySizeDescriptors, + ) -> Blas; /// Create a top level acceleration structure, used for ray tracing. /// - desc: The descriptor of the acceleration structure. - fn create_tlas(&self, desc: &CreateTlasDescriptor) -> Tlas; + fn create_tlas(&self, desc: &CreateTlasDescriptor<'_>) -> Tlas; } impl DeviceRayTracing for Device { - fn create_blas(&self, desc: &CreateBlasDescriptor, sizes: BlasGeometrySizeDescriptors) -> Blas { + fn create_blas( + &self, + desc: &CreateBlasDescriptor<'_>, + sizes: BlasGeometrySizeDescriptors, + ) -> Blas { let (id, handle, data) = DynContext::device_create_blas( &*self.context, &self.id, @@ -362,7 +376,7 @@ impl DeviceRayTracing for Device { } } - fn create_tlas(&self, desc: &CreateTlasDescriptor) -> Tlas { + fn create_tlas(&self, desc: &CreateTlasDescriptor<'_>) -> Tlas { let (id, data) = DynContext::device_create_tlas(&*self.context, &self.id, self.data.as_ref(), desc); @@ -401,7 +415,7 @@ pub trait CommandEncoderRayTracing { ); /// Build bottom and top level acceleration structures. - /// See [`build_acceleration_structures`] for the safe version and more details. + /// See [`CommandEncoderRayTracing::build_acceleration_structures`] for the safe version and more details. /// /// # Safety /// @@ -425,26 +439,28 @@ impl CommandEncoderRayTracing for CommandEncoder { ) { let id = self.id.as_ref().unwrap(); - let mut blas = blas.into_iter().map(|e: &BlasBuildEntry| { + let mut blas = blas.into_iter().map(|e: &BlasBuildEntry<'_>| { let geometries = match &e.geometry { BlasGeometries::TriangleGeometries(triangle_geometries) => { - let iter = triangle_geometries.iter().map(|tg: &BlasTriangleGeometry| { - DynContextBlasTriangleGeometry { - size: tg.size, - vertex_buffer: tg.vertex_buffer.id, - - index_buffer: tg.index_buffer.map(|index_buffer| index_buffer.id), - - transform_buffer: tg - .transform_buffer - .map(|transform_buffer| transform_buffer.id), - - first_vertex: tg.first_vertex, - vertex_stride: tg.vertex_stride, - index_buffer_offset: tg.index_buffer_offset, - transform_buffer_offset: tg.transform_buffer_offset, - } - }); + let iter = triangle_geometries + .iter() + .map( + |tg: &BlasTriangleGeometry<'_>| DynContextBlasTriangleGeometry { + size: tg.size, + vertex_buffer: tg.vertex_buffer.id, + + index_buffer: tg.index_buffer.map(|index_buffer| index_buffer.id), + + transform_buffer: tg + .transform_buffer + .map(|transform_buffer| transform_buffer.id), + + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + }, + ); DynContextBlasGeometries::TriangleGeometries(Box::new(iter)) } }; @@ -486,26 +502,28 @@ impl CommandEncoderRayTracing for CommandEncoder { ) { let id = self.id.as_ref().unwrap(); - let mut blas = blas.into_iter().map(|e: &BlasBuildEntry| { + let mut blas = blas.into_iter().map(|e: &BlasBuildEntry<'_>| { let geometries = match &e.geometry { BlasGeometries::TriangleGeometries(triangle_geometries) => { - let iter = triangle_geometries.iter().map(|tg: &BlasTriangleGeometry| { - DynContextBlasTriangleGeometry { - size: tg.size, - vertex_buffer: tg.vertex_buffer.id, - - index_buffer: tg.index_buffer.map(|index_buffer| index_buffer.id), - - transform_buffer: tg - .transform_buffer - .map(|transform_buffer| transform_buffer.id), - - first_vertex: tg.first_vertex, - vertex_stride: tg.vertex_stride, - index_buffer_offset: tg.index_buffer_offset, - transform_buffer_offset: tg.transform_buffer_offset, - } - }); + let iter = triangle_geometries + .iter() + .map( + |tg: &BlasTriangleGeometry<'_>| DynContextBlasTriangleGeometry { + size: tg.size, + vertex_buffer: tg.vertex_buffer.id, + + index_buffer: tg.index_buffer.map(|index_buffer| index_buffer.id), + + transform_buffer: tg + .transform_buffer + .map(|transform_buffer| transform_buffer.id), + + first_vertex: tg.first_vertex, + vertex_stride: tg.vertex_stride, + index_buffer_offset: tg.index_buffer_offset, + transform_buffer_offset: tg.transform_buffer_offset, + }, + ); DynContextBlasGeometries::TriangleGeometries(Box::new(iter)) } }; @@ -517,7 +535,7 @@ impl CommandEncoderRayTracing for CommandEncoder { let mut tlas = tlas .into_iter() - .map(|e: &TlasBuildEntry| DynContextTlasBuildEntry { + .map(|e: &TlasBuildEntry<'_>| DynContextTlasBuildEntry { tlas_id: e.tlas.id, instance_buffer_id: e.instance_buffer.id, instance_count: e.instance_count, diff --git a/wgpu/src/util/belt.rs b/wgpu/src/util/belt.rs index 9800718e82..d6ef7a0c46 100644 --- a/wgpu/src/util/belt.rs +++ b/wgpu/src/util/belt.rs @@ -11,6 +11,23 @@ struct Chunk { offset: BufferAddress, } +/// `Sync` wrapper that works by providing only exclusive access. +/// +/// See https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html +struct Exclusive(T); + +unsafe impl Sync for Exclusive {} + +impl Exclusive { + fn new(value: T) -> Self { + Self(value) + } + + fn get_mut(&mut self) -> &mut T { + &mut self.0 + } +} + /// Efficiently performs many buffer writes by sharing and reusing temporary buffers. /// /// Internally it uses a ring-buffer of staging buffers that are sub-allocated. @@ -36,9 +53,9 @@ pub struct StagingBelt { /// into `active_chunks`. free_chunks: Vec, /// When closed chunks are mapped again, the map callback sends them here. - sender: mpsc::Sender, + sender: Exclusive>, /// Free chunks are received here to be put on `self.free_chunks`. - receiver: mpsc::Receiver, + receiver: Exclusive>, } impl StagingBelt { @@ -53,14 +70,14 @@ impl StagingBelt { /// (per [`StagingBelt::finish()`]); and /// * bigger is better, within these bounds. pub fn new(chunk_size: BufferAddress) -> Self { - let (sender, receiver) = mpsc::channel(); + let (sender, receiver) = std::sync::mpsc::channel(); StagingBelt { chunk_size, active_chunks: Vec::new(), closed_chunks: Vec::new(), free_chunks: Vec::new(), - sender, - receiver, + sender: Exclusive::new(sender), + receiver: Exclusive::new(receiver), } } @@ -81,7 +98,7 @@ impl StagingBelt { offset: BufferAddress, size: BufferSize, device: &Device, - ) -> BufferViewMut { + ) -> BufferViewMut<'_> { let mut chunk = if let Some(index) = self .active_chunks .iter() @@ -148,9 +165,8 @@ impl StagingBelt { pub fn recall(&mut self) { self.receive_chunks(); - let sender = &self.sender; for chunk in self.closed_chunks.drain(..) { - let sender = sender.clone(); + let sender = self.sender.get_mut().clone(); chunk .buffer .clone() @@ -164,7 +180,7 @@ impl StagingBelt { /// Move all chunks that the GPU is done with (and are now mapped again) /// from `self.receiver` to `self.free_chunks`. fn receive_chunks(&mut self) { - while let Ok(mut chunk) = self.receiver.try_recv() { + while let Ok(mut chunk) = self.receiver.get_mut().try_recv() { chunk.offset = 0; self.free_chunks.push(chunk); } diff --git a/wgpu/src/util/device.rs b/wgpu/src/util/device.rs index 2d6f0ed7fe..370a9e7abb 100644 --- a/wgpu/src/util/device.rs +++ b/wgpu/src/util/device.rs @@ -10,27 +10,52 @@ pub struct BufferInitDescriptor<'a> { pub usage: crate::BufferUsages, } +/// Order in which TextureData is laid out in memory. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)] +pub enum TextureDataOrder { + /// The texture is laid out densely in memory as: + /// + /// ```text + /// Layer0Mip0 Layer0Mip1 Layer0Mip2 + /// Layer1Mip0 Layer1Mip1 Layer1Mip2 + /// Layer2Mip0 Layer2Mip1 Layer2Mip2 + /// ```` + /// + /// This is the layout used by dds files. + /// + /// This was the previous behavior of [`DeviceExt::create_texture_with_data`]. + #[default] + LayerMajor, + /// The texture is laid out densely in memory as: + /// + /// ```text + /// Layer0Mip0 Layer1Mip0 Layer2Mip0 + /// Layer0Mip1 Layer1Mip1 Layer2Mip1 + /// Layer0Mip2 Layer1Mip2 Layer2Mip2 + /// ``` + /// + /// This is the layout used by ktx and ktx2 files. + MipMajor, +} + /// Utility methods not meant to be in the main API. pub trait DeviceExt { /// Creates a [Buffer](crate::Buffer) with data to initialize it. - fn create_buffer_init(&self, desc: &BufferInitDescriptor) -> crate::Buffer; + fn create_buffer_init(&self, desc: &BufferInitDescriptor<'_>) -> crate::Buffer; /// Upload an entire texture and its mipmaps from a source buffer. /// /// Expects all mipmaps to be tightly packed in the data buffer. /// - /// If the texture is a 2DArray texture, uploads each layer in order, expecting - /// each layer and its mips to be tightly packed. - /// - /// Example: - /// Layer0Mip0 Layer0Mip1 Layer0Mip2 ... Layer1Mip0 Layer1Mip1 Layer1Mip2 ... + /// See [`TextureDataOrder`] for the order in which the data is laid out in memory. /// /// Implicitly adds the `COPY_DST` usage if it is not present in the descriptor, /// as it is required to be able to upload the data to the gpu. fn create_texture_with_data( &self, queue: &crate::Queue, - desc: &crate::TextureDescriptor, + desc: &crate::TextureDescriptor<'_>, + order: TextureDataOrder, data: &[u8], ) -> crate::Texture; } @@ -77,7 +102,8 @@ impl DeviceExt for crate::Device { fn create_texture_with_data( &self, queue: &crate::Queue, - desc: &crate::TextureDescriptor, + desc: &crate::TextureDescriptor<'_>, + order: TextureDataOrder, data: &[u8], ) -> crate::Texture { // Implicitly add the COPY_DST usage @@ -88,13 +114,31 @@ impl DeviceExt for crate::Device { // Will return None only if it's a combined depth-stencil format // If so, default to 4, validation will fail later anyway since the depth or stencil // aspect needs to be written to individually - let block_size = desc.format.block_size(None).unwrap_or(4); + let block_size = desc.format.block_copy_size(None).unwrap_or(4); let (block_width, block_height) = desc.format.block_dimensions(); let layer_iterations = desc.array_layer_count(); + let outer_iteration; + let inner_iteration; + match order { + TextureDataOrder::LayerMajor => { + outer_iteration = layer_iterations; + inner_iteration = desc.mip_level_count; + } + TextureDataOrder::MipMajor => { + outer_iteration = desc.mip_level_count; + inner_iteration = layer_iterations; + } + } + let mut binary_offset = 0; - for layer in 0..layer_iterations { - for mip in 0..desc.mip_level_count { + for outer in 0..outer_iteration { + for inner in 0..inner_iteration { + let (layer, mip) = match order { + TextureDataOrder::LayerMajor => (outer, inner), + TextureDataOrder::MipMajor => (inner, outer), + }; + let mut mip_size = desc.mip_level_size(mip).unwrap(); // copying layers separately if desc.dimension != wgt::TextureDimension::D3 { diff --git a/wgpu/src/util/encoder.rs b/wgpu/src/util/encoder.rs index 208c8c0a51..bef8fe9509 100644 --- a/wgpu/src/util/encoder.rs +++ b/wgpu/src/util/encoder.rs @@ -50,7 +50,7 @@ pub trait RenderEncoder<'a> { /// /// The active vertex buffers can be set with [`RenderEncoder::set_vertex_buffer`]. /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndirect`](crate::util::DrawIndirect). + /// The structure expected in `indirect_buffer` must conform to [`DrawIndirectArgs`](crate::util::DrawIndirectArgs). fn draw_indirect(&mut self, indirect_buffer: &'a Buffer, indirect_offset: BufferAddress); /// Draws indexed primitives using the active index buffer and the active vertex buffers, @@ -59,7 +59,7 @@ pub trait RenderEncoder<'a> { /// The active index buffer can be set with [`RenderEncoder::set_index_buffer`], while the active /// vertex buffers can be set with [`RenderEncoder::set_vertex_buffer`]. /// - /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirect`](crate::util::DrawIndexedIndirect). + /// The structure expected in `indirect_buffer` must conform to [`DrawIndexedIndirectArgs`](crate::util::DrawIndexedIndirectArgs). fn draw_indexed_indirect( &mut self, indirect_buffer: &'a Buffer, diff --git a/wgpu/src/util/indirect.rs b/wgpu/src/util/indirect.rs deleted file mode 100644 index 36e6e70633..0000000000 --- a/wgpu/src/util/indirect.rs +++ /dev/null @@ -1,81 +0,0 @@ -/// The structure expected in `indirect_buffer` for [`RenderEncoder::draw_indirect`](crate::util::RenderEncoder::draw_indirect). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default)] -pub struct DrawIndirect { - /// The number of vertices to draw. - pub vertex_count: u32, - /// The number of instances to draw. - pub instance_count: u32, - /// The Index of the first vertex to draw. - pub base_vertex: u32, - /// The instance ID of the first instance to draw. - /// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled. - pub base_instance: u32, -} - -impl DrawIndirect { - /// Returns the bytes representation of the struct, ready to be written in a [`Buffer`](crate::Buffer). - pub fn as_bytes(&self) -> &[u8] { - unsafe { - std::mem::transmute(std::slice::from_raw_parts( - self as *const _ as *const u8, - std::mem::size_of::(), - )) - } - } -} - -/// The structure expected in `indirect_buffer` for [`RenderEncoder::draw_indexed_indirect`](crate::util::RenderEncoder::draw_indexed_indirect). -#[repr(C)] -#[derive(Copy, Clone, Debug, Default)] -pub struct DrawIndexedIndirect { - /// The number of vertices to draw. - pub vertex_count: u32, - /// The number of instances to draw. - pub instance_count: u32, - /// The base index within the index buffer. - pub base_index: u32, - /// The value added to the vertex index before indexing into the vertex buffer. - pub vertex_offset: i32, - /// The instance ID of the first instance to draw. - /// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled. - pub base_instance: u32, -} - -impl DrawIndexedIndirect { - /// Returns the bytes representation of the struct, ready to be written in a [`Buffer`](crate::Buffer). - pub fn as_bytes(&self) -> &[u8] { - unsafe { - std::mem::transmute(std::slice::from_raw_parts( - self as *const _ as *const u8, - std::mem::size_of::(), - )) - } - } -} - -/// The structure expected in `indirect_buffer` for [`ComputePass::dispatch_workgroups_indirect`](crate::ComputePass::dispatch_workgroups_indirect). -/// -/// x, y and z denote the number of work groups to dispatch in each dimension. -#[repr(C)] -#[derive(Copy, Clone, Debug, Default)] -pub struct DispatchIndirect { - /// The number of work groups in X dimension. - pub x: u32, - /// The number of work groups in Y dimension. - pub y: u32, - /// The number of work groups in Z dimension. - pub z: u32, -} - -impl DispatchIndirect { - /// Returns the bytes representation of the struct, ready to be written in a [`Buffer`](crate::Buffer). - pub fn as_bytes(&self) -> &[u8] { - unsafe { - std::mem::transmute(std::slice::from_raw_parts( - self as *const _ as *const u8, - std::mem::size_of::(), - )) - } - } -} diff --git a/wgpu/src/util/init.rs b/wgpu/src/util/init.rs index 987e8400fd..016ce5f7f9 100644 --- a/wgpu/src/util/init.rs +++ b/wgpu/src/util/init.rs @@ -2,10 +2,11 @@ use wgt::{Backends, PowerPreference, RequestAdapterOptions}; use crate::{Adapter, Instance, Surface}; -#[cfg(any(not(target_arch = "wasm32"), feature = "wgc"))] +#[cfg(not(webgpu))] +#[cfg_attr(docsrs, doc(cfg(all())))] pub use wgc::instance::parse_backends_from_comma_list; /// Always returns WEBGPU on wasm over webgpu. -#[cfg(all(target_arch = "wasm32", not(feature = "wgc")))] +#[cfg(webgpu)] pub fn parse_backends_from_comma_list(_string: &str) -> Backends { Backends::BROWSER_WEBGPU } @@ -37,10 +38,10 @@ pub fn power_preference_from_env() -> Option { } /// Initialize the adapter obeying the WGPU_ADAPTER_NAME environment variable. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(native)] pub fn initialize_adapter_from_env( instance: &Instance, - compatible_surface: Option<&Surface>, + compatible_surface: Option<&Surface<'_>>, ) -> Option { let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME") .as_deref() @@ -69,10 +70,10 @@ pub fn initialize_adapter_from_env( } /// Initialize the adapter obeying the WGPU_ADAPTER_NAME environment variable. -#[cfg(target_arch = "wasm32")] +#[cfg(not(native))] pub fn initialize_adapter_from_env( _instance: &Instance, - _compatible_surface: Option<&Surface>, + _compatible_surface: Option<&Surface<'_>>, ) -> Option { None } @@ -80,7 +81,7 @@ pub fn initialize_adapter_from_env( /// Initialize the adapter obeying the WGPU_ADAPTER_NAME environment variable and if it doesn't exist fall back on a default adapter. pub async fn initialize_adapter_from_env_or_default( instance: &Instance, - compatible_surface: Option<&Surface>, + compatible_surface: Option<&Surface<'_>>, ) -> Option { match initialize_adapter_from_env(instance, compatible_surface) { Some(a) => Some(a), diff --git a/wgpu/src/util/mod.rs b/wgpu/src/util/mod.rs index 5c814e0c51..081346faf7 100644 --- a/wgpu/src/util/mod.rs +++ b/wgpu/src/util/mod.rs @@ -6,7 +6,6 @@ mod belt; mod device; mod encoder; -mod indirect; mod init; use std::sync::Arc; @@ -17,11 +16,10 @@ use std::{ }; pub use belt::StagingBelt; -pub use device::{BufferInitDescriptor, DeviceExt}; +pub use device::{BufferInitDescriptor, DeviceExt, TextureDataOrder}; pub use encoder::RenderEncoder; -pub use indirect::*; pub use init::*; -pub use wgt::math::*; +pub use wgt::{math::*, DispatchIndirectArgs, DrawIndexedIndirectArgs, DrawIndirectArgs}; /// Treat the given byte slice as a SPIR-V module. /// @@ -34,7 +32,7 @@ pub use wgt::math::*; /// - Input is empty /// - SPIR-V magic number is missing from beginning of stream #[cfg(feature = "spirv")] -pub fn make_spirv(data: &[u8]) -> super::ShaderSource { +pub fn make_spirv(data: &[u8]) -> super::ShaderSource<'_> { super::ShaderSource::SpirV(make_spirv_raw(data)) } @@ -42,7 +40,7 @@ pub fn make_spirv(data: &[u8]) -> super::ShaderSource { /// Returns raw slice instead of ShaderSource. /// /// [`Device::create_shader_module_spirv`]: crate::Device::create_shader_module_spirv -pub fn make_spirv_raw(data: &[u8]) -> Cow<[u32]> { +pub fn make_spirv_raw(data: &[u8]) -> Cow<'_, [u32]> { const MAGIC_NUMBER: u32 = 0x0723_0203; assert_eq!( data.len() % size_of::(), @@ -51,7 +49,7 @@ pub fn make_spirv_raw(data: &[u8]) -> Cow<[u32]> { ); assert_ne!(data.len(), 0, "data size must be larger than zero"); - //If the data happens to be aligned, directly use the byte array, + // If the data happens to be aligned, directly use the byte array, // otherwise copy the byte array in an owned vector and use that instead. let mut words = if data.as_ptr().align_offset(align_of::()) == 0 { let (pre, words, post) = unsafe { data.align_to::() }; @@ -94,7 +92,7 @@ impl DownloadBuffer { pub fn read_buffer( device: &super::Device, queue: &super::Queue, - buffer: &super::BufferSlice, + buffer: &super::BufferSlice<'_>, callback: impl FnOnce(Result) + Send + 'static, ) { let size = match buffer.size { diff --git a/xtask/Cargo.lock b/xtask/Cargo.lock deleted file mode 100644 index 9af30bebd2..0000000000 --- a/xtask/Cargo.lock +++ /dev/null @@ -1,638 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" -dependencies = [ - "memchr", -] - -[[package]] -name = "anyhow" -version = "1.0.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" - -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder", - "safemem", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "cargo-run-wasm" -version = "0.3.2" -source = "git+https://github.com/ErichDonGubler/cargo-run-wasm?branch=expose-args#e9b502e50eaccb0f360deb36ccd0f42c6c56f9ba" -dependencies = [ - "devserver_lib", - "pico-args", - "serde_json", - "wasm-bindgen-cli-support", -] - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "devserver_lib" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf215dbb8cb1409cca7645aaed35f9e39fb0a21855bba1ac48bc0334903bf66" - -[[package]] -name = "env_logger" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" -dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "id-arena" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "is-terminal" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "itoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - -[[package]] -name = "libc" -version = "0.2.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81" - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "log" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "proc-macro2" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustix" -version = "0.37.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "ryu" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" - -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - -[[package]] -name = "serde" -version = "1.0.163" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" - -[[package]] -name = "serde_json" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys 0.45.0", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "unicode-ident" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - -[[package]] -name = "walrus" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8" -dependencies = [ - "anyhow", - "id-arena", - "leb128", - "log", - "walrus-macro", - "wasmparser", -] - -[[package]] -name = "walrus-macro" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "wasm-bindgen-cli-support" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21c60239a09bf9bab8dfa752be4e6c637db22296b9ded493800090448692da9" -dependencies = [ - "anyhow", - "base64", - "log", - "rustc-demangle", - "serde_json", - "tempfile", - "unicode-ident", - "walrus", - "wasm-bindgen-externref-xform", - "wasm-bindgen-multi-value-xform", - "wasm-bindgen-shared", - "wasm-bindgen-threads-xform", - "wasm-bindgen-wasm-conventions", - "wasm-bindgen-wasm-interpreter", -] - -[[package]] -name = "wasm-bindgen-externref-xform" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafbe1984f67cc12645f12ab65e6145e8ddce1ab265d0be58435f25bb0ce2608" -dependencies = [ - "anyhow", - "walrus", -] - -[[package]] -name = "wasm-bindgen-multi-value-xform" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581419e3995571a1d2d066e360ca1c0c09da097f5a53c98e6f00d96eddaf0ffe" -dependencies = [ - "anyhow", - "walrus", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" - -[[package]] -name = "wasm-bindgen-threads-xform" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05d272073981137e8426cf2a6830d43d1f84f988a050b2f8b210f0e266b8983" -dependencies = [ - "anyhow", - "walrus", - "wasm-bindgen-wasm-conventions", -] - -[[package]] -name = "wasm-bindgen-wasm-conventions" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e9c65b1ff5041ea824ca24c519948aec16fb6611c617d601623c0657dfcd47b" -dependencies = [ - "anyhow", - "walrus", -] - -[[package]] -name = "wasm-bindgen-wasm-interpreter" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5c796220738ab5d44666f37205728a74141c0039d1166bcf8110b26bafaa1e" -dependencies = [ - "anyhow", - "log", - "walrus", - "wasm-bindgen-wasm-conventions", -] - -[[package]] -name = "wasmparser" -version = "0.77.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe3d5405e9ea6c1317a656d6e0820912d8b7b3607823a7596117c8f666daf6f" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "xtask" -version = "0.1.0" -dependencies = [ - "anyhow", - "cargo-run-wasm", - "env_logger", - "log", - "pico-args", -] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 343a2166a8..3bbc64c46f 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -5,12 +5,15 @@ edition = "2021" publish = false [dependencies] +# The dependencies in this config have no transitive dependencies. anyhow = "1.0.71" -# Current contents filed as a PR here: -# -cargo-run-wasm = { version = "0.3.2", git = "https://github.com/ErichDonGubler/cargo-run-wasm", branch = "expose-args" } -env_logger = "0.10.0" +env_logger = { version = "0.10.0", default-features = false } log = "0.4.18" -pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] } +pico-args = { version = "0.5.0", features = [ + "eq-separator", + "short-space-opt", + "combined-flags", +] } +xshell = "0.2.3" [workspace] diff --git a/xtask/src/cli.rs b/xtask/src/cli.rs index 924e429b48..78547268a9 100644 --- a/xtask/src/cli.rs +++ b/xtask/src/cli.rs @@ -8,19 +8,23 @@ Usage: xtask Commands: run-wasm + --release Build in release mode + --no-serve Just build the generated files, don't serve them + test + --llvm-cov Run tests with LLVM code coverage using the llvm-cov tool Options: -h, --help Print help "; -pub(crate) struct Args { - pub(crate) subcommand: Subcommand, +pub struct Args { + pub subcommand: Subcommand, + pub command_args: Arguments, } impl Args { pub fn parse() -> Self { let mut args = Arguments::from_env(); - log::debug!("parsing args: {args:?}"); if args.contains("--help") { eprint!("{HELP}"); // Emulate Cargo exit status: @@ -28,8 +32,11 @@ impl Args { let cargo_like_exit_code = 101; exit(cargo_like_exit_code); } - match Subcommand::parse(args).map(|subcommand| Self { subcommand }) { - Ok(this) => this, + match Subcommand::parse(&mut args) { + Ok(subcommand) => Self { + subcommand, + command_args: args, + }, Err(e) => { eprintln!("{:?}", anyhow!(e)); exit(1) @@ -38,18 +45,20 @@ impl Args { } } -pub(crate) enum Subcommand { - RunWasm { args: Arguments }, +pub enum Subcommand { + RunWasm, + Test, } impl Subcommand { - fn parse(mut args: Arguments) -> anyhow::Result { + fn parse(args: &mut Arguments) -> anyhow::Result { let subcmd = args .subcommand() .context("failed to parse subcommand")? .context("no subcommand specified; see `--help` for more details")?; match &*subcmd { - "run-wasm" => Ok(Self::RunWasm { args }), + "run-wasm" => Ok(Self::RunWasm), + "test" => Ok(Self::Test), other => { bail!("unrecognized subcommand {other:?}; see `--help` for more details") } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 2a79a4e242..1ff0aa8efd 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,12 +1,11 @@ -use std::process::ExitCode; - -use anyhow::Context; -use cli::{Args, Subcommand}; -use pico_args::Arguments; +use cli::Args; mod cli; +mod run_wasm; +mod test; +mod util; -fn main() -> ExitCode { +fn main() -> anyhow::Result<()> { env_logger::builder() .filter_level(log::LevelFilter::Info) .parse_default_env() @@ -15,34 +14,14 @@ fn main() -> ExitCode { let args = Args::parse(); - match run(args) { - Ok(()) => ExitCode::SUCCESS, - Err(e) => { - log::error!("{e:?}"); - ExitCode::FAILURE - } - } + run(args) } -fn run(args: Args) -> anyhow::Result<()> { - let Args { subcommand } = args; - match subcommand { - Subcommand::RunWasm { mut args } => { - // Use top-level Cargo.toml instead of xtask/Cargo.toml by default - let manifest_path = args.value_from_str("--manifest-path") - .unwrap_or_else(|_| "../Cargo.toml".to_string()); - let mut arg_vec = args.finish(); - arg_vec.push("--manifest-path".into()); - arg_vec.push(manifest_path.into()); - let args = Arguments::from_vec(arg_vec); - - cargo_run_wasm::run_wasm_with_css_and_args( - "body { margin: 0px; }", - cargo_run_wasm::Args::from_args(args) - .map_err(anyhow::Error::msg) - .context("failed to parse arguments for `cargo-run-wasm`")?, - ); - Ok(()) - } +#[allow(unused_mut, unreachable_patterns)] +fn run(mut args: Args) -> anyhow::Result<()> { + match args.subcommand { + cli::Subcommand::RunWasm => run_wasm::run_wasm(args.command_args), + cli::Subcommand::Test => test::run_tests(args.command_args), + _ => unreachable!(), } } diff --git a/xtask/src/run_wasm.rs b/xtask/src/run_wasm.rs new file mode 100644 index 0000000000..280ef82775 --- /dev/null +++ b/xtask/src/run_wasm.rs @@ -0,0 +1,109 @@ +use anyhow::Context; + +use pico_args::Arguments; + +use crate::util::{check_all_programs, Program}; + +pub(crate) fn run_wasm(mut args: Arguments) -> Result<(), anyhow::Error> { + let no_serve = args.contains("--no-serve"); + let release = args.contains("--release"); + + let programs_needed: &[_] = if no_serve { + &[Program { + crate_name: "wasm-bindgen-cli", + binary_name: "wasm-bindgen", + }] + } else { + &[ + Program { + crate_name: "wasm-bindgen-cli", + binary_name: "wasm-bindgen", + }, + Program { + crate_name: "simple-http-server", + binary_name: "simple-http-server", + }, + ] + }; + + check_all_programs(programs_needed)?; + + let release_flag: &[_] = if release { &["--release"] } else { &[] }; + let output_dir = if release { "release" } else { "debug" }; + + let shell = xshell::Shell::new().context("Couldn't create xshell shell")?; + shell.change_dir(String::from(env!("CARGO_MANIFEST_DIR")) + "/.."); + + log::info!("building webgpu examples"); + + let cargo_args = args.finish(); + + xshell::cmd!( + shell, + "cargo build --target wasm32-unknown-unknown --bin wgpu-examples {release_flag...}" + ) + .args(&cargo_args) + .quiet() + .run() + .context("Failed to build webgpu examples for wasm")?; + + log::info!("running wasm-bindgen on webgpu examples"); + + xshell::cmd!( + shell, + "wasm-bindgen target/wasm32-unknown-unknown/{output_dir}/wgpu-examples.wasm --target web --no-typescript --out-dir target/generated --out-name webgpu" + ) + .quiet() + .run() + .context("Failed to run wasm-bindgen")?; + + log::info!("building webgl examples"); + + xshell::cmd!( + shell, + "cargo build --target wasm32-unknown-unknown --bin wgpu-examples --no-default-features --features wgsl,webgl {release_flag...}" + ) + .args(&cargo_args) + .quiet() + .run() + .context("Failed to build webgl examples for wasm")?; + + log::info!("running wasm-bindgen on webgl examples"); + + xshell::cmd!( + shell, + "wasm-bindgen target/wasm32-unknown-unknown/{output_dir}/wgpu-examples.wasm --target web --no-typescript --out-dir target/generated --out-name webgl2" + ) + .quiet() + .run() + .context("Failed to run wasm-bindgen")?; + + let static_files = shell + .read_dir("examples/static") + .context("Failed to enumerate static files")?; + + for file in static_files { + log::info!( + "copying static file \"{}\"", + file.canonicalize().unwrap().display() + ); + + shell + .copy_file(&file, "target/generated") + .with_context(|| format!("Failed to copy static file \"{}\"", file.display()))?; + } + + if !no_serve { + log::info!("serving on port 8000"); + + xshell::cmd!( + shell, + "simple-http-server target/generated -c wasm,html,js -i --coep --coop" + ) + .quiet() + .run() + .context("Failed to simple-http-server")?; + } + + Ok(()) +} diff --git a/xtask/src/test.rs b/xtask/src/test.rs new file mode 100644 index 0000000000..71e084f044 --- /dev/null +++ b/xtask/src/test.rs @@ -0,0 +1,59 @@ +use anyhow::Context; +use pico_args::Arguments; + +pub fn run_tests(mut args: Arguments) -> anyhow::Result<()> { + let llvm_cov = args.contains("--llvm-cov"); + // These needs to match the command in "run wgpu-info" in `.github/workflows/ci.yml` + let llvm_cov_flags: &[_] = if llvm_cov { + &["llvm-cov", "--no-cfg-coverage", "--no-report"] + } else { + &[] + }; + let llvm_cov_nextest_flags: &[_] = if llvm_cov { + &["llvm-cov", "--no-cfg-coverage", "--no-report", "nextest"] + } else { + &["nextest", "run"] + }; + + let shell = xshell::Shell::new().context("Couldn't create xshell shell")?; + + shell.change_dir(String::from(env!("CARGO_MANIFEST_DIR")) + "/.."); + + log::info!("Generating .gpuconfig file based on gpus on the system"); + + xshell::cmd!( + shell, + "cargo {llvm_cov_flags...} run --bin wgpu-info -- --json -o .gpuconfig" + ) + .quiet() + .run() + .context("Failed to run wgpu-info to generate .gpuconfig")?; + + let gpu_count = shell + .read_file(".gpuconfig") + .unwrap() + .lines() + .filter(|line| line.contains("name")) + .count(); + + log::info!( + "Found {} gpu{}", + gpu_count, + if gpu_count == 1 { "" } else { "s" } + ); + + log::info!("Running cargo tests"); + + xshell::cmd!( + shell, + "cargo {llvm_cov_nextest_flags...} --all-features --no-fail-fast --retries 2" + ) + .args(args.finish()) + .quiet() + .run() + .context("Tests failed")?; + + log::info!("Finished tests"); + + Ok(()) +} diff --git a/xtask/src/util.rs b/xtask/src/util.rs new file mode 100644 index 0000000000..85f4444c4e --- /dev/null +++ b/xtask/src/util.rs @@ -0,0 +1,42 @@ +use std::{io, process::Command}; + +pub(crate) struct Program { + pub binary_name: &'static str, + pub crate_name: &'static str, +} + +pub(crate) fn check_all_programs(programs: &[Program]) -> anyhow::Result<()> { + let mut failed = Vec::new(); + for Program { + binary_name, + crate_name, + } in programs + { + let mut cmd = Command::new(binary_name); + cmd.arg("--help"); + let output = cmd.output(); + match output { + Ok(_output) => { + log::info!("Checking for {binary_name} in PATH: ✅"); + } + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => { + log::error!("Checking for {binary_name} in PATH: ❌"); + failed.push(*crate_name); + } + Err(e) => { + log::error!("Checking for {binary_name} in PATH: ❌"); + panic!("Unknown IO error: {:?}", e); + } + } + } + + if !failed.is_empty() { + log::error!( + "Please install them with: cargo install {}", + failed.join(" ") + ); + anyhow::bail!("Missing programs in PATH"); + } + + Ok(()) +}