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/.github/CODEOWNERS b/naga/.github/CODEOWNERS new file mode 100644 index 0000000000..312b7e1bff --- /dev/null +++ b/naga/.github/CODEOWNERS @@ -0,0 +1 @@ +* @gfx-rs/naga diff --git a/naga/.github/workflows/ci.yml b/naga/.github/workflows/ci.yml new file mode 100644 index 0000000000..ef2c497129 --- /dev/null +++ b/naga/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +name: CI +on: [push, pull_request] + +env: + CARGO_INCREMENTAL: false + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + MSRV: 1.65 + +jobs: + check-msrv: + name: Check MSRV and minimal-versions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install MSRV toolchain + run: rustup toolchain install $MSRV --no-self-update --profile=minimal --component clippy + + - name: Install nightly toolchain + run: rustup toolchain install nightly --no-self-update --profile=minimal + + - name: Install cargo-hack + uses: taiki-e/install-action@v2 + with: + tool: cargo-hack + + # -Z avoid-dev-deps doesn't work + - run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z minimal-versions + + - name: Test all features + run: cargo +$MSRV clippy --all-features --workspace -- -D warnings + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install cargo-nextest and cargo-llvm-cov + uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest,cargo-llvm-cov + + - name: Default test + # Our intention here is to test `naga` with no features enabled. But + # since `cli` is the default package, a plain `cargo test` will build + # `naga` with the features requested in `cli/Cargo.toml`. Passing + # `--package naga` causes us to use the default features in the + # top-level `Cargo.toml` instead. + run: cargo nextest run --package naga + + - name: Test all features + run: cargo llvm-cov --lcov --output-path lcov.info nextest --all-features --workspace + + - name: Upload coverage report to codecov + uses: codecov/codecov-action@v3 + with: + files: lcov.info + + - name: Check snapshots + run: git diff --exit-code -- tests/out + + check: + name: Check benchmarks and naga-fuzz + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Check benchmarks + run: cargo check --benches + + - name: Check naga-fuzz + run: | + cd fuzz + cargo check + + documentation: + name: Documentation + runs-on: ubuntu-latest + env: + RUSTDOCFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v3 + + - run: cargo doc -p naga --all-features --document-private-items + + fmt: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - run: cargo fmt -- --check diff --git a/naga/.github/workflows/lazy.yml b/naga/.github/workflows/lazy.yml new file mode 100644 index 0000000000..ab5e21f129 --- /dev/null +++ b/naga/.github/workflows/lazy.yml @@ -0,0 +1,162 @@ +# Lazy jobs running on master post merges. +name: lazy +on: + push: + branches: [master] + pull_request: + paths: + - '.github/workflows/lazy.yml' + +env: + CARGO_INCREMENTAL: false + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +jobs: + parse-dota2: + name: Parse Dota2 shaders + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - run: mkdir data + + - name: Download shaders + run: curl https://user.fm/files/v2-5573e18b9f03f42c6ae53c392083da35/dota2-shaders.zip -o data/all.zip + + - name: Unpack shaders + run: cd data && unzip all.zip + + - name: Build Naga + run: cargo build --release --bin naga + + - name: Convert shaders + run: for file in data/*.spv ; do echo "Translating" ${file} && target/release/naga --validate 27 ${file} ${file}.metal; done + + parse-vulkan-tutorial-shaders: + name: Parse Sascha Willems Vulkan tutorial shaders + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Download shaders + run: git clone https://github.com/SaschaWillems/Vulkan.git + + - name: Build Naga + run: cargo build --release --bin naga + + - name: Convert metal shaders + run: | + # No needed to stop workflow if we can't validate one file + set +e + 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: Parse dneto0 spirv-samples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Download shaders + run: git clone https://github.com/dneto0/spirv-samples.git + + - name: Build Naga + run: cargo build --release --bin naga + + - name: Install spirv-tools + run: | + 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 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 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 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 + + check-snapshots-extra: + name: Check snapshots (validated or not) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install cargo-nextest + uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + + - name: Test minimal (without span) + run: cargo nextest run --features validate -p naga + + - name: Test all (without validation) + run: cargo nextest run --features wgsl-in,wgsl-out,glsl-in,glsl-out,spv-in,spv-out,msl-out,hlsl-out,dot-out --workspace + + - name: Check snapshots (without validation) + run: git diff --exit-code -- tests/out diff --git a/naga/.github/workflows/validation-linux.yml b/naga/.github/workflows/validation-linux.yml new file mode 100644 index 0000000000..0c1525e7db --- /dev/null +++ b/naga/.github/workflows/validation-linux.yml @@ -0,0 +1,34 @@ +name: validation-linux +on: + pull_request: + paths: + - '.github/workflows/validation-linux.yml' + - 'tests/out/spv/*.spvasm' + - 'tests/out/glsl/*.glsl' + - 'tests/out/dot/*.dot' + - 'tests/out/wgsl/*.wgsl' + - 'src/front/wgsl/*' + - 'xtask/**' + +jobs: + validate-linux: + name: SPIR-V + GLSL + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install tools + run: sudo apt-get install spirv-tools glslang-tools graphviz + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + xtask -> target + + - run: cargo xtask validate spv + + - run: cargo xtask validate glsl + + - run: cargo xtask validate dot + + - run: cargo xtask validate wgsl diff --git a/naga/.github/workflows/validation-macos.yml b/naga/.github/workflows/validation-macos.yml new file mode 100644 index 0000000000..c06563dc12 --- /dev/null +++ b/naga/.github/workflows/validation-macos.yml @@ -0,0 +1,21 @@ +name: validation-macos +on: + pull_request: + paths: + - '.github/workflows/validation-macos.yml' + - 'tests/out/msl/*.msl' + - 'xtask/**' + +jobs: + validate-macos: + name: MSL + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + xtask -> target + + - run: cargo xtask validate msl diff --git a/naga/.github/workflows/validation-windows.yml b/naga/.github/workflows/validation-windows.yml new file mode 100644 index 0000000000..536b71d7b6 --- /dev/null +++ b/naga/.github/workflows/validation-windows.yml @@ -0,0 +1,48 @@ +name: validation-windows +on: + pull_request: + paths: + - '.github/workflows/validation-windows.yml' + - 'tests/out/hlsl/*.hlsl' + - 'tests/out/hlsl/*.ron' + - 'xtask/**' + - 'hlsl-snapshots/**' + +jobs: + validate-windows-dxc: + name: HLSL via DXC + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + + - name: Add DirectXShaderCompiler + uses: napokue/setup-dxc@v1.1.0 + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + xtask -> target + + - run: cargo xtask validate hlsl dxc + + validate-windows-fxc: + name: HLSL via FXC + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + + - name: Add fxc bin to PATH + 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 + + - uses: Swatinem/rust-cache@v2 + with: + workspaces: | + xtask -> target + + - run: cargo xtask validate hlsl fxc 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..da7eb276d3 --- /dev/null +++ b/naga/CHANGELOG.md @@ -0,0 +1,950 @@ +# Change Log + +## 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..c74a8cd861 --- /dev/null +++ b/naga/Cargo.toml @@ -0,0 +1,88 @@ +[package] +name = "naga" +version = "0.14.0" +authors = ["Naga Developers"] +edition = "2021" +description = "Shader translation infrastructure" +homepage = "https://github.com/gfx-rs/naga" +repository = "https://github.com/gfx-rs/naga" +keywords = ["shader", "SPIR-V", "GLSL", "MSL"] +license = "MIT OR Apache-2.0" +exclude = ["bin/**/*", "tests/**/*", "Cargo.lock", "target/**/*"] +resolver = "2" +rust-version = "1.65" + +[package.metadata.docs.rs] +all-features = true + +[profile.release] +panic = "abort" + +[profile.dev] +panic = "abort" + +[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 = ["codespan-reporting", "hexf-parse", "termcolor", "unicode-xid"] +wgsl-out = [] +hlsl-out = [] +span = ["codespan-reporting", "termcolor"] +validate = [] +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.0.4", optional = true } +# 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", optional = true } +rustc-hash = "1.1.0" +indexmap = { version = "2", features = ["std"] } +log = "0.4" +num-traits = "0.2" +spirv = { version = "0.2", optional = true } +thiserror = "1.0.21" +serde = { version = "1.0.103", 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 } + +[dev-dependencies] +bincode = "1" +criterion = { version = "0.5", features = [] } +diff = "0.1" +env_logger = "0.10" +hlsl-snapshots = { 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.2", features = ["deserialize"] } + +[workspace] +members = [".", "cli"] + +# Include "cli", so that `cargo run` runs the CLI by default. Include ".", so +# that `cargo test` includes naga's own tests by default (but note, using the +# features that `cli/Cargo.toml` requests). +default-members = [".", "cli"] diff --git a/naga/LICENSE-APACHE b/naga/LICENSE-APACHE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/naga/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/naga/LICENSE-MIT b/naga/LICENSE-MIT new file mode 100644 index 0000000000..58acb754a2 --- /dev/null +++ b/naga/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) [yyyy] [name of copyright owner] + +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. diff --git a/naga/README.md b/naga/README.md new file mode 100644 index 0000000000..04ed84ab5e --- /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.65+-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 to test 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..9c0dc225fc --- /dev/null +++ b/naga/benches/criterion.rs @@ -0,0 +1,277 @@ +#![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"); + #[cfg(feature = "validate")] + 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(); + } + }); + }); + #[cfg(feature = "validate")] + 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) { + #[cfg(feature = "validate")] + 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::>() + }; + #[cfg(not(feature = "validate"))] + let inputs = Vec::<(naga::Module, naga::valid::ModuleInfo)>::new(); + + 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/cli/Cargo.toml b/naga/cli/Cargo.toml new file mode 100644 index 0000000000..00581905f2 --- /dev/null +++ b/naga/cli/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "naga-cli" +version = "0.14.0" +authors = ["Naga Developers"] +edition = "2021" +description = "Shader translation command line tool" +homepage = "https://github.com/gfx-rs/naga" +repository = "https://github.com/gfx-rs/naga" +keywords = ["shader", "SPIR-V", "GLSL", "MSL"] +license = "MIT OR Apache-2.0" + +[dependencies] +bincode = "1" +log = "0.4" +codespan-reporting = "0.11" +env_logger = "0.10" +argh = "0.1.5" + +[dependencies.naga] +version = "0.14" +path = "../" +features = [ + "validate", + "compact", + "span", + "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..3b0873a376 --- /dev/null +++ b/naga/cli/src/bin/naga.rs @@ -0,0 +1,674 @@ +#![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, + + /// 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. + #[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(); + + // Initialize default parameters + //TODO: read the parameters from RON? + let mut params = Parameters::default(); + + // Parse commandline arguments + let args: Args = argh::from_env(); + if args.version { + println!("{}", env!("CARGO_PKG_VERSION")); + return Ok(()); + } + 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 output_paths = args.files.get(1..).unwrap_or(&[]); + + // 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.map(std::path::PathBuf::from), + }; + + params.entry_point = args.entry_point; + if let Some(version) = args.profile { + params.glsl.version = version.0; + } + if let Some(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, + ); + + let (mut 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 path = input_path.to_string_lossy(); + e.emit_to_stderr_with_path(&input, &path); + return Err(CliError("Could not parse WGSL").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()), + }; + + // 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() + ); + } + } + + // 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(()) +} + +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(()) +} + +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/codecov.yml b/naga/codecov.yml new file mode 100644 index 0000000000..959972a69c --- /dev/null +++ b/naga/codecov.yml @@ -0,0 +1 @@ +comment: false \ No newline at end of file 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..06c3e5a961 --- /dev/null +++ b/naga/fuzz/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "naga-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +arbitrary = { version = "1.0.2", features = ["derive"] } +libfuzzer-sys = "0.4" + +[dependencies.naga] +path = ".." +features = ["arbitrary", "spv-in", "wgsl-in", "glsl-in", "validate"] + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[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..18d7175e02 --- /dev/null +++ b/naga/fuzz/fuzz_targets/glsl_parser.rs @@ -0,0 +1,46 @@ +#![no_main] +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..ae1d907474 --- /dev/null +++ b/naga/fuzz/fuzz_targets/ir.rs @@ -0,0 +1,10 @@ +#![no_main] +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..dda3d5e63c --- /dev/null +++ b/naga/fuzz/fuzz_targets/spv_parser.rs @@ -0,0 +1,9 @@ +#![no_main] +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..a61e99979c --- /dev/null +++ b/naga/fuzz/fuzz_targets/wgsl_parser.rs @@ -0,0 +1,8 @@ +#![no_main] +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..07620dcfd3 --- /dev/null +++ b/naga/hlsl-snapshots/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hlsl-snapshots" +version = "0.1.0" +edition = "2021" +publish = 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..a9df066654 --- /dev/null +++ b/naga/src/arena.rs @@ -0,0 +1,809 @@ +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(feature = "span")] + #[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(), + #[cfg(feature = "span")] + 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 { + #[cfg(not(feature = "span"))] + let _ = span; + let index = self.data.len(); + self.data.push(value); + #[cfg(feature = "span")] + 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 { + #[cfg(feature = "span")] + { + *self + .span_info + .get(handle.index()) + .unwrap_or(&Span::default()) + } + #[cfg(not(feature = "span"))] + { + let _ = handle; + 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; + self.data.retain_mut(|elt| { + index += 1; + let handle = Handle::new(Index::new(index).unwrap()); + predicate(handle, elt) + }) + } +} + +#[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)?; + #[cfg(feature = "span")] + let span_info = std::iter::repeat(Span::default()) + .take(data.len()) + .collect(); + + Ok(Self { + data, + #[cfg(feature = "span")] + 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. + #[cfg(feature = "span")] + span_info: Vec, +} + +impl UniqueArena { + /// Create a new arena with no initial capacity allocated. + pub fn new() -> Self { + UniqueArena { + set: FastIndexSet::default(), + #[cfg(feature = "span")] + 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(); + #[cfg(feature = "span")] + 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. + /// + /// If the `span` feature is not enabled, always return `Span::default`. + /// This can be detected with [`Span::is_defined`]. + pub fn get_span(&self, handle: Handle) -> Span { + #[cfg(feature = "span")] + { + *self + .span_info + .get(handle.index()) + .unwrap_or(&Span::default()) + } + #[cfg(not(feature = "span"))] + { + let _ = handle; + Span::default() + } + } + + #[cfg(feature = "compact")] + pub(crate) fn drain_all(&mut self) -> UniqueArenaDrain { + UniqueArenaDrain { + inner_elts: self.set.drain(..), + #[cfg(feature = "span")] + 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>, + #[cfg(feature = "span")] + 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(); + #[cfg(feature = "span")] + let span = self.inner_spans.next().unwrap(); + #[cfg(not(feature = "span"))] + let span = Span::default(); + 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`. + /// + /// When the `span` feature is enabled, 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); + + #[cfg(feature = "span")] + { + if added { + debug_assert!(index == self.span_info.len()); + self.span_info.push(span); + } + + debug_assert!(self.set.len() == self.span_info.len()); + } + + #[cfg(not(feature = "span"))] + let _ = (span, added); + + 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)?; + #[cfg(feature = "span")] + let span_info = std::iter::repeat(Span::default()).take(set.len()).collect(); + + Ok(Self { + set, + #[cfg(feature = "span")] + 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?); + #[cfg(feature = "span")] + 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?); + #[cfg(feature = "span")] + 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..d6f3aae35d --- /dev/null +++ b/naga/src/back/glsl/features.rs @@ -0,0 +1,518 @@ +use super::{BackendResult, Error, Version, Writer}; +use crate::{ + AddressSpace, Binding, Bytes, Expression, Handle, ImageClass, ImageDimension, Interpolation, + Sampling, 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; + } +} + +/// 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 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, version: Version, mut out: impl Write) -> BackendResult { + if self.0.contains(Features::COMPUTE_SHADER) && !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) && !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) && 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 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 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) && 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) && 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) && version.is_es() { + // https://www.khronos.org/registry/OpenGL/extensions/NV/NV_image_formats.txt + writeln!(out, "#extension GL_NV_image_formats : require")?; + } + + if 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 version.is_es() { + // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_conservative_depth.txt + writeln!(out, "#extension GL_EXT_conservative_depth : require")?; + } + + if 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)) + && 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) && 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, .. } = 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) && 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) && version.is_es() { + // https://registry.khronos.org/OpenGL/extensions/EXT/EXT_blend_func_extended.txt + writeln!(out, "#extension GL_EXT_blend_func_extended : 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 { kind, width } => self.scalar_required_features(kind, width), + TypeInner::Vector { kind, width, .. } => self.scalar_required_features(kind, width), + TypeInner::Matrix { width, .. } => { + self.scalar_required_features(ScalarKind::Float, width) + } + 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, kind: ScalarKind, width: Bytes) { + if kind == ScalarKind::Float && 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) + } + _ => {} + }, + 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..afadd6e7f1 --- /dev/null +++ b/naga/src/back/glsl/keywords.rs @@ -0,0 +1,483 @@ +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, +]; diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs new file mode 100644 index 0000000000..592c72a9a5 --- /dev/null +++ b/naga/src/back/glsl/mod.rs @@ -0,0 +1,4320 @@ +/*! +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"; + +/// 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; + /// 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 = 0x4; + /// 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 = 0x10; + } +} + +/// 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, +} + +/// 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>, +} + +/// 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 + } +} + +/// 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, + output: bool, + targetting_webgl: bool, +} +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.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.output, self.targetting_webgl) + ) + } + } + } +} + +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:?} {1:?}")] + UnsupportedScalar(crate::ScalarKind, crate::Bytes), + /// 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_"], + &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.version, &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)?; + } + + // 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 { kind, width } + | TypeInner::Atomic { kind, width } + | TypeInner::ValuePointer { + size: None, + kind, + width, + space: _, + } => write!(self.out, "{}", glsl_scalar(kind, width)?.full)?, + // Vectors are just `gvecN` where `g` is the scalar prefix and `N` is the vector size + TypeInner::Vector { size, kind, width } + | TypeInner::ValuePointer { + size: Some(size), + kind, + width, + space: _, + } => write!( + self.out, + "{}vec{}", + glsl_scalar(kind, width)?.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, + width, + } => write!( + self.out, + "{}mat{}x{}", + glsl_scalar(crate::ScalarKind::Float, width)?.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(kind, 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 { 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 { + Some(ref br) => { + format!( + "_group_{}_binding_{}_{}", + br.group, + br.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 { + Some(ref br) => write!( + self.out, + "_group_{}_binding_{}_{}", + br.group, + br.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, output, self.options.version.is_webgl()) + )?; + } + } + } + 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, + output, + targetting_webgl: self.options.version.is_webgl(), + }; + 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, + output: false, + targetting_webgl: self.options.version.is_webgl(), + }; + 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, + output: false, + targetting_webgl: self.options.version.is_webgl(), + }; + 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, + output: true, + targetting_webgl: self.options.version.is_webgl(), + }; + 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, + output: true, + targetting_webgl: self.options.version.is_webgl(), + }; + 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)?, + } + } + 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 { + 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 { kind, .. }, &Ti::Vector { .. }) => match op { + Bo::Less + | Bo::LessEqual + | Bo::Greater + | Bo::GreaterEqual + | Bo::Equal + | Bo::NotEqual => BinaryOperation::VectorCompare, + Bo::Modulo if kind == Sk::Float => BinaryOperation::Modulo, + Bo::And if kind == Sk::Bool => { + op = crate::BinaryOperator::LogicalAnd; + BinaryOperation::VectorComponentWise + } + Bo::InclusiveOr if 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 { + 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, kind, .. } => { + let s = back::vector_size_str(size); + if let crate::ScalarKind::Uint = 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 { kind, .. } => { + if let crate::ScalarKind::Uint = 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, kind, .. } => { + let s = back::vector_size_str(size); + + if let crate::ScalarKind::Uint = 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 { kind, .. } => { + if let crate::ScalarKind::Uint = 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, kind, .. } => { + let s = back::vector_size_str(size); + + if let crate::ScalarKind::Uint = 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 { kind, .. } => { + if let crate::ScalarKind::Uint = 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 { + kind: crate::ScalarKind::Uint, + .. + } => Some(None), + crate::TypeInner::Vector { + 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(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, width, .. } => Some(TypeInner::Vector { + size, + width, + kind: target_kind, + }), + _ => 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")? + } + }; + + 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 { + kind: crate::ScalarKind::Uint, + .. + } => Some(None), + TypeInner::Vector { + size, + 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(kind, 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 { kind, .. } | TypeInner::Atomic { kind, .. } => { + self.write_zero_init_scalar(kind)?; + } + TypeInner::Vector { kind, .. } => { + self.write_value_type(inner)?; + write!(self.out, "(")?; + self.write_zero_init_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")?, + } + + 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); + } + } + } + } + + 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); + } + _ => (), + }, + } + } + + Ok(ReflectionInfo { + texture_mapping, + uniforms, + varying: mem::take(&mut self.varying), + }) + } +} + +/// 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( + kind: crate::ScalarKind, + width: crate::Bytes, +) -> Result, Error> { + use crate::ScalarKind as Sk; + + Ok(match kind { + Sk::Sint => ScalarString { + prefix: "i", + full: "int", + }, + Sk::Uint => ScalarString { + prefix: "u", + full: "uint", + }, + Sk::Float => match width { + 4 => ScalarString { + prefix: "", + full: "float", + }, + 8 => ScalarString { + prefix: "d", + full: "double", + }, + _ => return Err(Error::UnsupportedScalar(kind, width)), + }, + Sk::Bool => ScalarString { + prefix: "b", + full: "bool", + }, + }) +} + +/// Helper function that returns the glsl variable name for a builtin +const fn glsl_built_in( + built_in: crate::BuiltIn, + output: bool, + targetting_webgl: bool, +) -> &'static str { + use crate::BuiltIn as Bi; + + match built_in { + Bi::Position { .. } => { + if output { + "gl_Position" + } else { + "gl_FragCoord" + } + } + Bi::ViewIndex if 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 => "uint(gl_InstanceID)", + 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 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..19bde6926a --- /dev/null +++ b/naga/src/back/hlsl/conv.rs @@ -0,0 +1,217 @@ +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 => unreachable!(), + } + } + + /// Helper function that returns scalar related strings + /// + /// + pub(super) const fn to_hlsl_str(self, width: crate::Bytes) -> Result<&'static str, Error> { + match self { + Self::Sint => Ok("int"), + Self::Uint => Ok("uint"), + Self::Float => match width { + 2 => Ok("half"), + 4 => Ok("float"), + 8 => Ok("double"), + _ => Err(Error::UnsupportedScalar(self, width)), + }, + Self::Bool => Ok("bool"), + } + } +} + +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, + width, + } => { + let stride = Alignment::from(rows) * width as u32; + let last_row_size = rows as u32 * 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 { kind, width } => Cow::Borrowed(kind.to_hlsl_str(width)?), + crate::TypeInner::Vector { size, kind, width } => Cow::Owned(format!( + "{}{}", + kind.to_hlsl_str(width)?, + crate::back::vector_size_str(size) + )), + crate::TypeInner::Matrix { + columns, + rows, + width, + } => Cow::Owned(format!( + "{}{}x{}", + crate::ScalarKind::Float.to_hlsl_str(width)?, + 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..fcb9949fe1 --- /dev/null +++ b/naga/src/back/hlsl/help.rs @@ -0,0 +1,1143 @@ +/*! +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 = kind.to_hlsl_str(4)?; + 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, width, .. } => crate::TypeInner::Vector { + size: rows, + kind: crate::ScalarKind::Float, + width, + }, + _ => 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 { width, .. } => crate::TypeInner::Scalar { + kind: crate::ScalarKind::Float, + width, + }, + _ => 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..f3a6f9106c --- /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:?} {1:?}")] + UnsupportedScalar(crate::ScalarKind, crate::Bytes), + #[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..1e02e9e502 --- /dev/null +++ b/naga/src/back/hlsl/storage.rs @@ -0,0 +1,506 @@ +/*! +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 { kind, width: _ } => { + // 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 = 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, + kind, + width: _, + } => { + // 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 = 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, + width, + } => { + write!( + self.out, + "{}{}x{}(", + crate::ScalarKind::Float.to_hlsl_str(width)?, + columns as u8, + rows as u8, + )?; + + // Note: Matrices containing vec3s, due to padding, act like they contain vec4s. + let row_stride = Alignment::from(rows) * width as u32; + let iter = (0..columns as u32).map(|i| { + let ty_inner = crate::TypeInner::Vector { + size: rows, + kind: crate::ScalarKind::Float, + width, + }; + (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, + width, + } => { + // first, assign the value to a temporary + writeln!(self.out, "{level}{{")?; + let depth = level.0 + 1; + write!( + self.out, + "{}{}{}x{} {}{} = ", + level.next(), + crate::ScalarKind::Float.to_hlsl_str(width)?, + 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) * 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, + kind: crate::ScalarKind::Float, + width, + }; + 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 { width, .. } => Parent::Array { + stride: width as u32, + }, + crate::TypeInner::Matrix { rows, width, .. } => Parent::Array { + // The stride between matrices is the count of rows as this is how + // long each column is. + stride: Alignment::from(rows) * width as u32, + }, + _ => unreachable!(), + }, + crate::TypeInner::ValuePointer { width, .. } => Parent::Array { + stride: 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..f26604476a --- /dev/null +++ b/naga/src/back/hlsl/writer.rs @@ -0,0 +1,3360 @@ +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_BASE_VERTEX: &str = "base_vertex"; +const SPECIAL_BASE_INSTANCE: &str = "base_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_BASE_VERTEX)?; + writeln!(self.out, "{}int {};", back::INDENT, SPECIAL_BASE_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, + width, + } if member.binding.is_none() && rows == crate::VectorSize::Bi => { + let vec_ty = crate::TypeInner::Vector { + size: rows, + kind: crate::ScalarKind::Float, + width, + }; + 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 { kind, width } | TypeInner::Atomic { kind, width } => { + write!(self.out, "{}", kind.to_hlsl_str(width)?)?; + } + TypeInner::Vector { size, kind, width } => { + write!( + self.out, + "{}{}", + kind.to_hlsl_str(width)?, + back::vector_size_str(size) + )?; + } + TypeInner::Matrix { + columns, + rows, + width, + } => { + // 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{}", + crate::ScalarKind::Float.to_hlsl_str(width)?, + 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::Bool(value) => write!(self.out, "{}", value)?, + }, + 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 for base vertex/instance + 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_BASE_VERTEX} + ")?; + ")" + } + Some(crate::BuiltIn::InstanceIndex) => { + write!(self.out, "({SPECIAL_CBUF_VAR}.{SPECIAL_BASE_INSTANCE} + ",)?; + ")" + } + Some(crate::BuiltIn::NumWorkGroups) => { + //Note: despite their names (`BASE_VERTEX` and `BASE_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_BASE_VERTEX}, {SPECIAL_CBUF_VAR}.{SPECIAL_BASE_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) => { + match *inner { + TypeInner::Vector { size, .. } => { + write!( + self.out, + "{}{}(", + kind.to_hlsl_str(dst_width)?, + back::vector_size_str(size) + )?; + } + TypeInner::Scalar { .. } => { + write!(self.out, "{}(", kind.to_hlsl_str(dst_width)?,)?; + } + TypeInner::Matrix { columns, rows, .. } => { + write!( + self.out, + "{}{}x{}(", + kind.to_hlsl_str(dst_width)?, + 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, kind, .. } => { + let s = match size { + crate::VectorSize::Bi => ".xx", + crate::VectorSize::Tri => ".xxx", + crate::VectorSize::Quad => ".xxxx", + }; + + if let ScalarKind::Uint = 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 { kind, .. } => { + if let ScalarKind::Uint = 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, kind, .. } => { + let s = match size { + crate::VectorSize::Bi => ".xx", + crate::VectorSize::Tri => ".xxx", + crate::VectorSize::Quad => ".xxxx", + }; + + if let ScalarKind::Uint = 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 { kind, .. } => { + if let ScalarKind::Uint = 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, + width, + } => Some(MatrixType { + columns, + rows, + 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, + width, + } => { + mat_data = Some(MatrixType { + columns, + rows, + 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, + width, + } => { + mat_data = Some(MatrixType { + columns, + rows, + 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..f34b618db8 --- /dev/null +++ b/naga/src/back/msl/keywords.rs @@ -0,0 +1,219 @@ +//TODO: find a complete list +pub const RESERVED: &[&str] = &[ + // control flow + "break", + "if", + "else", + "continue", + "goto", + "do", + "while", + "for", + "switch", + "case", + // types and values + "void", + "unsigned", + "signed", + "bool", + "char", + "int", + "uint", + "long", + "float", + "double", + "char8_t", + "wchar_t", + "true", + "false", + "nullptr", + "union", + "class", + "struct", + "enum", + // other + "main", + "using", + "decltype", + "sizeof", + "typeof", + "typedef", + "explicit", + "export", + "friend", + "namespace", + "operator", + "public", + "template", + "typename", + "typeid", + "co_await", + "co_return", + "co_yield", + "module", + "import", + "ray_data", + "vec_step", + "visible", + "as_type", + "this", + // qualifiers + "mutable", + "static", + "volatile", + "restrict", + "const", + "non-temporal", + "dereferenceable", + "invariant", + // exceptions + "throw", + "try", + "catch", + // operators + "const_cast", + "dynamic_cast", + "reinterpret_cast", + "static_cast", + "new", + "delete", + "and", + "and_eq", + "bitand", + "bitor", + "compl", + "not", + "not_eq", + "or", + "or_eq", + "xor", + "xor_eq", + "compl", + // Metal-specific + "constant", + "device", + "threadgroup", + "threadgroup_imageblock", + "kernel", + "compute", + "vertex", + "fragment", + "read_only", + "write_only", + "read_write", + "auto", + // Metal reserved types + "llong", + "ullong", + "quad", + "complex", + "imaginary", + // Metal constants + "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", + "clamped_lod_e", + 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..3b95fa3781 --- /dev/null +++ b/naga/src/back/msl/sampler.rs @@ -0,0 +1,175 @@ +#[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(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..09f7b1c73f --- /dev/null +++ b/naga/src/back/msl/writer.rs @@ -0,0 +1,4624 @@ +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, + kind: crate::ScalarKind, + sizes: &[crate::VectorSize], +) -> Result<(), FmtError> { + match (kind, sizes) { + (kind, &[]) => { + write!(out, "{}", kind.to_msl_name()) + } + (kind, &[rows]) => { + write!( + out, + "{}::{}{}", + NAMESPACE, + kind.to_msl_name(), + back::vector_size_str(rows) + ) + } + (kind, &[rows, columns]) => { + write!( + out, + "{}::{}{}x{}", + NAMESPACE, + kind.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 { kind, .. } => put_numeric_type(out, kind, &[]), + crate::TypeInner::Atomic { kind, .. } => { + write!(out, "{}::atomic_{}", NAMESPACE, kind.to_msl_name()) + } + crate::TypeInner::Vector { size, kind, .. } => put_numeric_type(out, kind, &[size]), + crate::TypeInner::Matrix { columns, rows, .. } => { + put_numeric_type(out, crate::ScalarKind::Float, &[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, + kind, + width: _, + space, + } => { + match space.to_msl_name() { + Some(name) => write!(out, "{name} ")?, + None => return Ok(()), + }; + match size { + Some(rows) => put_numeric_type(out, kind, &[rows])?, + None => put_numeric_type(out, kind, &[])?, + }; + + 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 = kind.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::ScalarKind { + const fn to_msl_name(self) -> &'static str { + match self { + Self::Float => "float", + Self::Sint => "int", + Self::Uint => "uint", + Self::Bool => "bool", + } + } +} + +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]; + //Note: this is imperfect - the same structure can be used for host-shared + // things, where packed float would matter. + if member.binding.is_some() { + return None; + } + + 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, + width: 4, + kind, + } if member.offset & 0xF != 0 || is_tight => Some(kind), + _ => 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 coordinate_type = kind.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::ScalarKind::Uint, &[])? + } + crate::TypeInner::Vector { size, .. } => { + put_numeric_type(&mut self.out, crate::ScalarKind::Uint, &[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::Bool(value) => { + write!(self.out, "{value}")?; + } + }, + 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_kind = match *get_expr_ty(ctx, value).inner_with(&module.types) { + crate::TypeInner::Scalar { kind, .. } => kind, + _ => return Err(Error::Validation), + }; + put_numeric_type(&mut self.out, scalar_kind, &[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 { + 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 { + 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 { + 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, kind, .. } => { + let size = back::vector_size_str(size); + if let crate::ScalarKind::Sint = kind { + write!(self.out, "int{size}")?; + } else { + write!(self.out, "uint{size}")?; + } + } + crate::TypeInner::Scalar { kind, .. } => { + if let crate::ScalarKind::Sint = 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 { + kind: src_kind, + width: src_width, + } + | crate::TypeInner::Vector { + kind: src_kind, + width: 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, kind, &[size])? + } + _ => put_numeric_type(&mut self.out, kind, &[])?, + }; + write!(self.out, ">(")?; + self.put_expression(expr, context, true)?; + write!(self.out, ")")?; + } + crate::TypeInner::Matrix { columns, rows, .. } => { + put_numeric_type(&mut self.out, kind, &[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_kind) = context.get_packed_vec_kind(expr_handle) { + write!(self.out, "{}::{}3(", NAMESPACE, scalar_kind.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 { 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::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 { kind, .. }) => { + put_numeric_type(&mut self.out, kind, &[])?; + } + TypeResolution::Value(crate::TypeInner::Vector { size, kind, .. }) => { + put_numeric_type(&mut self.out, kind, &[size])?; + } + TypeResolution::Value(crate::TypeInner::Matrix { columns, rows, .. }) => { + put_numeric_type(&mut self.out, crate::ScalarKind::Float, &[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, + &[], + &[], + &[], + &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() { + // quick and dirty way to figure out if we need this... + if member.binding.is_none() && 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(kind) => { + writeln!( + self.out, + "{}{}::packed_{}3 {};", + back::INDENT, + NAMESPACE, + kind.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, + kind: _, + width, + } = *ty_inner + { + last_offset += 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.named_expressions.clear(); + self.update_expressions_to_bake(fun, fun_info, &context.expression); + self.put_block(back::Level(1), &fun.body, &context)?; + writeln!(self.out, "}}")?; + } + + 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, ", ")?; + } + 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.named_expressions.clear(); + 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)?; + } + } + + 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..0471d957f0 --- /dev/null +++ b/naga/src/back/spv/block.rs @@ -0,0 +1,2373 @@ +/*! +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, + kind: crate::ScalarKind::Float, + width: 4, + 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, + kind: crate::ScalarKind::Float, + width: 4, + 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, + kind: crate::ScalarKind::Float, + width: 4, + 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 { kind, .. } + | crate::TypeInner::Vector { kind, .. } => match kind { + crate::ScalarKind::Float => spirv::Op::FAdd, + _ => spirv::Op::IAdd, + }, + crate::TypeInner::Matrix { + columns, + rows, + width, + } => { + self.write_matrix_matrix_column_op( + block, + id, + result_type_id, + left_id, + right_id, + columns, + rows, + width, + spirv::Op::FAdd, + ); + + self.cached[expr_handle] = id; + return Ok(()); + } + _ => unimplemented!(), + }, + crate::BinaryOperator::Subtract => match *left_ty_inner { + crate::TypeInner::Scalar { kind, .. } + | crate::TypeInner::Vector { kind, .. } => match kind { + crate::ScalarKind::Float => spirv::Op::FSub, + _ => spirv::Op::ISub, + }, + crate::TypeInner::Matrix { + columns, + rows, + width, + } => { + self.write_matrix_matrix_column_op( + block, + id, + result_type_id, + left_id, + right_id, + columns, + rows, + 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, width) = match *arg_ty { + crate::TypeInner::Vector { size, width, .. } => (Some(size), width), + crate::TypeInner::Scalar { width, .. } => (None, width), + ref other => unimplemented!("Unexpected saturate({:?})", other), + }; + let kind = crate::ScalarKind::Float; + let mut arg1_id = self.writer.get_constant_scalar_with(0, kind, width)?; + let mut arg2_id = self.writer.get_constant_scalar_with(1, kind, width)?; + + if let Some(size) = maybe_size { + let ty = LocalType::Value { + vector_size: Some(size), + kind, + width, + 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 { + 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 { kind, width }, + ) => { + let selector_type_id = + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: Some(size), + kind, + width, + 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 kind = crate::ScalarKind::Uint; + + let uint_id = match *arg_ty { + crate::TypeInner::Vector { size, width, .. } => { + let ty = LocalType::Value { + vector_size: Some(size), + kind, + width, + pointer_space: None, + } + .into(); + + self.temp_list.clear(); + self.temp_list.resize( + size as _, + self.writer.get_constant_scalar_with(32, kind, width)?, + ); + + self.writer.get_constant_composite(ty, &self.temp_list) + } + crate::TypeInner::Scalar { width, .. } => { + self.writer.get_constant_scalar_with(32, kind, width)? + } + _ => 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 kind = crate::ScalarKind::Sint; + + let (int_type_id, int_id) = match *arg_ty { + crate::TypeInner::Vector { size, width, .. } => { + let ty = LocalType::Value { + vector_size: Some(size), + kind, + width, + pointer_space: None, + } + .into(); + + self.temp_list.clear(); + self.temp_list.resize( + size as _, + self.writer.get_constant_scalar_with(31, kind, width)?, + ); + + ( + self.get_type_id(ty), + self.writer.get_constant_composite(ty, &self.temp_list), + ) + } + crate::TypeInner::Scalar { width, .. } => ( + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + kind, + width, + pointer_space: None, + })), + self.writer.get_constant_scalar_with(31, kind, width)?, + ), + _ => 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_kind, src_size, src_width, is_matrix) = + match *self.fun_info[expr].ty.inner_with(&self.ir_module.types) { + crate::TypeInner::Scalar { kind, width } => (kind, None, width, false), + crate::TypeInner::Vector { kind, width, size } => { + (kind, Some(size), width, false) + } + crate::TypeInner::Matrix { width, .. } => (kind, None, width, 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_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.unwrap_or(src_width) == src_width => + { + 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_kind { + Sk::Sint | Sk::Uint => spirv::Op::INotEqual, + Sk::Float => spirv::Op::FUnordNotEqual, + Sk::Bool => unreachable!(), + }; + let zero_scalar_id = self + .writer + .get_constant_scalar_with(0, src_kind, src_width)?; + let zero_id = match src_size { + Some(size) => { + let ty = LocalType::Value { + vector_size: Some(size), + kind: src_kind, + width: src_width, + 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 zero_scalar_id = + self.writer.get_constant_scalar_with(0, kind, dst_width)?; + let one_scalar_id = + self.writer.get_constant_scalar_with(1, kind, dst_width)?; + let (accept_id, reject_id) = match src_size { + Some(size) => { + let ty = LocalType::Value { + vector_size: Some(size), + kind, + width: dst_width, + 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_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_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_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 { + kind: crate::ScalarKind::Bool, + width, + }, + &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), + kind: crate::ScalarKind::Bool, + width, + 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 of buffers 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] + { + let gvar: &crate::GlobalVariable = + &self.ir_module.global_variables[var_handle]; + match gvar.space { + crate::AddressSpace::Storage { .. } | crate::AddressSpace::Uniform => { + 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 buffer 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), + kind: crate::ScalarKind::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, 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 { + kind: crate::ScalarKind::Sint, + width: _, + } => spirv::Op::AtomicSMin, + crate::TypeInner::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 { + kind: crate::ScalarKind::Sint, + width: _, + } => spirv::Op::AtomicSMax, + crate::TypeInner::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 { kind, width } => { + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + kind, + width, + pointer_space: None, + })) + } + _ => unimplemented!(), + }; + let bool_type_id = + self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + kind: crate::ScalarKind::Bool, + width: crate::BOOL_WIDTH, + 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..1630dc0ddb --- /dev/null +++ b/naga/src/back/spv/image.rs @@ -0,0 +1,1217 @@ +/*! +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), + kind: crate::ScalarKind::Float, + width: 4, + 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_kind, size) = match *inner_ty { + Ti::Scalar { kind, width: 4 } => (kind, Some(Vs::Bi)), + Ti::Vector { + kind, + width: 4, + size: Vs::Bi, + } => (kind, Some(Vs::Tri)), + Ti::Vector { + kind, + width: 4, + size: Vs::Tri, + } => (kind, 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_kind = if let Ti::Scalar { kind, width: 4 } = *inner_ty { + debug_assert!(matches!( + kind, + crate::ScalarKind::Sint | crate::ScalarKind::Uint + )); + kind + } else { + unreachable!("we only allow i32 and u32"); + }; + let cast = match (component_kind, array_index_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") + } + }; + 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, + kind: component_kind, + width: 4, + 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, + kind: component_kind, + width: 4, + 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, + kind: crate::ScalarKind::Sint, + width: 4, + 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, + kind: crate::ScalarKind::Sint, + width: 4, + 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, + kind: crate::ScalarKind::Bool, + width: 1, + 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), + kind: crate::ScalarKind::Float, + width: 4, + 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, + kind: crate::ScalarKind::Uint, + width: 4, + 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), + kind: crate::ScalarKind::Uint, + width: 4, + 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..ac7281fc6b --- /dev/null +++ b/naga/src/back/spv/mod.rs @@ -0,0 +1,755 @@ +/*! +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, + kind: crate::ScalarKind, + width: crate::Bytes, + 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 { kind, width } | crate::TypeInner::Atomic { kind, width } => { + LocalType::Value { + vector_size: None, + kind, + width, + pointer_space: None, + } + } + crate::TypeInner::Vector { size, kind, width } => LocalType::Value { + vector_size: Some(size), + kind, + width, + pointer_space: None, + }, + crate::TypeInner::Matrix { + columns, + rows, + width, + } => LocalType::Matrix { + columns, + rows, + width, + }, + crate::TypeInner::Pointer { base, space } => LocalType::Pointer { + base, + class: helpers::map_storage_class(space), + }, + crate::TypeInner::ValuePointer { + size, + kind, + width, + space, + } => LocalType::Value { + vector_size: size, + kind, + width, + 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..ed61129f92 --- /dev/null +++ b/naga/src/back/spv/ray.rs @@ -0,0 +1,270 @@ +/*! +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 width = 4; + + let flag_type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + kind: crate::ScalarKind::Uint, + width, + 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, + kind: crate::ScalarKind::Float, + width, + 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), + kind: crate::ScalarKind::Float, + width, + 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 width = 4; + 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, + kind: crate::ScalarKind::Uint, + width, + 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, + kind: crate::ScalarKind::Float, + width, + 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), + kind: crate::ScalarKind::Float, + width, + 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, + kind: crate::ScalarKind::Bool, + width: crate::BOOL_WIDTH, + 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, + })); + 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..24cb14a161 --- /dev/null +++ b/naga/src/back/spv/writer.rs @@ -0,0 +1,2067 @@ +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, + kind: crate::ScalarKind::Uint, + width: 4, + 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, + kind: crate::ScalarKind::Float, + width: 4, + 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), + kind: crate::ScalarKind::Uint, + width: 4, + 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, + kind: crate::ScalarKind::Float, + width: 4, + 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), + kind: crate::ScalarKind::Uint, + width: 4, + 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, + kind: crate::ScalarKind::Bool, + width: 1, + 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), + kind: crate::ScalarKind::Bool, + width: 1, + 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, + kind: crate::ScalarKind, + width: crate::Bytes, + ) -> Instruction { + use crate::ScalarKind as Sk; + + let bits = (width * BITS_PER_BYTE) as u32; + match kind { + Sk::Sint | Sk::Uint => { + let signedness = if 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), + } + } + + 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, + kind, + width, + pointer_space: None, + } => self.make_scalar(id, kind, width), + LocalType::Value { + vector_size: Some(size), + kind, + width, + pointer_space: None, + } => { + let scalar_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size: None, + kind, + width, + 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), + kind: crate::ScalarKind::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, + kind, + width, + pointer_space: Some(class), + } => { + let type_id = self.get_type_id(LookupType::Local(LocalType::Value { + vector_size, + kind, + width, + pointer_space: None, + })); + Instruction::type_pointer(id, class, type_id) + } + LocalType::Image(image) => { + let local_type = LocalType::Value { + vector_size: None, + 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, + kind: crate::ScalarKind, + width: crate::Bytes, + ) -> Result { + Ok( + self.get_constant_scalar(crate::Literal::new(value, kind, width).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, + kind: value.scalar_kind(), + width: value.width(), + 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::Bool(true) => Instruction::constant_true(type_id, id), + crate::Literal::Bool(false) => Instruction::constant_false(type_id, id), + }; + + 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 { kind, .. } + | crate::TypeInner::Vector { kind, .. } => match kind { + Sk::Uint | Sk::Sint | Sk::Bool => true, + Sk::Float => false, + }, + _ => 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, + width, + } = *member_array_subty_inner + { + let byte_stride = Alignment::from(rows) * 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..075d85558c --- /dev/null +++ b/naga/src/back/wgsl/writer.rs @@ -0,0 +1,1922 @@ +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, kind, width } => write!( + self.out, + "vec{}<{}>", + back::vector_size_str(size), + scalar_kind_str(kind, width), + )?, + 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(kind, 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 { kind, width } => { + write!(self.out, "{}", scalar_kind_str(kind, width))?; + } + TypeInner::Atomic { kind, width } => { + write!(self.out, "atomic<{}>", scalar_kind_str(kind, width))?; + } + 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, + width: _, + } => { + write!( + self.out, + //TODO: Can matrix be other than f32? + "mat{}x{}", + back::vector_size_str(columns), + back::vector_size_str(rows), + )?; + } + 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, + kind, + width, + space, + } => { + let (address, maybe_access) = address_space_str(space); + if let Some(space) = address { + write!(self.out, "ptr<{}, {}", space, scalar_kind_str(kind, width))?; + 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), + kind, + width, + 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(kind, width) + )?; + 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 { + // Floats are written using `Debug` instead of `Display` because it always appends the + // decimal part even it's zero + crate::Literal::F64(_) => { + return Err(Error::Custom("unsupported f64 literal".to_string())); + } + 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::Bool(value) => write!(self.out, "{}", value)?, + } + } + 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, + width, + .. + } => { + let scalar_kind_str = scalar_kind_str(kind, convert.unwrap_or(width)); + write!( + self.out, + "mat{}x{}<{}>", + back::vector_size_str(columns), + back::vector_size_str(rows), + scalar_kind_str + )?; + } + TypeInner::Vector { size, width, .. } => { + let vector_size_str = back::vector_size_str(size); + let scalar_kind_str = scalar_kind_str(kind, convert.unwrap_or(width)); + if convert.is_some() { + write!(self.out, "vec{vector_size_str}<{scalar_kind_str}>")?; + } else { + write!(self.out, "bitcast>")?; + } + } + TypeInner::Scalar { width, .. } => { + let scalar_kind_str = scalar_kind_str(kind, convert.unwrap_or(width)); + 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(kind: crate::ScalarKind, width: u8) -> &'static str { + use crate::ScalarKind as Sk; + + match (kind, width) { + (Sk::Float, 8) => "f64", + (Sk::Float, 4) => "f32", + (Sk::Sint, 4) => "i32", + (Sk::Uint, 4) => "u32", + (Sk::Bool, 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..b375132ef7 --- /dev/null +++ b/naga/src/block.rs @@ -0,0 +1,144 @@ +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(feature = "span")] + #[cfg_attr(feature = "serialize", serde(skip))] + span_info: Vec, +} + +impl Block { + pub const fn new() -> Self { + Self { + body: Vec::new(), + #[cfg(feature = "span")] + span_info: Vec::new(), + } + } + + pub fn from_vec(body: Vec) -> Self { + #[cfg(feature = "span")] + let span_info = std::iter::repeat(Span::default()) + .take(body.len()) + .collect(); + Self { + body, + #[cfg(feature = "span")] + span_info, + } + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + body: Vec::with_capacity(capacity), + #[cfg(feature = "span")] + span_info: Vec::with_capacity(capacity), + } + } + + #[allow(unused_variables)] + pub fn push(&mut self, end: Statement, span: Span) { + self.body.push(end); + #[cfg(feature = "span")] + 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) { + #[cfg(feature = "span")] + self.span_info.extend(other.span_info); + self.body.extend(other.body); + } + + pub fn append(&mut self, other: &mut Self) { + #[cfg(feature = "span")] + self.span_info.append(&mut other.span_info); + self.body.append(&mut other.body); + } + + pub fn cull + Clone>(&mut self, range: R) { + #[cfg(feature = "span")] + self.span_info.drain(range.clone()); + self.body.drain(range); + } + + pub fn splice + Clone>(&mut self, range: R, other: Self) { + #[cfg(feature = "span")] + self.span_info.splice(range.clone(), other.span_info); + self.body.splice(range, other.body); + } + pub fn span_iter(&self) -> impl Iterator { + #[cfg(feature = "span")] + let span_iter = self.span_info.iter(); + #[cfg(not(feature = "span"))] + let span_iter = std::iter::repeat_with(|| &Span::UNDEFINED); + + self.body.iter().zip(span_iter) + } + + pub fn span_iter_mut(&mut self) -> impl Iterator)> { + #[cfg(feature = "span")] + let span_iter = self.span_info.iter_mut().map(Some); + #[cfg(not(feature = "span"))] + let span_iter = std::iter::repeat_with(|| None); + + 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..c1326e92be --- /dev/null +++ b/naga/src/compact/expressions.rs @@ -0,0 +1,394 @@ +use super::{HandleMap, HandleSet, ModuleMap}; +use crate::arena::{Arena, Handle, UniqueArena}; + +pub struct ExpressionTracer<'tracer> { + pub types: &'tracer UniqueArena, + 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 constant expression arena and its used map, if we haven't + /// switched to tracing constant expressions yet. + pub const_expressions: Option<( + &'tracer Arena, + &'tracer mut HandleSet, + )>, +} + +impl<'tracer> ExpressionTracer<'tracer> { + pub fn trace_expression(&mut self, expr: Handle) { + log::trace!( + "entering trace_expression of {}", + if self.const_expressions.is_some() { + "function expressions" + } else { + "const expressions" + } + ); + let mut work_list = vec![expr]; + while let Some(expr) = work_list.pop() { + // If we've already seen this expression, no need to trace further. + if !self.expressions_used.insert(expr) { + continue; + } + log::trace!("tracing new expression {:?}", expr); + + use crate::Expression as Ex; + match self.expressions[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); + let constant = &self.constants[handle]; + self.trace_type(constant.ty); + self.trace_const_expression(constant.init); + } + Ex::ZeroValue(ty) => self.trace_type(ty), + Ex::Compose { ty, ref components } => { + self.trace_type(ty); + work_list.extend(components); + } + Ex::Access { base, index } => work_list.extend([base, index]), + Ex::AccessIndex { base, index: _ } => work_list.push(base), + Ex::Splat { size: _, value } => work_list.push(value), + Ex::Swizzle { + size: _, + vector, + pattern: _, + } => work_list.push(vector), + Ex::Load { pointer } => work_list.push(pointer), + Ex::ImageSample { + image, + sampler, + gather: _, + coordinate, + array_index, + offset, + ref level, + depth_ref, + } => { + work_list.push(image); + work_list.push(sampler); + work_list.push(coordinate); + work_list.extend(array_index); + if let Some(offset) = offset { + self.trace_const_expression(offset); + } + use crate::SampleLevel as Sl; + match *level { + Sl::Auto | Sl::Zero => {} + Sl::Exact(expr) | Sl::Bias(expr) => work_list.push(expr), + Sl::Gradient { x, y } => work_list.extend([x, y]), + } + work_list.extend(depth_ref); + } + Ex::ImageLoad { + image, + coordinate, + array_index, + sample, + level, + } => { + work_list.push(image); + work_list.push(coordinate); + work_list.extend(array_index); + work_list.extend(sample); + work_list.extend(level); + } + Ex::ImageQuery { image, ref query } => { + work_list.push(image); + use crate::ImageQuery as Iq; + match *query { + Iq::Size { level } => work_list.extend(level), + Iq::NumLevels | Iq::NumLayers | Iq::NumSamples => {} + } + } + Ex::Unary { op: _, expr } => work_list.push(expr), + Ex::Binary { op: _, left, right } => work_list.extend([left, right]), + Ex::Select { + condition, + accept, + reject, + } => work_list.extend([condition, accept, reject]), + Ex::Derivative { + axis: _, + ctrl: _, + expr, + } => work_list.push(expr), + Ex::Relational { fun: _, argument } => work_list.push(argument), + Ex::Math { + fun: _, + arg, + arg1, + arg2, + arg3, + } => { + work_list.push(arg); + work_list.extend(arg1); + work_list.extend(arg2); + work_list.extend(arg3); + } + Ex::As { + expr, + kind: _, + convert: _, + } => work_list.push(expr), + Ex::AtomicResult { ty, comparison: _ } => self.trace_type(ty), + Ex::WorkGroupUniformLoadResult { ty } => self.trace_type(ty), + Ex::ArrayLength(expr) => work_list.push(expr), + Ex::RayQueryGetIntersection { + query, + committed: _, + } => work_list.push(query), + } + } + } + + fn trace_type(&mut self, ty: Handle) { + let mut types_used = super::types::TypeTracer { + types: self.types, + types_used: self.types_used, + }; + types_used.trace_type(ty); + } + + pub fn as_const_expression(&mut self) -> ExpressionTracer { + match self.const_expressions { + Some((ref mut exprs, ref mut exprs_used)) => ExpressionTracer { + expressions: exprs, + expressions_used: exprs_used, + types: self.types, + constants: self.constants, + types_used: self.types_used, + constants_used: self.constants_used, + const_expressions: None, + }, + None => ExpressionTracer { + types: self.types, + constants: self.constants, + expressions: self.expressions, + types_used: self.types_used, + constants_used: self.constants_used, + expressions_used: self.expressions_used, + const_expressions: None, + }, + } + } + + fn trace_const_expression(&mut self, const_expr: Handle) { + self.as_const_expression().trace_expression(const_expr); + } +} + +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..752c3eb7f1 --- /dev/null +++ b/naga/src/compact/functions.rs @@ -0,0 +1,121 @@ +use super::handle_set_map::HandleSet; +use super::{FunctionMap, ModuleMap}; +use crate::arena::Handle; + +pub struct FunctionTracer<'a> { + pub module: &'a crate::Module, + pub function: &'a crate::Function, + + 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.trace_type(argument.ty); + } + + if let Some(ref result) = self.function.result { + self.trace_type(result.ty); + } + + for (_, local) in self.function.local_variables.iter() { + self.trace_type(local.ty); + if let Some(init) = local.init { + self.trace_expression(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.trace_expression(*value); + } + + self.trace_block(&self.function.body); + } + + pub fn trace_type(&mut self, ty: Handle) { + self.as_type().trace_type(ty) + } + + pub fn trace_expression(&mut self, expr: Handle) { + self.as_expression().trace_expression(expr); + } + + fn as_type(&mut self) -> super::types::TypeTracer { + super::types::TypeTracer { + types: &self.module.types, + types_used: self.types_used, + } + } + + fn as_expression(&mut self) -> super::expressions::ExpressionTracer { + super::expressions::ExpressionTracer { + types: &self.module.types, + constants: &self.module.constants, + expressions: &self.function.expressions, + + types_used: self.types_used, + constants_used: self.constants_used, + expressions_used: &mut self.expressions_used, + const_expressions: Some(( + &self.module.const_expressions, + &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..bf74d3f0b9 --- /dev/null +++ b/naga/src/compact/handle_set_map.rs @@ -0,0 +1,158 @@ +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. + /// + /// Return `true` if the handle was not already in the set. In + /// other words, return true if it was newly inserted. + pub fn insert(&mut self, handle: Handle) -> bool { + // Note that, oddly, `Handle::index` does not return a 1-based + // `Index`, but rather a zero-based `usize`. + self.members.insert(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 { + 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..137f3bbe30 --- /dev/null +++ b/naga/src/compact/mod.rs @@ -0,0 +1,286 @@ +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.as_type().trace_type(global.ty); + if let Some(init) = global.init { + module_tracer.as_const_expression().trace_expression(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.as_type().trace_type(constant.ty); + module_tracer + .as_const_expression() + .trace_expression(constant.init); + } + } + + // We assume that all functions are used. + // + // Observe which types, constant expressions, constants, and + // expressions each function uses, and produce maps from + // pre-compaction to post-compaction 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.enter_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.enter_function(&e.function); + used.trace(); + FunctionMap::from(used) + }) + .collect(); + + // 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; + + let mut type_tracer = self.as_type(); + if let Some(ray_desc) = *ray_desc { + type_tracer.trace_type(ray_desc); + } + if let Some(ray_intersection) = *ray_intersection { + type_tracer.trace_type(ray_intersection); + } + for (_, &handle) in predeclared_types { + type_tracer.trace_type(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 { + types: &self.module.types, + constants: &self.module.constants, + expressions: &self.module.const_expressions, + types_used: &mut self.types_used, + constants_used: &mut self.constants_used, + expressions_used: &mut self.const_expressions_used, + const_expressions: None, + } + } + + pub fn enter_function<'tracer>( + &'tracer mut self, + function: &'tracer crate::Function, + ) -> FunctionTracer<'tracer> { + FunctionTracer { + module: self.module, + function, + + 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..4c62771023 --- /dev/null +++ b/naga/src/compact/statements.rs @@ -0,0 +1,294 @@ +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.trace_expression(condition); + worklist.push(accept); + worklist.push(reject); + } + St::Switch { + selector, + ref cases, + } => { + self.trace_expression(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.trace_expression(break_if); + } + worklist.push(body); + worklist.push(continuing); + } + St::Return { value: Some(value) } => self.trace_expression(value), + St::Store { pointer, value } => { + self.trace_expression(pointer); + self.trace_expression(value); + } + St::ImageStore { + image, + coordinate, + array_index, + value, + } => { + self.trace_expression(image); + self.trace_expression(coordinate); + if let Some(array_index) = array_index { + self.trace_expression(array_index); + } + self.trace_expression(value); + } + St::Atomic { + pointer, + ref fun, + value, + result, + } => { + self.trace_expression(pointer); + self.trace_atomic_function(fun); + self.trace_expression(value); + self.trace_expression(result); + } + St::WorkGroupUniformLoad { pointer, result } => { + self.trace_expression(pointer); + self.trace_expression(result); + } + St::Call { + function: _, + ref arguments, + result, + } => { + for expr in arguments { + self.trace_expression(*expr); + } + if let Some(result) = result { + self.trace_expression(result); + } + } + St::RayQuery { query, ref fun } => { + self.trace_expression(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.trace_expression(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.trace_expression(acceleration_structure); + self.trace_expression(descriptor); + } + Qf::Proceed { result } => self.trace_expression(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..d11ab8a731 --- /dev/null +++ b/naga/src/compact/types.rs @@ -0,0 +1,93 @@ +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> { + pub fn trace_type(&mut self, ty: Handle) { + let mut work_list = vec![ty]; + while let Some(ty) = work_list.pop() { + // If we've already seen this type, no need to traverse further. + if !self.types_used.insert(ty) { + continue; + } + + use crate::TypeInner as Ti; + match self.types[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: _ } => work_list.push(base), + Ti::Array { + base, + size: _, + stride: _, + } => work_list.push(base), + Ti::Struct { + ref members, + span: _, + } => { + work_list.extend(members.iter().map(|m| m.ty)); + } + Ti::BindingArray { base, size: _ } => work_list.push(base), + } + } + } +} + +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..c68370645c --- /dev/null +++ b/naga/src/front/glsl/builtins.rs @@ -0,0 +1,2400 @@ +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, 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 width = 4; + + match number_of_components { + 1 => TypeInner::Scalar { kind, width }, + _ => TypeInner::Vector { + size: match number_of_components { + 2 => VectorSize::Bi, + 3 => VectorSize::Tri, + _ => VectorSize::Quad, + }, + kind, + width, + }, + } +} + +/// 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) + } + + let width = 4; + 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 { + kind: Sk::Float, + width, + }); + } + + match level_type { + TextureLevelType::Lod => { + args.push(TypeInner::Scalar { + kind: Sk::Float, + width, + }); + } + 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 { + kind: Sk::Float, + width, + }); + } + + 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 { + kind: Sk::Sint, + width, + }) + } + + 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 { + kind: Sk::Sint, + width, + }, + ]; + + 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, + kind, + width, + }, + ]; + + 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, +) { + let width = 4; + 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 kind = Sk::Float; + + declaration.overloads.push(module.add_builtin( + vec![match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }], + 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 kind = match name { + "intBitsToFloat" => Sk::Sint, + _ => Sk::Uint, + }; + + declaration.overloads.push(module.add_builtin( + vec![match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }], + 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 kind = Sk::Float; + let ty = || match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }; + + 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 kind = match bits >> 2 { + 0b0 => Sk::Float, + _ => Sk::Sint, + }; + + let args = vec![match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }]; + + 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 kind = match bits & 0b1 { + 0b0 => Sk::Sint, + _ => Sk::Uint, + }; + 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, kind, width }, + None => TypeInner::Scalar { kind, width }, + }; + + let mut args = vec![ty()]; + + match fun { + MathFunction::ExtractBits => { + args.push(TypeInner::Scalar { + kind: Sk::Sint, + width: 4, + }); + args.push(TypeInner::Scalar { + kind: Sk::Sint, + width: 4, + }); + } + MathFunction::InsertBits => { + args.push(ty()); + args.push(TypeInner::Scalar { + kind: Sk::Sint, + width: 4, + }); + args.push(TypeInner::Scalar { + kind: Sk::Sint, + width: 4, + }); + } + _ => {} + } + + // we need to cast the return type of findLsb / findMsb + let mc = if 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, + kind: Sk::Float, + width: 4, + }, + MathFunction::Pack2x16unorm + | MathFunction::Pack2x16snorm + | MathFunction::Pack2x16float => TypeInner::Vector { + size: crate::VectorSize::Bi, + kind: Sk::Float, + width: 4, + }, + _ => 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 { + kind: Sk::Uint, + width: 4, + }]; + + 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 kind = Sk::Float; + let ty = || match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }; + + 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, + kind: Sk::Bool, + width: crate::BOOL_WIDTH, + }]; + + 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, kind) = match bits { + 0b0000 => (VectorSize::Bi, Sk::Float), + 0b0001 => (VectorSize::Tri, Sk::Float), + 0b0010 => (VectorSize::Quad, Sk::Float), + 0b0011 => (VectorSize::Bi, Sk::Sint), + 0b0100 => (VectorSize::Tri, Sk::Sint), + 0b0101 => (VectorSize::Quad, Sk::Sint), + 0b0110 => (VectorSize::Bi, Sk::Uint), + 0b0111 => (VectorSize::Tri, Sk::Uint), + _ => (VectorSize::Quad, Sk::Uint), + }; + + let ty = || TypeInner::Vector { size, kind, width }; + 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, kind) = match bits { + 0b0000 => (VectorSize::Bi, Sk::Float), + 0b0001 => (VectorSize::Tri, Sk::Float), + 0b0010 => (VectorSize::Quad, Sk::Float), + 0b0011 => (VectorSize::Bi, Sk::Sint), + 0b0100 => (VectorSize::Tri, Sk::Sint), + 0b0101 => (VectorSize::Quad, Sk::Sint), + 0b0110 => (VectorSize::Bi, Sk::Uint), + 0b0111 => (VectorSize::Tri, Sk::Uint), + 0b1000 => (VectorSize::Quad, Sk::Uint), + 0b1001 => (VectorSize::Bi, Sk::Bool), + 0b1010 => (VectorSize::Tri, Sk::Bool), + _ => (VectorSize::Quad, Sk::Bool), + }; + + let width = if let Sk::Bool = kind { + crate::BOOL_WIDTH + } else { + width + }; + + let ty = || TypeInner::Vector { size, kind, width }; + 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 kind = match bits & 0b11 { + 0b00 => Sk::Float, + 0b01 => Sk::Sint, + 0b10 => Sk::Uint, + _ => 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, kind, width }, + None => TypeInner::Scalar { kind, width }, + }, + match second_size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }, + ]; + + 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 (kind, splatted, boolean) = match bits >> 2 { + 0b000 => (Sk::Sint, false, true), + 0b001 => (Sk::Uint, false, true), + 0b010 => (Sk::Float, false, true), + 0b011 => (Sk::Float, false, false), + _ => (Sk::Float, true, false), + }; + + let ty = |kind, width| match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }; + let args = vec![ + ty(kind, width), + ty(kind, width), + match (boolean, splatted) { + (true, _) => ty(Sk::Bool, crate::BOOL_WIDTH), + (_, false) => TypeInner::Scalar { kind, width }, + _ => ty(kind, width), + }, + ]; + + 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 kind = match bits & 0b11 { + 0b00 => Sk::Float, + 0b01 => Sk::Sint, + 0b10 => Sk::Uint, + _ => 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, kind, width }, + None => TypeInner::Scalar { kind, width }, + }; + let limit_ty = || match splatted { + true => TypeInner::Scalar { kind, width }, + 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) { + let width = 8; + 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 kind = Sk::Float; + + let args = vec![match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }]; + + 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 kind = Sk::Float; + + let args = vec![ + match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }, + match second_size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }, + ]; + + 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 kind = Sk::Float; + let (splatted, boolean) = match bits >> 2 { + 0b00 => (false, false), + 0b01 => (false, true), + _ => (true, false), + }; + + let ty = |kind, width| match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }; + let args = vec![ + ty(kind, width), + ty(kind, width), + match (boolean, splatted) { + (true, _) => ty(Sk::Bool, crate::BOOL_WIDTH), + (_, false) => TypeInner::Scalar { kind, width }, + _ => ty(kind, width), + }, + ]; + + 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 kind = Sk::Float; + 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, kind, width }, + None => TypeInner::Scalar { kind, width }, + }; + let limit_ty = || match splatted { + true => TypeInner::Scalar { kind, width }, + 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" => { + for bits in 0..0b11 { + let (size, kind) = match bits { + 0b00 => (VectorSize::Bi, Sk::Float), + 0b01 => (VectorSize::Tri, Sk::Float), + _ => (VectorSize::Quad, Sk::Float), + }; + + let ty = || TypeInner::Vector { size, kind, width }; + 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, +) { + 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, + kind: Sk::Float, + width: float_width, + }, + None => TypeInner::Scalar { + kind: Sk::Float, + width: float_width, + }, + }]; + + 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 = |kind| match size { + Some(size) => TypeInner::Vector { + size, + kind, + width: float_width, + }, + None => TypeInner::Scalar { + kind, + width: float_width, + }, + }; + + 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_kind = if fun == MacroCall::MathFunction(MathFunction::Ldexp) { + Sk::Sint + } else { + Sk::Float + }; + + declaration + .overloads + .push(module.add_builtin(vec![ty(Sk::Float), ty(second_kind)], 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, + width: float_width, + }], + 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, + width: float_width, + }]; + + 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, + kind: Sk::Float, + width: float_width, + }, + None => TypeInner::Scalar { + kind: Sk::Float, + width: float_width, + }, + }) + } + + 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, + kind: Sk::Float, + width: float_width, + }, + TypeInner::Vector { + size: VectorSize::Tri, + kind: Sk::Float, + width: float_width, + }, + ]; + + 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, + kind: Sk::Float, + width: float_width, + }, + TypeInner::Vector { + size: size2, + kind: Sk::Float, + width: float_width, + }, + ]; + + 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, + kind: Sk::Float, + width: float_width, + }, + None => TypeInner::Scalar { + kind: Sk::Float, + width: float_width, + }, + }; + 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, + kind: Sk::Float, + width: float_width, + }, + None => TypeInner::Scalar { + kind: Sk::Float, + width: float_width, + }, + }; + let args = vec![ + ty(), + ty(), + TypeInner::Scalar { + kind: Sk::Float, + width: 4, + }, + ]; + 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, + kind: Sk::Float, + width: float_width, + }, + None => TypeInner::Scalar { + kind: Sk::Float, + width: float_width, + }, + }; + let ty = || match splatted { + true => TypeInner::Scalar { + kind: Sk::Float, + width: float_width, + }, + 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, + kind: crate::ScalarKind::Uint, + width: 4, + }, + }, + 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, Sk::Sint, 4)?; + } + + 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..a54e718aa9 --- /dev/null +++ b/naga/src/front/glsl/context.rs @@ -0,0 +1,1532 @@ +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, ScalarKind, + 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, ScalarKind::Uint, 4)? + } + _ => 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, + width: left_width, + }, + &TypeInner::Matrix { + columns: right_columns, + rows: right_rows, + width: right_width, + }, + ) => { + 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_width != right_width { + 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, + width: left_width, + }, + }, + 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 { + width: left_width, .. + }, + &TypeInner::Matrix { + rows, + columns, + width: right_width, + }, + ) => { + // Check that the two arguments have the same width + if left_width != right_width { + 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, + width: left_width, + }, + }, + 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, + width: left_width, + }, + &TypeInner::Scalar { + width: right_width, .. + }, + ) => { + // Check that the two arguments have the same width + if left_width != right_width { + 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, + width: left_width, + }, + }, + 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_width, accept_kind)), + Some((reject_power, reject_width, reject_kind)), + ) = ( + // Get the components of both branches and calculate the type power + self.expr_scalar_components(accept, accept_meta)? + .and_then(|(kind, width)| Some((type_power(kind, width)?, width, kind))), + self.expr_scalar_components(reject, reject_meta)? + .and_then(|(kind, width)| Some((type_power(kind, width)?, width, kind))), + ) { + 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_kind, + reject_width, + )?; + Ok(()) + })?; + } + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => { + reject_body = self.with_body(reject_body, |ctx| { + ctx.conversion( + &mut reject, + reject_meta, + accept_kind, + accept_width, + )?; + 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((kind, width)) = scalar_components(ty) { + self.implicit_conversion(&mut value, value_meta, kind, width)?; + } + + 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 { kind, width } => { + let ty = TypeInner::Scalar { kind, width }; + Literal::one(kind, width).map(|i| (ty, i, None, None)) + } + TypeInner::Vector { size, kind, width } => { + let ty = TypeInner::Vector { size, kind, width }; + Literal::one(kind, width).map(|i| (ty, i, Some(size), None)) + } + TypeInner::Matrix { + columns, + rows, + width, + } => { + let ty = TypeInner::Matrix { + columns, + rows, + width, + }; + Literal::one(ScalarKind::Float, width) + .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, + ScalarKind::Sint, + 4, + )?; + 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, ScalarKind::Sint, 4)?; + 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(|(kind, width)| type_power(kind, width))) + } + + pub fn conversion( + &mut self, + expr: &mut Handle, + meta: Span, + kind: ScalarKind, + width: crate::Bytes, + ) -> Result<()> { + *expr = self.add_expression( + Expression::As { + expr: *expr, + kind, + convert: Some(width), + }, + meta, + )?; + + Ok(()) + } + + pub fn implicit_conversion( + &mut self, + expr: &mut Handle, + meta: Span, + kind: ScalarKind, + width: crate::Bytes, + ) -> Result<()> { + if let (Some(tgt_power), Some(expr_power)) = + (type_power(kind, width), self.expr_power(*expr, meta)?) + { + if tgt_power > expr_power { + self.conversion(expr, meta, kind, width)?; + } + } + + Ok(()) + } + + pub fn forced_conversion( + &mut self, + expr: &mut Handle, + meta: Span, + kind: ScalarKind, + width: crate::Bytes, + ) -> Result<()> { + if let Some((expr_scalar_kind, expr_width)) = self.expr_scalar_components(*expr, meta)? { + if expr_scalar_kind != kind || expr_width != width { + self.conversion(expr, meta, kind, width)?; + } + } + + 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_width, left_kind)), + Some((right_power, right_width, right_kind)), + ) = ( + left_components.and_then(|(kind, width)| Some((type_power(kind, width)?, width, kind))), + right_components + .and_then(|(kind, width)| Some((type_power(kind, width)?, width, kind))), + ) { + match left_power.cmp(&right_power) { + std::cmp::Ordering::Less => { + self.conversion(left, left_meta, right_kind, right_width)?; + } + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => { + self.conversion(right, right_meta, left_kind, left_width)?; + } + } + } + + 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 { + &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..8bbef9162d --- /dev/null +++ b/naga/src/front/glsl/functions.rs @@ -0,0 +1,1614 @@ +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, + 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<(ScalarKind, crate::Bytes)>, +} + +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_kind() { + Some(result_scalar_kind) if expr_is_bool && result_scalar_kind != ScalarKind::Bool => { + let l0 = Literal::zero(result_scalar_kind, 4).unwrap(); + let l1 = Literal::one(result_scalar_kind, 4).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, kind, width } if vector_size.is_none() => { + ctx.forced_conversion(&mut value, expr_meta, kind, width)?; + + 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, + kind, + width, + &[(value, expr_meta)], + meta, + )? + } + } + TypeInner::Scalar { kind, width } => { + 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, + expr, + convert: Some(width), + }, + meta, + )? + } + TypeInner::Vector { size, kind, width } => { + if vector_size.map_or(true, |s| s != size) { + value = ctx.vector_resize(size, value, expr_meta)?; + } + + ctx.add_expression( + Expression::As { + kind, + expr: value, + convert: Some(width), + }, + meta, + )? + } + TypeInner::Matrix { + columns, + rows, + width, + } => self.matrix_one_arg(ctx, ty, columns, rows, width, (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((kind, width)) = scalar_components { + ctx.implicit_conversion(&mut value, expr_meta, kind, width)?; + } + + 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((kind, width)) = scalar_components { + ctx.implicit_conversion(&mut value, expr_meta, kind, width)?; + } + + 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, + width: crate::Bytes, + (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, ScalarKind::Float, width)?; + 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, + kind: ScalarKind::Float, + width, + }, + }, + meta, + ); + + let zero_literal = Literal::zero(ScalarKind::Float, width).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(ScalarKind::Float, width).unwrap(); + let one_literal = Literal::one(ScalarKind::Float, width).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, + kind: ScalarKind::Float, + width, + }, + }, + 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, + kind: ScalarKind, + width: crate::Bytes, + 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, kind, width)?; + + 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, + width, + } => { + 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, ScalarKind::Float, width)?; + + 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, + kind: ScalarKind::Float, + width, + }, + }, + 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, kind, width } => { + return self.vector_constructor(ctx, ty, size, kind, width, &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((kind, width)) = scalar_components { + ctx.implicit_conversion(&mut arg, meta, kind, width)?; + } + + 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((kind, width)) = scalar_components { + ctx.implicit_conversion(&mut arg, meta, kind, width)?; + } + + 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((kind, width)) = scalar_comps { + ctx.implicit_conversion(&mut handle, meta, kind, width)?; + } + + 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((kind, width)) = proxy_write.convert { + ctx.conversion(&mut value, meta, kind, width)?; + } + + 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, kind, width } => Some(ctx.module.types.insert( + Type { + name: None, + inner: TypeInner::Vector { size, kind, width }, + }, + 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, + kind, + width, + space, + } if space != AddressSpace::Function => { + let inner = match size { + Some(size) => TypeInner::Vector { size, kind, width }, + None => TypeInner::Scalar { kind, width }, + }; + + 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_kind, target_width, source_kind, source_width) = match (target, source) { + // Conversions between scalars are allowed + ( + &TypeInner::Scalar { + kind: tgt_kind, + width: tgt_width, + }, + &TypeInner::Scalar { + kind: src_kind, + width: src_width, + }, + ) => (tgt_kind, tgt_width, src_kind, src_width), + // Conversions between vectors of the same size are allowed + ( + &TypeInner::Vector { + kind: tgt_kind, + size: tgt_size, + width: tgt_width, + }, + &TypeInner::Vector { + kind: src_kind, + size: src_size, + width: src_width, + }, + ) if tgt_size == src_size => (tgt_kind, tgt_width, src_kind, src_width), + // Conversions between matrices of the same size are allowed + ( + &TypeInner::Matrix { + rows: tgt_rows, + columns: tgt_cols, + width: tgt_width, + }, + &TypeInner::Matrix { + rows: src_rows, + columns: src_cols, + width: src_width, + }, + ) if tgt_cols == src_cols && tgt_rows == src_rows => (Float, tgt_width, Float, src_width), + _ => 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_kind, target_width); + let source_power = type_power(source_kind, source_width); + if target_power < source_power { + return None; + } + + Some( + match ((target_kind, target_width), (source_kind, source_width)) { + // A conversion from a float to a double is special + ((Float, 8), (Float, 4)) => Conversion::FloatToDouble, + // A conversion from an integer to a float is special + ((Float, 4), (Sint | Uint, _)) => Conversion::IntToFloat, + // A conversion from an integer to a double is special + ((Float, 8), (Sint | Uint, _)) => 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 { kind, width, .. } + | TypeInner::Scalar { kind, width } + | TypeInner::Vector { kind, width, .. } => { + if kind == ScalarKind::Float && width == 8 { + variations |= BuiltinVariations::DOUBLE + } + } + TypeInner::Matrix { width, .. } => { + if width == 8 { + 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..2d41778522 --- /dev/null +++ b/naga/src/front/glsl/offset.rs @@ -0,0 +1,170 @@ +/*! +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, 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 { 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, 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, + width, + } => { + let mut align = Alignment::from(rows) * Alignment::from_width(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..02ee2bedea --- /dev/null +++ b/naga/src/front/glsl/parser/declarations.rs @@ -0,0 +1,682 @@ +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, 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 { kind, width, .. } => types.insert( + Type { + name: None, + inner: TypeInner::Scalar { kind, width }, + }, + 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, width, .. } => types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: rows, + kind: ScalarKind::Float, + width, + }, + }, + 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((kind, width)) = scalar_components { + ctx.implicit_conversion(&mut init, init_meta, kind, width)?; + } + + 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((kind, width)) = scalar_components { + ctx.ctx + .implicit_conversion(&mut expr, init_meta, kind, width)?; + } + + 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 { + 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..1813a4ce49 --- /dev/null +++ b/naga/src/front/glsl/parser_tests.rs @@ -0,0 +1,847 @@ +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, ScalarKind, 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 { + kind: ScalarKind::Float, + width: 4 + } + } + ); + + 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..74f10cbf85 --- /dev/null +++ b/naga/src/front/glsl/token.rs @@ -0,0 +1,137 @@ +pub use pp_rs::token::{Float, Integer, Location, PreprocessorError, 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..ffa879506e --- /dev/null +++ b/naga/src/front/glsl/types.rs @@ -0,0 +1,375 @@ +use super::{context::Context, Error, ErrorKind, Result, Span}; +use crate::{ + proc::ResolveContext, Bytes, Expression, Handle, ImageClass, ImageDimension, ScalarKind, Type, + TypeInner, VectorSize, +}; + +pub fn parse_type(type_name: &str) -> Option { + match type_name { + "bool" => Some(Type { + name: None, + inner: TypeInner::Scalar { + kind: ScalarKind::Bool, + width: crate::BOOL_WIDTH, + }, + }), + "float" => Some(Type { + name: None, + inner: TypeInner::Scalar { + kind: ScalarKind::Float, + width: 4, + }, + }), + "double" => Some(Type { + name: None, + inner: TypeInner::Scalar { + kind: ScalarKind::Float, + width: 8, + }, + }), + "int" => Some(Type { + name: None, + inner: TypeInner::Scalar { + kind: ScalarKind::Sint, + width: 4, + }, + }), + "uint" => Some(Type { + name: None, + inner: TypeInner::Scalar { + kind: ScalarKind::Uint, + width: 4, + }, + }), + "sampler" | "samplerShadow" => Some(Type { + name: None, + inner: TypeInner::Sampler { + comparison: type_name == "samplerShadow", + }, + }), + word => { + fn kind_width_parse(ty: &str) -> Option<(ScalarKind, u8)> { + Some(match ty { + "" => (ScalarKind::Float, 4), + "b" => (ScalarKind::Bool, crate::BOOL_WIDTH), + "i" => (ScalarKind::Sint, 4), + "u" => (ScalarKind::Uint, 4), + "d" => (ScalarKind::Float, 8), + _ => 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 (kind, width) = kind_width_parse(kind)?; + let size = size_parse(size)?; + + Some(Type { + name: None, + inner: TypeInner::Vector { size, kind, width }, + }) + }; + + let mat_parse = |word: &str| { + let mut iter = word.split("mat"); + + let kind = iter.next()?; + let size = iter.next()?; + let (_, width) = 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, + width, + }, + }) + }; + + 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<(ScalarKind, Bytes)> { + match *ty { + TypeInner::Scalar { kind, width } => Some((kind, width)), + TypeInner::Vector { kind, width, .. } => Some((kind, width)), + TypeInner::Matrix { width, .. } => Some((ScalarKind::Float, width)), + TypeInner::ValuePointer { kind, width, .. } => Some((kind, width)), + _ => None, + } +} + +pub const fn type_power(kind: ScalarKind, width: Bytes) -> Option { + Some(match kind { + ScalarKind::Sint => 0, + ScalarKind::Uint => 1, + ScalarKind::Float if width == 4 => 2, + ScalarKind::Float => 3, + ScalarKind::Bool => 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..794f181b56 --- /dev/null +++ b/naga/src/front/glsl/variables.rs @@ -0,0 +1,662 @@ +use super::{ + ast::*, + context::{Context, ExprPos}, + error::{Error, ErrorKind}, + Frontend, Result, Span, +}; +use crate::{ + AddressSpace, Binding, BuiltIn, Constant, Expression, GlobalVariable, Handle, Interpolation, + LocalVariable, ResourceBinding, 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, + kind: ScalarKind::Float, + width: 4, + }, + builtin: BuiltIn::Position { invariant: false }, + mutable: true, + storage: StorageQualifier::Output, + }, + "gl_FragCoord" => BuiltInData { + inner: TypeInner::Vector { + size: VectorSize::Quad, + kind: ScalarKind::Float, + width: 4, + }, + builtin: BuiltIn::Position { invariant: false }, + mutable: false, + storage: StorageQualifier::Input, + }, + "gl_PointCoord" => BuiltInData { + inner: TypeInner::Vector { + size: VectorSize::Bi, + kind: ScalarKind::Float, + width: 4, + }, + 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, + kind: ScalarKind::Uint, + width: 4, + }, + 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 { + kind: ScalarKind::Bool, + width: crate::BOOL_WIDTH, + }, + builtin: BuiltIn::FrontFacing, + mutable: false, + storage: StorageQualifier::Input, + }, + "gl_PointSize" | "gl_FragDepth" => BuiltInData { + inner: TypeInner::Scalar { + kind: ScalarKind::Float, + width: 4, + }, + 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 { + kind: ScalarKind::Float, + width: 4, + }, + }, + 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 { + kind: ScalarKind::Uint, + width: 4, + }, + 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..efd95898b8 --- /dev/null +++ b/naga/src/front/spv/convert.rs @@ -0,0 +1,180 @@ +use super::error::Error; +use num_traits::cast::FromPrimitive; +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..ee58c7ba14 --- /dev/null +++ b/naga/src/front/spv/image.rs @@ -0,0 +1,762 @@ +use crate::arena::{Handle, UniqueArena}; + +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 { kind, .. } => (None, kind), + crate::TypeInner::Vector { size, 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, + 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..083205a45b --- /dev/null +++ b/naga/src/front/spv/mod.rs @@ -0,0 +1,5362 @@ +/*! +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 num_traits::cast::FromPrimitive; +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_u16(opcode).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 (kind, width) = match ctx.type_arena[ty_lookup.handle].inner { + crate::TypeInner::Scalar { kind, width } + | crate::TypeInner::Vector { kind, width, .. } => (kind, width), + crate::TypeInner::Matrix { width, .. } => (crate::ScalarKind::Float, width), + _ => return Err(Error::InvalidAsType(ty_lookup.handle)), + }; + + let expr = crate::Expression::As { + expr: get_expr_handle!(value_id, value_lexp), + kind, + convert: if kind == crate::ScalarKind::Bool { + Some(crate::BOOL_WIDTH) + } else if inst.op == Op::Bitcast { + None + } else { + Some(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 { + 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 { + 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 { + kind: crate::ScalarKind::Bool, + width: crate::BOOL_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_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 { + 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 { + kind: crate::ScalarKind::Float, + 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_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 (kind, width) = match module.types[type_lookup.handle].inner { + crate::TypeInner::Scalar { kind, width } => (kind, width), + _ => return Err(Error::InvalidInnerType(type_id)), + }; + let component_count = self.next()?; + let inner = crate::TypeInner::Vector { + size: map_vector_size(component_count)?, + kind, + 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: 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, width, .. } => crate::TypeInner::Matrix { + columns: map_vector_size(num_columns)?, + rows: size, + width, + }, + _ => 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, + width, + } = *inner + { + if let Some(stride) = decor.matrix_stride { + let expected_stride = Alignment::from(rows) * width as u32; + if stride.get() != expected_stride { + return Err(Error::UnsupportedMatrixStride { + stride: stride.get(), + columns: columns as u8, + rows: rows as u8, + 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 kind = crate::ScalarKind::Float; + let width = 4; + match dim.required_coordinate_size() { + None => crate::TypeInner::Scalar { kind, width }, + Some(size) => crate::TypeInner::Vector { size, kind, width }, + } + }, + }, + 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 { + 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 { + kind: crate::ScalarKind::Sint, + width, + } => { + let low = self.next()?; + match width { + 4 => crate::Literal::I32(low as i32), + _ => return Err(Error::InvalidTypeWidth(width as u32)), + } + } + crate::TypeInner::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 { + kind: crate::ScalarKind::Uint, + width: 4, + }), + crate::BuiltIn::GlobalInvocationId + | crate::BuiltIn::LocalInvocationId + | crate::BuiltIn::WorkGroupId + | crate::BuiltIn::WorkGroupSize => Some(crate::TypeInner::Vector { + size: crate::VectorSize::Tri, + kind: crate::ScalarKind::Uint, + width: 4, + }), + _ => 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..0c608504c9 --- /dev/null +++ b/naga/src/front/type_gen.rs @@ -0,0 +1,464 @@ +/*! +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 width = 4; + let ty_flag = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar { + width, + kind: crate::ScalarKind::Uint, + }, + }, + Span::UNDEFINED, + ); + let ty_scalar = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar { + width, + kind: crate::ScalarKind::Float, + }, + }, + Span::UNDEFINED, + ); + let ty_vector = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size: crate::VectorSize::Tri, + kind: crate::ScalarKind::Float, + width, + }, + }, + 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 width = 4; + let ty_flag = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar { + width, + kind: crate::ScalarKind::Uint, + }, + }, + Span::UNDEFINED, + ); + let ty_scalar = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar { + width, + kind: crate::ScalarKind::Float, + }, + }, + Span::UNDEFINED, + ); + let ty_barycentrics = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + width, + size: crate::VectorSize::Bi, + kind: crate::ScalarKind::Float, + }, + }, + Span::UNDEFINED, + ); + let ty_bool = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar { + width: crate::BOOL_WIDTH, + kind: crate::ScalarKind::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, + width, + }, + }, + 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 { kind, width } => { + let bool_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar { + kind: crate::ScalarKind::Bool, + width: crate::BOOL_WIDTH, + }, + }, + Span::UNDEFINED, + ); + let scalar_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Scalar { kind, width }, + }, + Span::UNDEFINED, + ); + + crate::Type { + name: Some(format!( + "__atomic_compare_exchange_result<{kind:?},{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 { + kind: crate::ScalarKind::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, + kind: crate::ScalarKind::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 { + kind: crate::ScalarKind::Float, + width, + }, + }, + Span::UNDEFINED, + ); + + let int_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::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, + kind: crate::ScalarKind::Float, + width, + }, + }, + Span::UNDEFINED, + ); + let vec_int_ty = self.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size, + 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..9143a8c07e --- /dev/null +++ b/naga/src/front/wgsl/error.rs @@ -0,0 +1,709 @@ +use crate::front::wgsl::parse::lexer::Token; +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() + .map(|label| { + Label::primary((), label.0.to_range().unwrap()) + .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: &str) { + 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: &str) -> 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, crate::ScalarKind, u8), + BadAccessor(Span), + BadTexture(Span), + BadTypeCast { + span: Span, + from_type: String, + to_type: String, + }, + BadTextureSampleType { + span: Span, + kind: crate::ScalarKind, + width: u8, + }, + 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), +} + +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, kind, width) => ParseError { + message: format!( + "matrix scalar type must be floating-point, but found `{}`", + kind.to_wgsl(width) + ), + 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, kind, width } => ParseError { + message: format!( + "texture sample type must be one of f32, i32 or u32, but found {}", + kind.to_wgsl(width) + ), + 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![], + }, + } + } +} 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..912713f911 --- /dev/null +++ b/naga/src/front/wgsl/lower/construction.rs @@ -0,0 +1,559 @@ +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)) => ctx.format_type(handle), + } + } +} + +enum Components<'a> { + None, + One { + component: Handle, + span: Span, + ty_inner: &'a crate::TypeInner, + }, + Many { + components: Vec>, + spans: Vec, + first_component_ty_inner: &'a crate::TypeInner, + }, +} + +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>> { + 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(component, ctx)?; + let ty_inner = super::resolve_inner!(ctx, component); + + Components::One { + component, + span, + ty_inner, + } + } + [component, ref rest @ ..] => { + let span = ctx.ast_expressions.get_span(component); + let component = self.expression(component, ctx)?; + + let components = std::iter::once(Ok(component)) + .chain( + rest.iter() + .map(|&component| self.expression(component, ctx)), + ) + .collect::>()?; + let spans = std::iter::once(span) + .chain( + rest.iter() + .map(|&component| ctx.ast_expressions.get_span(component)), + ) + .collect(); + + let first_component_ty_inner = super::resolve_inner!(ctx, component); + + Components::Many { + components, + spans, + first_component_ty_inner, + } + } + }; + + // 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 { kind, width })), + ) => crate::Expression::As { + expr: component, + kind, + convert: Some(width), + }, + + // Vector conversion (vector -> vector) + ( + Components::One { + component, + ty_inner: &crate::TypeInner::Vector { size: src_size, .. }, + .. + }, + Constructor::Type(( + _, + &crate::TypeInner::Vector { + size: dst_size, + kind: dst_kind, + width: dst_width, + }, + )), + ) if dst_size == src_size => crate::Expression::As { + expr: component, + kind: dst_kind, + convert: Some(dst_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, + width: dst_width, + }, + )), + ) if dst_columns == src_columns && dst_rows == src_rows => crate::Expression::As { + expr: component, + kind: crate::ScalarKind::Float, + convert: Some(dst_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 }, + ) => crate::Expression::Splat { + size, + value: component, + }, + + // Vector constructor (splat) + ( + Components::One { + component, + ty_inner: + &crate::TypeInner::Scalar { + kind: src_kind, + width: src_width, + .. + }, + .. + }, + Constructor::Type(( + _, + &crate::TypeInner::Vector { + size, + kind: dst_kind, + width: dst_width, + }, + )), + ) if dst_kind == src_kind || dst_width == src_width => crate::Expression::Splat { + size, + value: component, + }, + + // Vector constructor (by elements) + ( + Components::Many { + components, + first_component_ty_inner: + &crate::TypeInner::Scalar { kind, width } + | &crate::TypeInner::Vector { kind, width, .. }, + .. + }, + Constructor::PartialVector { size }, + ) + | ( + Components::Many { + components, + first_component_ty_inner: + &crate::TypeInner::Scalar { .. } | &crate::TypeInner::Vector { .. }, + .. + }, + Constructor::Type((_, &crate::TypeInner::Vector { size, width, kind })), + ) => { + let inner = crate::TypeInner::Vector { size, kind, width }; + let ty = ctx.ensure_type_exists(inner); + crate::Expression::Compose { ty, components } + } + + // Matrix constructor (by elements) + ( + Components::Many { + components, + first_component_ty_inner: &crate::TypeInner::Scalar { width, .. }, + .. + }, + Constructor::PartialMatrix { columns, rows }, + ) + | ( + Components::Many { + components, + first_component_ty_inner: &crate::TypeInner::Scalar { .. }, + .. + }, + Constructor::Type(( + _, + &crate::TypeInner::Matrix { + columns, + rows, + width, + }, + )), + ) => { + let vec_ty = ctx.ensure_type_exists(crate::TypeInner::Vector { + width, + kind: crate::ScalarKind::Float, + size: 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, + width, + }); + crate::Expression::Compose { ty, components } + } + + // Matrix constructor (by columns) + ( + Components::Many { + components, + first_component_ty_inner: &crate::TypeInner::Vector { width, .. }, + .. + }, + Constructor::PartialMatrix { columns, rows }, + ) + | ( + Components::Many { + components, + first_component_ty_inner: &crate::TypeInner::Vector { .. }, + .. + }, + Constructor::Type(( + _, + &crate::TypeInner::Matrix { + columns, + rows, + width, + }, + )), + ) => { + let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix { + columns, + rows, + width, + }); + crate::Expression::Compose { ty, components } + } + + // Array constructor - infer type + (components, Constructor::PartialArray) => { + let components = components.into_components_vec(); + + 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); + + crate::Expression::Compose { ty, components } + } + + // Array or Struct constructor + ( + components, + Constructor::Type(( + ty, + &crate::TypeInner::Array { .. } | &crate::TypeInner::Struct { .. }, + )), + ) => { + let components = components.into_components_vec(); + crate::Expression::Compose { ty, components } + } + + // ERRORS + + // Bad conversion (type cast) + (Components::One { span, ty_inner, .. }, constructor) => { + let from_type = ctx.format_typeinner(ty_inner); + 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)); + } + + // Parameters are of the wrong type for vector or matrix constructor + ( + Components::Many { spans, .. }, + Constructor::Type(( + _, + &crate::TypeInner::Vector { .. } | &crate::TypeInner::Matrix { .. }, + )) + | Constructor::PartialVector { .. } + | Constructor::PartialMatrix { .. }, + ) => { + return Err(Error::InvalidConstructorComponentType(spans[0], 0)); + } + + // 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 { width, kind } => { + let ty = ctx.ensure_type_exists(crate::TypeInner::Scalar { width, kind }); + Constructor::Type(ty) + } + ast::ConstructorType::PartialVector { size } => Constructor::PartialVector { size }, + ast::ConstructorType::Vector { size, kind, width } => { + let ty = ctx.ensure_type_exists(crate::TypeInner::Vector { size, kind, width }); + 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, + 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/mod.rs b/naga/src/front/wgsl/lower/mod.rs new file mode 100644 index 0000000000..236656c45d --- /dev/null +++ b/naga/src/front/wgsl/lower/mod.rs @@ -0,0 +1,2676 @@ +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, + TypeResolution, +}; +use crate::{Arena, FastHashMap, FastIndexMap, Handle, Span}; + +mod construction; + +/// 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. +macro_rules! resolve { + ($ctx:ident, $expr:expr) => {{ + $ctx.grow_types($expr)?; + &$ctx.typifier()[$expr] + }}; +} + +/// 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, inner: crate::TypeInner) -> Handle { + self.module + .types + .insert(crate::Type { inner, name: None }, 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, TypedExpression>, + + 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, TypedExpression>, + + 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 + /// [`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: TypedExpression, + ) -> Result, Error<'source>> { + if expr.is_reference { + let load = crate::Expression::Load { + pointer: expr.handle, + }; + let span = self.get_expression_span(expr.handle); + self.append_expression(load, span) + } else { + Ok(expr.handle) + } + } + + fn format_typeinner(&self, inner: &crate::TypeInner) -> String { + inner.to_wgsl(self.module.to_ctx()) + } + + fn format_type(&self, handle: Handle) -> String { + let ty = &self.module.types[handle]; + match ty.name { + Some(ref name) => name.clone(), + None => self.format_typeinner(&ty.inner), + } + } + + fn format_type_resolution(&self, resolution: &TypeResolution) -> String { + match *resolution { + TypeResolution::Handle(handle) => self.format_type(handle), + TypeResolution::Value(ref inner) => self.format_typeinner(inner), + } + } + + fn ensure_type_exists(&mut self, inner: crate::TypeInner) -> Handle { + self.as_global().ensure_type_exists(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, + }), + } + } +} + +/// A Naga [`Expression`] handle, with WGSL type information. +/// +/// Naga and WGSL types are very close, but Naga lacks WGSL's 'reference' types, +/// which we need to know to apply the Load Rule. This struct carries a Naga +/// `Handle` along with enough information to determine its WGSL type. +/// +/// [`Expression`]: crate::Expression +#[derive(Debug, Copy, Clone)] +struct TypedExpression { + /// The handle of the Naga expression. + handle: Handle, + + /// True if this expression's WGSL type is a reference. + /// + /// When this is true, `handle` must be a pointer. + is_reference: bool, +} + +impl TypedExpression { + const fn non_reference(handle: Handle) -> TypedExpression { + TypedExpression { + handle, + is_reference: false, + } + } +} + +/// 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 = v + .init + .map(|init| self.expression(init, &mut ctx.as_const())) + .transpose()?; + + 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 init = self.expression(c.init, &mut ectx)?; + let inferred_type = ectx.register_type(init)?; + + let explicit_ty = + c.ty.map(|ty| self.resolve_ast_type(ty, &mut ctx)) + .transpose()?; + + if let Some(explicit) = explicit_ty { + if explicit != inferred_type { + let ty = &ctx.module.types[explicit]; + let expected = ty + .name + .clone() + .unwrap_or_else(|| ty.inner.to_wgsl(ctx.module.to_ctx())); + + let ty = &ctx.module.types[inferred_type]; + let got = ty + .name + .clone() + .unwrap_or_else(|| ty.inner.to_wgsl(ctx.module.to_ctx())); + + return Err(Error::InitializationTypeMismatch { + name: c.name.span, + expected, + got, + }); + } + } + + let handle = ctx.module.constants.append( + crate::Constant { + name: Some(c.name.name.to_string()), + r#override: crate::Override::None, + ty: inferred_type, + 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_ast_type(alias.ty, &mut ctx)?; + ctx.globals + .insert(alias.name.name, LoweredGlobalDecl::Type(ty)); + } + } + } + + 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, TypedExpression::non_reference(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) + { + return Err(Error::InitializationTypeMismatch { + name: l.name.span, + expected: ctx.format_type(ty), + got: ctx.format_type(init_ty), + }); + } + } + + block.extend(emitter.finish(&ctx.function.expressions)); + ctx.local_table + .insert(l.handle, TypedExpression::non_reference(value)); + ctx.named_expressions + .insert(value, (l.name.name.to_string(), l.name.span)); + + return Ok(()); + } + ast::LocalDecl::Var(ref v) => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let initializer = match v.init { + Some(init) => Some( + self.expression(init, &mut ctx.as_expression(block, &mut emitter))?, + ), + None => None, + }; + + let explicit_ty = + v.ty.map(|ty| self.resolve_ast_type(ty, &mut ctx.as_global())) + .transpose()?; + + let ty = match (explicit_ty, initializer) { + (Some(explicit), Some(initializer)) => { + let mut ctx = ctx.as_expression(block, &mut emitter); + let initializer_ty = resolve_inner!(ctx, initializer); + if !ctx.module.types[explicit] + .inner + .equivalent(initializer_ty, &ctx.module.types) + { + return Err(Error::InitializationTypeMismatch { + name: v.name.span, + expected: ctx.format_type(explicit), + got: ctx.format_typeinner(initializer_ty), + }); + } + explicit + } + (Some(explicit), None) => explicit, + (None, Some(initializer)) => ctx + .as_expression(block, &mut emitter) + .register_type(initializer)?, + (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, + TypedExpression { + handle, + is_reference: true, + }, + ); + + 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(block, &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, op, value } => { + let mut emitter = Emitter::default(); + emitter.start(&ctx.function.expressions); + + let expr = self.expression_for_reference( + target, + &mut ctx.as_expression(block, &mut emitter), + )?; + let mut value = + self.expression(value, &mut ctx.as_expression(block, &mut emitter))?; + + if !expr.is_reference { + let ty = ctx.invalid_assignment_type(expr.handle); + + return Err(Error::InvalidAssignment { + span: ctx.ast_expressions.get_span(target), + ty, + }); + } + + let value = match op { + Some(op) => { + let mut ctx = ctx.as_expression(block, &mut emitter); + let mut left = ctx.apply_load_rule(expr)?; + 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: expr.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 reference = self + .expression_for_reference(value, &mut ctx.as_expression(block, &mut emitter))?; + let mut ectx = ctx.as_expression(block, &mut emitter); + + let (kind, width) = match *resolve_inner!(ectx, reference.handle) { + crate::TypeInner::ValuePointer { + size: None, + kind, + width, + .. + } => (kind, width), + crate::TypeInner::Pointer { base, .. } => match ectx.module.types[base].inner { + crate::TypeInner::Scalar { kind, width } => (kind, width), + _ => return Err(Error::BadIncrDecrReferenceType(value_span)), + }, + _ => return Err(Error::BadIncrDecrReferenceType(value_span)), + }; + let literal = match kind { + crate::ScalarKind::Sint | crate::ScalarKind::Uint => { + crate::Literal::one(kind, width) + .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: reference.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: reference.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(()) + } + + fn expression( + &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> { + let span = ctx.ast_expressions.get_span(expr); + let expr = &ctx.ast_expressions[expr]; + + let (expr, is_reference) = 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(_) => { + unreachable!("got abstract numeric type when not expected"); + } + ast::Literal::Bool(b) => crate::Literal::Bool(b), + }; + let handle = ctx.interrupt_emitter(crate::Expression::Literal(literal), span)?; + return Ok(TypedExpression::non_reference(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)) => { + return if let Some(global) = ctx.globals.get(name) { + let (expr, is_reference) = match *global { + LoweredGlobalDecl::Var(handle) => ( + crate::Expression::GlobalVariable(handle), + ctx.module.global_variables[handle].space + != crate::AddressSpace::Handle, + ), + LoweredGlobalDecl::Const(handle) => { + (crate::Expression::Constant(handle), false) + } + _ => { + return Err(Error::Unexpected(span, ExpectedToken::Variable)); + } + }; + + let handle = ctx.interrupt_emitter(expr, span)?; + Ok(TypedExpression { + handle, + is_reference, + }) + } else { + Err(Error::UnknownIdent(span, name)) + } + } + ast::Expression::Construct { + ref ty, + ty_span, + ref components, + } => { + let handle = self.construct(span, ty, ty_span, components, ctx)?; + return Ok(TypedExpression::non_reference(handle)); + } + ast::Expression::Unary { op, expr } => { + let expr = self.expression(expr, ctx)?; + (crate::Expression::Unary { op, expr }, false) + } + 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. + let expr = self.expression_for_reference(expr, ctx)?; + if !expr.is_reference { + return Err(Error::NotReference("the operand of the `&` operator", span)); + } + + // No code is generated. We just declare the pointer a reference now. + return Ok(TypedExpression { + is_reference: false, + ..expr + }); + } + 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)); + } + + return Ok(TypedExpression { + handle: pointer, + is_reference: true, + }); + } + ast::Expression::Binary { op, left, right } => { + // Load both operands. + let mut left = self.expression(left, ctx)?; + let mut right = self.expression(right, ctx)?; + ctx.binary_op_splat(op, &mut left, &mut right)?; + (crate::Expression::Binary { op, left, right }, false) + } + ast::Expression::Call { + ref function, + ref arguments, + } => { + let handle = self + .call(span, function, arguments, ctx)? + .ok_or(Error::FunctionReturnsVoid(function.span))?; + return Ok(TypedExpression::non_reference(handle)); + } + ast::Expression::Index { base, index } => { + let expr = self.expression_for_reference(base, ctx)?; + let index = self.expression(index, ctx)?; + + let wgsl_pointer = resolve_inner!(ctx, expr.handle).pointer_space().is_some() + && !expr.is_reference; + + if wgsl_pointer { + return Err(Error::Pointer( + "the value indexed by a `[]` subscripting expression", + ctx.ast_expressions.get_span(base), + )); + } + + if let Some(index) = ctx.const_access(index) { + ( + crate::Expression::AccessIndex { + base: expr.handle, + index, + }, + expr.is_reference, + ) + } else { + ( + crate::Expression::Access { + base: expr.handle, + index, + }, + expr.is_reference, + ) + } + } + ast::Expression::Member { base, ref field } => { + let TypedExpression { + handle, + is_reference, + } = self.expression_for_reference(base, ctx)?; + + let temp_inner; + let (composite, wgsl_pointer) = match *resolve_inner!(ctx, handle) { + crate::TypeInner::Pointer { base, .. } => { + (&ctx.module.types[base].inner, !is_reference) + } + crate::TypeInner::ValuePointer { + size: None, + kind, + width, + .. + } => { + temp_inner = crate::TypeInner::Scalar { kind, width }; + (&temp_inner, !is_reference) + } + crate::TypeInner::ValuePointer { + size: Some(size), + kind, + width, + .. + } => { + temp_inner = crate::TypeInner::Vector { size, kind, width }; + (&temp_inner, !is_reference) + } + ref other => (other, false), + }; + + if wgsl_pointer { + return Err(Error::Pointer( + "the value accessed by a `.member` expression", + ctx.ast_expressions.get_span(base), + )); + } + + let access = match *composite { + 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; + + ( + crate::Expression::AccessIndex { + base: handle, + index, + }, + is_reference, + ) + } + crate::TypeInner::Vector { .. } | crate::TypeInner::Matrix { .. } => { + match Components::new(field.name, field.span)? { + Components::Swizzle { size, pattern } => { + let vector = ctx.apply_load_rule(TypedExpression { + handle, + is_reference, + })?; + + ( + crate::Expression::Swizzle { + size, + vector, + pattern, + }, + false, + ) + } + Components::Single(index) => ( + crate::Expression::AccessIndex { + base: handle, + index, + }, + is_reference, + ), + } + } + _ => 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 kind = match ctx.module.types[to_resolved].inner { + crate::TypeInner::Scalar { kind, .. } => kind, + crate::TypeInner::Vector { kind, .. } => kind, + _ => { + let ty = resolve!(ctx, expr); + return Err(Error::BadTypeCast { + from_type: ctx.format_type_resolution(ty), + span: ty_span, + to_type: ctx.format_type(to_resolved), + }); + } + }; + + ( + crate::Expression::As { + expr, + kind, + convert: None, + }, + false, + ) + } + }; + + let handle = ctx.append_expression(expr, span)?; + Ok(TypedExpression { + handle, + is_reference, + }) + } + + /// 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 { + 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 { width, .. } => Some((None, width)), + crate::TypeInner::Vector { size, 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 { kind, width } => { + crate::Expression::AtomicResult { + ty: ctx.module.generate_predeclared_type( + crate::PredeclaredType::AtomicCompareExchangeWeakResult { + kind, + width, + }, + ), + 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, + }) + } + + /// 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>> { + let inner = match ctx.types[handle] { + ast::Type::Scalar { kind, width } => crate::TypeInner::Scalar { kind, width }, + ast::Type::Vector { size, kind, width } => { + crate::TypeInner::Vector { size, kind, width } + } + ast::Type::Matrix { + rows, + columns, + width, + } => crate::TypeInner::Matrix { + columns, + rows, + width, + }, + ast::Type::Atomic { kind, width } => crate::TypeInner::Atomic { kind, width }, + 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(inner)) + } + + 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..56834d5d92 --- /dev/null +++ b/naga/src/front/wgsl/mod.rs @@ -0,0 +1,303 @@ +/*! +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; + +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; + +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) +} + +impl crate::StorageFormat { + 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", + } + } +} + +impl crate::TypeInner { + /// Formats the type as it is written in wgsl. + /// + /// For example `vec3`. + /// + /// Note: The names of a `TypeInner::Struct` is not known. Therefore this method will simply return "struct" for them. + fn to_wgsl(&self, gctx: crate::proc::GlobalCtx) -> String { + use crate::TypeInner as Ti; + + match *self { + Ti::Scalar { kind, width } => kind.to_wgsl(width), + Ti::Vector { size, kind, width } => { + format!("vec{}<{}>", size as u32, kind.to_wgsl(width)) + } + Ti::Matrix { + columns, + rows, + width, + } => { + format!( + "mat{}x{}<{}>", + columns as u32, + rows as u32, + crate::ScalarKind::Float.to_wgsl(width), + ) + } + Ti::Atomic { kind, width } => { + format!("atomic<{}>", kind.to_wgsl(width)) + } + Ti::Pointer { base, .. } => { + let base = &gctx.types[base]; + let name = base.name.as_deref().unwrap_or("unknown"); + format!("ptr<{name}>") + } + Ti::ValuePointer { kind, width, .. } => { + format!("ptr<{}>", kind.to_wgsl(width)) + } + Ti::Array { 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!("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 = kind.to_wgsl(4); + 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}>"), + } + } + } + } +} + +mod type_inner_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, + width: 8, + }; + 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"); + } +} + +impl crate::ScalarKind { + /// Format a scalar kind+width as a type is written in wgsl. + /// + /// Examples: `f32`, `u64`, `bool`. + fn to_wgsl(self, width: u8) -> String { + let prefix = match self { + crate::ScalarKind::Sint => "i", + crate::ScalarKind::Uint => "u", + crate::ScalarKind::Float => "f", + crate::ScalarKind::Bool => return "bool".to_string(), + }; + format!("{}{}", prefix, width * 8) + } +} diff --git a/naga/src/front/wgsl/parse/ast.rs b/naga/src/front/wgsl/parse/ast.rs new file mode 100644 index 0000000000..f88e880a3f --- /dev/null +++ b/naga/src/front/wgsl/parse/ast.rs @@ -0,0 +1,501 @@ +use crate::front::wgsl::parse::number::Number; +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 { + kind: crate::ScalarKind, + width: crate::Bytes, + }, + Vector { + size: crate::VectorSize, + kind: crate::ScalarKind, + width: crate::Bytes, + }, + Matrix { + columns: crate::VectorSize, + rows: crate::VectorSize, + width: crate::Bytes, + }, + Atomic { + kind: crate::ScalarKind, + width: crate::Bytes, + }, + 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 { + kind: crate::ScalarKind, + width: crate::Bytes, + }, + + /// 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, + kind: crate::ScalarKind, + width: crate::Bytes, + }, + + /// 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..51977173d6 --- /dev/null +++ b/naga/src/front/wgsl/parse/conv.rs @@ -0,0 +1,237 @@ +use super::Error; +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<(crate::ScalarKind, crate::Bytes)> { + match word { + // "f16" => Some((crate::ScalarKind::Float, 2)), + "f32" => Some((crate::ScalarKind::Float, 4)), + "f64" => Some((crate::ScalarKind::Float, 8)), + "i32" => Some((crate::ScalarKind::Sint, 4)), + "u32" => Some((crate::ScalarKind::Uint, 4)), + "bool" => Some((crate::ScalarKind::Bool, 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..ed273fbbb1 --- /dev/null +++ b/naga/src/front/wgsl/parse/lexer.rs @@ -0,0 +1,721 @@ +use super::{number::consume_number, Error, ExpectedToken}; +use crate::front::wgsl::error::NumberError; +use crate::front::wgsl::parse::{conv, Number}; +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('0'..='9' | '.') => consume_number(input), + 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<(crate::ScalarKind, crate::Bytes), Error<'a>> { + 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<(crate::ScalarKind, crate::Bytes, Span), Error<'a>> { + self.expect_generic_paren('<')?; + let pair = match self.next() { + (Token::Word(word), span) => conv::get_scalar_type(word) + .map(|(a, b)| (a, b, 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)] +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::I32(291))), + Token::Number(Ok(Number::U32(291))), + Token::Number(Ok(Number::U32(1))), + Token::Number(Ok(Number::I32(123))), + Token::Number(Ok(Number::I32(0))), + Token::Number(Ok(Number::I32(0))), + Token::Number(Ok(Number::I32(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::F32(1.))), + Token::Number(Ok(Number::F32(0.01))), + Token::Number(Ok(Number::F32(12.34))), + Token::Number(Ok(Number::F32(0.))), + Token::Number(Err(NumberError::UnimplementedF16)), + Token::Number(Ok(Number::F32(0.001))), + Token::Number(Ok(Number::F32(43.75))), + Token::Number(Ok(Number::F32(16.))), + Token::Number(Ok(Number::F32(0.1875))), + Token::Number(Err(NumberError::UnimplementedF16)), + Token::Number(Ok(Number::F32(0.12109375))), + Token::Number(Err(NumberError::UnimplementedF16)), + ], + ); + + // MIN / MAX // + + // min / max decimal signed integer + sub_test( + "-2147483648i 2147483647i -2147483649i 2147483648i", + &[ + Token::Number(Ok(Number::I32(i32::MIN))), + Token::Number(Ok(Number::I32(i32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + // min / max decimal unsigned integer + sub_test( + "0u 4294967295u -1u 4294967296u", + &[ + Token::Number(Ok(Number::U32(u32::MIN))), + Token::Number(Ok(Number::U32(u32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + + // min / max hexadecimal signed integer + sub_test( + "-0x80000000i 0x7FFFFFFFi -0x80000001i 0x80000000i", + &[ + Token::Number(Ok(Number::I32(i32::MIN))), + Token::Number(Ok(Number::I32(i32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + // min / max hexadecimal unsigned integer + sub_test( + "0x0u 0xFFFFFFFFu -0x1u 0x100000000u", + &[ + Token::Number(Ok(Number::U32(u32::MIN))), + Token::Number(Ok(Number::U32(u32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + 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 SMALLEST_NORMAL_F32: f32 = f32::MIN; + /// ≈ 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 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( + SMALLEST_NORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + LARGEST_NORMAL_F32, + ))), + ], + ); + sub_test( + "-3.40282367e+38f 3.40282367e+38f", + &[ + Token::Number(Err(NumberError::NotRepresentable)), // ≈ -2^128 + Token::Number(Err(NumberError::NotRepresentable)), // ≈ 2^128 + ], + ); + + // hexadecimal floating point + sub_test( + "0x1p-149f 0x7FFFFFp-149f 0x1p-126f 0xFFFFFFp-24f 0x800001p-23f -0xFFFFFFp+104f 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( + SMALLEST_NORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + LARGEST_NORMAL_F32, + ))), + ], + ); + sub_test( + "-0x1p128f 0x1p128f 0x1.000001p0f", + &[ + Token::Number(Err(NumberError::NotRepresentable)), // = -2^128 + Token::Number(Err(NumberError::NotRepresentable)), // = 2^128 + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); +} + +#[test] +fn test_tokens() { + sub_test("id123_OK", &[Token::Word("id123_OK")]); + sub_test( + "92No", + &[Token::Number(Ok(Number::I32(92))), Token::Word("No")], + ); + sub_test( + "2u3o", + &[ + Token::Number(Ok(Number::U32(2))), + Token::Number(Ok(Number::I32(3))), + Token::Word("o"), + ], + ); + sub_test( + "2.4f44po", + &[ + Token::Number(Ok(Number::F32(2.4))), + Token::Number(Ok(Number::I32(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('/'), + ], + ); +} + +#[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::I32(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..ae690018f1 --- /dev/null +++ b/naga/src/front/wgsl/parse/mod.rs @@ -0,0 +1,2320 @@ +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::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((kind, width)) = conv::get_scalar_type(word) { + return Ok(Some(ast::ConstructorType::Scalar { kind, width })); + } + + let partial = match word { + "vec2" => ast::ConstructorType::PartialVector { + size: crate::VectorSize::Bi, + }, + "vec2i" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Bi, + kind: crate::ScalarKind::Sint, + width: 4, + })) + } + "vec2u" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Bi, + kind: crate::ScalarKind::Uint, + width: 4, + })) + } + "vec2f" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Bi, + kind: crate::ScalarKind::Float, + width: 4, + })) + } + "vec3" => ast::ConstructorType::PartialVector { + size: crate::VectorSize::Tri, + }, + "vec3i" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Tri, + kind: crate::ScalarKind::Sint, + width: 4, + })) + } + "vec3u" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Tri, + kind: crate::ScalarKind::Uint, + width: 4, + })) + } + "vec3f" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Tri, + kind: crate::ScalarKind::Float, + width: 4, + })) + } + "vec4" => ast::ConstructorType::PartialVector { + size: crate::VectorSize::Quad, + }, + "vec4i" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Quad, + kind: crate::ScalarKind::Sint, + width: 4, + })) + } + "vec4u" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Quad, + kind: crate::ScalarKind::Uint, + width: 4, + })) + } + "vec4f" => { + return Ok(Some(ast::ConstructorType::Vector { + size: crate::VectorSize::Quad, + kind: crate::ScalarKind::Float, + width: 4, + })) + } + "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 (kind, width) = lexer.next_scalar_generic()?; + Ok(Some(ast::ConstructorType::Vector { size, kind, width })) + } + (Token::Paren('<'), ast::ConstructorType::PartialMatrix { columns, rows }) => { + let (kind, width, span) = lexer.next_scalar_generic_with_span()?; + match kind { + crate::ScalarKind::Float => Ok(Some(ast::ConstructorType::Matrix { + columns, + rows, + width, + })), + _ => Err(Error::BadMatrixScalarKind(span, kind, width)), + } + } + (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 (kind, width, span) = lexer.next_scalar_generic_with_span()?; + match kind { + crate::ScalarKind::Float => Ok(ast::Type::Matrix { + columns, + rows, + width, + }), + _ => Err(Error::BadMatrixScalarKind(span, kind, width)), + } + } + + fn type_decl_impl<'a>( + &mut self, + lexer: &mut Lexer<'a>, + word: &'a str, + ctx: &mut ExpressionContext<'a, '_, '_>, + ) -> Result>, Error<'a>> { + if let Some((kind, width)) = conv::get_scalar_type(word) { + return Ok(Some(ast::Type::Scalar { kind, width })); + } + + Ok(Some(match word { + "vec2" => { + let (kind, width) = lexer.next_scalar_generic()?; + ast::Type::Vector { + size: crate::VectorSize::Bi, + kind, + width, + } + } + "vec2i" => ast::Type::Vector { + size: crate::VectorSize::Bi, + kind: crate::ScalarKind::Sint, + width: 4, + }, + "vec2u" => ast::Type::Vector { + size: crate::VectorSize::Bi, + kind: crate::ScalarKind::Uint, + width: 4, + }, + "vec2f" => ast::Type::Vector { + size: crate::VectorSize::Bi, + kind: crate::ScalarKind::Float, + width: 4, + }, + "vec3" => { + let (kind, width) = lexer.next_scalar_generic()?; + ast::Type::Vector { + size: crate::VectorSize::Tri, + kind, + width, + } + } + "vec3i" => ast::Type::Vector { + size: crate::VectorSize::Tri, + kind: crate::ScalarKind::Sint, + width: 4, + }, + "vec3u" => ast::Type::Vector { + size: crate::VectorSize::Tri, + kind: crate::ScalarKind::Uint, + width: 4, + }, + "vec3f" => ast::Type::Vector { + size: crate::VectorSize::Tri, + kind: crate::ScalarKind::Float, + width: 4, + }, + "vec4" => { + let (kind, width) = lexer.next_scalar_generic()?; + ast::Type::Vector { + size: crate::VectorSize::Quad, + kind, + width, + } + } + "vec4i" => ast::Type::Vector { + size: crate::VectorSize::Quad, + kind: crate::ScalarKind::Sint, + width: 4, + }, + "vec4u" => ast::Type::Vector { + size: crate::VectorSize::Quad, + kind: crate::ScalarKind::Uint, + width: 4, + }, + "vec4f" => ast::Type::Vector { + size: crate::VectorSize::Quad, + kind: crate::ScalarKind::Float, + width: 4, + }, + "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 (kind, width) = lexer.next_scalar_generic()?; + ast::Type::Atomic { kind, width } + } + "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 (kind, width, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(kind, width, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D1, + arrayed: false, + class: crate::ImageClass::Sampled { kind, multi: false }, + } + } + "texture_1d_array" => { + let (kind, width, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(kind, width, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D1, + arrayed: true, + class: crate::ImageClass::Sampled { kind, multi: false }, + } + } + "texture_2d" => { + let (kind, width, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(kind, width, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::Sampled { kind, multi: false }, + } + } + "texture_2d_array" => { + let (kind, width, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(kind, width, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: true, + class: crate::ImageClass::Sampled { kind, multi: false }, + } + } + "texture_3d" => { + let (kind, width, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(kind, width, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D3, + arrayed: false, + class: crate::ImageClass::Sampled { kind, multi: false }, + } + } + "texture_cube" => { + let (kind, width, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(kind, width, span)?; + ast::Type::Image { + dim: crate::ImageDimension::Cube, + arrayed: false, + class: crate::ImageClass::Sampled { kind, multi: false }, + } + } + "texture_cube_array" => { + let (kind, width, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(kind, width, span)?; + ast::Type::Image { + dim: crate::ImageDimension::Cube, + arrayed: true, + class: crate::ImageClass::Sampled { kind, multi: false }, + } + } + "texture_multisampled_2d" => { + let (kind, width, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(kind, width, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: false, + class: crate::ImageClass::Sampled { kind, multi: true }, + } + } + "texture_multisampled_2d_array" => { + let (kind, width, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(kind, width, span)?; + ast::Type::Image { + dim: crate::ImageDimension::D2, + arrayed: true, + class: crate::ImageClass::Sampled { 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( + kind: crate::ScalarKind, + width: u8, + span: Span, + ) -> Result<(), Error<'static>> { + use crate::ScalarKind::*; + // Validate according to https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type + match (kind, width) { + (Float | Sint | Uint, 4) => Ok(()), + _ => Err(Error::BadTextureSampleType { span, kind, width }), + } + } + + /// 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..57a2be6142 --- /dev/null +++ b/naga/src/front/wgsl/parse/number.rs @@ -0,0 +1,443 @@ +use std::borrow::Cow; + +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), +} + +impl Number { + /// Convert abstract numbers to a plausible concrete counterpart. + /// + /// Return concrete numbers unchanged. If the conversion would be + /// lossy, return an error. + fn abstract_to_concrete(self) -> Result { + match self { + Number::AbstractInt(num) => i32::try_from(num) + .map(Number::I32) + .map_err(|_| NumberError::NotRepresentable), + Number::AbstractFloat(num) => { + let num = num as f32; + if num.is_finite() { + Ok(Number::F32(num)) + } else { + Err(NumberError::NotRepresentable) + } + } + num => Ok(num), + } + } +} + +// TODO: when implementing Creation-Time Expressions, remove the ability to match the minus sign + +pub(in crate::front::wgsl) fn consume_number(input: &str) -> (Token<'_>, &str) { + let (result, rest) = parse(input); + ( + Token::Number(result.and_then(Number::abstract_to_concrete)), + rest, + ) +} + +enum Kind { + Int(IntKind), + Float(FloatKind), +} + +enum IntKind { + I32, + U32, +} + +enum FloatKind { + F32, + F16, +} + +// 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]?)) + +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() + }}; + } + + /// 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); + + let is_negative = consume!(bytes, b'-'); + + 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_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]); + + (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_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]); + + ( + 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(is_negative, 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_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]); + + (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_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]); + + (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_with_sign = general_extract.end(bytes); + + let kind = consume_map!(bytes, [ + b'i' => Kind::Int(IntKind::I32), + b'u' => Kind::Int(IntKind::U32), + b'f' => Kind::Float(FloatKind::F32), + b'h' => Kind::Float(FloatKind::F16) + ]); + + ( + parse_dec(is_negative, digits_with_sign, 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( + is_negative: bool, + // format: [0-9a-fA-F]+ + digits: &str, + kind: Option, +) -> Result { + let digits_with_sign = if is_negative { + Cow::Owned(format!("-{digits}")) + } else { + Cow::Borrowed(digits) + }; + parse_int(&digits_with_sign, kind, 16, is_negative) +} + +fn parse_dec( + is_negative: bool, + // format: -? ( [0-9] | [1-9][0-9]+ ) + digits_with_sign: &str, + kind: Option, +) -> Result { + match kind { + None => parse_int(digits_with_sign, None, 10, is_negative), + Some(Kind::Int(kind)) => parse_int(digits_with_sign, Some(kind), 10, is_negative), + Some(Kind::Float(kind)) => parse_dec_float(digits_with_sign, 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::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::F16) => Err(NumberError::UnimplementedF16), + } +} + +// 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::F16) => Err(NumberError::UnimplementedF16), + } +} + +fn parse_int( + input: &str, + kind: Option, + radix: u32, + is_negative: bool, +) -> 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) if is_negative => Err(NumberError::NotRepresentable), + 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..9e3ba2fab6 --- /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(0); + } + ", + ) + .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(0.0, 1.0, 2.0), 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/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..300c6e4820 --- /dev/null +++ b/naga/src/lib.rs @@ -0,0 +1,2049 @@ +/*! 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; + +/// 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, +} + +/// 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 { kind: ScalarKind, width: Bytes }, + /// Vector of numbers. + Vector { + size: VectorSize, + kind: ScalarKind, + width: Bytes, + }, + /// Matrix of floats. + Matrix { + columns: VectorSize, + rows: VectorSize, + width: Bytes, + }, + /// Atomic scalar. + Atomic { kind: ScalarKind, width: Bytes }, + /// 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, + kind: ScalarKind, + width: Bytes, + 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), + Bool(bool), +} + +#[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 { + kind: ScalarKind, + width: Bytes, + }, + 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..2082743975 --- /dev/null +++ b/naga/src/proc/constant_evaluator.rs @@ -0,0 +1,1785 @@ +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 type")] + InvalidCastArg, + #[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("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, + }), + } + } + + 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, 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) + } + } + } + + fn splat( + &mut self, + value: Handle, + size: crate::VectorSize, + span: Span, + ) -> Result, ConstantEvaluatorError> { + match self.expressions[value] { + Expression::Literal(literal) => { + let kind = literal.scalar_kind(); + let width = literal.width(); + let ty = self.types.insert( + Type { + name: None, + inner: TypeInner::Vector { size, kind, width }, + }, + 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 { kind, width } => TypeInner::Vector { size, kind, width }, + _ => 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: _, + kind, + width, + } => Ok(self.types.insert( + Type { + name: None, + inner: crate::TypeInner::Vector { size, kind, width }, + }, + 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 { + kind: crate::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 { + kind: crate::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 { + kind: crate::ScalarKind::Uint, + .. + } + ) => + { + Ok(0) + } + Expression::Literal(Literal::U32(index)) => Ok(index as usize), + _ => Err(ConstantEvaluatorError::InvalidAccessIndexTy), + } + } + + /// Transforms `Expression::ZeroValue` and `Expression::Splat` into either `Expression::Literal` or `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), + } + } + + fn eval_zero_value_impl( + &mut self, + ty: Handle, + span: Span, + ) -> Result, ConstantEvaluatorError> { + match self.types[ty].inner { + TypeInner::Scalar { kind, width } => { + let expr = Expression::Literal( + Literal::zero(kind, width) + .ok_or(ConstantEvaluatorError::TypeNotConstructible)?, + ); + self.register_evaluated_expr(expr, span) + } + TypeInner::Vector { size, kind, width } => { + let scalar_ty = self.types.insert( + Type { + name: None, + inner: TypeInner::Scalar { kind, width }, + }, + 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, + width, + } => { + let vec_ty = self.types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: rows, + kind: ScalarKind::Float, + width, + }, + }, + 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), + } + } + + fn cast( + &mut self, + expr: Handle, + kind: ScalarKind, + target_width: crate::Bytes, + span: Span, + ) -> Result, ConstantEvaluatorError> { + let expr = self.eval_zero_value_and_splat(expr, span)?; + + let expr = match self.expressions[expr] { + Expression::Literal(literal) => { + let literal = match (kind, target_width) { + (ScalarKind::Sint, 4) => 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(_) => return Err(ConstantEvaluatorError::InvalidCastArg), + }), + (ScalarKind::Uint, 4) => 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(_) => return Err(ConstantEvaluatorError::InvalidCastArg), + }), + (ScalarKind::Float, 4) => 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(_) => return Err(ConstantEvaluatorError::InvalidCastArg), + }), + (ScalarKind::Bool, crate::BOOL_WIDTH) => 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(_) => return Err(ConstantEvaluatorError::InvalidCastArg), + }), + _ => return Err(ConstantEvaluatorError::InvalidCastArg), + }; + Expression::Literal(literal) + } + Expression::Compose { + ty, + components: ref src_components, + } => { + let ty_inner = match self.types[ty].inner { + TypeInner::Vector { size, .. } => TypeInner::Vector { + size, + kind, + width: target_width, + }, + TypeInner::Matrix { columns, rows, .. } => TypeInner::Matrix { + columns, + rows, + width: target_width, + }, + _ => return Err(ConstantEvaluatorError::InvalidCastArg), + }; + + let mut components = src_components.clone(); + for component in &mut components { + *component = self.cast(*component, kind, target_width, span)?; + } + + let ty = self.types.insert( + Type { + name: None, + inner: ty_inner, + }, + span, + ); + + Expression::Compose { ty, components } + } + _ => return Err(ConstantEvaluatorError::InvalidCastArg), + }; + + self.register_evaluated_expr(expr, 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), + Literal::F32(v) => Literal::F32(-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), + _ => 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::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 } + } + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }; + + self.register_evaluated_expr(expr, span) + } + + /// 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)) + } + } +} + +#[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 { + kind: ScalarKind::Sint, + width: 4, + }, + }, + Default::default(), + ); + + let vec_ty = types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: VectorSize::Bi, + kind: ScalarKind::Sint, + width: 4, + }, + }, + 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 { + kind: ScalarKind::Sint, + width: 4, + }, + }, + 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, + width: 4, + }, + }, + Default::default(), + ); + + let vec_ty = types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: VectorSize::Tri, + kind: ScalarKind::Float, + width: 4, + }, + }, + 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 { + kind: ScalarKind::Sint, + width: 4, + }, + }, + Default::default(), + ); + + let vec2_i32_ty = types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: VectorSize::Bi, + kind: ScalarKind::Sint, + width: 4, + }, + }, + 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 { + kind: ScalarKind::Sint, + width: 4, + }, + }, + Default::default(), + ); + + let vec2_i32_ty = types.insert( + Type { + name: None, + inner: TypeInner::Vector { + size: VectorSize::Bi, + kind: ScalarKind::Sint, + width: 4, + }, + }, + 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") + } + } +} diff --git a/naga/src/proc/emitter.rs b/naga/src/proc/emitter.rs new file mode 100644 index 0000000000..281a55e2ad --- /dev/null +++ b/naga/src/proc/emitter.rs @@ -0,0 +1,40 @@ +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); + #[cfg(feature = "span")] + 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..4c52e5ec81 --- /dev/null +++ b/naga/src/proc/layouter.rs @@ -0,0 +1,252 @@ +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 { width, .. } | Ti::Atomic { width, .. } => { + let alignment = Alignment::new(width as u32) + .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; + TypeLayout { size, alignment } + } + Ti::Vector { + size: vec_size, + width, + .. + } => { + let alignment = Alignment::new(width as u32) + .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; + TypeLayout { + size, + alignment: Alignment::from(vec_size) * alignment, + } + } + Ti::Matrix { + columns: _, + rows, + width, + } => { + let alignment = Alignment::new(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..d50f165a56 --- /dev/null +++ b/naga/src/proc/mod.rs @@ -0,0 +1,712 @@ +/*! +[`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 => true, + crate::ScalarKind::Bool => false, + } + } +} + +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::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) => { + 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); + } + } + } +} + +impl crate::Literal { + pub const fn new(value: u8, kind: crate::ScalarKind, width: crate::Bytes) -> Option { + match (value, kind, 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 _)), + (1, crate::ScalarKind::Bool, 4) => Some(Self::Bool(true)), + (0, crate::ScalarKind::Bool, 4) => Some(Self::Bool(false)), + _ => None, + } + } + + pub const fn zero(kind: crate::ScalarKind, width: crate::Bytes) -> Option { + Self::new(0, kind, width) + } + + pub const fn one(kind: crate::ScalarKind, width: crate::Bytes) -> Option { + Self::new(1, kind, width) + } + + pub const fn width(&self) -> crate::Bytes { + match *self { + Self::F64(_) => 8, + Self::F32(_) | Self::U32(_) | Self::I32(_) => 4, + Self::Bool(_) => 1, + } + } + pub const fn scalar_kind(&self) -> crate::ScalarKind { + match *self { + Self::F64(_) | Self::F32(_) => crate::ScalarKind::Float, + Self::U32(_) => crate::ScalarKind::Uint, + Self::I32(_) => crate::ScalarKind::Sint, + Self::Bool(_) => crate::ScalarKind::Bool, + } + } + pub const fn ty_inner(&self) -> crate::TypeInner { + crate::TypeInner::Scalar { + kind: self.scalar_kind(), + width: self.width(), + } + } +} + +pub const POINTER_SPAN: u32 = 4; + +impl super::TypeInner { + pub const fn scalar_kind(&self) -> Option { + match *self { + super::TypeInner::Scalar { kind, .. } | super::TypeInner::Vector { kind, .. } => { + Some(kind) + } + super::TypeInner::Matrix { .. } => Some(super::ScalarKind::Float), + _ => None, + } + } + + pub const fn scalar_width(&self) -> Option { + // Multiply by 8 to get the bit width + match *self { + super::TypeInner::Scalar { width, .. } | super::TypeInner::Vector { width, .. } => { + Some(width * 8) + } + super::TypeInner::Matrix { width, .. } => Some(width * 8), + _ => None, + } + } + + 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 { kind: _, width } | Self::Atomic { kind: _, width } => width as u32, + Self::Vector { + size, + kind: _, + width, + } => size as u32 * width as u32, + // matrices are treated as arrays of aligned columns + Self::Matrix { + columns, + rows, + width, + } => Alignment::from(rows) * 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 { kind, width } => Some(Ti::ValuePointer { + size: None, + kind, + width, + space, + }), + Ti::Vector { size, kind, width } => Some(Ti::ValuePointer { + size: Some(size), + kind, + width, + 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 { kind, width, .. } => { + TypeResolution::Value(crate::TypeInner::Scalar { kind, width }) + } + Self::Matrix { rows, width, .. } => TypeResolution::Value(crate::TypeInner::Vector { + size: rows, + kind: crate::ScalarKind::Float, + width, + }), + 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), + } + } + + 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 { kind, width } => crate::Literal::zero(kind, width), + _ => 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. The below is 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) + }; + + fn flattener<'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) + } + + // Expressions like `vec4(vec3(vec2(6, 7), 8), 9)` require us to flatten + // two levels. + components + .iter() + .flat_map(move |component| flattener(component, is_vector, expressions)) + .flat_map(move |component| flattener(component, is_vector, expressions)) + .take(size) + .cloned() +} + +#[test] +fn test_matrix_size() { + let module = crate::Module::default(); + assert_eq!( + crate::TypeInner::Matrix { + columns: crate::VectorSize::Tri, + rows: crate::VectorSize::Tri, + width: 4 + } + .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..ad9eec94d2 --- /dev/null +++ b/naga/src/proc/typifier.rs @@ -0,0 +1,916 @@ +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 { kind, width } => Ti::Scalar { kind, width }, + Ti::Vector { size, kind, width } => Ti::Vector { size, kind, width }, + Ti::Matrix { + rows, + columns, + width, + } => Ti::Matrix { + rows, + columns, + width, + }, + Ti::Pointer { base, space } => Ti::Pointer { base, space }, + Ti::ValuePointer { + size, + kind, + width, + space, + } => Ti::ValuePointer { + size, + kind, + width, + 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, width, .. } => TypeResolution::Value(Ti::Vector { + size: rows, + kind: crate::ScalarKind::Float, + width, + }), + Ti::Vector { + size: _, + kind, + width, + } => TypeResolution::Value(Ti::Scalar { kind, width }), + Ti::ValuePointer { + size: Some(_), + kind, + width, + space, + } => TypeResolution::Value(Ti::ValuePointer { + size: None, + kind, + width, + space, + }), + Ti::Pointer { base, space } => { + TypeResolution::Value(match types[base].inner { + Ti::Array { base, .. } => Ti::Pointer { base, space }, + Ti::Vector { + size: _, + kind, + width, + } => Ti::ValuePointer { + size: None, + kind, + width, + space, + }, + // Matrices are only dynamically indexed behind a pointer + Ti::Matrix { + columns: _, + rows, + width, + } => Ti::ValuePointer { + kind: crate::ScalarKind::Float, + size: Some(rows), + width, + 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, kind, width } => { + if index >= size as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + TypeResolution::Value(Ti::Scalar { kind, width }) + } + Ti::Matrix { + columns, + rows, + width, + } => { + if index >= columns as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + TypeResolution::Value(crate::TypeInner::Vector { + size: rows, + kind: crate::ScalarKind::Float, + width, + }) + } + 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), + kind, + width, + space, + } => { + if index >= size as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + TypeResolution::Value(Ti::ValuePointer { + size: None, + kind, + width, + space, + }) + } + Ti::Pointer { + base: ty_base, + space, + } => TypeResolution::Value(match types[ty_base].inner { + Ti::Array { base, .. } => Ti::Pointer { base, space }, + Ti::Vector { size, kind, width } => { + if index >= size as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + Ti::ValuePointer { + size: None, + kind, + width, + space, + } + } + Ti::Matrix { + rows, + columns, + width, + } => { + if index >= columns as u32 { + return Err(ResolveError::OutOfBoundsIndex { expr: base, index }); + } + Ti::ValuePointer { + size: Some(rows), + kind: crate::ScalarKind::Float, + width, + 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 { kind, width } => { + TypeResolution::Value(Ti::Vector { size, kind, width }) + } + 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: _, + kind, + width, + } => TypeResolution::Value(Ti::Vector { size, kind, width }), + 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 { kind, width } = types[base].inner { + TypeResolution::Value(Ti::Scalar { kind, width }) + } else { + TypeResolution::Handle(base) + } + } + Ti::ValuePointer { + size, + kind, + width, + space: _, + } => TypeResolution::Value(match size { + Some(size) => Ti::Vector { size, kind, width }, + None => Ti::Scalar { kind, width }, + }), + 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 { + 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 { + kind: crate::ScalarKind::Float, + width: 4, + }, + crate::ImageClass::Sampled { kind, multi: _ } => Ti::Vector { + kind, + width: 4, + size: crate::VectorSize::Quad, + }, + crate::ImageClass::Storage { format, .. } => Ti::Vector { + 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 { + kind: crate::ScalarKind::Uint, + width: 4, + }, + crate::ImageDimension::D2 | crate::ImageDimension::Cube => Ti::Vector { + size: crate::VectorSize::Bi, + kind: crate::ScalarKind::Uint, + width: 4, + }, + crate::ImageDimension::D3 => Ti::Vector { + size: crate::VectorSize::Tri, + kind: crate::ScalarKind::Uint, + width: 4, + }, + }, + ref other => { + log::error!("Image type {:?}", other); + return Err(ResolveError::InvalidImage(image)); + } + }, + crate::ImageQuery::NumLevels + | crate::ImageQuery::NumLayers + | crate::ImageQuery::NumSamples => Ti::Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, + }), + 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, + width, + }, + &Ti::Matrix { columns, .. }, + ) => TypeResolution::Value(Ti::Matrix { + columns, + rows, + width, + }), + ( + &Ti::Matrix { + columns: _, + rows, + width, + }, + &Ti::Vector { .. }, + ) => TypeResolution::Value(Ti::Vector { + size: rows, + kind: crate::ScalarKind::Float, + width, + }), + ( + &Ti::Vector { .. }, + &Ti::Matrix { + columns, + rows: _, + width, + }, + ) => TypeResolution::Value(Ti::Vector { + size: columns, + kind: crate::ScalarKind::Float, + width, + }), + (&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 kind = crate::ScalarKind::Bool; + let width = crate::BOOL_WIDTH; + let inner = match *past(left)?.inner_with(types) { + Ti::Scalar { .. } => Ti::Scalar { kind, width }, + Ti::Vector { size, .. } => Ti::Vector { size, kind, width }, + 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 { + kind: crate::ScalarKind::Bool, + width: crate::BOOL_WIDTH, + }) + } + crate::RelationalFunction::IsNan | crate::RelationalFunction::IsInf => { + match *past(argument)?.inner_with(types) { + Ti::Scalar { .. } => TypeResolution::Value(Ti::Scalar { + kind: crate::ScalarKind::Bool, + width: crate::BOOL_WIDTH, + }), + Ti::Vector { size, .. } => TypeResolution::Value(Ti::Vector { + kind: crate::ScalarKind::Bool, + width: crate::BOOL_WIDTH, + 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 { + kind: crate::ScalarKind::Float, + width, + } => (None, width), + &Ti::Vector { + kind: crate::ScalarKind::Float, + size, + width, + } => (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 { + kind, + size: _, + width, + } => TypeResolution::Value(Ti::Scalar { kind, width }), + 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 {kind: _, size: columns,width}, &Ti::Vector{ size: rows, .. }) => TypeResolution::Value(Ti::Matrix { columns, rows, width }), + (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 {width,kind} | + Ti::Vector {width,kind,size:_} => TypeResolution::Value(Ti::Scalar { kind, width }), + 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, + width, + } => TypeResolution::Value(Ti::Matrix { + columns: rows, + rows: columns, + width, + }), + ref other => return Err(ResolveError::IncompatibleOperands( + format!("{fun:?}({other:?})") + )), + }, + Mf::Inverse => match *res_arg.inner_with(types) { + Ti::Matrix { + columns, + rows, + width, + } if columns == rows => TypeResolution::Value(Ti::Matrix { + columns, + rows, + width, + }), + ref other => return Err(ResolveError::IncompatibleOperands( + format!("{fun:?}({other:?})") + )), + }, + Mf::Determinant => match *res_arg.inner_with(types) { + Ti::Matrix { + width, + .. + } => TypeResolution::Value(Ti::Scalar { kind: crate::ScalarKind::Float, width }), + 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 { kind: kind @ (crate::ScalarKind::Sint | crate::ScalarKind::Uint), width } => + TypeResolution::Value(Ti::Scalar { kind, width }), + Ti::Vector { size, kind: kind @ (crate::ScalarKind::Sint | crate::ScalarKind::Uint), width } => + TypeResolution::Value(Ti::Vector { size, kind, width }), + 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 { kind: crate::ScalarKind::Uint, width: 4 }), + // data unpacking + Mf::Unpack4x8snorm | + Mf::Unpack4x8unorm => TypeResolution::Value(Ti::Vector { size: crate::VectorSize::Quad, kind: crate::ScalarKind::Float, width: 4 }), + Mf::Unpack2x16snorm | + Mf::Unpack2x16unorm | + Mf::Unpack2x16float => TypeResolution::Value(Ti::Vector { size: crate::VectorSize::Bi, kind: crate::ScalarKind::Float, width: 4 }), + } + } + crate::Expression::As { + expr, + kind, + convert, + } => match *past(expr)?.inner_with(types) { + Ti::Scalar { kind: _, width } => TypeResolution::Value(Ti::Scalar { + kind, + width: convert.unwrap_or(width), + }), + Ti::Vector { + kind: _, + size, + width, + } => TypeResolution::Value(Ti::Vector { + kind, + size, + width: convert.unwrap_or(width), + }), + Ti::Matrix { + columns, + rows, + width, + } => TypeResolution::Value(Ti::Matrix { + columns, + rows, + width: convert.unwrap_or(width), + }), + 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 { + kind: crate::ScalarKind::Uint, + width: 4, + }), + crate::Expression::RayQueryProceedResult => TypeResolution::Value(Ti::Scalar { + kind: crate::ScalarKind::Bool, + width: crate::BOOL_WIDTH, + }), + 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..0bc97ff460 --- /dev/null +++ b/naga/src/span.rs @@ -0,0 +1,523 @@ +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, + #[cfg(feature = "span")] + 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, + #[cfg(feature = "span")] + 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 { + #[cfg(feature = "span")] + return self.spans.iter(); + #[cfg(not(feature = "span"))] + return std::iter::empty(); + } + + /// Add a new span with description. + #[cfg_attr(not(feature = "span"), allow(unused_variables, unused_mut))] + pub fn with_span(mut self, span: Span, description: S) -> Self + where + S: ToString, + { + #[cfg(feature = "span")] + 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(), + #[cfg(feature = "span")] + 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, + { + #[cfg_attr(not(feature = "span"), allow(unused_mut))] + let mut res = func(self.inner); + #[cfg(feature = "span")] + res.spans.extend(self.spans); + res + } + + #[cfg(feature = "span")] + /// 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)) + } + + #[cfg(not(feature = "span"))] + /// Return a [`SourceLocation`] for our first span, if we have one. + pub fn location(&self, _source: &str) -> Option { + None + } + + #[cfg(feature = "span")] + 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. + #[cfg(feature = "span")] + 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. + #[cfg(feature = "span")] + 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. + #[cfg(feature = "span")] + 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. + #[cfg(feature = "span")] + 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..ff1db071c8 --- /dev/null +++ b/naga/src/valid/analyzer.rs @@ -0,0 +1,1284 @@ +/*! 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 { + 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; + #[cfg(feature = "validate")] + 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. + + /* #[cfg(feature = "validate")] + 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] +#[cfg(feature = "validate")] +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, + kind: crate::ScalarKind::Float, + width: 4, + }, + }, + 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..87bc2352ce --- /dev/null +++ b/naga/src/valid/compose.rs @@ -0,0 +1,133 @@ +#[cfg(feature = "validate")] +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 }, +} + +#[cfg(feature = "validate")] +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, kind, width } => { + let mut total = 0; + for (index, comp_res) in component_resolutions.enumerate() { + total += match *comp_res.inner_with(gctx.types) { + Ti::Scalar { + kind: comp_kind, + width: comp_width, + } if comp_kind == kind && comp_width == width => 1, + Ti::Vector { + size: comp_size, + kind: comp_kind, + width: comp_width, + } if comp_kind == kind && comp_width == width => comp_size as u32, + ref other => { + log::error!("Vector component[{}] type {:?}", index, other); + 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, + width, + } => { + let inner = Ti::Vector { + size: rows, + kind: crate::ScalarKind::Float, + width, + }; + 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..f77844b4b1 --- /dev/null +++ b/naga/src/valid/expression.rs @@ -0,0 +1,1601 @@ +#[cfg(feature = "validate")] +use super::{ + compose::validate_compose, validate_atomic_compare_exchange_struct, FunctionInfo, ModuleInfo, + ShaderStages, TypeFlags, +}; +#[cfg(feature = "validate")] +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), +} + +#[cfg(feature = "validate")] +struct ExpressionTypeResolver<'a> { + root: Handle, + types: &'a UniqueArena, + info: &'a FunctionInfo, +} + +#[cfg(feature = "validate")] +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 + ) + } + } +} + +#[cfg(feature = "validate")] +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, 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 { + kind: Sk::Sint | Sk::Uint, + width: _, + } => {} + 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 { + kind: Sk::Sint | Sk::Uint, + width: _, + } => {} + _ => 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 { + kind: Sk::Float, .. + } if num_components == 1 => {} + Ti::Vector { + size, + 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 { kind: Sk::Sint, .. } if num_components == 1 => {} + Ti::Vector { + size, + 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 { + 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 { + kind: Sk::Float, .. + } => {} + _ => return Err(ExpressionError::InvalidSampleLevelExactType(expr)), + } + ShaderStages::all() + } + crate::SampleLevel::Bias(expr) => { + match resolver[expr] { + Ti::Scalar { + kind: Sk::Float, .. + } => {} + _ => return Err(ExpressionError::InvalidSampleLevelBiasType(expr)), + } + ShaderStages::FRAGMENT + } + crate::SampleLevel::Gradient { x, y } => { + match resolver[x] { + Ti::Scalar { + kind: Sk::Float, .. + } if num_components == 1 => {} + Ti::Vector { + size, + kind: Sk::Float, + .. + } if size as u32 == num_components => {} + _ => { + return Err(ExpressionError::InvalidSampleLevelGradientType(dim, x)) + } + } + match resolver[y] { + Ti::Scalar { + kind: Sk::Float, .. + } if num_components == 1 => {} + Ti::Vector { + size, + 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 { + 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 { kind, .. } | Ti::Vector { kind, .. } => match kind { + Sk::Uint | Sk::Sint | Sk::Float => left_inner == right_inner, + Sk::Bool => false, + }, + Ti::Matrix { .. } => left_inner == right_inner, + _ => false, + }, + Bo::Divide | Bo::Modulo => match *left_inner { + Ti::Scalar { kind, .. } | Ti::Vector { kind, .. } => match kind { + Sk::Uint | Sk::Sint | Sk::Float => left_inner == right_inner, + Sk::Bool => false, + }, + _ => false, + }, + Bo::Multiply => { + let kind_allowed = match left_inner.scalar_kind() { + Some(Sk::Uint | Sk::Sint | Sk::Float) => true, + Some(Sk::Bool) | None => false, + }; + let types_match = match (left_inner, right_inner) { + // Straight scalar and mixed scalar/vector. + (&Ti::Scalar { kind: kind1, .. }, &Ti::Scalar { kind: kind2, .. }) + | (&Ti::Vector { kind: kind1, .. }, &Ti::Scalar { kind: kind2, .. }) + | (&Ti::Scalar { kind: kind1, .. }, &Ti::Vector { kind: kind2, .. }) => { + kind1 == kind2 + } + // Scalar/matrix. + ( + &Ti::Scalar { + kind: Sk::Float, .. + }, + &Ti::Matrix { .. }, + ) + | ( + &Ti::Matrix { .. }, + &Ti::Scalar { + kind: Sk::Float, .. + }, + ) => true, + // Vector/vector. + ( + &Ti::Vector { + kind: kind1, + size: size1, + .. + }, + &Ti::Vector { + kind: kind2, + size: size2, + .. + }, + ) => kind1 == kind2 && size1 == size2, + // Matrix * vector. + ( + &Ti::Matrix { columns, .. }, + &Ti::Vector { + kind: Sk::Float, + size, + .. + }, + ) => columns == size, + // Vector * matrix. + ( + &Ti::Vector { + kind: Sk::Float, + size, + .. + }, + &Ti::Matrix { rows, .. }, + ) => size == rows, + (&Ti::Matrix { columns, .. }, &Ti::Matrix { rows, .. }) => { + columns == rows + } + _ => false, + }; + let left_width = match *left_inner { + Ti::Scalar { width, .. } + | Ti::Vector { width, .. } + | Ti::Matrix { width, .. } => width, + _ => 0, + }; + let right_width = match *right_inner { + Ti::Scalar { width, .. } + | Ti::Vector { width, .. } + | Ti::Matrix { width, .. } => width, + _ => 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 { kind, .. } | Ti::Vector { kind, .. } => match kind { + Sk::Uint | Sk::Sint | Sk::Float => left_inner == right_inner, + Sk::Bool => false, + }, + ref other => { + log::error!("Op {:?} left type {:?}", op, other); + false + } + } + } + Bo::LogicalAnd | Bo::LogicalOr => match *left_inner { + Ti::Scalar { kind: Sk::Bool, .. } | Ti::Vector { 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 { kind, .. } | Ti::Vector { kind, .. } => match kind { + Sk::Bool | Sk::Sint | Sk::Uint => left_inner == right_inner, + Sk::Float => false, + }, + ref other => { + log::error!("Op {:?} left type {:?}", op, other); + false + } + }, + Bo::ExclusiveOr => match *left_inner { + Ti::Scalar { kind, .. } | Ti::Vector { kind, .. } => match kind { + Sk::Sint | Sk::Uint => left_inner == right_inner, + Sk::Bool | Sk::Float => false, + }, + ref other => { + log::error!("Op {:?} left type {:?}", op, other); + false + } + }, + Bo::ShiftLeft | Bo::ShiftRight => { + let (base_size, base_kind) = match *left_inner { + Ti::Scalar { kind, .. } => (Ok(None), kind), + Ti::Vector { size, kind, .. } => (Ok(Some(size)), kind), + ref other => { + log::error!("Op {:?} base type {:?}", op, other); + (Err(()), Sk::Bool) + } + }; + let shift_size = match *right_inner { + Ti::Scalar { kind: Sk::Uint, .. } => Ok(None), + Ti::Vector { + size, + kind: Sk::Uint, + .. + } => Ok(Some(size)), + ref other => { + log::error!("Op {:?} shift type {:?}", op, other); + Err(()) + } + }; + match base_kind { + Sk::Sint | Sk::Uint => base_size.is_ok() && base_size == shift_size, + Sk::Float | 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 { + 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, + 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 { + kind: Sk::Float, .. + } + | Ti::Vector { + 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 { 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 { + kind: Sk::Float, .. + } + | Ti::Vector { + 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 { kind, .. } | Ti::Vector { kind, .. } => 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 { kind, .. } | Ti::Vector { kind, .. } => 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 { kind, .. } | Ti::Vector { kind, .. } => 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 { + kind: Sk::Float, .. + } + | Ti::Vector { + 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 { + kind: Sk::Float | Sk::Sint, + .. + } + | Ti::Vector { + 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 { + kind: Sk::Float, .. + } + | Ti::Vector { + 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 { + kind: Sk::Float, + .. + } | Ti::Vector { + 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 { + kind: Sk::Float, .. + } => None, + Ti::Vector { + kind: Sk::Float, + size, + .. + } => Some(size), + _ => { + return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)); + } + }; + let good = match *arg1_ty { + Ti::Scalar { kind: Sk::Sint, .. } if size0.is_none() => true, + Ti::Vector { + size, + 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 { + 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 { + 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 { + 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 { + width: vector_width, + .. + }, + &Ti::Scalar { + 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 { + 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 { + kind: Sk::Float, .. + } + | Ti::Vector { + 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 { + kind: Sk::Float, + width, + } + | Ti::Vector { + 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 { + 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 { + kind: Sk::Sint | Sk::Uint, + .. + } + | Ti::Vector { + 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 { + kind: Sk::Sint | Sk::Uint, + .. + } + | Ti::Vector { + 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 { kind: Sk::Uint, .. } => {} + _ => { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg2.unwrap(), + )) + } + } + match *arg3_ty { + Ti::Scalar { 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 { + kind: Sk::Sint | Sk::Uint, + .. + } + | Ti::Vector { + kind: Sk::Sint | Sk::Uint, + .. + } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + match *arg1_ty { + Ti::Scalar { kind: Sk::Uint, .. } => {} + _ => { + return Err(ExpressionError::InvalidArgumentType( + fun, + 2, + arg1.unwrap(), + )) + } + } + match *arg2_ty { + Ti::Scalar { 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, + 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, + 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 { kind: Sk::Uint, .. } => {} + _ => return Err(ExpressionError::InvalidArgumentType(fun, 0, arg)), + } + } + } + ShaderStages::all() + } + E::As { + expr, + kind, + convert, + } => { + let base_width = match resolver[expr] { + crate::TypeInner::Scalar { width, .. } + | crate::TypeInner::Vector { width, .. } + | crate::TypeInner::Matrix { width, .. } => width, + _ => return Err(ExpressionError::InvalidCastArgument), + }; + let width = convert.unwrap_or(base_width); + if self.check_width(kind, width).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 { + kind: kind @ (crate::ScalarKind::Uint | crate::ScalarKind::Sint), + width, + } => self.check_width(kind, width).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> { + let kind = literal.scalar_kind(); + let width = literal.width(); + self.check_width(kind, width)?; + 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(()) +} diff --git a/naga/src/valid/function.rs b/naga/src/valid/function.rs new file mode 100644 index 0000000000..d967f4b1f3 --- /dev/null +++ b/naga/src/valid/function.rs @@ -0,0 +1,1080 @@ +use crate::arena::Handle; +#[cfg(feature = "validate")] +use crate::arena::{Arena, UniqueArena}; + +#[cfg(feature = "validate")] +use super::validate_atomic_compare_exchange_struct; + +use super::{ + analyzer::{UniformityDisruptor, UniformityRequirements}, + ExpressionError, FunctionInfo, ModuleInfo, +}; +use crate::span::WithSpan; +#[cfg(feature = "validate")] +use crate::span::{AddSpan as _, MapErrWithSpan as _}; + +#[cfg(feature = "validate")] +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("Store of {value:?} into {pointer:?} doesn't have matching types")] + 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; + } +} + +#[cfg(feature = "validate")] +struct BlockInfo { + stages: super::ShaderStages, + finished: bool, +} + +#[cfg(feature = "validate")] +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>, +} + +#[cfg(feature = "validate")] +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 { + #[cfg(feature = "validate")] + 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) + } + + #[cfg(feature = "validate")] + 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)) + } + } + + #[cfg(feature = "validate")] + 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_kind, ptr_width) = match *pointer_inner { + crate::TypeInner::Pointer { base, .. } => match context.types[base].inner { + crate::TypeInner::Atomic { kind, width } => (kind, width), + 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 { width, kind } if kind == ptr_kind && width == ptr_width => {} + 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 { + kind: ptr_kind, + width: ptr_width, + } + }; + 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(()) + } + + #[cfg(feature = "validate")] + 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 { + 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 { + 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 { kind, width } => *value_ty == Ti::Scalar { kind, width }, + ref other => value_ty == other, + }, + Ti::ValuePointer { + size: Some(size), + kind, + width, + space: _, + } => *value_ty == Ti::Vector { size, kind, width }, + Ti::ValuePointer { + size: None, + kind, + width, + space: _, + } => *value_ty == Ti::Scalar { kind, width }, + _ => 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 { + 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 { + kind: format.into(), + size: crate::VectorSize::Quad, + 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 }) + } + + #[cfg(feature = "validate")] + 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) + } + + #[cfg(feature = "validate")] + 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, + #[cfg_attr(not(feature = "validate"), allow(unused))] entry_point: bool, + ) -> Result> { + #[cfg_attr(not(feature = "validate"), allow(unused_mut))] + let mut info = mod_info.process_function(fun, module, self.flags, self.capabilities)?; + + #[cfg(feature = "validate")] + let expression_constness = + crate::proc::ExpressionConstnessTracker::from_arena(&fun.expressions); + + #[cfg(feature = "validate")] + 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) + })?; + } + + #[cfg(feature = "validate")] + 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)); + } + } + + #[cfg(feature = "validate")] + 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()); + } + #[cfg(feature = "validate")] + 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)) + } + } + } + } + + #[cfg(feature = "validate")] + 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..c68ded074b --- /dev/null +++ b/naga/src/valid/handles.rs @@ -0,0 +1,712 @@ +//! Implementation of `Validator::validate_module_handles`. + +use crate::{ + arena::{BadHandle, BadRangeError}, + Handle, +}; + +#[cfg(feature = "validate")] +use crate::{Arena, UniqueArena}; + +#[cfg(feature = "validate")] +use super::ValidationError; + +#[cfg(feature = "validate")] +use std::{convert::TryInto, hash::Hash, num::NonZeroU32}; + +#[cfg(feature = "validate")] +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(()), + }) + } +} + +#[cfg(feature = "validate")] +impl From for ValidationError { + fn from(source: BadHandle) -> Self { + Self::InvalidHandle(source.into()) + } +} + +#[cfg(feature = "validate")] +impl From for ValidationError { + fn from(source: FwdDepError) -> Self { + Self::InvalidHandle(source.into()) + } +} + +#[cfg(feature = "validate")] +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, +} + +#[cfg(feature = "validate")] +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) + } +} + +#[cfg(feature = "validate")] +impl crate::arena::Range { + pub(self) fn check_valid_for(&self, arena: &Arena) -> Result<(), BadRangeError> { + arena.check_contains_range(self) + } +} + +#[test] +#[cfg(feature = "validate")] +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 { + kind: crate::ScalarKind::Sint, + width: 4, + }, + }, + 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..6c41ece81f --- /dev/null +++ b/naga/src/valid/interface.rs @@ -0,0 +1,779 @@ +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; + +#[cfg(feature = "validate")] +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 }, +} + +#[cfg(feature = "validate")] +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, + + #[cfg(feature = "validate")] + flags: super::ValidationFlags, +} + +impl VaryingContext<'_> { + fn validate_impl( + &mut self, + ty: Handle, + binding: &crate::Binding, + ) -> Result<(), VaryingError> { + use crate::{ + BuiltIn as Bi, ScalarKind as Sk, 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 width = 4; + 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 { + kind: Sk::Uint, + width, + }, + ), + Bi::ClipDistance | Bi::CullDistance => ( + self.stage == St::Vertex && self.output, + match *ty_inner { + Ti::Array { base, .. } => { + self.types[base].inner + == Ti::Scalar { + kind: Sk::Float, + width, + } + } + _ => false, + }, + ), + Bi::PointSize => ( + self.stage == St::Vertex && self.output, + *ty_inner + == Ti::Scalar { + kind: Sk::Float, + width, + }, + ), + Bi::PointCoord => ( + self.stage == St::Fragment && !self.output, + *ty_inner + == Ti::Vector { + size: Vs::Bi, + kind: Sk::Float, + width, + }, + ), + Bi::Position { .. } => ( + match self.stage { + St::Vertex => self.output, + St::Fragment => !self.output, + St::Compute => false, + }, + *ty_inner + == Ti::Vector { + size: Vs::Quad, + kind: Sk::Float, + width, + }, + ), + Bi::ViewIndex => ( + match self.stage { + St::Vertex | St::Fragment => !self.output, + St::Compute => false, + }, + *ty_inner + == Ti::Scalar { + kind: Sk::Sint, + width, + }, + ), + Bi::FragDepth => ( + self.stage == St::Fragment && self.output, + *ty_inner + == Ti::Scalar { + kind: Sk::Float, + width, + }, + ), + Bi::FrontFacing => ( + self.stage == St::Fragment && !self.output, + *ty_inner + == Ti::Scalar { + kind: Sk::Bool, + width: crate::BOOL_WIDTH, + }, + ), + Bi::PrimitiveIndex => ( + self.stage == St::Fragment && !self.output, + *ty_inner + == Ti::Scalar { + kind: Sk::Uint, + width, + }, + ), + Bi::SampleIndex => ( + self.stage == St::Fragment && !self.output, + *ty_inner + == Ti::Scalar { + kind: Sk::Uint, + width, + }, + ), + Bi::SampleMask => ( + self.stage == St::Fragment, + *ty_inner + == Ti::Scalar { + kind: Sk::Uint, + width, + }, + ), + Bi::LocalInvocationIndex => ( + self.stage == St::Compute && !self.output, + *ty_inner + == Ti::Scalar { + kind: Sk::Uint, + width, + }, + ), + Bi::GlobalInvocationId + | Bi::LocalInvocationId + | Bi::WorkGroupId + | Bi::WorkGroupSize + | Bi::NumWorkGroups => ( + self.stage == St::Compute && !self.output, + *ty_inner + == Ti::Vector { + size: Vs::Tri, + kind: Sk::Uint, + width, + }, + ), + }; + + 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) { + #[cfg(feature = "validate")] + 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 => { + #[cfg(feature = "validate")] + if self.flags.contains(super::ValidationFlags::BINDINGS) { + return Err(VaryingError::MemberMissingBinding( + index as u32, + ) + .with_span_context(span_context)); + } + #[cfg(not(feature = "validate"))] + let _ = index; + } + Some(ref binding) => self + .validate_impl(member.ty, binding) + .map_err(|e| e.with_span_context(span_context))?, + } + } + } + _ => + { + #[cfg(feature = "validate")] + if self.flags.contains(super::ValidationFlags::BINDINGS) { + return Err(VaryingError::MissingBinding.with_span()); + } + } + } + Ok(()) + } + } + } +} + +impl super::Validator { + #[cfg(feature = "validate")] + 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> { + #[cfg(feature = "validate")] + 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()); + } + } + + #[cfg(feature = "validate")] + 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()); + } + + #[cfg_attr(not(feature = "validate"), allow(unused_mut))] + let mut info = self + .validate_function(&ep.function, module, mod_info, true) + .map_err(WithSpan::into_other)?; + + #[cfg(feature = "validate")] + { + 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, + + #[cfg(feature = "validate")] + 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, + + #[cfg(feature = "validate")] + flags: self.flags, + }; + ctx.validate(fr.ty, fr.binding.as_ref()) + .map_err_inner(|e| EntryPointError::Result(e).with_span())?; + #[cfg(feature = "validate")] + 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()); + } + } + + #[cfg(feature = "validate")] + 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 { + #[cfg(feature = "validate")] + return Err(EntryPointError::MissingVertexOutputPosition.with_span()); + } + + #[cfg(feature = "validate")] + { + 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(); + #[cfg(feature = "validate")] + 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..2fb0a72775 --- /dev/null +++ b/naga/src/valid/mod.rs @@ -0,0 +1,490 @@ +/*! +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()`. If Naga's `"validate"` feature is + /// enabled, this requests full validation; otherwise, this + /// requests no validation. (The `"validate"` feature is disabled + /// by default.) + #[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. + #[cfg(feature = "validate")] + const EXPRESSIONS = 0x1; + /// Statements and blocks of them. + #[cfg(feature = "validate")] + const BLOCKS = 0x2; + /// Uniformity of control flow for operations that require it. + #[cfg(feature = "validate")] + const CONTROL_FLOW_UNIFORMITY = 0x4; + /// Host-shareable structure layouts. + #[cfg(feature = "validate")] + const STRUCT_LAYOUTS = 0x8; + /// Constants. + #[cfg(feature = "validate")] + const CONSTANTS = 0x10; + /// Group, binding, and location attributes. + #[cfg(feature = "validate")] + 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 { + #[cfg(feature = "validate")] + 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. + #[cfg(feature = "validate")] + const fn image_storage_coordinates(&self) -> Option { + match *self { + Self::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + .. + } => Some(crate::ImageDimension::D1), + Self::Vector { + size: crate::VectorSize::Bi, + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + .. + } => Some(crate::ImageDimension::D2), + Self::Vector { + size: crate::VectorSize::Tri, + 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(); + } + + #[cfg(feature = "validate")] + 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()); + + #[cfg(feature = "validate")] + 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) + })?; + + let placeholder = TypeResolution::Value(crate::TypeInner::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) + })? + } + } + + #[cfg(feature = "validate")] + 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) + })? + } + } + + #[cfg(feature = "validate")] + 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) + } +} + +#[cfg(feature = "validate")] +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 { + kind: crate::ScalarKind::Bool, + width: crate::BOOL_WIDTH, + } +} diff --git a/naga/src/valid/type.rs b/naga/src/valid/type.rs new file mode 100644 index 0000000000..a5321178c1 --- /dev/null +++ b/naga/src/valid/type.rs @@ -0,0 +1,629 @@ +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("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, +} + +// 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, + kind: crate::ScalarKind, + width: crate::Bytes, + ) -> Result<(), WidthError> { + let good = match kind { + crate::ScalarKind::Bool => width == crate::BOOL_WIDTH, + crate::ScalarKind::Float => { + if width == 8 { + if !self.capabilities.contains(Capabilities::FLOAT64) { + return Err(WidthError::MissingCapability { + name: "f64", + flag: "FLOAT64", + }); + } + true + } else { + width == 4 + } + } + crate::ScalarKind::Sint | crate::ScalarKind::Uint => width == 4, + }; + if good { + Ok(()) + } else { + Err(WidthError::Invalid(kind, 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 { kind, width } => { + self.check_width(kind, width)?; + let shareable = if 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(width), + ) + } + Ti::Vector { size, kind, width } => { + self.check_width(kind, width)?; + let shareable = if 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(width), + ) + } + Ti::Matrix { + columns: _, + rows, + width, + } => { + self.check_width(crate::ScalarKind::Float, width)?; + TypeInfo::new( + TypeFlags::DATA + | TypeFlags::SIZED + | TypeFlags::COPY + | TypeFlags::HOST_SHAREABLE + | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE, + Alignment::from(rows) * Alignment::from_width(width), + ) + } + Ti::Atomic { kind, width } => { + let good = match kind { + crate::ScalarKind::Bool | crate::ScalarKind::Float => 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: _, + kind, + width, + 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(kind, width)?; + + // `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/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..a948edf146 --- /dev/null +++ b/naga/tests/in/break-if.wgsl @@ -0,0 +1,32 @@ +@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; + } + } +} 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..24d48665e6 --- /dev/null +++ b/naga/tests/in/const-exprs.wgsl @@ -0,0 +1,81 @@ +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(); +} + +// 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; } + } +} 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/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/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/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..40d1ce8ead --- /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() -> vec4 { + let a = (1.0 + vec2(2.0) - 3.0) / 4.0; + let b = vec4(5) % 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() { + builtins(); + splat(); + 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..b3dc515230 --- /dev/null +++ b/naga/tests/in/push-constants.wgsl @@ -0,0 +1,21 @@ +struct PushConstants { + multiplier: f32 +} +var pc: PushConstants; + +struct FragmentIn { + @location(0) color: vec4 +} + +@vertex +fn vert_main( + @location(0) pos : vec2, + @builtin(vertex_index) vi: u32, +) -> @builtin(position) vec4 { + return vec4(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/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..93eda7f396 --- /dev/null +++ b/naga/tests/out/analysis/access.info.ron @@ -0,0 +1,3857 @@ +( + 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), + kind: Float, + width: 4, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Vector( + size: Bi, + 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), + kind: Float, + width: 4, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Vector( + size: Bi, + 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), + kind: Float, + width: 4, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(3), + ty: Value(ValuePointer( + size: None, + 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), + 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, + 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), + 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, + 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), + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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), + 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, + 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), + 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, + 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), + kind: Float, + width: 4, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + 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), + 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, + 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), + kind: Float, + width: 4, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(50), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + 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), + 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, + 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), + kind: Float, + width: 4, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Vector( + size: Bi, + 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), + kind: Float, + width: 4, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(2), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Value(Vector( + size: Bi, + 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), + kind: Float, + width: 4, + space: Uniform, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(5), + ty: Value(ValuePointer( + size: None, + 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), + 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, + 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), + 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, + 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), + 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, + 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, + 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, + 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, + 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, + 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), + 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, + 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), + 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, + 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), + kind: Float, + width: 4, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + 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), + 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, + 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), + kind: Float, + width: 4, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(54), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(ValuePointer( + size: None, + 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), + 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, + 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, + 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, + 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), + 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, + 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, + kind: Sint, + width: 4, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(40), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Quad, + kind: Float, + width: 4, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Vector( + size: Tri, + 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), + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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..5b32bf44ad --- /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..3553f9030b --- /dev/null +++ b/naga/tests/out/analysis/shadow.info.ron @@ -0,0 +1,1723 @@ +( + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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..b4554d5d31 --- /dev/null +++ b/naga/tests/out/glsl/break-if.main.Compute.glsl @@ -0,0 +1,63 @@ +#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 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..b6bbe5daa7 --- /dev/null +++ b/naga/tests/out/glsl/const-exprs.main.Compute.glsl @@ -0,0 +1,86 @@ +#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; + + +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); +} + +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(); + 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/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..83dd56f8a4 --- /dev/null +++ b/naga/tests/out/glsl/operators.main.Compute.glsl @@ -0,0 +1,260 @@ +#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() { + vec2 a_2 = (((vec2(1.0) + vec2(2.0)) - vec2(3.0)) / vec2(4.0)); + ivec4 b = (ivec4(5) % 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() { + vec4 _e0 = builtins(); + vec4 _e1 = splat(); + vec3 _e6 = 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..fa1be9f61f --- /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 pc; + +layout(location = 0) smooth in vec4 _vs2fs_location0; +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + FragmentIn in_ = FragmentIn(_vs2fs_location0); + float _e4 = pc.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..27cd7037ab --- /dev/null +++ b/naga/tests/out/glsl/push-constants.vert_main.Vertex.glsl @@ -0,0 +1,23 @@ +#version 320 es + +precision highp float; +precision highp int; + +struct PushConstants { + float multiplier; +}; +struct FragmentIn { + vec4 color; +}; +uniform PushConstants pc; + +layout(location = 0) in vec2 _p2vs_location0; + +void main() { + vec2 pos = _p2vs_location0; + uint vi = uint(gl_VertexID); + float _e5 = pc.multiplier; + gl_Position = vec4(((float(vi) * _e5) * 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/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..f1aaaac092 --- /dev/null +++ b/naga/tests/out/hlsl/break-if.hlsl @@ -0,0 +1,61 @@ +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; +} + +[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..fc52a1129e --- /dev/null +++ b/naga/tests/out/hlsl/const-exprs.hlsl @@ -0,0 +1,92 @@ +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; + +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); + +} + +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(); + 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/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..3784864edf --- /dev/null +++ b/naga/tests/out/hlsl/interface.hlsl @@ -0,0 +1,94 @@ +struct NagaConstants { + int base_vertex; + int base_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.base_vertex + vertex_index) + (_NagaConstants.base_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.base_vertex, _NagaConstants.base_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.base_vertex + in1_.index)), float((_NagaConstants.base_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..6d18d07ed6 --- /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() +{ + float2 a_2 = ((((1.0).xx + (2.0).xx) - (3.0).xx) / (4.0).xx); + int4 b = ((5).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() +{ + const float4 _e0 = builtins(); + const float4 _e1 = splat(); + const float3 _e6 = 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..22b4b6acdf --- /dev/null +++ b/naga/tests/out/hlsl/push-constants.hlsl @@ -0,0 +1,33 @@ +struct NagaConstants { + int base_vertex; + int base_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 vi : SV_VertexID) : SV_Position +{ + float _expr5 = pc.multiplier; + return float4(((float((_NagaConstants.base_vertex + vi)) * _expr5) * 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..7a6a36b87f --- /dev/null +++ b/naga/tests/out/hlsl/skybox.hlsl @@ -0,0 +1,65 @@ +struct NagaConstants { + int base_vertex; + int base_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.base_vertex + vertex_index)) / 2); + tmp2_ = (int((_NagaConstants.base_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/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..65f9622f2e --- /dev/null +++ b/naga/tests/out/ir/access.compact.ron @@ -0,0 +1,2197 @@ +( + types: [ + ( + name: None, + inner: Scalar( + kind: Uint, + width: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + 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, + width: 4, + ), + ), + ( + name: None, + inner: Matrix( + columns: Bi, + rows: Bi, + 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, + 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, + 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, + kind: Sint, + width: 4, + ), + ), + ( + name: None, + inner: Matrix( + columns: Quad, + rows: Bi, + 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, + 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: 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: 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..77d95dd58f --- /dev/null +++ b/naga/tests/out/ir/access.ron @@ -0,0 +1,2295 @@ +( + types: [ + ( + name: None, + inner: Scalar( + kind: Uint, + width: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + 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, + width: 4, + ), + ), + ( + name: None, + inner: Matrix( + columns: Bi, + rows: Bi, + 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, + 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, + 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, + kind: Sint, + width: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Matrix( + columns: Quad, + rows: Bi, + width: 4, + ), + ), + ( + name: None, + inner: Array( + base: 19, + size: Constant(2), + stride: 32, + ), + ), + ( + name: Some("MatCx2InArray"), + inner: Struct( + members: [ + ( + name: Some("am"), + ty: 20, + binding: None, + offset: 0, + ), + ], + span: 64, + ), + ), + ( + name: None, + inner: Scalar( + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Pointer( + base: 22, + space: Function, + ), + ), + ( + name: None, + inner: Array( + base: 22, + size: Constant(10), + stride: 4, + ), + ), + ( + name: None, + inner: Array( + base: 24, + size: Constant(5), + stride: 40, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Pointer( + base: 3, + space: Storage( + access: ("LOAD | STORE"), + ), + ), + ), + ( + name: None, + inner: Array( + base: 3, + size: Constant(5), + stride: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + kind: Sint, + width: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Pointer( + base: 1, + space: Function, + ), + ), + ( + name: None, + inner: Array( + base: 26, + size: Constant(2), + stride: 16, + ), + ), + ( + name: None, + inner: Pointer( + base: 32, + 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: 21, + 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, + ], + ), + Literal(I32(8)), + Literal(I32(2)), + Literal(I32(10)), + Literal(I32(2)), + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(1)), + Literal(I32(0)), + Literal(I32(2)), + Literal(I32(2)), + Literal(I32(0)), + Literal(I32(3)), + Literal(I32(2)), + Literal(I32(2)), + Literal(I32(10)), + Literal(I32(5)), + Literal(I32(5)), + Literal(I32(10)), + Literal(I32(5)), + Literal(I32(0)), + Literal(I32(2)), + Literal(I32(2)), + Literal(I32(2)), + Literal(I32(2)), + Literal(I32(1)), + ], + 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(54), + ), + ], + 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, + ), + Literal(I32(0)), + AccessIndex( + base: 10, + index: 0, + ), + Load( + pointer: 12, + ), + GlobalVariable(3), + AccessIndex( + base: 14, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 15, + index: 16, + ), + Load( + pointer: 17, + ), + GlobalVariable(3), + AccessIndex( + base: 19, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 20, + index: 0, + ), + Literal(I32(1)), + AccessIndex( + base: 22, + index: 1, + ), + Load( + pointer: 24, + ), + GlobalVariable(3), + AccessIndex( + base: 26, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 27, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 29, + index: 30, + ), + Load( + pointer: 31, + ), + GlobalVariable(3), + AccessIndex( + base: 33, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 34, + index: 35, + ), + Literal(I32(1)), + AccessIndex( + base: 36, + index: 1, + ), + Load( + pointer: 38, + ), + GlobalVariable(3), + AccessIndex( + base: 40, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 41, + index: 42, + ), + Load( + pointer: 2, + ), + Access( + base: 43, + index: 44, + ), + Load( + pointer: 45, + ), + Literal(F32(1.0)), + Splat( + size: Bi, + value: 47, + ), + Literal(F32(2.0)), + Splat( + size: Bi, + value: 49, + ), + Literal(F32(3.0)), + Splat( + size: Bi, + value: 51, + ), + Compose( + ty: 15, + components: [ + 48, + 50, + 52, + ], + ), + Compose( + ty: 16, + components: [ + 53, + ], + ), + LocalVariable(2), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Add, + left: 57, + right: 56, + ), + AccessIndex( + base: 55, + index: 0, + ), + Literal(F32(6.0)), + Splat( + size: Bi, + value: 60, + ), + Literal(F32(5.0)), + Splat( + size: Bi, + value: 62, + ), + Literal(F32(4.0)), + Splat( + size: Bi, + value: 64, + ), + Compose( + ty: 15, + components: [ + 61, + 63, + 65, + ], + ), + AccessIndex( + base: 55, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 67, + index: 0, + ), + Literal(F32(9.0)), + Splat( + size: Bi, + value: 70, + ), + AccessIndex( + base: 55, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 72, + index: 73, + ), + Literal(F32(90.0)), + Splat( + size: Bi, + value: 75, + ), + AccessIndex( + base: 55, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 77, + index: 0, + ), + Literal(I32(1)), + AccessIndex( + base: 79, + index: 1, + ), + Literal(F32(10.0)), + AccessIndex( + base: 55, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 83, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 85, + index: 86, + ), + Literal(F32(20.0)), + AccessIndex( + base: 55, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 89, + index: 90, + ), + Literal(I32(1)), + AccessIndex( + base: 91, + index: 1, + ), + Literal(F32(30.0)), + AccessIndex( + base: 55, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 95, + index: 96, + ), + Load( + pointer: 2, + ), + Access( + base: 97, + index: 98, + ), + Literal(F32(40.0)), + ], + named_expressions: { + 8: "l0", + 13: "l1", + 18: "l2", + 25: "l3", + 32: "l4", + 39: "l5", + 46: "l6", + }, + body: [ + Emit(( + start: 3, + end: 5, + )), + Store( + pointer: 2, + value: 5, + ), + Emit(( + start: 6, + end: 8, + )), + Emit(( + start: 9, + end: 10, + )), + Emit(( + start: 11, + end: 13, + )), + Emit(( + start: 14, + end: 18, + )), + Emit(( + start: 19, + end: 20, + )), + Emit(( + start: 21, + end: 22, + )), + Emit(( + start: 23, + end: 25, + )), + Emit(( + start: 26, + end: 27, + )), + Emit(( + start: 28, + end: 32, + )), + Emit(( + start: 33, + end: 36, + )), + Emit(( + start: 37, + end: 39, + )), + Emit(( + start: 40, + end: 46, + )), + Emit(( + start: 47, + end: 48, + )), + Emit(( + start: 49, + end: 50, + )), + Emit(( + start: 51, + end: 54, + )), + Emit(( + start: 56, + end: 58, + )), + Store( + pointer: 2, + value: 58, + ), + Emit(( + start: 58, + end: 59, + )), + Emit(( + start: 60, + end: 61, + )), + Emit(( + start: 62, + end: 63, + )), + Emit(( + start: 64, + end: 66, + )), + Store( + pointer: 59, + value: 66, + ), + Emit(( + start: 66, + end: 67, + )), + Emit(( + start: 68, + end: 69, + )), + Emit(( + start: 70, + end: 71, + )), + Store( + pointer: 69, + value: 71, + ), + Emit(( + start: 71, + end: 74, + )), + Emit(( + start: 75, + end: 76, + )), + Store( + pointer: 74, + value: 76, + ), + Emit(( + start: 76, + end: 77, + )), + Emit(( + start: 78, + end: 79, + )), + Emit(( + start: 80, + end: 81, + )), + Store( + pointer: 81, + value: 82, + ), + Emit(( + start: 82, + end: 83, + )), + Emit(( + start: 84, + end: 87, + )), + Store( + pointer: 87, + value: 88, + ), + Emit(( + start: 88, + end: 91, + )), + Emit(( + start: 92, + end: 93, + )), + Store( + pointer: 93, + value: 94, + ), + Emit(( + start: 94, + end: 99, + )), + Store( + pointer: 99, + value: 100, + ), + 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: 21, + init: Some(65), + ), + ], + 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, + ), + Literal(I32(0)), + AccessIndex( + base: 10, + index: 0, + ), + Load( + pointer: 12, + ), + GlobalVariable(5), + AccessIndex( + base: 14, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 15, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 17, + index: 0, + ), + Load( + pointer: 19, + ), + GlobalVariable(5), + AccessIndex( + base: 21, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 22, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 24, + index: 25, + ), + Load( + pointer: 26, + ), + GlobalVariable(5), + AccessIndex( + base: 28, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 29, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 31, + index: 0, + ), + Literal(I32(1)), + AccessIndex( + base: 33, + index: 1, + ), + Load( + pointer: 35, + ), + GlobalVariable(5), + AccessIndex( + base: 37, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 38, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 40, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 42, + index: 43, + ), + Load( + pointer: 44, + ), + GlobalVariable(5), + AccessIndex( + base: 46, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 47, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 49, + index: 50, + ), + Literal(I32(1)), + AccessIndex( + base: 51, + index: 1, + ), + Load( + pointer: 53, + ), + GlobalVariable(5), + AccessIndex( + base: 55, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 56, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 58, + index: 59, + ), + Load( + pointer: 2, + ), + Access( + base: 60, + index: 61, + ), + Load( + pointer: 62, + ), + ZeroValue(20), + Compose( + ty: 21, + components: [ + 64, + ], + ), + LocalVariable(2), + Literal(I32(1)), + Load( + pointer: 2, + ), + Binary( + op: Add, + left: 68, + right: 67, + ), + AccessIndex( + base: 66, + index: 0, + ), + ZeroValue(20), + AccessIndex( + base: 66, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 72, + index: 0, + ), + Literal(F32(8.0)), + Splat( + size: Bi, + value: 75, + ), + Literal(F32(7.0)), + Splat( + size: Bi, + value: 77, + ), + Literal(F32(6.0)), + Splat( + size: Bi, + value: 79, + ), + Literal(F32(5.0)), + Splat( + size: Bi, + value: 81, + ), + Compose( + ty: 19, + components: [ + 76, + 78, + 80, + 82, + ], + ), + AccessIndex( + base: 66, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 84, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 86, + index: 0, + ), + Literal(F32(9.0)), + Splat( + size: Bi, + value: 89, + ), + AccessIndex( + base: 66, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 91, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 93, + index: 94, + ), + Literal(F32(90.0)), + Splat( + size: Bi, + value: 96, + ), + AccessIndex( + base: 66, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 98, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 100, + index: 0, + ), + Literal(I32(1)), + AccessIndex( + base: 102, + index: 1, + ), + Literal(F32(10.0)), + AccessIndex( + base: 66, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 106, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 108, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 110, + index: 111, + ), + Literal(F32(20.0)), + AccessIndex( + base: 66, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 114, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 116, + index: 117, + ), + Literal(I32(1)), + AccessIndex( + base: 118, + index: 1, + ), + Literal(F32(30.0)), + AccessIndex( + base: 66, + index: 0, + ), + Literal(I32(0)), + AccessIndex( + base: 122, + index: 0, + ), + Load( + pointer: 2, + ), + Access( + base: 124, + index: 125, + ), + Load( + pointer: 2, + ), + Access( + base: 126, + index: 127, + ), + Literal(F32(40.0)), + ], + named_expressions: { + 8: "l0", + 13: "l1", + 20: "l2", + 27: "l3", + 36: "l4", + 45: "l5", + 54: "l6", + 63: "l7", + }, + body: [ + Emit(( + start: 3, + end: 5, + )), + Store( + pointer: 2, + value: 5, + ), + Emit(( + start: 6, + end: 8, + )), + Emit(( + start: 9, + end: 10, + )), + Emit(( + start: 11, + end: 13, + )), + Emit(( + start: 14, + end: 15, + )), + Emit(( + start: 16, + end: 17, + )), + Emit(( + start: 18, + end: 20, + )), + Emit(( + start: 21, + end: 22, + )), + Emit(( + start: 23, + end: 27, + )), + Emit(( + start: 28, + end: 29, + )), + Emit(( + start: 30, + end: 31, + )), + Emit(( + start: 32, + end: 33, + )), + Emit(( + start: 34, + end: 36, + )), + Emit(( + start: 37, + end: 38, + )), + Emit(( + start: 39, + end: 40, + )), + Emit(( + start: 41, + end: 45, + )), + Emit(( + start: 46, + end: 47, + )), + Emit(( + start: 48, + end: 51, + )), + Emit(( + start: 52, + end: 54, + )), + Emit(( + start: 55, + end: 56, + )), + Emit(( + start: 57, + end: 63, + )), + Emit(( + start: 64, + end: 65, + )), + Emit(( + start: 67, + end: 69, + )), + Store( + pointer: 2, + value: 69, + ), + Emit(( + start: 69, + end: 70, + )), + Store( + pointer: 70, + value: 71, + ), + Emit(( + start: 71, + end: 72, + )), + Emit(( + start: 73, + end: 74, + )), + Emit(( + start: 75, + end: 76, + )), + Emit(( + start: 77, + end: 78, + )), + Emit(( + start: 79, + end: 80, + )), + Emit(( + start: 81, + end: 83, + )), + Store( + pointer: 74, + value: 83, + ), + Emit(( + start: 83, + end: 84, + )), + Emit(( + start: 85, + end: 86, + )), + Emit(( + start: 87, + end: 88, + )), + Emit(( + start: 89, + end: 90, + )), + Store( + pointer: 88, + value: 90, + ), + Emit(( + start: 90, + end: 91, + )), + Emit(( + start: 92, + end: 95, + )), + Emit(( + start: 96, + end: 97, + )), + Store( + pointer: 95, + value: 97, + ), + Emit(( + start: 97, + end: 98, + )), + Emit(( + start: 99, + end: 100, + )), + Emit(( + start: 101, + end: 102, + )), + Emit(( + start: 103, + end: 104, + )), + Store( + pointer: 104, + value: 105, + ), + Emit(( + start: 105, + end: 106, + )), + Emit(( + start: 107, + end: 108, + )), + Emit(( + start: 109, + end: 112, + )), + Store( + pointer: 112, + value: 113, + ), + Emit(( + start: 113, + end: 114, + )), + Emit(( + start: 115, + end: 118, + )), + Emit(( + start: 119, + end: 120, + )), + Store( + pointer: 120, + value: 121, + ), + Emit(( + start: 121, + end: 122, + )), + Emit(( + start: 123, + end: 128, + )), + Store( + pointer: 128, + value: 129, + ), + Return( + value: None, + ), + ], + ), + ( + name: Some("read_from_private"), + arguments: [ + ( + name: Some("foo"), + ty: 23, + binding: None, + ), + ], + result: Some(( + ty: 22, + 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: 25, + binding: None, + ), + ], + result: Some(( + ty: 22, + binding: None, + )), + local_variables: [], + expressions: [ + FunctionArgument(0), + Literal(I32(4)), + AccessIndex( + base: 1, + index: 4, + ), + Literal(I32(9)), + AccessIndex( + base: 3, + index: 9, + ), + ], + named_expressions: { + 1: "a", + }, + body: [ + Emit(( + start: 2, + end: 3, + )), + Emit(( + start: 4, + end: 5, + )), + Return( + value: Some(5), + ), + ], + ), + ( + name: Some("assign_through_ptr_fn"), + arguments: [ + ( + name: Some("p"), + ty: 31, + 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: 33, + 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: 32, + components: [ + 3, + 5, + ], + ), + ], + named_expressions: { + 1: "foo", + }, + body: [ + 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: 26, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + )), + local_variables: [ + ( + name: Some("foo"), + ty: 22, + init: Some(2), + ), + ( + name: Some("c2"), + ty: 28, + 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, + ), + Literal(I32(0)), + AccessIndex( + base: 31, + index: 0, + ), + AccessIndex( + base: 33, + index: 0, + ), + CallResult(3), + As( + expr: 17, + kind: Sint, + convert: Some(4), + ), + Literal(I32(3)), + Literal(I32(4)), + Literal(I32(5)), + Compose( + ty: 28, + components: [ + 27, + 36, + 37, + 38, + 39, + ], + ), + LocalVariable(2), + Literal(U32(1)), + Binary( + op: Add, + left: 1, + right: 42, + ), + Access( + base: 41, + index: 43, + ), + Literal(I32(42)), + Access( + base: 41, + index: 1, + ), + Load( + pointer: 46, + ), + ZeroValue(25), + CallResult(4), + Splat( + size: Quad, + value: 47, + ), + As( + expr: 50, + kind: Float, + convert: Some(4), + ), + Binary( + op: Multiply, + left: 8, + right: 51, + ), + Literal(F32(2.0)), + Compose( + ty: 26, + components: [ + 52, + 53, + ], + ), + ], + named_expressions: { + 1: "vi", + 4: "baz", + 8: "_matrix", + 11: "arr", + 12: "index", + 17: "b", + 27: "a", + 29: "c", + 34: "data_pointer", + 35: "foo_value", + 47: "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: 32, + end: 34, + )), + Call( + function: 3, + arguments: [ + 3, + ], + result: Some(35), + ), + Emit(( + start: 35, + end: 36, + )), + Emit(( + start: 39, + end: 40, + )), + Store( + pointer: 41, + value: 40, + ), + Emit(( + start: 42, + end: 44, + )), + Store( + pointer: 44, + value: 45, + ), + Emit(( + start: 45, + end: 47, + )), + Call( + function: 4, + arguments: [ + 48, + ], + result: Some(49), + ), + Emit(( + start: 49, + end: 52, + )), + Emit(( + start: 53, + end: 54, + )), + Return( + value: Some(54), + ), + ], + ), + ), + ( + name: "foo_frag", + stage: Fragment, + early_depth_test: None, + workgroup_size: (0, 0, 0), + function: ( + name: Some("foo_frag"), + arguments: [], + result: Some(( + ty: 26, + 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, + ), + Literal(I32(1)), + AccessIndex( + base: 2, + index: 1, + ), + AccessIndex( + base: 4, + index: 2, + ), + Literal(F32(1.0)), + GlobalVariable(2), + AccessIndex( + base: 7, + index: 0, + ), + Literal(F32(0.0)), + Splat( + size: Tri, + value: 9, + ), + Literal(F32(1.0)), + Splat( + size: Tri, + value: 11, + ), + Literal(F32(2.0)), + Splat( + size: Tri, + value: 13, + ), + Literal(F32(3.0)), + Splat( + size: Tri, + value: 15, + ), + Compose( + ty: 6, + components: [ + 10, + 12, + 14, + 16, + ], + ), + GlobalVariable(2), + AccessIndex( + base: 18, + index: 4, + ), + Literal(U32(0)), + Splat( + size: Bi, + value: 20, + ), + Literal(U32(1)), + Splat( + size: Bi, + value: 22, + ), + Compose( + ty: 12, + components: [ + 21, + 23, + ], + ), + GlobalVariable(2), + AccessIndex( + base: 25, + index: 5, + ), + Literal(I32(1)), + AccessIndex( + base: 26, + index: 1, + ), + AccessIndex( + base: 28, + index: 0, + ), + Literal(I32(1)), + GlobalVariable(4), + ZeroValue(17), + Literal(F32(0.0)), + Splat( + size: Quad, + value: 33, + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 1, + end: 2, + )), + Emit(( + start: 3, + end: 5, + )), + Store( + pointer: 5, + value: 6, + ), + Emit(( + start: 7, + end: 8, + )), + Emit(( + start: 9, + end: 10, + )), + Emit(( + start: 11, + end: 12, + )), + Emit(( + start: 13, + end: 14, + )), + Emit(( + start: 15, + end: 17, + )), + Store( + pointer: 8, + value: 17, + ), + Emit(( + start: 18, + end: 19, + )), + Emit(( + start: 20, + end: 21, + )), + Emit(( + start: 22, + end: 24, + )), + Store( + pointer: 19, + value: 24, + ), + Emit(( + start: 25, + end: 26, + )), + Emit(( + start: 27, + end: 29, + )), + Store( + pointer: 29, + value: 30, + ), + Store( + pointer: 31, + value: 32, + ), + Emit(( + start: 33, + end: 34, + )), + Return( + value: Some(34), + ), + ], + ), + ), + ( + 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: 32, + 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: 32, + components: [ + 4, + 6, + ], + ), + LocalVariable(2), + ], + named_expressions: {}, + body: [ + Call( + function: 5, + arguments: [ + 2, + ], + result: None, + ), + 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..7cad54b713 --- /dev/null +++ b/naga/tests/out/ir/collatz.compact.ron @@ -0,0 +1,330 @@ +( + 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, + 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..8146909c1e --- /dev/null +++ b/naga/tests/out/ir/collatz.ron @@ -0,0 +1,334 @@ +( + 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, + 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: [ + Literal(I32(0)), + Literal(I32(0)), + Literal(I32(1)), + ], + 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..9ca6799c21 --- /dev/null +++ b/naga/tests/out/ir/shadow.compact.ron @@ -0,0 +1,1036 @@ +( + types: [ + ( + name: None, + inner: Scalar( + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Scalar( + kind: Uint, + width: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + 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, + 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, + 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..07bf66fcc8 --- /dev/null +++ b/naga/tests/out/ir/shadow.ron @@ -0,0 +1,1330 @@ +( + types: [ + ( + name: None, + inner: Scalar( + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Scalar( + kind: Uint, + width: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + kind: Float, + width: 4, + ), + ), + ( + name: None, + inner: Scalar( + kind: Bool, + width: 1, + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + 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, + 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, + 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/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..657fdf9f77 --- /dev/null +++ b/naga/tests/out/msl/break-if.msl @@ -0,0 +1,67 @@ +// 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; +} + +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..c61ca952e7 --- /dev/null +++ b/naga/tests/out/msl/const-exprs.msl @@ -0,0 +1,92 @@ +// 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; + +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); +} + +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(); + 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..d03912fabd --- /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 compute_Input { +}; +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..960a87f01c --- /dev/null +++ b/naga/tests/out/msl/operators.msl @@ -0,0 +1,269 @@ +// 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( +) { + metal::float2 a_2 = ((metal::float2(1.0) + metal::float2(2.0)) - metal::float2(3.0)) / metal::float2(4.0); + metal::int4 b = metal::int4(5) % 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))))); +} + +kernel void main_( +) { + metal::float4 _e0 = builtins(); + metal::float4 _e1 = splat(); + metal::float3 _e6 = 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..75fdafb6da --- /dev/null +++ b/naga/tests/out/msl/quad.msl @@ -0,0 +1,58 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + +struct VertexOutput { + metal::float2 uv; + 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..0d4560f313 --- /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/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/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..3d3b9f8e57 --- /dev/null +++ b/naga/tests/out/spv/binding-arrays.spvasm @@ -0,0 +1,552 @@ +; 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 %91 NonUniform +OpDecorate %114 NonUniform +OpDecorate %116 NonUniform +OpDecorate %141 NonUniform +OpDecorate %143 NonUniform +OpDecorate %180 NonUniform +OpDecorate %208 NonUniform +OpDecorate %224 NonUniform +OpDecorate %240 NonUniform +OpDecorate %261 NonUniform +OpDecorate %263 NonUniform +OpDecorate %285 NonUniform +OpDecorate %287 NonUniform +OpDecorate %309 NonUniform +OpDecorate %311 NonUniform +OpDecorate %333 NonUniform +OpDecorate %335 NonUniform +OpDecorate %357 NonUniform +OpDecorate %359 NonUniform +OpDecorate %381 NonUniform +OpDecorate %383 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..ea944130e7 --- /dev/null +++ b/naga/tests/out/spv/break-if.spvasm @@ -0,0 +1,88 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 50 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %48 "main" +OpExecutionMode %48 LocalSize 1 1 1 +%2 = OpTypeVoid +%3 = OpTypeBool +%6 = OpTypeFunction %2 +%7 = OpConstantTrue %3 +%16 = OpTypeFunction %2 %3 +%18 = OpTypePointer Function %3 +%19 = OpConstantNull %3 +%21 = OpConstantNull %3 +%35 = OpConstantNull %3 +%37 = OpConstantNull %3 +%5 = OpFunction %2 None %6 +%4 = OpLabel +OpBranch %8 +%8 = OpLabel +OpBranch %9 +%9 = OpLabel +OpLoopMerge %10 %12 None +OpBranch %11 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +OpBranchConditional %7 %10 %9 +%10 = OpLabel +OpReturn +OpFunctionEnd +%15 = OpFunction %2 None %16 +%14 = OpFunctionParameter %3 +%13 = OpLabel +%17 = OpVariable %18 Function %19 +%20 = OpVariable %18 Function %21 +OpBranch %22 +%22 = OpLabel +OpBranch %23 +%23 = OpLabel +OpLoopMerge %24 %26 None +OpBranch %25 +%25 = OpLabel +OpBranch %26 +%26 = OpLabel +OpStore %17 %14 +%27 = OpLoad %3 %17 +%28 = OpLogicalNotEqual %3 %14 %27 +OpStore %20 %28 +%29 = OpLoad %3 %20 +%30 = OpLogicalEqual %3 %14 %29 +OpBranchConditional %30 %24 %23 +%24 = OpLabel +OpReturn +OpFunctionEnd +%33 = OpFunction %2 None %16 +%32 = OpFunctionParameter %3 +%31 = OpLabel +%34 = OpVariable %18 Function %35 +%36 = OpVariable %18 Function %37 +OpBranch %38 +%38 = OpLabel +OpBranch %39 +%39 = OpLabel +OpLoopMerge %40 %42 None +OpBranch %41 +%41 = OpLabel +OpStore %34 %32 +%43 = OpLoad %3 %34 +%44 = OpLogicalNotEqual %3 %32 %43 +OpStore %36 %44 +OpBranch %42 +%42 = OpLabel +%45 = OpLoad %3 %36 +%46 = OpLogicalEqual %3 %32 %45 +OpBranchConditional %46 %40 %39 +%40 = OpLabel +OpReturn +OpFunctionEnd +%48 = OpFunction %2 None %6 +%47 = OpLabel +OpBranch %49 +%49 = 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..e6aff5079a --- /dev/null +++ b/naga/tests/out/spv/const-exprs.spvasm @@ -0,0 +1,131 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 91 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %83 "main" +OpExecutionMode %83 LocalSize 2 3 1 +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeInt 32 1 +%5 = OpTypeVector %4 4 +%6 = OpTypeFloat 32 +%7 = OpTypeVector %6 4 +%8 = OpConstant %3 2 +%9 = OpConstant %4 3 +%10 = OpConstant %4 4 +%11 = OpConstant %4 8 +%12 = OpConstant %6 3.141 +%13 = OpConstant %6 6.282 +%14 = OpConstant %6 0.44444445 +%15 = OpConstant %6 0.0 +%16 = OpConstantComposite %7 %14 %15 %15 %15 +%17 = OpConstant %4 0 +%18 = OpConstant %4 1 +%19 = OpConstant %4 2 +%22 = OpTypeFunction %2 +%23 = OpConstantComposite %5 %10 %9 %19 %18 +%25 = OpTypePointer Function %5 +%30 = OpTypePointer Function %4 +%34 = OpConstant %4 6 +%39 = OpConstant %4 30 +%40 = OpConstant %4 70 +%43 = OpConstantNull %4 +%45 = OpConstantNull %4 +%48 = OpConstantNull %5 +%59 = OpConstant %4 -4 +%60 = OpConstantComposite %5 %59 %59 %59 %59 +%70 = OpTypeFunction %3 %4 +%71 = OpConstant %3 10 +%72 = OpConstant %3 20 +%73 = OpConstant %3 30 +%74 = OpConstant %3 0 +%81 = OpConstantNull %3 +%21 = OpFunction %2 None %22 +%20 = OpLabel +%24 = OpVariable %25 Function %23 +OpBranch %26 +%26 = OpLabel +OpReturn +OpFunctionEnd +%28 = OpFunction %2 None %22 +%27 = OpLabel +%29 = OpVariable %30 Function %19 +OpBranch %31 +%31 = OpLabel +OpReturn +OpFunctionEnd +%33 = OpFunction %2 None %22 +%32 = OpLabel +%35 = OpVariable %30 Function %34 +OpBranch %36 +%36 = OpLabel +OpReturn +OpFunctionEnd +%38 = OpFunction %2 None %22 +%37 = OpLabel +%47 = OpVariable %25 Function %48 +%42 = OpVariable %30 Function %43 +%46 = OpVariable %30 Function %40 +%41 = OpVariable %30 Function %39 +%44 = OpVariable %30 Function %45 +OpBranch %49 +%49 = OpLabel +%50 = OpLoad %4 %41 +OpStore %42 %50 +%51 = OpLoad %4 %42 +OpStore %44 %51 +%52 = OpLoad %4 %41 +%53 = OpLoad %4 %42 +%54 = OpLoad %4 %44 +%55 = OpLoad %4 %46 +%56 = OpCompositeConstruct %5 %52 %53 %54 %55 +OpStore %47 %56 +OpReturn +OpFunctionEnd +%58 = OpFunction %2 None %22 +%57 = OpLabel +%61 = OpVariable %25 Function %60 +OpBranch %62 +%62 = OpLabel +OpReturn +OpFunctionEnd +%64 = OpFunction %2 None %22 +%63 = OpLabel +%65 = OpVariable %25 Function %60 +OpBranch %66 +%66 = OpLabel +OpReturn +OpFunctionEnd +%69 = OpFunction %3 None %70 +%68 = OpFunctionParameter %4 +%67 = OpLabel +OpBranch %75 +%75 = OpLabel +OpSelectionMerge %76 None +OpSwitch %68 %80 0 %77 1 %78 2 %79 +%77 = OpLabel +OpReturnValue %71 +%78 = OpLabel +OpReturnValue %72 +%79 = OpLabel +OpReturnValue %73 +%80 = OpLabel +OpReturnValue %74 +%76 = OpLabel +OpReturnValue %81 +OpFunctionEnd +%83 = OpFunction %2 None %22 +%82 = OpLabel +OpBranch %84 +%84 = OpLabel +%85 = OpFunctionCall %2 %21 +%86 = OpFunctionCall %2 %28 +%87 = OpFunctionCall %2 %33 +%88 = OpFunctionCall %2 %38 +%89 = OpFunctionCall %2 %58 +%90 = OpFunctionCall %2 %64 +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..36fae9d60a --- /dev/null +++ b/naga/tests/out/spv/debug-symbol-terrain.spvasm @@ -0,0 +1,1451 @@ +; 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 1.7928429 +%83 = OpConstant %5 0.85373473 +%84 = OpConstantComposite %4 %82 %82 %82 +%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 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 +%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 13 +%165 = OpVectorShuffle %4 %73 %73 3 3 3 +%166 = OpFMul %4 %144 %165 +%167 = OpExtInst %4 %1 Fract %166 +%168 = OpVectorTimesScalar %4 %167 %81 +OpLine %3 24 13 +%169 = OpFSub %4 %168 %57 +OpLine %3 25 13 +%170 = OpExtInst %4 %1 FAbs %169 +OpLine %3 25 13 +%171 = OpFSub %4 %170 %79 +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 9 +%176 = OpFMul %4 %174 %174 +%177 = OpFMul %4 %171 %171 +%178 = OpFAdd %4 %176 %177 +%179 = OpVectorTimesScalar %4 %178 %83 +%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 12 +%199 = OpLoad %4 %94 +%200 = OpDot %5 %199 %198 +%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 +%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/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..89bc4ccf28 --- /dev/null +++ b/naga/tests/out/spv/operators.spvasm @@ -0,0 +1,437 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 377 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %366 "main" +OpExecutionMode %366 LocalSize 1 1 1 +%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 +%15 = OpConstant %4 1.0 +%16 = OpConstantComposite %3 %15 %15 %15 %15 +%17 = OpConstant %4 0.0 +%18 = OpConstantComposite %3 %17 %17 %17 %17 +%19 = OpConstant %4 0.5 +%20 = OpConstantComposite %3 %19 %19 %19 %19 +%21 = OpConstant %6 1 +%22 = OpConstantComposite %5 %21 %21 %21 %21 +%25 = OpTypeFunction %3 +%26 = OpConstantTrue %8 +%27 = OpConstant %6 0 +%28 = OpConstantFalse %8 +%29 = OpConstantComposite %7 %28 %28 %28 %28 +%30 = OpConstant %4 0.1 +%31 = OpConstantComposite %5 %27 %27 %27 %27 +%53 = OpConstant %4 2.0 +%54 = OpConstantComposite %9 %53 %53 +%55 = OpConstantComposite %9 %15 %15 +%56 = OpConstant %4 3.0 +%57 = OpConstantComposite %9 %56 %56 +%58 = OpConstant %4 4.0 +%59 = OpConstantComposite %9 %58 %58 +%60 = OpConstant %6 5 +%61 = OpConstantComposite %5 %60 %60 %60 %60 +%62 = OpConstant %6 2 +%63 = OpConstantComposite %5 %62 %62 %62 %62 +%74 = OpTypeFunction %9 +%76 = OpTypePointer Function %9 +%88 = OpTypeFunction %10 %10 +%90 = OpTypeVector %8 3 +%91 = OpConstantComposite %10 %17 %17 %17 +%93 = OpConstantComposite %10 %15 %15 %15 +%97 = OpTypeFunction %2 +%98 = OpTypeVector %8 2 +%99 = OpConstantComposite %98 %26 %26 +%100 = OpConstantComposite %90 %26 %26 %26 +%101 = OpConstantComposite %90 %28 %28 %28 +%102 = OpConstantComposite %7 %26 %26 %26 %26 +%103 = OpConstantComposite %7 %28 %28 %28 %28 +%115 = OpTypeInt 32 0 +%116 = OpConstant %115 1 +%117 = OpConstant %115 2 +%118 = OpTypeVector %6 2 +%119 = OpConstantComposite %118 %21 %21 +%120 = OpConstantComposite %118 %62 %62 +%121 = OpTypeVector %115 3 +%122 = OpConstantComposite %121 %117 %117 %117 +%123 = OpConstantComposite %121 %116 %116 %116 +%124 = OpConstantComposite %3 %53 %53 %53 %53 +%125 = OpConstantComposite %3 %15 %15 %15 %15 +%126 = OpTypeVector %115 2 +%127 = OpConstantComposite %126 %117 %117 +%128 = OpConstantComposite %126 %116 %116 +%129 = OpConstantNull %11 +%130 = OpConstantNull %12 +%131 = OpConstantComposite %10 %53 %53 %53 +%132 = OpConstantNull %13 +%296 = OpConstantNull %14 +%298 = OpTypePointer Function %6 +%299 = OpConstantNull %6 +%301 = OpTypePointer Function %14 +%329 = OpTypePointer Function %6 +%367 = OpConstantComposite %10 %15 %15 %15 +%24 = OpFunction %3 None %25 +%23 = OpLabel +OpBranch %32 +%32 = OpLabel +%33 = OpSelect %6 %26 %21 %27 +%35 = OpCompositeConstruct %7 %26 %26 %26 %26 +%34 = OpSelect %3 %35 %16 %18 +%36 = OpSelect %3 %29 %18 %16 +%37 = OpExtInst %3 %1 FMix %18 %16 %20 +%39 = OpCompositeConstruct %3 %30 %30 %30 %30 +%38 = OpExtInst %3 %1 FMix %18 %16 %39 +%40 = OpBitcast %4 %21 +%41 = OpBitcast %3 %22 +%42 = OpCompositeConstruct %5 %33 %33 %33 %33 +%43 = OpIAdd %5 %42 %31 +%44 = OpConvertSToF %3 %43 +%45 = OpFAdd %3 %44 %34 +%46 = OpFAdd %3 %45 %37 +%47 = OpFAdd %3 %46 %38 +%48 = OpCompositeConstruct %3 %40 %40 %40 %40 +%49 = OpFAdd %3 %47 %48 +%50 = OpFAdd %3 %49 %41 +OpReturnValue %50 +OpFunctionEnd +%52 = OpFunction %3 None %25 +%51 = OpLabel +OpBranch %64 +%64 = OpLabel +%65 = OpFAdd %9 %55 %54 +%66 = OpFSub %9 %65 %57 +%67 = OpFDiv %9 %66 %59 +%68 = OpSRem %5 %61 %63 +%69 = OpVectorShuffle %3 %67 %67 0 1 0 1 +%70 = OpConvertSToF %3 %68 +%71 = OpFAdd %3 %69 %70 +OpReturnValue %71 +OpFunctionEnd +%73 = OpFunction %9 None %74 +%72 = OpLabel +%75 = OpVariable %76 Function %54 +OpBranch %77 +%77 = OpLabel +%78 = OpLoad %9 %75 +%79 = OpFAdd %9 %78 %55 +OpStore %75 %79 +%80 = OpLoad %9 %75 +%81 = OpFSub %9 %80 %57 +OpStore %75 %81 +%82 = OpLoad %9 %75 +%83 = OpFDiv %9 %82 %59 +OpStore %75 %83 +%84 = OpLoad %9 %75 +OpReturnValue %84 +OpFunctionEnd +%87 = OpFunction %10 None %88 +%86 = OpFunctionParameter %10 +%85 = OpLabel +OpBranch %89 +%89 = OpLabel +%92 = OpFUnordNotEqual %90 %86 %91 +%94 = OpSelect %10 %92 %93 %91 +OpReturnValue %94 +OpFunctionEnd +%96 = OpFunction %2 None %97 +%95 = OpLabel +OpBranch %104 +%104 = OpLabel +%105 = OpLogicalNot %8 %26 +%106 = OpLogicalNot %98 %99 +%107 = OpLogicalOr %8 %26 %28 +%108 = OpLogicalAnd %8 %26 %28 +%109 = OpLogicalOr %8 %26 %28 +%110 = OpLogicalOr %90 %100 %101 +%111 = OpLogicalAnd %8 %26 %28 +%112 = OpLogicalAnd %7 %102 %103 +OpReturn +OpFunctionEnd +%114 = OpFunction %2 None %97 +%113 = OpLabel +OpBranch %133 +%133 = OpLabel +%134 = OpFNegate %4 %15 +%135 = OpSNegate %118 %119 +%136 = OpFNegate %9 %55 +%137 = OpIAdd %6 %62 %21 +%138 = OpIAdd %115 %117 %116 +%139 = OpFAdd %4 %53 %15 +%140 = OpIAdd %118 %120 %119 +%141 = OpIAdd %121 %122 %123 +%142 = OpFAdd %3 %124 %125 +%143 = OpISub %6 %62 %21 +%144 = OpISub %115 %117 %116 +%145 = OpFSub %4 %53 %15 +%146 = OpISub %118 %120 %119 +%147 = OpISub %121 %122 %123 +%148 = OpFSub %3 %124 %125 +%149 = OpIMul %6 %62 %21 +%150 = OpIMul %115 %117 %116 +%151 = OpFMul %4 %53 %15 +%152 = OpIMul %118 %120 %119 +%153 = OpIMul %121 %122 %123 +%154 = OpFMul %3 %124 %125 +%155 = OpSDiv %6 %62 %21 +%156 = OpUDiv %115 %117 %116 +%157 = OpFDiv %4 %53 %15 +%158 = OpSDiv %118 %120 %119 +%159 = OpUDiv %121 %122 %123 +%160 = OpFDiv %3 %124 %125 +%161 = OpSRem %6 %62 %21 +%162 = OpUMod %115 %117 %116 +%163 = OpFRem %4 %53 %15 +%164 = OpSRem %118 %120 %119 +%165 = OpUMod %121 %122 %123 +%166 = OpFRem %3 %124 %125 +OpBranch %167 +%167 = OpLabel +%169 = OpIAdd %118 %120 %119 +%170 = OpIAdd %118 %120 %119 +%171 = OpIAdd %126 %127 %128 +%172 = OpIAdd %126 %127 %128 +%173 = OpFAdd %9 %54 %55 +%174 = OpFAdd %9 %54 %55 +%175 = OpISub %118 %120 %119 +%176 = OpISub %118 %120 %119 +%177 = OpISub %126 %127 %128 +%178 = OpISub %126 %127 %128 +%179 = OpFSub %9 %54 %55 +%180 = OpFSub %9 %54 %55 +%182 = OpCompositeConstruct %118 %21 %21 +%181 = OpIMul %118 %120 %182 +%184 = OpCompositeConstruct %118 %62 %62 +%183 = OpIMul %118 %119 %184 +%186 = OpCompositeConstruct %126 %116 %116 +%185 = OpIMul %126 %127 %186 +%188 = OpCompositeConstruct %126 %117 %117 +%187 = OpIMul %126 %128 %188 +%189 = OpVectorTimesScalar %9 %54 %15 +%190 = OpVectorTimesScalar %9 %55 %53 +%191 = OpSDiv %118 %120 %119 +%192 = OpSDiv %118 %120 %119 +%193 = OpUDiv %126 %127 %128 +%194 = OpUDiv %126 %127 %128 +%195 = OpFDiv %9 %54 %55 +%196 = OpFDiv %9 %54 %55 +%197 = OpSRem %118 %120 %119 +%198 = OpSRem %118 %120 %119 +%199 = OpUMod %126 %127 %128 +%200 = OpUMod %126 %127 %128 +%201 = OpFRem %9 %54 %55 +%202 = OpFRem %9 %54 %55 +OpBranch %168 +%168 = OpLabel +%204 = OpCompositeExtract %10 %129 0 +%205 = OpCompositeExtract %10 %129 0 +%206 = OpFAdd %10 %204 %205 +%207 = OpCompositeExtract %10 %129 1 +%208 = OpCompositeExtract %10 %129 1 +%209 = OpFAdd %10 %207 %208 +%210 = OpCompositeExtract %10 %129 2 +%211 = OpCompositeExtract %10 %129 2 +%212 = OpFAdd %10 %210 %211 +%203 = OpCompositeConstruct %11 %206 %209 %212 +%214 = OpCompositeExtract %10 %129 0 +%215 = OpCompositeExtract %10 %129 0 +%216 = OpFSub %10 %214 %215 +%217 = OpCompositeExtract %10 %129 1 +%218 = OpCompositeExtract %10 %129 1 +%219 = OpFSub %10 %217 %218 +%220 = OpCompositeExtract %10 %129 2 +%221 = OpCompositeExtract %10 %129 2 +%222 = OpFSub %10 %220 %221 +%213 = OpCompositeConstruct %11 %216 %219 %222 +%223 = OpMatrixTimesScalar %11 %129 %15 +%224 = OpMatrixTimesScalar %11 %129 %53 +%225 = OpMatrixTimesVector %10 %130 %125 +%226 = OpVectorTimesMatrix %3 %131 %130 +%227 = OpMatrixTimesMatrix %11 %130 %132 +OpReturn +OpFunctionEnd +%229 = OpFunction %2 None %97 +%228 = OpLabel +OpBranch %230 +%230 = OpLabel +%231 = OpNot %6 %21 +%232 = OpNot %115 %116 +%233 = OpNot %118 %119 +%234 = OpNot %121 %123 +%235 = OpBitwiseOr %6 %62 %21 +%236 = OpBitwiseOr %115 %117 %116 +%237 = OpBitwiseOr %118 %120 %119 +%238 = OpBitwiseOr %121 %122 %123 +%239 = OpBitwiseAnd %6 %62 %21 +%240 = OpBitwiseAnd %115 %117 %116 +%241 = OpBitwiseAnd %118 %120 %119 +%242 = OpBitwiseAnd %121 %122 %123 +%243 = OpBitwiseXor %6 %62 %21 +%244 = OpBitwiseXor %115 %117 %116 +%245 = OpBitwiseXor %118 %120 %119 +%246 = OpBitwiseXor %121 %122 %123 +%247 = OpShiftLeftLogical %6 %62 %116 +%248 = OpShiftLeftLogical %115 %117 %116 +%249 = OpShiftLeftLogical %118 %120 %128 +%250 = OpShiftLeftLogical %121 %122 %123 +%251 = OpShiftRightArithmetic %6 %62 %116 +%252 = OpShiftRightLogical %115 %117 %116 +%253 = OpShiftRightArithmetic %118 %120 %128 +%254 = OpShiftRightLogical %121 %122 %123 +OpReturn +OpFunctionEnd +%256 = OpFunction %2 None %97 +%255 = OpLabel +OpBranch %257 +%257 = OpLabel +%258 = OpIEqual %8 %62 %21 +%259 = OpIEqual %8 %117 %116 +%260 = OpFOrdEqual %8 %53 %15 +%261 = OpIEqual %98 %120 %119 +%262 = OpIEqual %90 %122 %123 +%263 = OpFOrdEqual %7 %124 %125 +%264 = OpINotEqual %8 %62 %21 +%265 = OpINotEqual %8 %117 %116 +%266 = OpFOrdNotEqual %8 %53 %15 +%267 = OpINotEqual %98 %120 %119 +%268 = OpINotEqual %90 %122 %123 +%269 = OpFOrdNotEqual %7 %124 %125 +%270 = OpSLessThan %8 %62 %21 +%271 = OpULessThan %8 %117 %116 +%272 = OpFOrdLessThan %8 %53 %15 +%273 = OpSLessThan %98 %120 %119 +%274 = OpULessThan %90 %122 %123 +%275 = OpFOrdLessThan %7 %124 %125 +%276 = OpSLessThanEqual %8 %62 %21 +%277 = OpULessThanEqual %8 %117 %116 +%278 = OpFOrdLessThanEqual %8 %53 %15 +%279 = OpSLessThanEqual %98 %120 %119 +%280 = OpULessThanEqual %90 %122 %123 +%281 = OpFOrdLessThanEqual %7 %124 %125 +%282 = OpSGreaterThan %8 %62 %21 +%283 = OpUGreaterThan %8 %117 %116 +%284 = OpFOrdGreaterThan %8 %53 %15 +%285 = OpSGreaterThan %98 %120 %119 +%286 = OpUGreaterThan %90 %122 %123 +%287 = OpFOrdGreaterThan %7 %124 %125 +%288 = OpSGreaterThanEqual %8 %62 %21 +%289 = OpUGreaterThanEqual %8 %117 %116 +%290 = OpFOrdGreaterThanEqual %8 %53 %15 +%291 = OpSGreaterThanEqual %98 %120 %119 +%292 = OpUGreaterThanEqual %90 %122 %123 +%293 = OpFOrdGreaterThanEqual %7 %124 %125 +OpReturn +OpFunctionEnd +%295 = OpFunction %2 None %97 +%294 = OpLabel +%297 = OpVariable %298 Function %299 +%300 = OpVariable %301 Function %296 +OpBranch %302 +%302 = OpLabel +OpStore %297 %21 +%303 = OpLoad %6 %297 +%304 = OpIAdd %6 %303 %21 +OpStore %297 %304 +%305 = OpLoad %6 %297 +%306 = OpISub %6 %305 %21 +OpStore %297 %306 +%307 = OpLoad %6 %297 +%308 = OpLoad %6 %297 +%309 = OpIMul %6 %308 %307 +OpStore %297 %309 +%310 = OpLoad %6 %297 +%311 = OpLoad %6 %297 +%312 = OpSDiv %6 %311 %310 +OpStore %297 %312 +%313 = OpLoad %6 %297 +%314 = OpSRem %6 %313 %21 +OpStore %297 %314 +%315 = OpLoad %6 %297 +%316 = OpBitwiseAnd %6 %315 %27 +OpStore %297 %316 +%317 = OpLoad %6 %297 +%318 = OpBitwiseOr %6 %317 %27 +OpStore %297 %318 +%319 = OpLoad %6 %297 +%320 = OpBitwiseXor %6 %319 %27 +OpStore %297 %320 +%321 = OpLoad %6 %297 +%322 = OpShiftLeftLogical %6 %321 %117 +OpStore %297 %322 +%323 = OpLoad %6 %297 +%324 = OpShiftRightArithmetic %6 %323 %116 +OpStore %297 %324 +%325 = OpLoad %6 %297 +%326 = OpIAdd %6 %325 %21 +OpStore %297 %326 +%327 = OpLoad %6 %297 +%328 = OpISub %6 %327 %21 +OpStore %297 %328 +%330 = OpAccessChain %329 %300 %21 +%331 = OpLoad %6 %330 +%332 = OpIAdd %6 %331 %21 +%333 = OpAccessChain %329 %300 %21 +OpStore %333 %332 +%334 = OpAccessChain %329 %300 %21 +%335 = OpLoad %6 %334 +%336 = OpISub %6 %335 %21 +%337 = OpAccessChain %329 %300 %21 +OpStore %337 %336 +OpReturn +OpFunctionEnd +%339 = OpFunction %2 None %97 +%338 = OpLabel +OpBranch %340 +%340 = OpLabel +%341 = OpSNegate %6 %21 +%342 = OpSNegate %6 %21 +%343 = OpSNegate %6 %342 +%344 = OpSNegate %6 %21 +%345 = OpSNegate %6 %344 +%346 = OpSNegate %6 %21 +%347 = OpSNegate %6 %346 +%348 = OpSNegate %6 %21 +%349 = OpSNegate %6 %348 +%350 = OpSNegate %6 %349 +%351 = OpSNegate %6 %21 +%352 = OpSNegate %6 %351 +%353 = OpSNegate %6 %352 +%354 = OpSNegate %6 %353 +%355 = OpSNegate %6 %21 +%356 = OpSNegate %6 %355 +%357 = OpSNegate %6 %356 +%358 = OpSNegate %6 %357 +%359 = OpSNegate %6 %358 +%360 = OpSNegate %6 %21 +%361 = OpSNegate %6 %360 +%362 = OpSNegate %6 %361 +%363 = OpSNegate %6 %362 +%364 = OpSNegate %6 %363 +OpReturn +OpFunctionEnd +%366 = OpFunction %2 None %97 +%365 = OpLabel +OpBranch %368 +%368 = OpLabel +%369 = OpFunctionCall %3 %24 +%370 = OpFunctionCall %3 %52 +%371 = OpFunctionCall %10 %87 %367 +%372 = OpFunctionCall %2 %96 +%373 = OpFunctionCall %2 %114 +%374 = OpFunctionCall %2 %229 +%375 = OpFunctionCall %2 %256 +%376 = OpFunctionCall %2 %295 +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..d96dbb315b --- /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 = OpConstant %6 0.1 +%51 = OpConstant %6 100.0 +%52 = OpConstantComposite %5 %27 %27 %27 +%53 = OpConstantComposite %14 %48 %49 %50 %51 %52 %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..d88b35b0ba --- /dev/null +++ b/naga/tests/out/spv/runtime-array-in-unused-struct.spvasm @@ -0,0 +1,9 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 3 +OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +%2 = OpTypeVoid \ 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/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..365fa3470e --- /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, 1.0)); + let _e20 = global.ViewProj; + let _e21 = global_1.Model; + let _e23 = position; + gl_Position = ((_e20 * _e21) * vec4(_e23.x, _e23.y, _e23.z, 1.0)); + 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..f7932979b5 --- /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, 1.0)).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, 1.0)).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, 1.0)); + 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..5d1ea64833 --- /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 / 2.0)) * 2.0)) == 0.0) { + { + 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..87761aff4e --- /dev/null +++ b/naga/tests/out/wgsl/277-casting.frag.wgsl @@ -0,0 +1,11 @@ +fn main_1() { + var a: f32 = 1.0; + + 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..7d432ee496 --- /dev/null +++ b/naga/tests/out/wgsl/280-matrix-cast.frag.wgsl @@ -0,0 +1,10 @@ +fn main_1() { + var a: mat4x4 = 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)); + +} + +@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..d749d5a1f4 --- /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, 0.0, 1.0)); + let _e20 = gl_Position; + let _e22 = gl_Position; + gl_Position.z = ((_e20.z + _e22.w) / 2.0); + 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..dc4855efa5 --- /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(1); + implicit(1.0); + implicit_dims_2(vec3(1.0, 1.0, 1.0)); + 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..341ef150e1 --- /dev/null +++ b/naga/tests/out/wgsl/901-lhs-field-select.frag.wgsl @@ -0,0 +1,12 @@ +fn main_1() { + var a: vec4 = vec4(1.0); + + a.x = 2.0; + 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..ba5d223ef7 --- /dev/null +++ b/naga/tests/out/wgsl/931-constant-emitting.frag.wgsl @@ -0,0 +1,15 @@ +const constant: i32 = 10; + +fn function() -> f32 { + return 0.0; +} + +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..ad1fdd34ee --- /dev/null +++ b/naga/tests/out/wgsl/932-for-loop-if.frag.wgsl @@ -0,0 +1,23 @@ +fn main_1() { + var i: i32 = 0; + + loop { + let _e2 = i; + if !((_e2 < 1)) { + break; + } + { + } + continuing { + let _e6 = i; + i = (_e6 + 1); + } + } + return; +} + +@fragment +fn main() { + main_1(); + return; +} diff --git a/naga/tests/out/wgsl/access.wgsl b/naga/tests/out/wgsl/access.wgsl new file mode 100644 index 0000000000..e6904bd62d --- /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), 0); +@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 = 1; + var t: Baz = Baz(mat3x2(vec2(1.0), vec2(2.0), vec2(3.0))); + + let _e3 = idx; + idx = (_e3 - 1); + 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 + 1); + t.m = mat3x2(vec2(6.0), vec2(5.0), vec2(4.0)); + t.m[0] = vec2(9.0); + let _e66 = idx; + t.m[_e66] = vec2(90.0); + t.m[0][1] = 10.0; + let _e76 = idx; + t.m[0][_e76] = 20.0; + let _e80 = idx; + t.m[_e80][1] = 30.0; + let _e85 = idx; + let _e87 = idx; + t.m[_e85][_e87] = 40.0; + return; +} + +fn test_matrix_within_array_within_struct_accesses() { + var idx_1: i32 = 1; + var t_1: MatCx2InArray = MatCx2InArray(array, 2>()); + + let _e3 = idx_1; + idx_1 = (_e3 - 1); + 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 + 1); + t_1.am = array, 2>(); + 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); + let _e77 = idx_1; + t_1.am[0][_e77] = vec2(90.0); + t_1.am[0][0][1] = 10.0; + let _e89 = idx_1; + t_1.am[0][0][_e89] = 20.0; + let _e94 = idx_1; + t_1.am[0][_e94][1] = 30.0; + let _e100 = idx_1; + let _e102 = idx_1; + t_1.am[0][_e100][_e102] = 40.0; + 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(1.0), vec4(2.0)); + return; +} + +@vertex +fn foo_vert(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4 { + var foo: f32 = 0.0; + var c2_: array; + + let baz_1 = foo; + foo = 1.0; + 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), 3, 4, 5); + c2_[(vi + 1u)] = 42; + let value = c2_[vi]; + let _e47 = test_arr_as_arg(array, 5>()); + return vec4((_matrix * vec4(vec4(value))), 2.0); +} + +@fragment +fn foo_frag() -> @location(0) vec4 { + bar._matrix[1][2] = 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); +} + +@compute @workgroup_size(1, 1, 1) +fn assign_through_ptr() { + var val: u32 = 33u; + var arr: array, 2> = array, 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/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..2d5b1e84aa --- /dev/null +++ b/naga/tests/out/wgsl/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 _e0 = ret_array(); + return vec4(_e0[0], _e0[1], 0.0, 1.0); +} diff --git a/naga/tests/out/wgsl/atomicCompareExchange.wgsl b/naga/tests/out/wgsl/atomicCompareExchange.wgsl new file mode 100644 index 0000000000..5f98c61969 --- /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) + 1.0)); + 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) + 1.0)); + 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..dcffe38e14 --- /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]), 1); + atomicStore((&storage_struct.atomic_scalar), 1u); + atomicStore((&storage_struct.atomic_arr[1]), 1); + atomicStore((&workgroup_atomic_scalar), 1u); + atomicStore((&workgroup_atomic_arr[1]), 1); + atomicStore((&workgroup_struct.atomic_scalar), 1u); + atomicStore((&workgroup_struct.atomic_arr[1]), 1); + 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]), 1); + let _e59 = atomicAdd((&storage_struct.atomic_scalar), 1u); + let _e64 = atomicAdd((&storage_struct.atomic_arr[1]), 1); + let _e67 = atomicAdd((&workgroup_atomic_scalar), 1u); + let _e71 = atomicAdd((&workgroup_atomic_arr[1]), 1); + let _e75 = atomicAdd((&workgroup_struct.atomic_scalar), 1u); + let _e80 = atomicAdd((&workgroup_struct.atomic_arr[1]), 1); + workgroupBarrier(); + let _e83 = atomicSub((&storage_atomic_scalar), 1u); + let _e87 = atomicSub((&storage_atomic_arr[1]), 1); + let _e91 = atomicSub((&storage_struct.atomic_scalar), 1u); + let _e96 = atomicSub((&storage_struct.atomic_arr[1]), 1); + let _e99 = atomicSub((&workgroup_atomic_scalar), 1u); + let _e103 = atomicSub((&workgroup_atomic_arr[1]), 1); + let _e107 = atomicSub((&workgroup_struct.atomic_scalar), 1u); + let _e112 = atomicSub((&workgroup_struct.atomic_arr[1]), 1); + workgroupBarrier(); + let _e115 = atomicMax((&storage_atomic_scalar), 1u); + let _e119 = atomicMax((&storage_atomic_arr[1]), 1); + let _e123 = atomicMax((&storage_struct.atomic_scalar), 1u); + let _e128 = atomicMax((&storage_struct.atomic_arr[1]), 1); + let _e131 = atomicMax((&workgroup_atomic_scalar), 1u); + let _e135 = atomicMax((&workgroup_atomic_arr[1]), 1); + let _e139 = atomicMax((&workgroup_struct.atomic_scalar), 1u); + let _e144 = atomicMax((&workgroup_struct.atomic_arr[1]), 1); + workgroupBarrier(); + let _e147 = atomicMin((&storage_atomic_scalar), 1u); + let _e151 = atomicMin((&storage_atomic_arr[1]), 1); + let _e155 = atomicMin((&storage_struct.atomic_scalar), 1u); + let _e160 = atomicMin((&storage_struct.atomic_arr[1]), 1); + let _e163 = atomicMin((&workgroup_atomic_scalar), 1u); + let _e167 = atomicMin((&workgroup_atomic_arr[1]), 1); + let _e171 = atomicMin((&workgroup_struct.atomic_scalar), 1u); + let _e176 = atomicMin((&workgroup_struct.atomic_arr[1]), 1); + workgroupBarrier(); + let _e179 = atomicAnd((&storage_atomic_scalar), 1u); + let _e183 = atomicAnd((&storage_atomic_arr[1]), 1); + let _e187 = atomicAnd((&storage_struct.atomic_scalar), 1u); + let _e192 = atomicAnd((&storage_struct.atomic_arr[1]), 1); + let _e195 = atomicAnd((&workgroup_atomic_scalar), 1u); + let _e199 = atomicAnd((&workgroup_atomic_arr[1]), 1); + let _e203 = atomicAnd((&workgroup_struct.atomic_scalar), 1u); + let _e208 = atomicAnd((&workgroup_struct.atomic_arr[1]), 1); + workgroupBarrier(); + let _e211 = atomicOr((&storage_atomic_scalar), 1u); + let _e215 = atomicOr((&storage_atomic_arr[1]), 1); + let _e219 = atomicOr((&storage_struct.atomic_scalar), 1u); + let _e224 = atomicOr((&storage_struct.atomic_arr[1]), 1); + let _e227 = atomicOr((&workgroup_atomic_scalar), 1u); + let _e231 = atomicOr((&workgroup_atomic_arr[1]), 1); + let _e235 = atomicOr((&workgroup_struct.atomic_scalar), 1u); + let _e240 = atomicOr((&workgroup_struct.atomic_arr[1]), 1); + workgroupBarrier(); + let _e243 = atomicXor((&storage_atomic_scalar), 1u); + let _e247 = atomicXor((&storage_atomic_arr[1]), 1); + let _e251 = atomicXor((&storage_struct.atomic_scalar), 1u); + let _e256 = atomicXor((&storage_struct.atomic_arr[1]), 1); + let _e259 = atomicXor((&workgroup_atomic_scalar), 1u); + let _e263 = atomicXor((&workgroup_atomic_arr[1]), 1); + let _e267 = atomicXor((&workgroup_struct.atomic_scalar), 1u); + let _e272 = atomicXor((&workgroup_struct.atomic_arr[1]), 1); + let _e275 = atomicExchange((&storage_atomic_scalar), 1u); + let _e279 = atomicExchange((&storage_atomic_arr[1]), 1); + let _e283 = atomicExchange((&storage_struct.atomic_scalar), 1u); + let _e288 = atomicExchange((&storage_struct.atomic_arr[1]), 1); + let _e291 = atomicExchange((&workgroup_atomic_scalar), 1u); + let _e295 = atomicExchange((&workgroup_atomic_arr[1]), 1); + let _e299 = atomicExchange((&workgroup_struct.atomic_scalar), 1u); + let _e304 = atomicExchange((&workgroup_struct.atomic_arr[1]), 1); + 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..c40b6dd73d --- /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 = 10; +const MAX_DIRECTIONAL_LIGHTS: i32 = 1; +const PI: f32 = 3.1415927; + +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((1.0 - (_e56 * _e57)), 0.0, 1.0); + let _e64 = smoothFactor; + let _e65 = smoothFactor; + attenuation = (_e64 * _e65); + let _e68 = attenuation; + let _e73 = distanceSquare_1; + return ((_e68 * 1.0) / max(_e73, 0.001)); +} + +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 = (1.0 - (_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.31830987); + 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.5 / (_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_((1.0 - _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_((1.0 - _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.5)), 0.0, 1.0); + 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.5 + (((2.0 * _e50) * _e52) * _e54)); + let _e62 = f90_5; + let _e63 = NoL_5; + let _e64 = F_Schlick_1(1.0, _e62, _e63); + lightScatter = _e64; + let _e70 = f90_5; + let _e71 = NoV_5; + let _e72 = F_Schlick_1(1.0, _e70, _e71); + viewScatter = _e72; + let _e74 = lightScatter; + let _e75 = viewScatter; + return ((_e74 * _e75) * 0.31830987); +} + +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(-1.0, -0.0275, -0.572, 0.022); + var c1_: vec4 = vec4(1.0, 0.0425, 1.04, -0.04); + 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.28 * _e94))) * _e98.x) + _e101.y); + let _e109 = a004_; + let _e112 = r; + AB = ((vec2(-1.04, 1.04) * 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.089, 1.0); + 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(1.0) + _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(1.0) + (_e47 / vec3((_e48 * _e49))))); + let _e56 = numerator; + let _e59 = color_3; + return (_e56 / (vec3(1.0) + _e59)); +} + +fn luminance(v_1: vec3) -> f32 { + var v_2: vec3; + + v_2 = v_1; + let _e47 = v_2; + return dot(_e47, vec3(0.2126, 0.7152, 0.0722)); +} + +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 / (1.0 + _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 * (1.0 + (_e50 / (_e51 * _e52)))); + let _e58 = numerator_1; + let _e60 = l_old_1; + l_new_1 = (_e58 / (1.0 + _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))), 0.0, 1.0))); + 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.5) * _e157)), 0.0, 1.0)); + 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), 0.0, 1.0); + let _e199 = N_1; + let _e200 = H; + let _e206 = N_1; + let _e207 = H; + NoH_4 = clamp(dot(_e206, _e207), 0.0, 1.0); + let _e215 = L; + let _e216 = H; + let _e222 = L; + let _e223 = H; + LoH_6 = clamp(dot(_e222, _e223), 0.0, 1.0); + 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), 0.0, 1.0); + let _e274 = N_1; + let _e275 = H; + let _e281 = N_1; + let _e282 = H; + NoH_4 = clamp(dot(_e281, _e282), 0.0, 1.0); + let _e289 = L; + let _e290 = H; + let _e296 = L; + let _e297 = H; + LoH_6 = clamp(dot(_e296, _e297), 0.0, 1.0); + 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 = 1.0; + 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), 0.0, 1.0); + let _e86 = normal_1; + let _e87 = half_vector; + let _e93 = normal_1; + let _e94 = half_vector; + NoH_5 = clamp(dot(_e93, _e94), 0.0, 1.0); + let _e102 = incident_light; + let _e103 = half_vector; + let _e109 = incident_light; + let _e110 = half_vector; + LoH_7 = clamp(dot(_e109, _e110), 0.0, 1.0); + 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(0.0); + var i: i32 = 0; + var i_1: i32 = 0; + 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 * 2.0) - vec3(1.0)))); + 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.001); + let _e190 = global_6.reflectance; + let _e192 = global_6.reflectance; + let _e195 = metallic; + let _e199 = output_color; + let _e201 = metallic; + F0_4 = (vec3((((0.16 * _e190) * _e192) * (1.0 - _e195))) + (_e199.xyz * vec3(_e201))); + let _e206 = output_color; + let _e209 = metallic; + diffuseColor_4 = (_e206.xyz * vec3((1.0 - _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 + 1); + } + } + 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 + 1); + } + } + let _e302 = diffuseColor_4; + let _e304 = NdotV_4; + let _e305 = EnvBRDFApprox(_e302, 1.0, _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..ac13dcdfdc --- /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, 1.0)); + 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..3a3b116643 --- /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[1], global_1[1], vec2(0.5, 0.5), 0.0); + 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..ff969359f0 --- /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[1], global_1[1], vec2(0.5, 0.5), 0.0); + 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..c7a01fc5c3 --- /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 = 0.0; + var v4_: vec4 = vec4(0.0); + + let uniform_index = uni.index; + let non_uniform_index = fragment_in.index; + let uv = vec2(0.0); + let pix = vec2(0); + 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, 0.0); + let _e61 = v4_; + v4_ = (_e61 + _e60); + let _e68 = textureGatherCompare(texture_array_depth[uniform_index], samp_comp[uniform_index], uv, 0.0); + let _e69 = v4_; + v4_ = (_e69 + _e68); + let _e76 = textureGatherCompare(texture_array_depth[non_uniform_index], samp_comp[non_uniform_index], uv, 0.0); + let _e77 = v4_; + v4_ = (_e77 + _e76); + let _e82 = textureLoad(texture_array_unbounded[0], pix, 0); + let _e83 = v4_; + v4_ = (_e83 + _e82); + let _e88 = textureLoad(texture_array_unbounded[uniform_index], pix, 0); + let _e89 = v4_; + v4_ = (_e89 + _e88); + let _e94 = textureLoad(texture_array_unbounded[non_uniform_index], pix, 0); + 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, 0.0); + let _e169 = v4_; + v4_ = (_e169 + _e168); + let _e176 = textureSampleBias(texture_array_bounded[uniform_index], samp[uniform_index], uv, 0.0); + let _e177 = v4_; + v4_ = (_e177 + _e176); + let _e184 = textureSampleBias(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv, 0.0); + let _e185 = v4_; + v4_ = (_e185 + _e184); + let _e192 = textureSampleCompare(texture_array_depth[0], samp_comp[0], uv, 0.0); + let _e193 = v1_; + v1_ = (_e193 + _e192); + let _e200 = textureSampleCompare(texture_array_depth[uniform_index], samp_comp[uniform_index], uv, 0.0); + let _e201 = v1_; + v1_ = (_e201 + _e200); + let _e208 = textureSampleCompare(texture_array_depth[non_uniform_index], samp_comp[non_uniform_index], uv, 0.0); + let _e209 = v1_; + v1_ = (_e209 + _e208); + let _e216 = textureSampleCompareLevel(texture_array_depth[0], samp_comp[0], uv, 0.0); + let _e217 = v1_; + v1_ = (_e217 + _e216); + let _e224 = textureSampleCompareLevel(texture_array_depth[uniform_index], samp_comp[uniform_index], uv, 0.0); + let _e225 = v1_; + v1_ = (_e225 + _e224); + let _e232 = textureSampleCompareLevel(texture_array_depth[non_uniform_index], samp_comp[non_uniform_index], uv, 0.0); + 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, 0.0); + let _e262 = v4_; + v4_ = (_e262 + _e261); + let _e269 = textureSampleLevel(texture_array_bounded[uniform_index], samp[uniform_index], uv, 0.0); + let _e270 = v4_; + v4_ = (_e270 + _e269); + let _e277 = textureSampleLevel(texture_array_bounded[non_uniform_index], samp[non_uniform_index], uv, 0.0); + 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..a426c26ca0 --- /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(0); + var i3_: vec3 = vec3(0); + var i4_: vec4 = vec4(0); + var u2_: vec2 = vec2(0u); + var u3_: vec3 = vec3(0u); + var u4_: vec4 = vec4(0u); + var f2_: vec2 = vec2(0.0); + var f3_: vec3 = vec3(0.0); + var f4_: vec4 = vec4(0.0); + + 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..fb4f396a52 --- /dev/null +++ b/naga/tests/out/wgsl/bits.wgsl @@ -0,0 +1,119 @@ +@compute @workgroup_size(1, 1, 1) +fn main() { + var i: i32 = 0; + var i2_: vec2 = vec2(0); + var i3_: vec3 = vec3(0); + var i4_: vec4 = vec4(0); + var u: u32 = 0u; + var u2_: vec2 = vec2(0u); + var u3_: vec3 = vec3(0u); + var u4_: vec4 = vec4(0u); + var f2_: vec2 = vec2(0.0); + var f4_: vec4 = vec4(0.0); + + 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..597d5aa1c5 --- /dev/null +++ b/naga/tests/out/wgsl/bits_glsl.frag.wgsl @@ -0,0 +1,112 @@ +fn main_1() { + var i: i32 = 0; + var i2_: vec2 = vec2(0); + var i3_: vec3 = vec3(0); + var i4_: vec4 = vec4(0); + var u: u32 = 0u; + var u2_: vec2 = vec2(0u); + var u3_: vec3 = vec3(0u); + var u4_: vec4 = vec4(0u); + var f2_: vec2 = vec2(0.0); + var f4_: vec4 = vec4(0.0); + + 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..b87d3ed91d --- /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(0.0, 0.0); + var cVel: vec2 = vec2(0.0, 0.0); + var colVel: vec2 = vec2(0.0, 0.0); + var cMassCount: i32 = 0; + var cVelCount: i32 = 0; + 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 + 1); + } + 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 + 1); + } + continuing { + let _e91 = i; + i = (_e91 + 1u); + } + } + let _e94 = cMassCount; + if (_e94 > 0) { + let _e97 = cMass; + let _e98 = cMassCount; + let _e102 = vPos; + cMass = ((_e97 / vec2(f32(_e98))) - _e102); + } + let _e104 = cVelCount; + if (_e104 > 0) { + 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), 0.0, 0.1)); + let _e139 = vPos; + let _e140 = vVel; + let _e143 = params.deltaT; + vPos = (_e139 + (_e140 * _e143)); + let _e147 = vPos.x; + if (_e147 < -1.0) { + vPos.x = 1.0; + } + let _e153 = vPos.x; + if (_e153 > 1.0) { + vPos.x = -1.0; + } + let _e159 = vPos.y; + if (_e159 < -1.0) { + vPos.y = 1.0; + } + let _e165 = vPos.y; + if (_e165 > 1.0) { + vPos.y = -1.0; + } + 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..f34c54243d --- /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(0.0, 1.0, (_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(0.0), vec3(1.0), (_e7 > _e8)); +} + +fn main_1() { + let _e1 = o_color; + let _e11 = TevPerCompGT_1(vec3(3.0), vec3(5.0)); + o_color.x = _e11.x; + o_color.y = _e11.y; + o_color.z = _e11.z; + let _e23 = TevPerCompGT(3.0, 5.0); + 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..6e65c52154 --- /dev/null +++ b/naga/tests/out/wgsl/break-if.wgsl @@ -0,0 +1,45 @@ +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; +} + +@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..43bcceb0d5 --- /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[1] = 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..6b5fe96de2 --- /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(0.0), vec2(1.0)); + gl_Position = vec4(_e10.x, _e10.y, 0.0, 1.0); + 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..fffc11f34f --- /dev/null +++ b/naga/tests/out/wgsl/const-exprs.wgsl @@ -0,0 +1,84 @@ +const TWO: u32 = 2u; +const THREE: i32 = 3; +const FOUR: i32 = 4; +const FOUR_ALIAS: i32 = 4; +const TEST_CONSTANT_ADDITION: i32 = 8; +const TEST_CONSTANT_ALIAS_ADDITION: i32 = 8; +const PI: f32 = 3.141; +const phi_sun: f32 = 6.282; +const DIV: vec4 = vec4(0.44444445, 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 swizzle_of_compose() { + var out: vec4 = vec4(4, 3, 2, 1); + +} + +fn index_of_compose() { + var out_1: i32 = 2; + +} + +fn compose_three_deep() { + var out_2: i32 = 6; + +} + +fn non_constant_initializers() { + var w: i32 = 30; + var x: i32; + var y: i32; + var z: i32 = 70; + 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(-4, -4, -4, -4); + +} + +fn compose_of_constant() { + var out_5: vec4 = vec4(-4, -4, -4, -4); + +} + +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(); + return; +} 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..f4baa8c129 --- /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 = 42; + +@group(1) @binding(0) +var global: Data; + +fn function() -> vec4 { + var sum: vec4 = vec4(0.0); + var i: i32 = 0; + + 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 + 1); + } + } + 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..a52fb00217 --- /dev/null +++ b/naga/tests/out/wgsl/constructors.wgsl @@ -0,0 +1,32 @@ +struct Foo { + a: vec4, + b: i32, +} + +const const2_: vec3 = vec3(0.0, 1.0, 2.0); +const const3_: mat2x2 = mat2x2(vec2(0.0, 1.0), vec2(2.0, 3.0)); +const const4_: array, 1> = array, 1>(mat2x2(vec2(0.0, 1.0), vec2(2.0, 3.0))); +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(0, 1, 2, 3); + +@compute @workgroup_size(1, 1, 1) +fn main() { + var foo: Foo; + + foo = Foo(vec4(1.0), 1); + let m0_ = mat2x2(vec2(1.0, 0.0), vec2(0.0, 1.0)); + let 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)); + let cit0_ = vec2(0u); + let cit1_ = mat2x2(vec2(0.0), vec2(0.0)); + let cit2_ = array(0, 1, 2, 3); + let ic0_ = bool(bool()); + let ic4_ = vec2(0u, 0u); + let ic5_ = mat2x3(vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.0)); +} diff --git a/naga/tests/out/wgsl/control-flow.wgsl b/naga/tests/out/wgsl/control-flow.wgsl new file mode 100644 index 0000000000..2872406d76 --- /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 0 { + 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 1 { + default: { + pos = 1; + } + } + let _e4 = pos; + switch _e4 { + case 1: { + pos = 0; + break; + } + case 2: { + pos = 1; + } + case 3, 4: { + pos = 2; + } + case 5: { + pos = 3; + } + case default, 6: { + pos = 4; + } + } + switch 0u { + case 0u: { + } + default: { + } + } + let _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/wgsl/declarations.frag.wgsl b/naga/tests/out/wgsl/declarations.frag.wgsl new file mode 100644 index 0000000000..bfe6da8e2d --- /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(-1.0, 1.0, 0.0), vec3(-1.0, -1.0, 0.0)); + var strct: TestStruct = TestStruct(1.0, 2.0); + 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[0] = vec4(2.0); + 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/dualsource.wgsl b/naga/tests/out/wgsl/dualsource.wgsl new file mode 100644 index 0000000000..8d03f244a9 --- /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.4, 0.3, 0.2, 0.1); + var mask: vec4 = vec4(0.9, 0.8, 0.7, 0.6); + + 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..5114003beb --- /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 + 1); + 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..c3817d8d78 --- /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 * 2.0); + let _e8 = a_1; + v = (_e8 / vec4(2.0)); + let _e12 = a_1; + v = (_e12 + vec4(2.0)); + let _e16 = a_1; + v = (_e16 - vec4(2.0)); + 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(1.0); + let _e9 = (_e5 - mat3x3(_e7, _e7, _e7)); + a_17 = _e9; + v_8 = _e9; + let _e10 = a_17; + let _e12 = vec3(1.0); + a_17 = (_e10 - mat3x3(_e12, _e12, _e12)); + v_8 = _e10; + return; +} + +fn testStructConstructor() { + var tree: BST = BST(1); + +} + +fn testNonScalarToScalarConstructor() { + var f: f32 = 1.0; + +} + +fn testArrayConstructor() { + var tree_1: array = array(0.0); + +} + +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 = 4; + + 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 = 3.0; + a_28.x = 4.0; + let _e14 = a_28; + let _e16 = a_28; + let _e19 = (_e16.xy * 5.0); + a_28.x = _e19.x; + a_28.y = _e19.y; + let _e24 = a_28; + let _e28 = (_e24.zy + vec2(1.0)); + 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 = 1.0; + o_color.y = 1.0; + o_color.z = 1.0; + o_color.w = 1.0; + 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..b6cfaea7d3 --- /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(1.0) - in.color.xyz), in.color.w); + } +} diff --git a/naga/tests/out/wgsl/fma.frag.wgsl b/naga/tests/out/wgsl/fma.frag.wgsl new file mode 100644 index 0000000000..8708539786 --- /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 = 1.0; + o_color.y = 1.0; + o_color.z = 1.0; + o_color.w = 1.0; + 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..f478faa70f --- /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(0.0); + output.vec4i = vec4(0); + output.vec4u = vec4(0u); + output.vec3f = vec3(0.0); + output.vec3i = vec3(0); + output.vec3u = vec3(0u); + let _e19 = output; + return _e19; +} + +@fragment +fn main_vec2scalar() -> FragmentOutputVec2Scalar { + var output_1: FragmentOutputVec2Scalar; + + output_1.vec2f = vec2(0.0); + output_1.vec2i = vec2(0); + output_1.vec2u = vec2(0u); + output_1.scalarf = 0.0; + output_1.scalari = 0; + 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..f48a864461 --- /dev/null +++ b/naga/tests/out/wgsl/functions.wgsl @@ -0,0 +1,24 @@ +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); +} + +fn test_integer_dot_product() -> i32 { + let a_2_ = vec2(1); + let b_2_ = vec2(1); + 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(4), vec4(2)); + 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..d6d4bfb61a --- /dev/null +++ b/naga/tests/out/wgsl/global-constant-array.frag.wgsl @@ -0,0 +1,15 @@ +const array_: array = array(1.0, 2.0); + +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..562533156e --- /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 = 1; + + alignment.v3_ = vec3(1.0); + alignment.v3_.x = 1.0; + alignment.v3_.x = 2.0; + let _e16 = idx; + alignment.v3_[_e16] = 3.0; + 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_ * 2.0); + let svm1_ = (2.0 * data.v3_); +} + +@compute @workgroup_size(1, 1, 1) +fn main() { + var Foo: f32 = 1.0; + 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_ = 4.0; + 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..062d377139 --- /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(10, 20)); + 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)); + 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)); + 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(10, 20)); + 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, 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_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.5); + let tc3_ = vec3(0.5); + 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(3, 1)); + let _e20 = a; + a = (_e20 + _e19); + let _e24 = textureSampleLevel(image_2d, sampler_reg, tc, 2.3); + let _e25 = a; + a = (_e25 + _e24); + let _e29 = textureSampleLevel(image_2d, sampler_reg, tc, 2.3, vec2(3, 1)); + let _e30 = a; + a = (_e30 + _e29); + let _e35 = textureSampleBias(image_2d, sampler_reg, tc, 2.0, vec2(3, 1)); + 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(3, 1)); + let _e48 = a; + a = (_e48 + _e47); + let _e53 = textureSampleLevel(image_2d_array, sampler_reg, tc, 0u, 2.3); + let _e54 = a; + a = (_e54 + _e53); + let _e59 = textureSampleLevel(image_2d_array, sampler_reg, tc, 0u, 2.3, vec2(3, 1)); + let _e60 = a; + a = (_e60 + _e59); + let _e66 = textureSampleBias(image_2d_array, sampler_reg, tc, 0u, 2.0, vec2(3, 1)); + let _e67 = a; + a = (_e67 + _e66); + let _e72 = textureSample(image_2d_array, sampler_reg, tc, 0); + let _e73 = a; + a = (_e73 + _e72); + let _e78 = textureSample(image_2d_array, sampler_reg, tc, 0, vec2(3, 1)); + let _e79 = a; + a = (_e79 + _e78); + let _e84 = textureSampleLevel(image_2d_array, sampler_reg, tc, 0, 2.3); + let _e85 = a; + a = (_e85 + _e84); + let _e90 = textureSampleLevel(image_2d_array, sampler_reg, tc, 0, 2.3, vec2(3, 1)); + let _e91 = a; + a = (_e91 + _e90); + let _e97 = textureSampleBias(image_2d_array, sampler_reg, tc, 0, 2.0, vec2(3, 1)); + 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.3); + let _e110 = a; + a = (_e110 + _e109); + let _e116 = textureSampleBias(image_cube_array, sampler_reg, tc3_, 0u, 2.0); + let _e117 = a; + a = (_e117 + _e116); + let _e122 = textureSample(image_cube_array, sampler_reg, tc3_, 0); + let _e123 = a; + a = (_e123 + _e122); + let _e128 = textureSampleLevel(image_cube_array, sampler_reg, tc3_, 0, 2.3); + let _e129 = a; + a = (_e129 + _e128); + let _e135 = textureSampleBias(image_cube_array, sampler_reg, tc3_, 0, 2.0); + 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.5); + let tc3_1 = vec3(0.5); + let _e8 = textureSampleCompare(image_2d_depth, sampler_cmp, tc_1, 0.5); + let _e9 = a_1; + a_1 = (_e9 + _e8); + let _e14 = textureSampleCompare(image_2d_array_depth, sampler_cmp, tc_1, 0u, 0.5); + let _e15 = a_1; + a_1 = (_e15 + _e14); + let _e20 = textureSampleCompare(image_2d_array_depth, sampler_cmp, tc_1, 0, 0.5); + let _e21 = a_1; + a_1 = (_e21 + _e20); + let _e25 = textureSampleCompare(image_cube_depth, sampler_cmp, tc3_1, 0.5); + let _e26 = a_1; + a_1 = (_e26 + _e25); + let _e30 = textureSampleCompareLevel(image_2d_depth, sampler_cmp, tc_1, 0.5); + let _e31 = a_1; + a_1 = (_e31 + _e30); + let _e36 = textureSampleCompareLevel(image_2d_array_depth, sampler_cmp, tc_1, 0u, 0.5); + let _e37 = a_1; + a_1 = (_e37 + _e36); + let _e42 = textureSampleCompareLevel(image_2d_array_depth, sampler_cmp, tc_1, 0, 0.5); + let _e43 = a_1; + a_1 = (_e43 + _e42); + let _e47 = textureSampleCompareLevel(image_cube_depth, sampler_cmp, tc3_1, 0.5); + 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.5); + let s2d = textureGather(1, image_2d, sampler_reg, tc_2); + let s2d_offset = textureGather(3, image_2d, sampler_reg, tc_2, vec2(3, 1)); + let s2d_depth = textureGatherCompare(image_2d_depth, sampler_cmp, tc_2, 0.5); + let s2d_depth_offset = textureGatherCompare(image_2d_depth, sampler_cmp, tc_2, 0.5, vec2(3, 1)); + 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.5); + 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..2edcb9c0ea --- /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(2.0)); + 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(2.0)); + 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(2.0)); + 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(2.0)); + 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(2.0)); + 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(2.0)); + 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..96713aae95 --- /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(1.0), 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(0.0, 1.0, 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), 0.0); +} diff --git a/naga/tests/out/wgsl/interpolate.wgsl b/naga/tests/out/wgsl/interpolate.wgsl new file mode 100644 index 0000000000..e2c4ce28b4 --- /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(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; + 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..f3756b61bf --- /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 = 0; + + loop { + let _e3 = a_4; + if (_e3 < 1) { + } else { + break; + } + { + } + continuing { + let _e8 = a_4; + a_4 = (_e8 + 1); + } + } + return; +} + +fn whileLexicalScope(a_5: i32) { + loop { + if (a_5 > 2) { + } else { + break; + } + { + } + } + return; +} + +fn switchLexicalScope(a_6: i32) { + switch a_6 { + case 0: { + } + case 1: { + } + default: { + } + } + let test = (a_6 == 2); +} + 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..7675a54cef --- /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(0.0); + var i: i32 = 0; + var a: vec4; + + loop { + let _e6 = i; + if !((_e6 < 4)) { + break; + } + { + a = vec4(1.0); + let _e17 = sum; + let _e18 = a; + sum = (_e17 + _e18); + } + continuing { + let _e10 = i; + i = (_e10 + 1); + } + } + 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..cadfe936c4 --- /dev/null +++ b/naga/tests/out/wgsl/long-form-matrix.frag.wgsl @@ -0,0 +1,17 @@ +fn main_1() { + var splat: mat2x2 = mat2x2(vec2(1.0, 0.0), vec2(0.0, 1.0)); + var normal: mat2x2 = mat2x2(vec2(1.0, 1.0), vec2(2.0, 2.0)); + var from_matrix: mat2x4 = mat2x4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0)); + var a: mat2x2 = mat2x2(vec2(1.0, 2.0), vec2(3.0, 4.0)); + var b: mat2x2 = mat2x2(vec2(1.0, 2.0), vec2(3.0, 4.0)); + var c: mat3x3 = mat3x3(vec3(1.0, 2.0, 3.0), vec3(1.0, 1.0, 1.0), vec3(1.0, 1.0, 1.0)); + var d: mat3x3 = mat3x3(vec3(2.0, 2.0, 1.0), vec3(1.0, 1.0, 1.0), vec3(1.0, 1.0, 1.0)); + var e: mat4x4 = mat4x4(vec4(2.0, 2.0, 1.0, 1.0), vec4(1.0, 1.0, 2.0, 2.0), vec4(1.0, 1.0, 1.0, 1.0), vec4(1.0, 1.0, 1.0, 1.0)); + +} + +@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..0e3b5c3844 --- /dev/null +++ b/naga/tests/out/wgsl/math-functions.frag.wgsl @@ -0,0 +1,168 @@ +fn main_1() { + var a: vec4 = vec4(1.0); + var b: vec4 = vec4(2.0); + var m: mat4x4; + var i: i32 = 5; + 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(0.0, 1.0, 0.5); + smoothStepVector = smoothstep(vec4(0.0), vec4(1.0), vec4(0.5)); + smoothStepMixed = smoothstep(vec4(0.0), vec4(1.0), vec4(0.5)); + 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..e6742fd31e --- /dev/null +++ b/naga/tests/out/wgsl/math-functions.wgsl @@ -0,0 +1,47 @@ +@fragment +fn main() { + let v = vec4(0.0); + let a = degrees(1.0); + let b = radians(1.0); + let c = degrees(v); + let d = radians(v); + let e = saturate(v); + let g = refract(v, v, 1.0); + 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(4294967295u); + 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 = modf(vec2(1.5, 1.5)).fract.y; + let frexp_a = frexp(1.5); + let frexp_b = frexp(1.5).fract; + let frexp_c = frexp(1.5).exp; + let frexp_d = frexp(vec4(1.5, 1.5, 1.5, 1.5)).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..b746ff37ca --- /dev/null +++ b/naga/tests/out/wgsl/module-scope.wgsl @@ -0,0 +1,25 @@ +struct S { + x: i32, +} + +const Value: i32 = 1; + +@group(0) @binding(0) +var Texture: texture_2d; +@group(0) @binding(1) +var Sampler: sampler; + +fn statement() { + return; +} + +fn returns() -> S { + return S(1); +} + +fn call() { + statement(); + let _e0 = returns(); + let s = textureSample(Texture, Sampler, vec2(1.0)); +} + 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..95621e32e2 --- /dev/null +++ b/naga/tests/out/wgsl/operators.wgsl @@ -0,0 +1,254 @@ +const v_f32_one: vec4 = vec4(1.0, 1.0, 1.0, 1.0); +const v_f32_zero: vec4 = vec4(0.0, 0.0, 0.0, 0.0); +const v_f32_half: vec4 = vec4(0.5, 0.5, 0.5, 0.5); +const v_i32_one: vec4 = vec4(1, 1, 1, 1); + +fn builtins() -> vec4 { + let s1_ = select(0, 1, 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.1); + let b1_ = bitcast(1); + let b2_ = bitcast>(v_i32_one); + let v_i32_zero = vec4(0, 0, 0, 0); + return (((((vec4((vec4(s1_) + v_i32_zero)) + s2_) + m1_) + m2_) + vec4(b1_)) + b2_); +} + +fn splat() -> vec4 { + let a_2 = (((vec2(1.0) + vec2(2.0)) - vec2(3.0)) / vec2(4.0)); + let b = (vec4(5) % vec4(2)); + return (a_2.xyxy + vec4(b)); +} + +fn splat_assignment() -> vec2 { + var a: vec2 = vec2(2.0); + + let _e4 = a; + a = (_e4 + vec2(1.0)); + let _e8 = a; + a = (_e8 - vec2(3.0)); + let _e12 = a; + a = (_e12 / vec2(4.0)); + 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 = -(1.0); + let neg1_1 = -(vec2(1)); + let neg2_ = -(vec2(1.0)); + let add0_ = (2 + 1); + let add1_ = (2u + 1u); + let add2_ = (2.0 + 1.0); + let add3_ = (vec2(2) + vec2(1)); + let add4_ = (vec3(2u) + vec3(1u)); + let add5_ = (vec4(2.0) + vec4(1.0)); + let sub0_ = (2 - 1); + let sub1_ = (2u - 1u); + let sub2_ = (2.0 - 1.0); + let sub3_ = (vec2(2) - vec2(1)); + let sub4_ = (vec3(2u) - vec3(1u)); + let sub5_ = (vec4(2.0) - vec4(1.0)); + let mul0_ = (2 * 1); + let mul1_ = (2u * 1u); + let mul2_ = (2.0 * 1.0); + let mul3_ = (vec2(2) * vec2(1)); + let mul4_ = (vec3(2u) * vec3(1u)); + let mul5_ = (vec4(2.0) * vec4(1.0)); + let div0_ = (2 / 1); + let div1_ = (2u / 1u); + let div2_ = (2.0 / 1.0); + let div3_ = (vec2(2) / vec2(1)); + let div4_ = (vec3(2u) / vec3(1u)); + let div5_ = (vec4(2.0) / vec4(1.0)); + let rem0_ = (2 % 1); + let rem1_ = (2u % 1u); + let rem2_ = (2.0 % 1.0); + let rem3_ = (vec2(2) % vec2(1)); + let rem4_ = (vec3(2u) % vec3(1u)); + let rem5_ = (vec4(2.0) % vec4(1.0)); + { + let add0_1 = (vec2(2) + vec2(1)); + let add1_1 = (vec2(2) + vec2(1)); + let add2_1 = (vec2(2u) + vec2(1u)); + let add3_1 = (vec2(2u) + vec2(1u)); + let add4_1 = (vec2(2.0) + vec2(1.0)); + let add5_1 = (vec2(2.0) + vec2(1.0)); + let sub0_1 = (vec2(2) - vec2(1)); + let sub1_1 = (vec2(2) - vec2(1)); + let sub2_1 = (vec2(2u) - vec2(1u)); + let sub3_1 = (vec2(2u) - vec2(1u)); + let sub4_1 = (vec2(2.0) - vec2(1.0)); + let sub5_1 = (vec2(2.0) - vec2(1.0)); + let mul0_1 = (vec2(2) * 1); + let mul1_1 = (2 * vec2(1)); + let mul2_1 = (vec2(2u) * 1u); + let mul3_1 = (2u * vec2(1u)); + let mul4_1 = (vec2(2.0) * 1.0); + let mul5_1 = (2.0 * vec2(1.0)); + let div0_1 = (vec2(2) / vec2(1)); + let div1_1 = (vec2(2) / vec2(1)); + let div2_1 = (vec2(2u) / vec2(1u)); + let div3_1 = (vec2(2u) / vec2(1u)); + let div4_1 = (vec2(2.0) / vec2(1.0)); + let div5_1 = (vec2(2.0) / vec2(1.0)); + let rem0_1 = (vec2(2) % vec2(1)); + let rem1_1 = (vec2(2) % vec2(1)); + let rem2_1 = (vec2(2u) % vec2(1u)); + let rem3_1 = (vec2(2u) % vec2(1u)); + let rem4_1 = (vec2(2.0) % vec2(1.0)); + let rem5_1 = (vec2(2.0) % vec2(1.0)); + } + let add = (mat3x3() + mat3x3()); + let sub = (mat3x3() - mat3x3()); + let mul_scalar0_ = (mat3x3() * 1.0); + let mul_scalar1_ = (2.0 * mat3x3()); + let mul_vector0_ = (mat4x3() * vec4(1.0)); + let mul_vector1_ = (vec3(2.0) * mat4x3()); + let mul = (mat4x3() * mat3x4()); +} + +fn bit() { + let flip0_ = ~(1); + let flip1_ = ~(1u); + let flip2_ = ~(vec2(1)); + let flip3_ = ~(vec3(1u)); + let or0_ = (2 | 1); + let or1_ = (2u | 1u); + let or2_ = (vec2(2) | vec2(1)); + let or3_ = (vec3(2u) | vec3(1u)); + let and0_ = (2 & 1); + let and1_ = (2u & 1u); + let and2_ = (vec2(2) & vec2(1)); + let and3_ = (vec3(2u) & vec3(1u)); + let xor0_ = (2 ^ 1); + let xor1_ = (2u ^ 1u); + let xor2_ = (vec2(2) ^ vec2(1)); + let xor3_ = (vec3(2u) ^ vec3(1u)); + let shl0_ = (2 << 1u); + let shl1_ = (2u << 1u); + let shl2_ = (vec2(2) << vec2(1u)); + let shl3_ = (vec3(2u) << vec3(1u)); + let shr0_ = (2 >> 1u); + let shr1_ = (2u >> 1u); + let shr2_ = (vec2(2) >> vec2(1u)); + let shr3_ = (vec3(2u) >> vec3(1u)); +} + +fn comparison() { + let eq0_ = (2 == 1); + let eq1_ = (2u == 1u); + let eq2_ = (2.0 == 1.0); + let eq3_ = (vec2(2) == vec2(1)); + let eq4_ = (vec3(2u) == vec3(1u)); + let eq5_ = (vec4(2.0) == vec4(1.0)); + let neq0_ = (2 != 1); + let neq1_ = (2u != 1u); + let neq2_ = (2.0 != 1.0); + let neq3_ = (vec2(2) != vec2(1)); + let neq4_ = (vec3(2u) != vec3(1u)); + let neq5_ = (vec4(2.0) != vec4(1.0)); + let lt0_ = (2 < 1); + let lt1_ = (2u < 1u); + let lt2_ = (2.0 < 1.0); + let lt3_ = (vec2(2) < vec2(1)); + let lt4_ = (vec3(2u) < vec3(1u)); + let lt5_ = (vec4(2.0) < vec4(1.0)); + let lte0_ = (2 <= 1); + let lte1_ = (2u <= 1u); + let lte2_ = (2.0 <= 1.0); + let lte3_ = (vec2(2) <= vec2(1)); + let lte4_ = (vec3(2u) <= vec3(1u)); + let lte5_ = (vec4(2.0) <= vec4(1.0)); + let gt0_ = (2 > 1); + let gt1_ = (2u > 1u); + let gt2_ = (2.0 > 1.0); + let gt3_ = (vec2(2) > vec2(1)); + let gt4_ = (vec3(2u) > vec3(1u)); + let gt5_ = (vec4(2.0) > vec4(1.0)); + let gte0_ = (2 >= 1); + let gte1_ = (2u >= 1u); + let gte2_ = (2.0 >= 1.0); + let gte3_ = (vec2(2) >= vec2(1)); + let gte4_ = (vec3(2u) >= vec3(1u)); + let gte5_ = (vec4(2.0) >= vec4(1.0)); +} + +fn assignment() { + var a_1: i32; + var vec0_: vec3 = vec3(); + + a_1 = 1; + let _e5 = a_1; + a_1 = (_e5 + 1); + let _e7 = a_1; + a_1 = (_e7 - 1); + 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 % 1); + let _e17 = a_1; + a_1 = (_e17 & 0); + let _e19 = a_1; + a_1 = (_e19 | 0); + let _e21 = a_1; + a_1 = (_e21 ^ 0); + let _e23 = a_1; + a_1 = (_e23 << 2u); + let _e25 = a_1; + a_1 = (_e25 >> 1u); + let _e28 = a_1; + a_1 = (_e28 + 1); + let _e31 = a_1; + a_1 = (_e31 - 1); + let _e37 = vec0_[1]; + vec0_[1] = (_e37 + 1); + let _e41 = vec0_[1]; + vec0_[1] = (_e41 - 1); + return; +} + +fn negation_avoids_prefix_decrement() { + let p0_ = -(1); + let p1_ = -(-(1)); + let p2_ = -(-(1)); + let p3_ = -(-(1)); + let p4_ = -(-(-(1))); + let p5_ = -(-(-(-(1)))); + let p6_ = -(-(-(-(-(1))))); + let p7_ = -(-(-(-(-(1))))); +} + +@compute @workgroup_size(1, 1, 1) +fn main() { + let _e0 = builtins(); + let _e1 = splat(); + let _e6 = bool_cast(vec3(1.0, 1.0, 1.0)); + 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..9893747ca7 --- /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(1.0) * _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..5269e16f7a --- /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) = 10; + 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..3a98925a90 --- /dev/null +++ b/naga/tests/out/wgsl/prepostfix.frag.wgsl @@ -0,0 +1,39 @@ +fn main_1() { + var scalar_target: i32; + var scalar: i32 = 1; + var vec_target: vec2; + var vec: vec2 = vec2(1u); + var mat_target: mat4x3; + var mat: mat4x3 = mat4x3(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, 0.0)); + + let _e3 = scalar; + scalar = (_e3 + 1); + scalar_target = _e3; + let _e6 = scalar; + let _e8 = (_e6 - 1); + 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(1.0); + mat = (_e32 + mat4x3(_e34, _e34, _e34, _e34)); + mat_target = _e32; + let _e37 = mat; + let _e39 = vec3(1.0); + 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..df7c6d0001 --- /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(0.0, 0.0, 0.0, 1.0), 1.0, 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, 0.0, 1.0); + 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..3a92981d95 --- /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.2; + +@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), 0.0, 1.0)); +} + +@fragment +fn frag_main(@location(0) uv_1: vec2) -> @location(0) vec4 { + let color = textureSample(u_texture, u_sampler, uv_1); + if (color.w == 0.0) { + discard; + } + let premultiplied = (color.w * color); + return premultiplied; +} + +@fragment +fn fs_extra() -> @location(0) vec4 { + return vec4(0.0, 0.5, 0.0, 0.5); +} 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..e90e5640d3 --- /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(1.0, 1.0, 1.0, 1.0); + 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..1cec36afaf --- /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.2; + +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, 0.0, 1.0); + 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..39e51e93de --- /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 = 0.0; + + 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.2)); + 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..11565d1f5a --- /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, 0); + 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, 2.0); + c = _e31; + let _e35 = coord_1; + let _e38 = textureSampleGrad(tex1D, samp, _e35, 4.0, 4.0); + c = _e38; + let _e43 = coord_1; + let _e47 = textureSampleGrad(tex1D, samp, _e43, 4.0, 4.0, 5); + c = _e47; + let _e50 = coord_1; + let _e52 = textureSampleLevel(tex1D, samp, _e50, 3.0); + c = _e52; + let _e56 = coord_1; + let _e59 = textureSampleLevel(tex1D, samp, _e56, 3.0, 5); + c = _e59; + let _e62 = coord_1; + let _e64 = textureSample(tex1D, samp, _e62, 5); + c = _e64; + let _e68 = coord_1; + let _e71 = textureSampleBias(tex1D, samp, _e68, 2.0, 5); + c = _e71; + let _e72 = coord_1; + let _e75 = coord_1; + let _e77 = vec2(_e75, 6.0); + let _e81 = textureSample(tex1D, samp, (_e77.x / _e77.y)); + c = _e81; + let _e82 = coord_1; + let _e87 = coord_1; + let _e91 = vec4(_e87, 0.0, 0.0, 6.0); + 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, 6.0); + let _e109 = textureSampleBias(tex1D, samp, (_e104.x / _e104.y), 2.0); + c = _e109; + let _e110 = coord_1; + let _e116 = coord_1; + let _e120 = vec4(_e116, 0.0, 0.0, 6.0); + let _e127 = textureSampleBias(tex1D, samp, (_e120.xyz / vec3(_e120.w)).x, 2.0); + c = _e127; + let _e128 = coord_1; + let _e133 = coord_1; + let _e135 = vec2(_e133, 6.0); + let _e141 = textureSampleGrad(tex1D, samp, (_e135.x / _e135.y), 4.0, 4.0); + c = _e141; + let _e142 = coord_1; + let _e149 = coord_1; + let _e153 = vec4(_e149, 0.0, 0.0, 6.0); + let _e161 = textureSampleGrad(tex1D, samp, (_e153.xyz / vec3(_e153.w)).x, 4.0, 4.0); + c = _e161; + let _e162 = coord_1; + let _e168 = coord_1; + let _e170 = vec2(_e168, 6.0); + let _e177 = textureSampleGrad(tex1D, samp, (_e170.x / _e170.y), 4.0, 4.0, 5); + c = _e177; + let _e178 = coord_1; + let _e186 = coord_1; + let _e190 = vec4(_e186, 0.0, 0.0, 6.0); + let _e199 = textureSampleGrad(tex1D, samp, (_e190.xyz / vec3(_e190.w)).x, 4.0, 4.0, 5); + c = _e199; + let _e200 = coord_1; + let _e204 = coord_1; + let _e206 = vec2(_e204, 6.0); + let _e211 = textureSampleLevel(tex1D, samp, (_e206.x / _e206.y), 3.0); + c = _e211; + let _e212 = coord_1; + let _e218 = coord_1; + let _e222 = vec4(_e218, 0.0, 0.0, 6.0); + let _e229 = textureSampleLevel(tex1D, samp, (_e222.xyz / vec3(_e222.w)).x, 3.0); + c = _e229; + let _e230 = coord_1; + let _e235 = coord_1; + let _e237 = vec2(_e235, 6.0); + let _e243 = textureSampleLevel(tex1D, samp, (_e237.x / _e237.y), 3.0, 5); + c = _e243; + let _e244 = coord_1; + let _e251 = coord_1; + let _e255 = vec4(_e251, 0.0, 0.0, 6.0); + let _e263 = textureSampleLevel(tex1D, samp, (_e255.xyz / vec3(_e255.w)).x, 3.0, 5); + c = _e263; + let _e264 = coord_1; + let _e268 = coord_1; + let _e270 = vec2(_e268, 6.0); + let _e275 = textureSample(tex1D, samp, (_e270.x / _e270.y), 5); + c = _e275; + let _e276 = coord_1; + let _e282 = coord_1; + let _e286 = vec4(_e282, 0.0, 0.0, 6.0); + let _e293 = textureSample(tex1D, samp, (_e286.xyz / vec3(_e286.w)).x, 5); + c = _e293; + let _e294 = coord_1; + let _e299 = coord_1; + let _e301 = vec2(_e299, 6.0); + let _e307 = textureSampleBias(tex1D, samp, (_e301.x / _e301.y), 2.0, 5); + c = _e307; + let _e308 = coord_1; + let _e315 = coord_1; + let _e319 = vec4(_e315, 0.0, 0.0, 6.0); + let _e327 = textureSampleBias(tex1D, samp, (_e319.xyz / vec3(_e319.w)).x, 2.0, 5); + c = _e327; + let _e328 = coord_1; + let _e331 = coord_1; + let _e334 = textureLoad(tex1D, i32(_e331), 3); + c = _e334; + let _e335 = coord_1; + let _e339 = coord_1; + let _e343 = textureLoad(tex1D, i32(_e339), 3); + 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, 0); + 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), 2.0); + c_1 = _e39; + let _e43 = coord_3; + let _e49 = textureSampleGrad(tex1DArray, samp, _e43.x, i32(_e43.y), 4.0, 4.0); + c_1 = _e49; + let _e54 = coord_3; + let _e61 = textureSampleGrad(tex1DArray, samp, _e54.x, i32(_e54.y), 4.0, 4.0, 5); + c_1 = _e61; + let _e64 = coord_3; + let _e69 = textureSampleLevel(tex1DArray, samp, _e64.x, i32(_e64.y), 3.0); + c_1 = _e69; + let _e73 = coord_3; + let _e79 = textureSampleLevel(tex1DArray, samp, _e73.x, i32(_e73.y), 3.0, 5); + c_1 = _e79; + let _e82 = coord_3; + let _e87 = textureSample(tex1DArray, samp, _e82.x, i32(_e82.y), 5); + c_1 = _e87; + let _e91 = coord_3; + let _e97 = textureSampleBias(tex1DArray, samp, _e91.x, i32(_e91.y), 2.0, 5); + c_1 = _e97; + let _e98 = coord_3; + let _e101 = coord_3; + let _e102 = vec2(_e101); + let _e106 = textureLoad(tex1DArray, _e102.x, _e102.y, 3); + c_1 = _e106; + let _e107 = coord_3; + let _e111 = coord_3; + let _e112 = vec2(_e111); + let _e117 = textureLoad(tex1DArray, _e112.x, _e112.y, 3); + 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, 0); + 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, 2.0); + c_2 = _e31; + let _e37 = coord_5; + let _e42 = textureSampleGrad(tex2D, samp, _e37, vec2(4.0), vec2(4.0)); + c_2 = _e42; + let _e50 = coord_5; + let _e57 = textureSampleGrad(tex2D, samp, _e50, vec2(4.0), vec2(4.0), vec2(5)); + c_2 = _e57; + let _e60 = coord_5; + let _e62 = textureSampleLevel(tex2D, samp, _e60, 3.0); + c_2 = _e62; + let _e67 = coord_5; + let _e71 = textureSampleLevel(tex2D, samp, _e67, 3.0, vec2(5)); + c_2 = _e71; + let _e75 = coord_5; + let _e78 = textureSample(tex2D, samp, _e75, vec2(5)); + c_2 = _e78; + let _e83 = coord_5; + let _e87 = textureSampleBias(tex2D, samp, _e83, 2.0, vec2(5)); + c_2 = _e87; + let _e88 = coord_5; + let _e93 = coord_5; + let _e97 = vec3(_e93.x, _e93.y, 6.0); + 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, 0.0, 6.0); + 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, 6.0); + let _e137 = textureSampleBias(tex2D, samp, (_e131.xy / vec2(_e131.z)), 2.0); + c_2 = _e137; + let _e138 = coord_5; + let _e145 = coord_5; + let _e150 = vec4(_e145.x, _e145.y, 0.0, 6.0); + let _e157 = textureSampleBias(tex2D, samp, (_e150.xyz / vec3(_e150.w)).xy, 2.0); + c_2 = _e157; + let _e158 = coord_5; + let _e167 = coord_5; + let _e171 = vec3(_e167.x, _e167.y, 6.0); + let _e180 = textureSampleGrad(tex2D, samp, (_e171.xy / vec2(_e171.z)), vec2(4.0), vec2(4.0)); + c_2 = _e180; + let _e181 = coord_5; + let _e191 = coord_5; + let _e196 = vec4(_e191.x, _e191.y, 0.0, 6.0); + let _e206 = textureSampleGrad(tex2D, samp, (_e196.xyz / vec3(_e196.w)).xy, vec2(4.0), vec2(4.0)); + c_2 = _e206; + let _e207 = coord_5; + let _e218 = coord_5; + let _e222 = vec3(_e218.x, _e218.y, 6.0); + let _e233 = textureSampleGrad(tex2D, samp, (_e222.xy / vec2(_e222.z)), vec2(4.0), vec2(4.0), vec2(5)); + c_2 = _e233; + let _e234 = coord_5; + let _e246 = coord_5; + let _e251 = vec4(_e246.x, _e246.y, 0.0, 6.0); + let _e263 = textureSampleGrad(tex2D, samp, (_e251.xyz / vec3(_e251.w)).xy, vec2(4.0), vec2(4.0), vec2(5)); + c_2 = _e263; + let _e264 = coord_5; + let _e270 = coord_5; + let _e274 = vec3(_e270.x, _e270.y, 6.0); + let _e280 = textureSampleLevel(tex2D, samp, (_e274.xy / vec2(_e274.z)), 3.0); + c_2 = _e280; + let _e281 = coord_5; + let _e288 = coord_5; + let _e293 = vec4(_e288.x, _e288.y, 0.0, 6.0); + let _e300 = textureSampleLevel(tex2D, samp, (_e293.xyz / vec3(_e293.w)).xy, 3.0); + c_2 = _e300; + let _e301 = coord_5; + let _e309 = coord_5; + let _e313 = vec3(_e309.x, _e309.y, 6.0); + let _e321 = textureSampleLevel(tex2D, samp, (_e313.xy / vec2(_e313.z)), 3.0, vec2(5)); + c_2 = _e321; + let _e322 = coord_5; + let _e331 = coord_5; + let _e336 = vec4(_e331.x, _e331.y, 0.0, 6.0); + let _e345 = textureSampleLevel(tex2D, samp, (_e336.xyz / vec3(_e336.w)).xy, 3.0, vec2(5)); + c_2 = _e345; + let _e346 = coord_5; + let _e353 = coord_5; + let _e357 = vec3(_e353.x, _e353.y, 6.0); + let _e364 = textureSample(tex2D, samp, (_e357.xy / vec2(_e357.z)), vec2(5)); + c_2 = _e364; + let _e365 = coord_5; + let _e373 = coord_5; + let _e378 = vec4(_e373.x, _e373.y, 0.0, 6.0); + let _e386 = textureSample(tex2D, samp, (_e378.xyz / vec3(_e378.w)).xy, vec2(5)); + c_2 = _e386; + let _e387 = coord_5; + let _e395 = coord_5; + let _e399 = vec3(_e395.x, _e395.y, 6.0); + let _e407 = textureSampleBias(tex2D, samp, (_e399.xy / vec2(_e399.z)), 2.0, vec2(5)); + c_2 = _e407; + let _e408 = coord_5; + let _e417 = coord_5; + let _e422 = vec4(_e417.x, _e417.y, 0.0, 6.0); + let _e431 = textureSampleBias(tex2D, samp, (_e422.xyz / vec3(_e422.w)).xy, 2.0, vec2(5)); + c_2 = _e431; + let _e432 = coord_5; + let _e435 = coord_5; + let _e438 = textureLoad(tex2D, vec2(_e435), 3); + c_2 = _e438; + let _e439 = coord_5; + let _e444 = coord_5; + let _e449 = textureLoad(tex2D, vec2(_e444), 3); + 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, 0); + size2DShadow = vec2(_e20); + let _e24 = coord_7; + let _e29 = coord_7; + let _e33 = vec3(_e29.x, _e29.y, 1.0); + 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, 1.0); + 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, 1.0); + let _e82 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e73.xy, _e73.z, vec2(5)); + d = _e82; + let _e83 = coord_7; + let _e89 = coord_7; + let _e93 = vec3(_e89.x, _e89.y, 1.0); + 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, 1.0); + let _e116 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e110.xy, _e110.z, vec2(5)); + d = _e116; + let _e117 = coord_7; + let _e124 = coord_7; + let _e128 = vec3(_e124.x, _e124.y, 1.0); + let _e133 = textureSampleCompare(tex2DShadow, sampShadow, _e128.xy, _e128.z, vec2(5)); + d = _e133; + let _e134 = coord_7; + let _e140 = coord_7; + let _e145 = vec4(_e140.x, _e140.y, 1.0, 6.0); + 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, 1.0, 6.0); + 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, 1.0, 6.0); + let _e207 = (_e197.xyz / vec3(_e197.w)); + let _e210 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e207.xy, _e207.z, vec2(5)); + d = _e210; + let _e211 = coord_7; + let _e218 = coord_7; + let _e223 = vec4(_e218.x, _e218.y, 1.0, 6.0); + 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, 1.0, 6.0); + let _e253 = (_e246.xyz / vec3(_e246.w)); + let _e256 = textureSampleCompareLevel(tex2DShadow, sampShadow, _e253.xy, _e253.z, vec2(5)); + d = _e256; + let _e257 = coord_7; + let _e265 = coord_7; + let _e270 = vec4(_e265.x, _e265.y, 1.0, 6.0); + let _e276 = (_e270.xyz / vec3(_e270.w)); + let _e279 = textureSampleCompare(tex2DShadow, sampShadow, _e276.xy, _e276.z, vec2(5)); + 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, 0); + 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), 2.0); + c_3 = _e41; + let _e47 = coord_9; + let _e55 = textureSampleGrad(tex2DArray, samp, _e47.xy, i32(_e47.z), vec2(4.0), vec2(4.0)); + c_3 = _e55; + let _e63 = coord_9; + let _e73 = textureSampleGrad(tex2DArray, samp, _e63.xy, i32(_e63.z), vec2(4.0), vec2(4.0), vec2(5)); + c_3 = _e73; + let _e76 = coord_9; + let _e81 = textureSampleLevel(tex2DArray, samp, _e76.xy, i32(_e76.z), 3.0); + c_3 = _e81; + let _e86 = coord_9; + let _e93 = textureSampleLevel(tex2DArray, samp, _e86.xy, i32(_e86.z), 3.0, vec2(5)); + c_3 = _e93; + let _e97 = coord_9; + let _e103 = textureSample(tex2DArray, samp, _e97.xy, i32(_e97.z), vec2(5)); + c_3 = _e103; + let _e108 = coord_9; + let _e115 = textureSampleBias(tex2DArray, samp, _e108.xy, i32(_e108.z), 2.0, vec2(5)); + c_3 = _e115; + let _e116 = coord_9; + let _e119 = coord_9; + let _e120 = vec3(_e119); + let _e124 = textureLoad(tex2DArray, _e120.xy, _e120.z, 3); + c_3 = _e124; + let _e125 = coord_9; + let _e130 = coord_9; + let _e131 = vec3(_e130); + let _e137 = textureLoad(tex2DArray, _e131.xy, _e131.z, 3); + 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, 0); + 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, 1.0); + 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, 1.0); + 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, 1.0); + let _e98 = textureSampleCompareLevel(tex2DArrayShadow, sampShadow, _e87.xy, i32(_e87.z), _e87.w, vec2(5)); + d_1 = _e98; + let _e99 = coord_11; + let _e107 = coord_11; + let _e112 = vec4(_e107.x, _e107.y, _e107.z, 1.0); + let _e119 = textureSampleCompare(tex2DArrayShadow, sampShadow, _e112.xy, i32(_e112.z), _e112.w, vec2(5)); + 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, 0); + 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, 2.0); + c_4 = _e31; + let _e37 = coord_13; + let _e42 = textureSampleGrad(texCube, samp, _e37, vec3(4.0), vec3(4.0)); + c_4 = _e42; + let _e45 = coord_13; + let _e47 = textureSampleLevel(texCube, samp, _e45, 3.0); + 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, 0); + sizeCubeShadow = vec2(_e20); + let _e24 = coord_15; + let _e30 = coord_15; + let _e35 = vec4(_e30.x, _e30.y, _e30.z, 1.0); + 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, 1.0); + 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, 0); + 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), 2.0); + c_5 = _e41; + let _e47 = coord_17; + let _e55 = textureSampleGrad(texCubeArray, samp, _e47.xyz, i32(_e47.w), vec3(4.0), vec3(4.0)); + c_5 = _e55; + let _e58 = coord_17; + let _e63 = textureSampleLevel(texCubeArray, samp, _e58.xyz, i32(_e58.w), 3.0); + 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, 0); + 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), 1.0); + 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, 0); + 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, 2.0); + c_6 = _e31; + let _e32 = coord_21; + let _e38 = coord_21; + let _e43 = vec4(_e38.x, _e38.y, _e38.z, 6.0); + 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, 6.0); + let _e67 = textureSampleBias(tex3D, samp, (_e61.xyz / vec3(_e61.w)), 2.0); + c_6 = _e67; + let _e68 = coord_21; + let _e76 = coord_21; + let _e81 = vec4(_e76.x, _e76.y, _e76.z, 6.0); + let _e88 = textureSample(tex3D, samp, (_e81.xyz / vec3(_e81.w)), vec3(5)); + c_6 = _e88; + let _e89 = coord_21; + let _e98 = coord_21; + let _e103 = vec4(_e98.x, _e98.y, _e98.z, 6.0); + let _e111 = textureSampleBias(tex3D, samp, (_e103.xyz / vec3(_e103.w)), 2.0, vec3(5)); + c_6 = _e111; + let _e112 = coord_21; + let _e119 = coord_21; + let _e124 = vec4(_e119.x, _e119.y, _e119.z, 6.0); + let _e130 = textureSampleLevel(tex3D, samp, (_e124.xyz / vec3(_e124.w)), 3.0); + c_6 = _e130; + let _e131 = coord_21; + let _e140 = coord_21; + let _e145 = vec4(_e140.x, _e140.y, _e140.z, 6.0); + let _e153 = textureSampleLevel(tex3D, samp, (_e145.xyz / vec3(_e145.w)), 3.0, vec3(5)); + c_6 = _e153; + let _e154 = coord_21; + let _e164 = coord_21; + let _e169 = vec4(_e164.x, _e164.y, _e164.z, 6.0); + let _e178 = textureSampleGrad(tex3D, samp, (_e169.xyz / vec3(_e169.w)), vec3(4.0), vec3(4.0)); + c_6 = _e178; + let _e179 = coord_21; + let _e191 = coord_21; + let _e196 = vec4(_e191.x, _e191.y, _e191.z, 6.0); + let _e207 = textureSampleGrad(tex3D, samp, (_e196.xyz / vec3(_e196.w)), vec3(4.0), vec3(4.0), vec3(5)); + c_6 = _e207; + let _e213 = coord_21; + let _e218 = textureSampleGrad(tex3D, samp, _e213, vec3(4.0), vec3(4.0)); + c_6 = _e218; + let _e226 = coord_21; + let _e233 = textureSampleGrad(tex3D, samp, _e226, vec3(4.0), vec3(4.0), vec3(5)); + c_6 = _e233; + let _e236 = coord_21; + let _e238 = textureSampleLevel(tex3D, samp, _e236, 3.0); + c_6 = _e238; + let _e243 = coord_21; + let _e247 = textureSampleLevel(tex3D, samp, _e243, 3.0, vec3(5)); + c_6 = _e247; + let _e251 = coord_21; + let _e254 = textureSample(tex3D, samp, _e251, vec3(5)); + c_6 = _e254; + let _e259 = coord_21; + let _e263 = textureSampleBias(tex3D, samp, _e259, 2.0, vec3(5)); + c_6 = _e263; + let _e264 = coord_21; + let _e267 = coord_21; + let _e270 = textureLoad(tex3D, vec3(_e267), 3); + c_6 = _e270; + let _e271 = coord_21; + let _e276 = coord_21; + let _e281 = textureLoad(tex3D, vec3(_e276), 3); + 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), 3); + 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, 3); + 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..b768fab2f1 --- /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.05, 0.05, 0.05); +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 <= 0.0) { + return 1.0; + } + let flip_correction = vec2(0.5, -0.5); + let proj_correction = (1.0 / homogeneous_coords.w); + let light_local = (((homogeneous_coords.xy * flip_correction) * proj_correction) + vec2(0.5, 0.5)); + 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(0.0, 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, 1.0) * _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(0.0, 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, 1.0) * _e47); +} diff --git a/naga/tests/out/wgsl/skybox.wgsl b/naga/tests/out/wgsl/skybox.wgsl new file mode 100644 index 0000000000..73d4da78a4 --- /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) / 2); + tmp2_ = (i32(vertex_index) & 1); + let _e9 = tmp1_; + let _e15 = tmp2_; + let pos = vec4(((f32(_e9) * 4.0) - 1.0), ((f32(_e15) * 4.0) - 1.0), 0.0, 1.0); + 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/texture-arg.wgsl b/naga/tests/out/wgsl/texture-arg.wgsl new file mode 100644 index 0000000000..6edf0250e1 --- /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(0.0, 0.0)); + 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..fea2dec108 --- /dev/null +++ b/naga/tests/out/wgsl/type-alias.wgsl @@ -0,0 +1,10 @@ +fn main() { + let a = vec3(0.0, 0.0, 0.0); + let c = vec3(0.0); + let b = vec3(vec2(0.0), 0.0); + let d = vec3(vec2(0.0), 0.0); + let e = vec3(d); + let f = mat2x2(vec2(1.0, 2.0), vec2(3.0, 4.0)); + 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..727ced8d6e --- /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 > 10) { + 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/snapshots.rs b/naga/tests/snapshots.rs new file mode 100644 index 0000000000..c3455dd864 --- /dev/null +++ b/naga/tests/snapshots.rs @@ -0,0 +1,924 @@ +// 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).expect(&format!( + "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) + .expect(&format!( + "Naga module validation failed on test '{}'", + name.display() + )); + + #[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) + .expect(&format!( + "Post-compaction module validation failed on test '{}'", + name.display() + )) + }; + + #[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 = if cfg!(feature = "span") { + source_code.map(|code| naga::back::spv::DebugInfo { + source_code: code, + file_name: name.as_ref(), + }) + } else { + None + }; + + 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), + ]; + + 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(&source)), + } + } + + #[cfg(feature = "span")] + { + 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(&source)), + } + } + } +} + +#[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..10dcd54062 --- /dev/null +++ b/naga/tests/wgsl-errors.rs @@ -0,0 +1,1994 @@ +/*! +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(&output, snapshot) { + 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 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: invalid type for constructor component at index [0] + ┌─ wgsl:3:33 + │ +3 │ _ = mat2x2(array(0, 1), vec2(2, 3)); + │ ^^^^^^^^^^^ invalid component type + +"#, + ); +} + +#[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 `f32` + ┌─ 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`) + +"#, + ); +} + +/// 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, 2u); + } + + 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 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(_), + .. + }) + } +} 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..88ece956bd --- /dev/null +++ b/naga/xtask/Cargo.lock @@ -0,0 +1,117 @@ +# 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 = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[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 = "hlsl-snapshots" +version = "0.1.0" +dependencies = [ + "anyhow", + "nanoserde", +] + +[[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 = "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 = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[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 = "xtask" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger", + "glob", + "hlsl-snapshots", + "log", + "pico-args", + "shell-words", + "which", +] diff --git a/naga/xtask/Cargo.toml b/naga/xtask/Cargo.toml new file mode 100644 index 0000000000..579c521ea5 --- /dev/null +++ b/naga/xtask/Cargo.toml @@ -0,0 +1,17 @@ +[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"} +log = "0.4.17" +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..3b5b1ed69f --- /dev/null +++ b/naga/xtask/src/cli.rs @@ -0,0 +1,161 @@ +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") + } + } + } +} + +#[derive(Debug)] +pub(crate) enum ValidateSubcommand { + Spirv, + Metal, + Glsl, + Dot, + Wgsl, + Hlsl(ValidateHlslCommand), +} + +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" => return Ok(Self::Hlsl(ValidateHlslCommand::parse(args)?)), + other => { + bail!("unrecognized `validate` subcommand {other:?}; see `--help` for more details") + } + } + } +} + +#[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..d69891e78a --- /dev/null +++ b/naga/xtask/src/glob.rs @@ -0,0 +1,37 @@ +use std::path::Path; + +use anyhow::Context; +use glob::glob; + +use crate::result::{ErrorStatus, LogIfError}; + +pub(crate) fn visit_files( + path: impl AsRef, + glob_expr: &str, + mut f: impl FnMut(&Path) -> anyhow::Result<()>, +) -> ErrorStatus { + let path = path.as_ref(); + let glob_expr = path.join(glob_expr); + let glob_expr = glob_expr.to_str().unwrap(); + + let mut status = ErrorStatus::NoFailuresFound; + glob(glob_expr) + .context("glob pattern {path:?} is invalid") + .unwrap() + .for_each(|path_res| { + if let Some(path) = path_res + .with_context(|| format!("error while iterating over glob {path:?}")) + .log_if_err_found(&mut status) + { + if path + .metadata() + .with_context(|| format!("failed to fetch metadata for {path:?}")) + .log_if_err_found(&mut status) + .map_or(false, |m| m.is_file()) + { + f(&path).log_if_err_found(&mut status); + } + } + }); + status +} diff --git a/naga/xtask/src/main.rs b/naga/xtask/src/main.rs new file mode 100644 index 0000000000..48e3f52764 --- /dev/null +++ b/naga/xtask/src/main.rs @@ -0,0 +1,312 @@ +use std::{ + io::{BufRead, BufReader}, + path::Path, + process::{ExitCode, Stdio}, +}; + +use anyhow::{bail, Context}; +use cli::Args; + +use crate::{ + cli::{Subcommand, ValidateHlslCommand, ValidateSubcommand}, + fs::{open_file, remove_dir_all}, + glob::visit_files, + path::join_path, + process::{which, EasyCommand}, + result::{ErrorStatus, LogIfError}, +}; + +mod cli; +mod fs; +mod glob; +mod path; +mod process; +mod result; + +fn main() -> ExitCode { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .parse_default_env() + .format_indent(Some(0)) + .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 snapshots_base_out = join_path(["tests", "out"]); + + 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) => { + let ack_visiting = |path: &Path| log::info!("Validating {}", path.display()); + let err_status = match cmd { + ValidateSubcommand::Spirv => { + let spirv_as = "spirv-as"; + which(spirv_as)?; + + let spirv_val = "spirv-val"; + which(spirv_val)?; + + visit_files(snapshots_base_out, "spv/*.spvasm", |path| { + ack_visiting(path); + 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 {cmd:?}"))?; + EasyCommand::new(spirv_val, |cmd| cmd.stdin(child.stdout.unwrap())) + .success() + }) + } + ValidateSubcommand::Metal => { + let xcrun = "xcrun"; + which(xcrun)?; + visit_files(snapshots_base_out, "msl/*.msl", |path| { + ack_visiting(path); + 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() + }) + } + ValidateSubcommand::Glsl => { + let glslang_validator = "glslangValidator"; + which(glslang_validator)?; + let mut err_status = ErrorStatus::NoFailuresFound; + for (glob, type_arg) in [ + ("glsl/*.Vertex.glsl", "vert"), + ("glsl/*.Fragment.glsl", "frag"), + ("glsl/*.Compute.glsl", "comp"), + ] { + let type_err_status = visit_files(&snapshots_base_out, glob, |path| { + ack_visiting(path); + let file = open_file(path)?; + EasyCommand::new(glslang_validator, |cmd| { + cmd.stdin(Stdio::from(file)) + .args(["--stdin", "-S"]) + .arg(type_arg) + }) + .success() + }); + err_status = err_status.merge(type_err_status); + } + err_status + } + ValidateSubcommand::Dot => { + let dot = "dot"; + which(dot)?; + visit_files(snapshots_base_out, "dot/*.dot", |path| { + ack_visiting(path); + let file = open_file(path)?; + EasyCommand::new(dot, |cmd| { + cmd.stdin(Stdio::from(file)).stdout(Stdio::null()) + }) + .success() + }) + } + ValidateSubcommand::Wgsl => { + visit_files(snapshots_base_out, "wgsl/*.wgsl", |path| { + ack_visiting(path); + EasyCommand::new("cargo", |cmd| cmd.args(["run", "--"]).arg(path)).success() + }) + } + ValidateSubcommand::Hlsl(cmd) => { + let visit_hlsl = |consume_config_item: &mut dyn FnMut( + &Path, + hlsl_snapshots::ConfigItem, + ) + -> anyhow::Result<()>| { + visit_files(snapshots_base_out, "hlsl/*.hlsl", |path| { + ack_visiting(path); + let hlsl_snapshots::Config { + vertex, + fragment, + compute, + } = hlsl_snapshots::Config::from_path(path.with_extension("ron"))?; + let mut status = ErrorStatus::NoFailuresFound; + [vertex, fragment, compute] + .into_iter() + .flatten() + .for_each(|shader| { + consume_config_item(path, shader).log_if_err_found(&mut status); + }); + match status { + ErrorStatus::NoFailuresFound => Ok(()), + ErrorStatus::OneOrMoreFailuresFound => bail!( + "one or more shader HLSL shader tests failed for {}", + path.display() + ), + } + }) + }; + let validate = |bin, file: &_, config_item, params: &[_]| { + 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:?}" + ) + }) + }; + match cmd { + ValidateHlslCommand::Dxc => { + let bin = "dxc"; + which(bin)?; + visit_hlsl(&mut |file, config_item| { + // Reference: + // . + validate( + bin, + file, + config_item, + &[ + "-Wno-parentheses-equality", + "-Zi", + "-Qembed_debug", + "-Od", + "-HV", + "2018", + ], + ) + }) + } + ValidateHlslCommand::Fxc => { + let bin = "fxc"; + which(bin)?; + visit_hlsl(&mut |file, config_item| { + 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(bin, file, config_item, &["-Zi", "-Od"]) + } else { + log::debug!( + "skipping config. item {config_item:?} because the \ + shader model major version is > 6" + ); + Ok(()) + } + }) + } + } + } + }; + match err_status { + ErrorStatus::NoFailuresFound => Ok(()), + ErrorStatus::OneOrMoreFailuresFound => { + bail!("failed to validate one or more files, see above output for more details") + } + } + } + } +} 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..37a24f1b6e --- /dev/null +++ b/naga/xtask/src/process.rs @@ -0,0 +1,78 @@ +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 status = inner + .status() + .with_context(|| format!("failed to run {self}"))?; + ensure!( + status.success(), + "{self} failed to run; exit code: {:?}", + status.code() + ); + 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 + } + } + } +}