Compare commits
254 commits
update-win
...
master
Author | SHA1 | Date | |
---|---|---|---|
Alex Janka | 95a9e2c5be | ||
Alex Janka | 89090757fd | ||
Alex Janka | 1406ddc5b3 | ||
Alex Janka | bf974ac642 | ||
d1e49b7eb4 | |||
5ef0055e05 | |||
1c6581d737 | |||
ac66b1b6f6 | |||
977975f4c7 | |||
3b9514ad38 | |||
899fb50da6 | |||
162226ce44 | |||
039fdfd41a | |||
e13bb88df2 | |||
7b375658c5 | |||
3fb6e3843e | |||
3cda5b706d | |||
4d6793d305 | |||
35f499f5e1 | |||
6ce711db26 | |||
d4b4366836 | |||
c646086df4 | |||
f6cf642e50 | |||
98958dfb5e | |||
b5d523e9f3 | |||
5e9ce1207c | |||
1b0574c140 | |||
b84e104212 | |||
fff80df5a0 | |||
875968d097 | |||
fa48b936be | |||
0a9fa16855 | |||
d558c6e50d | |||
48d91dfe58 | |||
d7665cac9b | |||
e8ffd8fdf3 | |||
d893b6ec97 | |||
8c8e386a6c | |||
b7071958bd | |||
5feac91af2 | |||
9c895caa51 | |||
7593f9f9b5 | |||
af3ea252ba | |||
9dc0cf26fd | |||
2550bf7ed5 | |||
35fc21bbef | |||
89b620a7c1 | |||
66a0ee21e3 | |||
31b7a6f33f | |||
ed7216990a | |||
623c6776f7 | |||
bacfbf0791 | |||
e02e1ae26a | |||
d72519b9fd | |||
752417f320 | |||
10ad2d927c | |||
8a9adebb96 | |||
7719b939f9 | |||
a849f5e745 | |||
f61bed3a22 | |||
4ef4b8762b | |||
a5c684a7ee | |||
8ba4b72cf1 | |||
7a3a690166 | |||
b5bc3c11e1 | |||
c7dd7796db | |||
9741ab2cd1 | |||
b378c45039 | |||
c7d1d347a4 | |||
95ac8adc20 | |||
372315022d | |||
699243c0ab | |||
6d25a653a9 | |||
be11953516 | |||
e38f2636d9 | |||
f073c76ade | |||
7ef3780222 | |||
d60ff76fb2 | |||
50aa582fa8 | |||
e8eee02bfb | |||
22aa59b598 | |||
b75a614873 | |||
a8ae407ddb | |||
fe84e6a490 | |||
913ede3852 | |||
1f5b4380a3 | |||
ce3a8c6e52 | |||
c22328f025 | |||
af49128ee7 | |||
d3d8e85461 | |||
017a1a6232 | |||
e622479c76 | |||
b47b27fadb | |||
ba6c32e858 | |||
edca0f1749 | |||
efdfd56e0e | |||
c0ecae844c | |||
cbac011969 | |||
350508a7aa | |||
d6f1af8691 | |||
adeb9435fc | |||
499b8f5791 | |||
e944330692 | |||
f7dd955c0a | |||
227e6e743d | |||
6fbc4b3075 | |||
05467c2c78 | |||
b7673de811 | |||
4247e64336 | |||
b348e8591f | |||
dc1ab35d89 | |||
7f4a883288 | |||
a2987555a2 | |||
3afcd6223c | |||
fb62a1e3f4 | |||
dca93a1310 | |||
4259b65ee0 | |||
f058134944 | |||
ad4e72f359 | |||
5c08205360 | |||
cc93e37701 | |||
76aa5ce4c6 | |||
d89839be16 | |||
a1696813aa | |||
004b073b1a | |||
bceb0623a3 | |||
ab31abb3d7 | |||
3b0531dc62 | |||
363657deef | |||
05f634a9b9 | |||
43da6e60c6 | |||
30dfa1a655 | |||
325e39063a | |||
5554703af7 | |||
a7b1682a37 | |||
ba3154b92d | |||
6780397d49 | |||
1aedb1bea7 | |||
8dc0e0d100 | |||
f40df9a54a | |||
12d55e928e | |||
d5ef5904f3 | |||
aca5b5420c | |||
c121087348 | |||
2d98ebec1b | |||
849a749f1a | |||
daf30c83c0 | |||
3c3f024ef8 | |||
4762055dc1 | |||
d0a5224c10 | |||
178790a202 | |||
528dd1b53c | |||
a495b693a6 | |||
c67e9f4801 | |||
e1f62fc984 | |||
b98d86a940 | |||
252f685967 | |||
a7ca391ef6 | |||
11d12730eb | |||
4733831500 | |||
abbec84594 | |||
12af3c3f3a | |||
b9a6b869e3 | |||
fa8ee5d143 | |||
8f89b3e720 | |||
c34fa4195b | |||
2fbc7f92da | |||
9732812b91 | |||
4da6c98655 | |||
a14b36e05b | |||
b2d8d084be | |||
f1524f6049 | |||
6d4e6590de | |||
e776ee2823 | |||
3d74f27d77 | |||
f9fdb93c0c | |||
7f0f985a14 | |||
b7f62dc378 | |||
b796494cc6 | |||
91794dd353 | |||
2b208f1848 | |||
47f6e0f10e | |||
665570342c | |||
fb2bcc5d52 | |||
8015a2a796 | |||
5c8428eac8 | |||
8fb2179ae8 | |||
f6268a621c | |||
bbfd5153da | |||
37397ff216 | |||
754e8da620 | |||
6c50880600 | |||
acc9bfeb53 | |||
54e86e7b06 | |||
2450217c29 | |||
962a81c2e3 | |||
11ab4b7c9a | |||
121dbc4ed6 | |||
31891e414f | |||
e39834547c | |||
d5aa6b2e4a | |||
cc26be486b | |||
10358b4966 | |||
32148cdff4 | |||
555ff6f9fc | |||
34f224cc5d | |||
7586ed4633 | |||
2b995539f2 | |||
8cfe1e9da2 | |||
f9df72a02d | |||
4e052159e7 | |||
171c842c97 | |||
4dfcdf2725 | |||
1a16c4fadf | |||
c05d8ff06a | |||
ec98494202 | |||
ef4e2353ff | |||
a5e978f158 | |||
97ff76276f | |||
797625903a | |||
81ba694ba4 | |||
18ff9cf05a | |||
ae2a427b5e | |||
552be8c34e | |||
7c0190004f | |||
a6c91a07df | |||
d700234c3c | |||
96f937586c | |||
9c5a8f4042 | |||
d5bf7e312c | |||
80325fda9e | |||
60fac06332 | |||
92e8a05f8a | |||
617bfdd93e | |||
fa3b6bf5fc | |||
ee0587310c | |||
2bd6f8f80f | |||
d17503be71 | |||
2be2178502 | |||
f4bdf160ab | |||
c0a1b56f4e | |||
43c0f3d676 | |||
25bf4904e1 | |||
7ce02014d4 | |||
6e071138dc | |||
7fbf3e23f0 | |||
f5da7d8421 | |||
b09a5295ab | |||
59cc3deb09 | |||
3735659604 | |||
ab8072c4f7 | |||
24f28bb605 | |||
916cd1a681 | |||
020fac87ba |
85
.github/workflows/build.yml
vendored
85
.github/workflows/build.yml
vendored
|
@ -14,34 +14,85 @@ jobs:
|
|||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
profile: ['debug', 'release', 'optimized']
|
||||
os: ['windows-latest', 'ubuntu-latest', 'macos-latest', 'macos-14']
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
output: x86_64-ubuntu
|
||||
- os: windows-latest
|
||||
output: x86_64-windows
|
||||
- os: macos-latest
|
||||
output: x86_64-macos
|
||||
- os: macos-14
|
||||
output: aarch64-macos
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.output }} (${{ matrix.profile }})
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Install nightly Rust
|
||||
uses: actions-rs/toolchain@v1.0.6
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- name: Setup Vulkan SDK
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Cross, SPIRV-Headers, SPIRV-Reflect, SPIRV-Tools, Glslang
|
||||
vulkan-use-cache: true
|
||||
- uses: actions/setup-python@v1
|
||||
name: Setup Python
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- run: pip install meson ninja mako
|
||||
name: Install Meson for spirv-to-dxil-sys
|
||||
- name: Build dynamic library
|
||||
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }}
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: ${{ format('build-outputs-{0}-{1}', matrix.os, matrix.profile) }}
|
||||
name: ${{ format('librashader-{0}-{1}-{2}', matrix.output, github.sha, matrix.profile) }}
|
||||
path: ${{ format('target/{0}/librashader.*', matrix.profile) }}
|
||||
build-ubuntu-arm64:
|
||||
strategy:
|
||||
matrix:
|
||||
profile: ['debug', 'release', 'optimized']
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
name: aarch64-ubuntu
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly
|
||||
targets: aarch64-unknown-linux-gnu
|
||||
- name: Install ARM64 cross-compilation dependencies
|
||||
continue-on-error: true
|
||||
run: |
|
||||
sudo apt-get update || true
|
||||
sudo dpkg --add-architecture arm64
|
||||
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy main restricted" | sudo tee -a /etc/apt/sources.list
|
||||
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted" | sudo tee -a /etc/apt/sources.list
|
||||
sudo apt-get update || true
|
||||
sudo apt-get -y install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
|
||||
- name: Build dynamic library
|
||||
run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target aarch64-unknown-linux-gnu
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: ${{ format('librashader-aarch64-ubuntu-{0}-{1}', github.sha, matrix.profile) }}
|
||||
path: ${{ format('target/aarch64-unknown-linux-gnu/{0}/librashader.*', matrix.profile) }}
|
||||
build-windows-arm64:
|
||||
strategy:
|
||||
matrix:
|
||||
profile: ['debug', 'release', 'optimized']
|
||||
fail-fast: false
|
||||
runs-on: windows-latest
|
||||
name: aarch64-windows
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly-2024-01-15 # pinned because it seems like there's a segfault on nightly
|
||||
override: true
|
||||
targets: aarch64-pc-windows-msvc
|
||||
- name: Build dynamic library
|
||||
run: cargo run -p librashader-build-script -- --profile ${{ matrix.profile }} --target aarch64-pc-windows-msvc
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: ${{ format('librashader-aarch64-windows-{0}-{1}', github.sha, matrix.profile) }}
|
||||
path: ${{ format('target/aarch64-pc-windows-msvc/{0}/librashader.*', matrix.profile) }}
|
||||
|
|
61
.github/workflows/package-obs.yml
vendored
Normal file
61
.github/workflows/package-obs.yml
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
name: build Linux packages with Open Build Service
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ "master" ]
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
approve-obs-build:
|
||||
name: "approval"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Approve
|
||||
run: echo OBS build CI test runs need to be approved by a maintainer.
|
||||
build-obs-binary:
|
||||
environment:
|
||||
name: obs-build-env
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- repo: Fedora_40
|
||||
spec: librashader.spec
|
||||
can_fail: true
|
||||
name: Fedora 40 (.rpm)
|
||||
- repo: xUbuntu_24.04
|
||||
spec: librashader.spec
|
||||
can_fail: true
|
||||
name: Ubuntu 24.04 (.deb)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [approve-obs-build]
|
||||
continue-on-error: ${{ matrix.can_fail }}
|
||||
name: ${{ matrix.name }}
|
||||
container:
|
||||
image: fedora:39
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install OSC and dependencies
|
||||
env:
|
||||
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}
|
||||
run: |
|
||||
sudo dnf install -y osc obs-service-obs_scm obs-service-cargo_vendor cargo python3-zstandard
|
||||
mkdir -p ~/.config/osc
|
||||
echo "$OBS_CONFIG" > ~/.config/osc/oscrc
|
||||
- name: Checkout Open Build Service repository
|
||||
run: |
|
||||
osc co home:chyyran:librashader/librashader
|
||||
- name: Copy spec from repository
|
||||
run: |
|
||||
cp -r ./pkg/obs/ home:chyyran:librashader/librashader/
|
||||
sed -r -i 's/(<param name="revision">)(.+)(<\/param>)/<param name="revision">${{ github.sha }}<\/param>/g' home:chyyran:librashader/librashader/_service
|
||||
- name: Vendor sources for Open Build Service
|
||||
run: |
|
||||
cd home:chyyran:librashader/librashader/
|
||||
osc service mr
|
||||
- name: Build package
|
||||
run: |
|
||||
cd home:chyyran:librashader/librashader/
|
||||
osc build --no-verify --trust-all-projects ${{ matrix.repo }} x86_64 ${{ matrix.spec }}
|
83
.github/workflows/pr-full-test.yml
vendored
Normal file
83
.github/workflows/pr-full-test.yml
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
name: integration test shader reflection
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
approve-full-test:
|
||||
name: "approval"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Approve
|
||||
run: echo Full test suite for PRs needs approval by a maintainer
|
||||
test-presets:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
environment:
|
||||
name: full-test
|
||||
needs: [approve-full-test]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
- name: Test preset processing
|
||||
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture preprocess_all_slang_presets_parsed
|
||||
test-naga:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
environment:
|
||||
name: full-test
|
||||
needs: [ approve-full-test ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
- name: Test Naga reflection
|
||||
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_wgsl_naga compile_all_slang_presets_msl_naga compile_all_slang_presets_spirv_naga
|
||||
test-cross:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
environment:
|
||||
name: full-test
|
||||
needs: [ approve-full-test ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
- name: Test SPIRV-Cross
|
||||
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_msl_cross compile_all_slang_presets_glsl_cross compile_all_slang_presets_hlsl_cross compile_all_slang_presets_spirv_cross
|
||||
test-dxil:
|
||||
runs-on: windows-latest
|
||||
continue-on-error: false
|
||||
environment:
|
||||
name: full-test
|
||||
needs: [ approve-full-test ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
- name: Test DXIL
|
||||
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_dxil_cross
|
40
.github/workflows/publish-obs.yml
vendored
Normal file
40
.github/workflows/publish-obs.yml
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
name: publish packages using Open Build Service
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
publish-obs:
|
||||
if: github.repository == 'SnowflakePowered/librashader'
|
||||
runs-on: ubuntu-latest
|
||||
container: fedora:39
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Install OSC and dependencies
|
||||
env:
|
||||
OBS_CONFIG: ${{ secrets.OBS_CONFIG }}
|
||||
run: |
|
||||
sudo dnf install -y osc obs-service-obs_scm obs-service-cargo_vendor cargo python3-zstandard
|
||||
mkdir -p ~/.config/osc
|
||||
echo "$OBS_CONFIG" > ~/.config/osc/oscrc
|
||||
- name: Checkout Open Build Service repository
|
||||
run: |
|
||||
osc co home:chyyran:librashader/librashader
|
||||
- name: Copy spec from repository
|
||||
run: |
|
||||
cp -r ./pkg/obs/ home:chyyran:librashader/librashader/
|
||||
sed -r -i 's/(<param name="revision">)(.+)(<\/param>)/<param name="revision">${{ github.sha }}<\/param>/g' home:chyyran:librashader/librashader/_service
|
||||
- name: Vendor sources for Open Build Service
|
||||
run: |
|
||||
cd home:chyyran:librashader/librashader/
|
||||
rm *.obscpio
|
||||
osc service mr
|
||||
- name: Commit source artifacts to Open Build Service
|
||||
run: |
|
||||
cd home:chyyran:librashader/librashader/
|
||||
osc ar
|
||||
osc commit -f -m "git-rev ${{ github.sha }}"
|
67
.github/workflows/push-full-test.yml
vendored
Normal file
67
.github/workflows/push-full-test.yml
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
name: integration test shader reflection
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: "0 0 * * 6"
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
jobs:
|
||||
test-presets:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
- name: Test preset preprocessing
|
||||
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture preprocess_all_slang_presets_parsed
|
||||
test-naga:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
- name: Test Naga Reflection
|
||||
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_wgsl_naga compile_all_slang_presets_msl_naga compile_all_slang_presets_spirv_naga
|
||||
test-cross:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
- name: Test SPIRV-Cross reflection
|
||||
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_msl_cross compile_all_slang_presets_glsl_cross compile_all_slang_presets_hlsl_cross compile_all_slang_presets_spirv_cross
|
||||
test-dxil:
|
||||
runs-on: windows-latest
|
||||
continue-on-error: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: nightly
|
||||
- name: Test DXIL
|
||||
run: cargo test -p librashader --features=github-ci --test reflect -- --nocapture compile_all_slang_presets_dxil_cross
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
|||
*.rdc
|
||||
*.cap
|
||||
/.vs/
|
||||
.idea/
|
||||
librashader_runtime_*.exe
|
||||
/test/capi-tests/librashader-capi-tests/.vs/
|
||||
/test/capi-tests/librashader-capi-tests/x64/
|
||||
|
|
8
.idea/.gitignore
vendored
8
.idea/.gitignore
vendored
|
@ -1,8 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakeWorkspace">
|
||||
<contentRoot DIR="$PROJECT_DIR$" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/src.iml" filepath="$PROJECT_DIR$/.idea/src.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CMake" type="CPP_MODULE" version="4" />
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/test/shaders_slang" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
3474
Cargo.lock
generated
3474
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -10,10 +10,15 @@ members = [
|
|||
"librashader-runtime-d3d12",
|
||||
"librashader-runtime-gl",
|
||||
"librashader-runtime-vk",
|
||||
"librashader-runtime-mtl",
|
||||
"librashader-runtime-wgpu",
|
||||
"librashader-cache",
|
||||
"librashader-capi",
|
||||
"librashader-build-script"
|
||||
]
|
||||
"librashader-build-script", "librashader-runtime-d3d9"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
windows = "0.58.0"
|
||||
|
||||
[workspace.metadata.release]
|
||||
|
||||
|
|
115
README.md
115
README.md
|
@ -6,25 +6,37 @@
|
|||
|
||||
librashader (*/ˈli:brəʃeɪdɚ/*) is a preprocessor, compiler, and runtime for RetroArch 'slang' shaders, rewritten in pure Rust.
|
||||
|
||||
[![Latest Version](https://img.shields.io/crates/v/librashader.svg)](https://crates.io/crates/librashader) [![Docs](https://docs.rs/librashader/badge.svg)](https://docs.rs/librashader) ![License](https://img.shields.io/crates/l/librashader)
|
||||
![Nightly rust](https://img.shields.io/badge/rust-nightly-orange.svg)
|
||||
[![Latest Version](https://img.shields.io/crates/v/librashader.svg)](https://crates.io/crates/librashader) [![Docs](https://docs.rs/librashader/badge.svg)](https://docs.rs/librashader) [![build result](https://build.opensuse.org/projects/home:chyyran:librashader/packages/librashader/badge.svg)](https://software.opensuse.org//download.html?project=home%3Achyyran%3Alibrashader&package=librashader)
|
||||
![License](https://img.shields.io/crates/l/librashader)
|
||||
![Nightly rust](https://img.shields.io/badge/rust-nightly-orange.svg)
|
||||
|
||||
## Installation
|
||||
For end-users, librashader is available from the [Open Build Service](https://software.opensuse.org//download.html?project=home%3Achyyran%3Alibrashader&package=librashader) for a variety of Linux distributions and platforms.
|
||||
Windows and macOS users can grab the latest binaries from [GitHub Releases](https://github.com/SnowflakePowered/librashader/releases).
|
||||
|
||||
## Supported Render APIs
|
||||
librashader supports OpenGL 3, OpenGL 4.6, Vulkan, Direct3D 11, and Direct3D 12. Metal and WebGPU
|
||||
are not currently supported (but pull-requests are welcome). librashader does not support legacy render
|
||||
APIs such as older versions of OpenGL, or legacy versions of Direct3D.
|
||||
librashader supports all modern graphics runtimes, including wgpu, Vulkan, OpenGL 3.3+ and 4.6 (with DSA),
|
||||
Direct3D 11, Direct3D 12, and Metal.
|
||||
|
||||
librashader does not support legacy render APIs such as older versions of OpenGL or Direct3D, except for experimental
|
||||
support for Direct3D 9.
|
||||
|
||||
| **API** | **Status** | **`librashader` feature** |
|
||||
|-------------|------------|--------------------------|
|
||||
| OpenGL 3.3+ | ✔ | `gl` |
|
||||
| OpenGL 4.6 | ✔ | `gl` |
|
||||
| Vulkan | ✔ | `vk` |
|
||||
| Direct3D 11 | ✔ | `d3d11` |
|
||||
| Direct3D 12 | ✔ | `d3d12` |
|
||||
| Metal | ❌ | |
|
||||
| WebGPU | ❌ | |
|
||||
|-------------|------------|---------------------------|
|
||||
| OpenGL 3.3+ | ✅ | `gl` |
|
||||
| OpenGL 4.6 | ✅ | `gl` |
|
||||
| Vulkan | ✅ | `vk` |
|
||||
| Direct3D 9 | ⚠️ | `d3d9` |
|
||||
| Direct3D 11 | ✅ | `d3d11` |
|
||||
| Direct3D 12 | ✅ | `d3d12` |
|
||||
| Metal | ✅ | `metal` |
|
||||
| wgpu | 🆗 | `wgpu` |
|
||||
|
||||
✔ = Render API is supported — ❌ Render API is not supported
|
||||
✅ Full Support — 🆗 Secondary Support — ⚠️ ️Experimental Support
|
||||
|
||||
wgpu may not support all shaders due to restrictions from WGSL. Direct3D 9 support is experimental and does not fully
|
||||
support features such as previous frame feedback or history, as well as being unable to support shaders that need Direct3D 10+
|
||||
only features.
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -33,20 +45,21 @@ The C API is geared more towards integration with existing projects. The Rust `l
|
|||
of the internals if you wish to use parts of librashader piecemeal.
|
||||
|
||||
The librashader C API is best used by including `librashader_ld.h` in your project, which implements a loader that dynamically
|
||||
loads the librashader (`librashader.so` or `librashader.dll`) implementation in the search path.
|
||||
loads the librashader (`librashader.so`, `librashader.dll`, or `librashader.dylib`) implementation in the search path.
|
||||
|
||||
|
||||
### C compatibility
|
||||
The recommended way of integrating `librashader` is by the `librashader_ld` single header library which implements
|
||||
a dynamic loader for `librashader.dll` / `librashader.so`. See the [versioning policy](https://github.com/SnowflakePowered/librashader#versioning)
|
||||
for details on how librashader handles C ABI and API stability with regards to library updates.
|
||||
a dynamic loader for `librashader.dll` / `librashader.so` / `librashader.dylib`. See the [versioning policy](https://github.com/SnowflakePowered/librashader#versioning)
|
||||
for details on how librashader handles C ABI and API stability with regards to library updates. You can also link dynamically
|
||||
with just `librashader.h` and the equivalent of `-lrashader`.
|
||||
|
||||
Linking statically against `librashader.h` is possible, but is not officially supported. You will need to ensure
|
||||
linkage parameters are correct in order to successfully link with `librashader.lib` or `librashader.a`.
|
||||
The [corrosion](https://github.com/corrosion-rs/) CMake package is highly recommended.
|
||||
|
||||
### Thread safety
|
||||
In general, it is **safe** to create a filter chain instance from a different thread, but drawing frames requires
|
||||
Except for the Metal runtime, in general, it is **safe** to create a filter chain instance from a different thread, but drawing frames requires
|
||||
**external synchronization** of the filter chain object.
|
||||
|
||||
Filter chains can be created from any thread, but requires external synchronization of the graphics device queue where applicable
|
||||
|
@ -58,8 +71,11 @@ OpenGL has an additional restriction where creating the filter chain instance in
|
|||
the thread local OpenGL context is initialized to the same context as the drawing thread. Support for deferral of GPU resource initialization
|
||||
is not available to OpenGL.
|
||||
|
||||
The Metal runtime is **not thread safe**. However you can still defer submission of GPU resource initialization through the
|
||||
`filter_chain_create_deferred` function.
|
||||
|
||||
### Quad vertices and rotations
|
||||
All runtimes except OpenGL render with an identity matrix MVP and a VBO for with range `[-1, 1]`. The final pass uses a
|
||||
All runtimes render intermediate passes with an identity matrix MVP and a VBO for with range `[-1, 1]`. The final pass uses a
|
||||
Quad VBO with range `[0, 1]` and the following projection matrix by default.
|
||||
|
||||
```rust
|
||||
|
@ -74,28 +90,8 @@ static DEFAULT_MVP: &[f32; 16] = &[
|
|||
As with RetroArch, a rotation on this MVP will be applied only on the final pass for these runtimes. This is the only way to
|
||||
pass orientation information to shaders.
|
||||
|
||||
The OpenGL runtime uses a VBO for range `[0, 1]` for all passes and the following MVP for all passes.
|
||||
|
||||
```rust
|
||||
static GL_DEFAULT_MVP: &[f32; 16] = &[
|
||||
2.0, 0.0, 0.0, 0.0,
|
||||
0.0, 2.0, 0.0, 0.0,
|
||||
0.0, 0.0, 2.0, 0.0,
|
||||
-1.0, -1.0, 0.0, 1.0,
|
||||
];
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
librashader requires the following build time dependencies
|
||||
|
||||
* The [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/)
|
||||
* [Meson](https://mesonbuild.com/)
|
||||
* [CMake 3.8 or later](https://cmake.org/)
|
||||
* [Python 3.6 or later](https://www.python.org/)
|
||||
|
||||
---
|
||||
|
||||
For Rust projects, simply add the crate to your `Cargo.toml`.
|
||||
|
||||
```
|
||||
|
@ -111,6 +107,9 @@ cargo run -p librashader-build-script -- --profile optimized
|
|||
This will output a `librashader.dll` or `librashader.so` in the target folder. Profile can be `debug`, `release`, or
|
||||
`optimized` for full LTO.
|
||||
|
||||
While librashader has no build-time dependencies, using `librashader_ld.h` may require headers from
|
||||
the relevant runtime graphics API.
|
||||
|
||||
### Writing a librashader Runtime
|
||||
|
||||
If you wish to contribute a runtime implementation not already available, see the [librashader-runtime](https://docs.rs/librashader-runtime/latest/librashader_runtime/)
|
||||
|
@ -127,9 +126,12 @@ The following Rust examples show how to use each librashader runtime.
|
|||
* [OpenGL](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-gl/tests/triangle.rs)
|
||||
* [Direct3D 11](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d11/tests/triangle.rs)
|
||||
* [Direct3D 12](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-d3d12/tests/triangle.rs)
|
||||
* [wgpu](https://github.com/SnowflakePowered/librashader/blob/master/librashader-runtime-wgpu/tests/hello_triangle.rs)
|
||||
|
||||
Some basic examples on using the C API are also provided in the [librashader-capi-tests](https://github.com/SnowflakePowered/librashader/tree/master/test/capi-tests/librashader-capi-tests)
|
||||
directory.
|
||||
Some basic examples on using the C API are also provided.
|
||||
|
||||
* [Direct3D 11](https://github.com/SnowflakePowered/librashader/tree/master/test/capi-tests/librashader-capi-tests)
|
||||
* [Metal with Objective-C](https://github.com/SnowflakePowered/librashader/tree/master/test/capi-tests/objctest)
|
||||
|
||||
## Compatibility
|
||||
|
||||
|
@ -155,14 +157,16 @@ Please report an issue if you run into a shader that works in RetroArch, but not
|
|||
* Sampler objects are used rather than `glTexParameter`.
|
||||
* Sampler inputs and outputs are not renamed. This is useful for debugging shaders in RenderDoc.
|
||||
* UBO and Push Constant Buffer sizes are padded to 16-byte boundaries.
|
||||
* The OpenGL runtime uses the same VBOs as the other runtimes as well as the identity matrix MVP for intermediate passes. RetroArch's OpenGL driver uses only the final VBO.
|
||||
* OpenGL 4.6+
|
||||
* All caveats from the OpenGL 3.3+ section should be considered.
|
||||
* Should work on OpenGL 4.5 but this is not guaranteed. The OpenGL 4.6 runtime may eventually switch to using `ARB_spirv_extensions` for loading shaders, and this will not be marked as a breaking change.
|
||||
* The OpenGL 4.6 runtime uses Direct State Access to minimize changes to the OpenGL state. For GPUs released within the last 5 years, this may improve performance.
|
||||
* The OpenGL runtime uses the same VBOs as the other runtimes as well as the identity matrix MVP for intermediate passes. RetroArch's OpenGL driver uses only the final VBO.
|
||||
* Vulkan
|
||||
* The Vulkan runtime uses [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html) by default.
|
||||
This extension must be enabled at device creation. Explicit render passes can be used by configuring filter chain options, but may have reduced performance
|
||||
compared to dynamic rendering.
|
||||
* The Vulkan runtime can use [`VK_KHR_dynamic_rendering`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_dynamic_rendering.html).
|
||||
This extension must be enabled at device creation.
|
||||
Dynamic rendering may have improved performance when enabled, and supported by the host hardware.
|
||||
* Allocations within the runtime are done through [gpu-allocator](https://github.com/Traverse-Research/gpu-allocator) rather than handled manually.
|
||||
* Direct3D 11
|
||||
* Framebuffer copies are done via `ID3D11DeviceContext::CopySubresourceRegion` rather than a CPU conversion + copy.
|
||||
|
@ -171,7 +175,9 @@ Please report an issue if you run into a shader that works in RetroArch, but not
|
|||
which was released in late 2018.
|
||||
* For maximum compatibility with shaders, a shader compile pipeline based on [`spirv-to-dxil`](https://github.com/SnowflakePowered/spirv-to-dxil-rs) is used, with the SPIRV-Cross HLSL pipeline used as a fallback.
|
||||
This brings shader compatibility beyond what the RetroArch Direct3D 12 driver provides. The HLSL pipeline fallback may be removed in the future as `spirv-to-dxil` improves.
|
||||
* The Direct3D 12 runtime requires `dxil.dll` and `dxcompiler.dll` from the [DirectX Shader Compiler](https://github.com/microsoft/DirectXShaderCompiler).
|
||||
* The Direct3D 12 runtime requires `dxcompiler.dll` from the [DirectX Shader Compiler](https://github.com/microsoft/DirectXShaderCompiler), which may already be installed as part of Direct3D12. `dxil.dll` is not required.
|
||||
* Metal
|
||||
* The Metal runtime uses the same VBOs as the other runtimes as well as the identity matrix MVP for intermediate passes. RetroArch's Metal driver uses only the final VBO.
|
||||
|
||||
Most, if not all shader presets should work fine on librashader. The runtime specific differences should not affect the output,
|
||||
and are more a heads-up for integrating librashader into your project.
|
||||
|
@ -179,7 +185,7 @@ and are more a heads-up for integrating librashader into your project.
|
|||
## Versioning
|
||||
[![Latest Version](https://img.shields.io/crates/v/librashader.svg)](https://crates.io/crates/librashader)
|
||||
![C ABI](https://img.shields.io/badge/ABI%20version-1-yellowgreen)
|
||||
![C API](https://img.shields.io/badge/API%20version-0-blue)
|
||||
![C API](https://img.shields.io/badge/API%20version-1-blue)
|
||||
|
||||
|
||||
librashader typically follows [Semantic Versioning](https://semver.org/) with respect to the Rust API, where a minor version
|
||||
|
@ -206,9 +212,20 @@ will check to ensure that `LIBRASHADER_CURRENT_ABI` matches that of the loaded l
|
|||
librashader will not load. A value of `0` for `LIBRASHADER_CURRENT_ABI` indicates the "null" instance where every operation
|
||||
is a no-op, which occurs if no compatible librashader implementation could be found.
|
||||
|
||||
The `SONAME` of `librashader.so` when installed via package manager is set to `LIBRASHADER_CURRENT_ABI`.
|
||||
|
||||
The above does not apply to releases of librashader prior to `0.1.0`, which were allowed to break API and ABI compatibility
|
||||
in both the Rust and C API without an increase to either `LIBRASHADER_CURRENT_VERSION` or `LIBRASHADER_CURRENT_ABI`.
|
||||
|
||||
### MSRV Policy
|
||||
|
||||
While librashader requires nightly Rust, the following MSRV policy is enforced for unstable library features.
|
||||
|
||||
* Windows and macOS: **latest** nightly
|
||||
* Linux: **1.76**
|
||||
|
||||
A CI job runs weekly to ensure librashader continues to build on nightly. Note that the MSRV is only intended to ease distribution on Linux and is allowed to change any time. It generally tracks the latest version of Rust available in the latest version of Ubuntu, but this may change with no warning in a patch release.
|
||||
|
||||
## License
|
||||
The core parts of librashader such as the preprocessor, the preset parser,
|
||||
the reflection library, and the runtimes, are all licensed under the Mozilla Public License version 2.0.
|
||||
|
@ -218,13 +235,13 @@ are more permissively licensed, and may allow you to use librashader in your per
|
|||
licensed or proprietary project.
|
||||
|
||||
To facilitate easier use of librashader in projects incompatible with MPL-2.0, `librashader_ld`
|
||||
implements a loader which thunks its calls to any `librashader.so` or `librashader.dll`
|
||||
implements a loader which thunks its calls to any `librashader.so`, `librashader.dll`, or `librashader.dylib`.
|
||||
library found in the load path. A non-MPL-2.0 compatible project may link against
|
||||
`librashader_ld` to use the librashader runtime, *provided that `librashader.so` or `librashader.dll`
|
||||
`librashader_ld` to use the librashader runtime, *provided that `librashader.so`, `librashader.dll` or `librashader.dylib`
|
||||
are distributed under the restrictions of MPLv2*.
|
||||
|
||||
Note that this means that if your project is unable to comply with the requirements of MPL-2.0,
|
||||
you **can not distribute `librashader.so` or `librashader.dll`** alongside your project.
|
||||
you **can not distribute `librashader.so`, `librashader.dll` or `librashader.dylib`** alongside your project.
|
||||
The end user must obtain the implementation of librashader themselves. For more information,
|
||||
see the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/).
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -6,8 +6,8 @@ publish = false
|
|||
|
||||
|
||||
[dependencies]
|
||||
cbindgen = { git = "https://github.com/eqrion/cbindgen" }
|
||||
clap = { version = "4.1.0", features = ["derive"] }
|
||||
cbindgen = "0.26.0"
|
||||
clap = { version = "=4.1.0", features = ["derive"] }
|
||||
|
||||
|
||||
[package.metadata.release]
|
||||
|
|
|
@ -2,7 +2,7 @@ use clap::Parser;
|
|||
use std::fs::File;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::process::{Command, ExitCode};
|
||||
use std::{env, fs};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
|
@ -10,12 +10,16 @@ use std::{env, fs};
|
|||
struct Args {
|
||||
#[arg(long, default_value = "debug", global = true)]
|
||||
profile: String,
|
||||
#[arg(long, global = true)]
|
||||
target: Option<String>,
|
||||
#[arg(last = true)]
|
||||
cargoflags: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
pub fn main() -> ExitCode {
|
||||
// Do not update files on docsrs
|
||||
if env::var("DOCS_RS").is_ok() {
|
||||
return;
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
let args = Args::parse();
|
||||
|
@ -32,9 +36,31 @@ pub fn main() {
|
|||
"--profile={}",
|
||||
if profile == "debug" { "dev" } else { &profile }
|
||||
));
|
||||
Some(cmd.status().expect("Failed to build librashader-capi"));
|
||||
|
||||
let output_dir = PathBuf::from(format!("target/{}", profile))
|
||||
// If we're on RUSTC_BOOTSTRAP, it's likely because we're building for a package..
|
||||
if env::var("RUSTC_BOOTSTRAP").is_ok() {
|
||||
cmd.arg("--ignore-rust-version");
|
||||
}
|
||||
|
||||
if let Some(target) = &args.target {
|
||||
cmd.arg(format!("--target={}", &target));
|
||||
}
|
||||
|
||||
if !args.cargoflags.is_empty() {
|
||||
cmd.args(args.cargoflags);
|
||||
}
|
||||
|
||||
let status = cmd.status().expect("Failed to build librashader-capi");
|
||||
if !status.success() {
|
||||
return ExitCode::from(status.code().unwrap_or(1) as u8);
|
||||
}
|
||||
|
||||
let mut output_dir = PathBuf::from(format!("target/{}", profile));
|
||||
if let Some(target) = &args.target {
|
||||
output_dir = PathBuf::from(format!("target/{}/{}", target, profile));
|
||||
}
|
||||
|
||||
let output_dir = output_dir
|
||||
.canonicalize()
|
||||
.expect("Could not find output directory.");
|
||||
|
||||
|
@ -54,7 +80,14 @@ pub fn main() {
|
|||
.expect("Unable to write bindings.");
|
||||
|
||||
println!("Moving artifacts...");
|
||||
if cfg!(target_os = "linux") {
|
||||
if cfg!(target_os = "macos") {
|
||||
let artifacts = &["liblibrashader_capi.dylib", "liblibrashader_capi.a"];
|
||||
for artifact in artifacts {
|
||||
let ext = artifact.strip_prefix("lib").unwrap();
|
||||
let ext = ext.replace("_capi", "");
|
||||
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
|
||||
}
|
||||
} else if cfg!(target_family = "unix") {
|
||||
let artifacts = &["liblibrashader_capi.so", "liblibrashader_capi.a"];
|
||||
for artifact in artifacts {
|
||||
let ext = artifact.strip_prefix("lib").unwrap();
|
||||
|
@ -70,11 +103,22 @@ pub fn main() {
|
|||
"librashader_capi.d",
|
||||
"librashader_capi.dll.exp",
|
||||
"librashader_capi.dll.lib",
|
||||
"librashader_capi.pdb",
|
||||
];
|
||||
for artifact in artifacts {
|
||||
let ext = artifact.replace("_capi", "");
|
||||
println!("Renaming {artifact} to {ext}");
|
||||
fs::rename(output_dir.join(artifact), output_dir.join(ext)).unwrap();
|
||||
}
|
||||
|
||||
if output_dir.join("librashader_capi.pdb").exists() {
|
||||
println!("Renaming librashader_capi.pdb to librashader.pdb");
|
||||
fs::rename(
|
||||
output_dir.join("librashader_capi.pdb"),
|
||||
output_dir.join("librashader.pdb"),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "librashader-cache"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.1.3"
|
||||
version = "0.3.0"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -12,18 +12,18 @@ description = "RetroArch shaders for all."
|
|||
|
||||
[dependencies]
|
||||
serde = { version = "1.0" }
|
||||
librashader-reflect = { path = "../librashader-reflect", version = "0.1.3", features = ["serialize", "dxil"] }
|
||||
librashader-preprocess = { path = "../librashader-preprocess", version = "0.1.3" }
|
||||
librashader-reflect = { path = "../librashader-reflect", version = "0.3.0", features = ["serialize"] }
|
||||
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
|
||||
platform-dirs = "0.3.0"
|
||||
blake3 = { version = "1.3.3" }
|
||||
thiserror = "1.0.38"
|
||||
bincode = { version = "2.0.0-rc.2", features = ["serde"] }
|
||||
rusqlite = { version = "0.28.0", features = ["bundled"] }
|
||||
persy = "1.4.7"
|
||||
|
||||
bytemuck = "1.13.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.44.0"
|
||||
workspace = true
|
||||
features = [
|
||||
"Win32_Graphics_Direct3D",
|
||||
"Win32_Graphics_Direct3D_Fxc",
|
||||
|
@ -32,10 +32,10 @@ features = [
|
|||
optional = true
|
||||
|
||||
[features]
|
||||
d3d = ["windows"]
|
||||
d3d = ["windows", "librashader-reflect/dxil"]
|
||||
|
||||
# hack to get building on docsrs
|
||||
docsrs = ["blake3/pure", "rusqlite/in_gecko"]
|
||||
docsrs = ["blake3/pure"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docsrs"]
|
||||
|
|
|
@ -3,10 +3,11 @@ use crate::key::CacheKey;
|
|||
|
||||
pub(crate) mod internal {
|
||||
use platform_dirs::AppDirs;
|
||||
use rusqlite::{Connection, DatabaseName};
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use persy::{ByteVec, Config, Persy, ValueMode};
|
||||
|
||||
pub(crate) fn get_cache_dir() -> Result<PathBuf, Box<dyn Error>> {
|
||||
let cache_dir = if let Some(cache_dir) =
|
||||
AppDirs::new(Some("librashader"), false).map(|a| a.cache_dir)
|
||||
|
@ -23,46 +24,73 @@ pub(crate) mod internal {
|
|||
Ok(cache_dir)
|
||||
}
|
||||
|
||||
pub(crate) fn get_cache() -> Result<Connection, Box<dyn Error>> {
|
||||
let cache_dir = get_cache_dir()?;
|
||||
let mut conn = Connection::open(&cache_dir.join("librashader.db"))?;
|
||||
// pub(crate) fn get_cache() -> Result<Connection, Box<dyn Error>> {
|
||||
// let cache_dir = get_cache_dir()?;
|
||||
// let mut conn = Connection::open(&cache_dir.join("librashader.db"))?;
|
||||
//
|
||||
// let tx = conn.transaction()?;
|
||||
// tx.pragma_update(Some(DatabaseName::Main), "journal_mode", "wal2")?;
|
||||
// tx.execute(
|
||||
// r#"create table if not exists cache (
|
||||
// type text not null,
|
||||
// id blob not null,
|
||||
// value blob not null unique,
|
||||
// primary key (id, type)
|
||||
// )"#,
|
||||
// [],
|
||||
// )?;
|
||||
// tx.commit()?;
|
||||
// Ok(conn)
|
||||
// }
|
||||
|
||||
let tx = conn.transaction()?;
|
||||
tx.pragma_update(Some(DatabaseName::Main), "journal_mode", "wal2")?;
|
||||
tx.execute(
|
||||
r#"create table if not exists cache (
|
||||
type text not null,
|
||||
id blob not null,
|
||||
value blob not null unique,
|
||||
primary key (id, type)
|
||||
)"#,
|
||||
[],
|
||||
)?;
|
||||
tx.commit()?;
|
||||
Ok(conn)
|
||||
pub(crate) fn get_cache() -> Result<Persy, Box<dyn Error>> {
|
||||
let cache_dir = get_cache_dir()?;
|
||||
match Persy::open_or_create_with(
|
||||
&cache_dir.join("librashader.db.1"),
|
||||
Config::new(),
|
||||
|persy| {
|
||||
let tx = persy.begin()?;
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
},
|
||||
) {
|
||||
Ok(conn) => Ok(conn),
|
||||
Err(e) => {
|
||||
let path = &cache_dir.join("librashader.db.1");
|
||||
let _ = std::fs::remove_file(path).ok();
|
||||
Err(e)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_blob(
|
||||
conn: &Connection,
|
||||
conn: &Persy,
|
||||
index: &str,
|
||||
key: &[u8],
|
||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let value = conn.query_row(
|
||||
&*format!("select value from cache where (type = (?1) and id = (?2))"),
|
||||
rusqlite::params![index, key],
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
Ok(value)
|
||||
) -> Result<Option<Vec<u8>>, Box<dyn Error>> {
|
||||
if !conn.exists_index(index)? {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let value = conn.get::<_, ByteVec>(index, &ByteVec::from(key))?.next();
|
||||
Ok(value.map(|v| v.to_vec()))
|
||||
}
|
||||
|
||||
pub(crate) fn set_blob(conn: &Connection, index: &str, key: &[u8], value: &[u8]) {
|
||||
match conn.execute(
|
||||
&*format!("insert or replace into cache (type, id, value) values (?1, ?2, ?3)"),
|
||||
rusqlite::params![index, key, value],
|
||||
) {
|
||||
Ok(_) => return,
|
||||
Err(e) => println!("err: {:?}", e),
|
||||
pub(crate) fn set_blob(
|
||||
conn: &Persy,
|
||||
index: &str,
|
||||
key: &[u8],
|
||||
value: &[u8],
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut tx = conn.begin()?;
|
||||
if !tx.exists_index(index)? {
|
||||
tx.create_index::<ByteVec, ByteVec>(index, ValueMode::Replace)?;
|
||||
}
|
||||
|
||||
tx.put(index, ByteVec::from(key), ByteVec::from(value))?;
|
||||
tx.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,7 +129,7 @@ where
|
|||
};
|
||||
|
||||
'attempt: {
|
||||
if let Ok(blob) = internal::get_blob(&cache, index, hashkey.as_bytes()) {
|
||||
if let Ok(Some(blob)) = internal::get_blob(&cache, index, hashkey.as_bytes()) {
|
||||
let cached = T::from_bytes(&blob).map(&load);
|
||||
|
||||
match cached {
|
||||
|
@ -115,7 +143,7 @@ where
|
|||
let blob = factory(keys)?;
|
||||
|
||||
if let Some(slice) = T::to_bytes(&blob) {
|
||||
internal::set_blob(&cache, index, hashkey.as_bytes(), &slice);
|
||||
let _ = internal::set_blob(&cache, index, hashkey.as_bytes(), &slice);
|
||||
}
|
||||
Ok(load(blob)?)
|
||||
}
|
||||
|
@ -157,7 +185,7 @@ where
|
|||
};
|
||||
|
||||
let pipeline = 'attempt: {
|
||||
if let Ok(blob) = internal::get_blob(&cache, index, hashkey.as_bytes()) {
|
||||
if let Ok(Some(blob)) = internal::get_blob(&cache, index, hashkey.as_bytes()) {
|
||||
let cached = restore_pipeline(Some(blob));
|
||||
match cached {
|
||||
Ok(res) => {
|
||||
|
@ -173,7 +201,8 @@ where
|
|||
// update the pso every time just in case.
|
||||
if let Ok(state) = fetch_pipeline_state(&pipeline) {
|
||||
if let Some(slice) = T::to_bytes(&state) {
|
||||
internal::set_blob(&cache, index, hashkey.as_bytes(), &slice);
|
||||
// We don't really care if the transaction fails, just try again next time.
|
||||
let _ = internal::set_blob(&cache, index, hashkey.as_bytes(), &slice);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,3 +16,13 @@ impl Cacheable for Vec<u8> {
|
|||
Some(self.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl Cacheable for Option<Vec<u8>> {
|
||||
fn from_bytes(bytes: &[u8]) -> Option<Self> {
|
||||
Some(Some(Vec::from(bytes)))
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Option<Vec<u8>> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,35 @@
|
|||
//! Cache helpers for `ShaderCompilation` objects to cache compiled SPIRV.
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use librashader_reflect::back::targets::{DXIL, GLSL, HLSL, SPIRV};
|
||||
#[cfg(all(target_os = "windows", feature = "d3d"))]
|
||||
use librashader_reflect::back::targets::DXIL;
|
||||
use librashader_reflect::back::targets::{GLSL, HLSL, SPIRV};
|
||||
|
||||
use librashader_reflect::back::{CompilerBackend, FromCompilation};
|
||||
use librashader_reflect::error::{ShaderCompileError, ShaderReflectError};
|
||||
use librashader_reflect::front::{GlslangCompilation, ShaderCompilation};
|
||||
use librashader_reflect::front::{
|
||||
Glslang, ShaderInputCompiler, ShaderReflectObject, SpirvCompilation,
|
||||
};
|
||||
|
||||
pub struct CachedCompilation<T> {
|
||||
compilation: T,
|
||||
}
|
||||
|
||||
impl<T: ShaderCompilation + for<'de> serde::Deserialize<'de> + serde::Serialize + Clone>
|
||||
ShaderCompilation for CachedCompilation<T>
|
||||
impl<T: ShaderReflectObject> ShaderReflectObject for CachedCompilation<T> {
|
||||
type Compiler = T::Compiler;
|
||||
}
|
||||
|
||||
impl<T: ShaderReflectObject + for<'de> serde::Deserialize<'de> + serde::Serialize + Clone>
|
||||
ShaderInputCompiler<CachedCompilation<T>> for Glslang
|
||||
where
|
||||
Glslang: ShaderInputCompiler<T>,
|
||||
{
|
||||
fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
|
||||
fn compile(source: &ShaderSource) -> Result<CachedCompilation<T>, ShaderCompileError> {
|
||||
let cache = crate::cache::internal::get_cache();
|
||||
|
||||
let Ok(cache) = cache else {
|
||||
return Ok(CachedCompilation {
|
||||
compilation: T::compile(source)?
|
||||
})
|
||||
compilation: Glslang::compile(source)?,
|
||||
});
|
||||
};
|
||||
|
||||
let key = {
|
||||
|
@ -30,7 +41,9 @@ impl<T: ShaderCompilation + for<'de> serde::Deserialize<'de> + serde::Serialize
|
|||
};
|
||||
|
||||
let compilation = 'cached: {
|
||||
if let Ok(cached) = crate::cache::internal::get_blob(&cache, "spirv", key.as_bytes()) {
|
||||
if let Ok(Some(cached)) =
|
||||
crate::cache::internal::get_blob(&cache, "spirv", key.as_bytes())
|
||||
{
|
||||
let decoded =
|
||||
bincode::serde::decode_from_slice(&cached, bincode::config::standard())
|
||||
.map(|(compilation, _)| CachedCompilation { compilation })
|
||||
|
@ -42,67 +55,84 @@ impl<T: ShaderCompilation + for<'de> serde::Deserialize<'de> + serde::Serialize
|
|||
}
|
||||
|
||||
CachedCompilation {
|
||||
compilation: T::compile(source)?,
|
||||
compilation: Glslang::compile(source)?,
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(updated) =
|
||||
bincode::serde::encode_to_vec(&compilation.compilation, bincode::config::standard())
|
||||
{
|
||||
crate::cache::internal::set_blob(&cache, "spirv", key.as_bytes(), &updated)
|
||||
let Ok(()) =
|
||||
crate::cache::internal::set_blob(&cache, "spirv", key.as_bytes(), &updated)
|
||||
else {
|
||||
return Ok(compilation);
|
||||
};
|
||||
}
|
||||
|
||||
Ok(compilation)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromCompilation<CachedCompilation<GlslangCompilation>> for DXIL {
|
||||
type Target = <DXIL as FromCompilation<GlslangCompilation>>::Target;
|
||||
type Options = <DXIL as FromCompilation<GlslangCompilation>>::Options;
|
||||
type Context = <DXIL as FromCompilation<GlslangCompilation>>::Context;
|
||||
type Output = <DXIL as FromCompilation<GlslangCompilation>>::Output;
|
||||
#[cfg(all(target_os = "windows", feature = "d3d"))]
|
||||
impl<T> FromCompilation<CachedCompilation<SpirvCompilation>, T> for DXIL
|
||||
where
|
||||
DXIL: FromCompilation<SpirvCompilation, T>,
|
||||
{
|
||||
type Target = <DXIL as FromCompilation<SpirvCompilation, T>>::Target;
|
||||
type Options = <DXIL as FromCompilation<SpirvCompilation, T>>::Options;
|
||||
type Context = <DXIL as FromCompilation<SpirvCompilation, T>>::Context;
|
||||
type Output = <DXIL as FromCompilation<SpirvCompilation, T>>::Output;
|
||||
|
||||
fn from_compilation(
|
||||
compile: CachedCompilation<GlslangCompilation>,
|
||||
compile: CachedCompilation<SpirvCompilation>,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
DXIL::from_compilation(compile.compilation)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromCompilation<CachedCompilation<GlslangCompilation>> for HLSL {
|
||||
type Target = <HLSL as FromCompilation<GlslangCompilation>>::Target;
|
||||
type Options = <HLSL as FromCompilation<GlslangCompilation>>::Options;
|
||||
type Context = <HLSL as FromCompilation<GlslangCompilation>>::Context;
|
||||
type Output = <HLSL as FromCompilation<GlslangCompilation>>::Output;
|
||||
impl<T> FromCompilation<CachedCompilation<SpirvCompilation>, T> for HLSL
|
||||
where
|
||||
HLSL: FromCompilation<SpirvCompilation, T>,
|
||||
{
|
||||
type Target = <HLSL as FromCompilation<SpirvCompilation, T>>::Target;
|
||||
type Options = <HLSL as FromCompilation<SpirvCompilation, T>>::Options;
|
||||
type Context = <HLSL as FromCompilation<SpirvCompilation, T>>::Context;
|
||||
type Output = <HLSL as FromCompilation<SpirvCompilation, T>>::Output;
|
||||
|
||||
fn from_compilation(
|
||||
compile: CachedCompilation<GlslangCompilation>,
|
||||
compile: CachedCompilation<SpirvCompilation>,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
HLSL::from_compilation(compile.compilation)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromCompilation<CachedCompilation<GlslangCompilation>> for GLSL {
|
||||
type Target = <GLSL as FromCompilation<GlslangCompilation>>::Target;
|
||||
type Options = <GLSL as FromCompilation<GlslangCompilation>>::Options;
|
||||
type Context = <GLSL as FromCompilation<GlslangCompilation>>::Context;
|
||||
type Output = <GLSL as FromCompilation<GlslangCompilation>>::Output;
|
||||
impl<T> FromCompilation<CachedCompilation<SpirvCompilation>, T> for GLSL
|
||||
where
|
||||
GLSL: FromCompilation<SpirvCompilation, T>,
|
||||
{
|
||||
type Target = <GLSL as FromCompilation<SpirvCompilation, T>>::Target;
|
||||
type Options = <GLSL as FromCompilation<SpirvCompilation, T>>::Options;
|
||||
type Context = <GLSL as FromCompilation<SpirvCompilation, T>>::Context;
|
||||
type Output = <GLSL as FromCompilation<SpirvCompilation, T>>::Output;
|
||||
|
||||
fn from_compilation(
|
||||
compile: CachedCompilation<GlslangCompilation>,
|
||||
compile: CachedCompilation<SpirvCompilation>,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
GLSL::from_compilation(compile.compilation)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromCompilation<CachedCompilation<GlslangCompilation>> for SPIRV {
|
||||
type Target = <SPIRV as FromCompilation<GlslangCompilation>>::Target;
|
||||
type Options = <SPIRV as FromCompilation<GlslangCompilation>>::Options;
|
||||
type Context = <SPIRV as FromCompilation<GlslangCompilation>>::Context;
|
||||
type Output = <SPIRV as FromCompilation<GlslangCompilation>>::Output;
|
||||
impl<T> FromCompilation<CachedCompilation<SpirvCompilation>, T> for SPIRV
|
||||
where
|
||||
SPIRV: FromCompilation<SpirvCompilation, T>,
|
||||
{
|
||||
type Target = <SPIRV as FromCompilation<SpirvCompilation, T>>::Target;
|
||||
type Options = <SPIRV as FromCompilation<SpirvCompilation, T>>::Options;
|
||||
type Context = <SPIRV as FromCompilation<SpirvCompilation, T>>::Context;
|
||||
type Output = <SPIRV as FromCompilation<SpirvCompilation, T>>::Output;
|
||||
|
||||
fn from_compilation(
|
||||
compile: CachedCompilation<GlslangCompilation>,
|
||||
compile: CachedCompilation<SpirvCompilation>,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
SPIRV::from_compilation(compile.compilation)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
//! here because of the orphan rule.
|
||||
|
||||
use crate::{CacheKey, Cacheable};
|
||||
use windows::core::Interface;
|
||||
|
||||
impl CacheKey for windows::Win32::Graphics::Direct3D::ID3DBlob {
|
||||
fn hash_bytes(&self) -> &[u8] {
|
||||
|
@ -17,7 +18,9 @@ impl CacheKey for windows::Win32::Graphics::Direct3D::Dxc::IDxcBlob {
|
|||
|
||||
impl Cacheable for windows::Win32::Graphics::Direct3D::ID3DBlob {
|
||||
fn from_bytes(bytes: &[u8]) -> Option<Self> {
|
||||
let Some(blob) = (unsafe { windows::Win32::Graphics::Direct3D::Fxc::D3DCreateBlob(bytes.len()).ok() }) else {
|
||||
let Some(blob) =
|
||||
(unsafe { windows::Win32::Graphics::Direct3D::Fxc::D3DCreateBlob(bytes.len()).ok() })
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
|
@ -43,16 +46,23 @@ impl Cacheable for windows::Win32::Graphics::Direct3D::Dxc::IDxcBlob {
|
|||
fn from_bytes(bytes: &[u8]) -> Option<Self> {
|
||||
let Some(blob) = (unsafe {
|
||||
windows::Win32::Graphics::Direct3D::Dxc::DxcCreateInstance(
|
||||
&windows::Win32::Graphics::Direct3D::Dxc::CLSID_DxcLibrary)
|
||||
.and_then(|library: windows::Win32::Graphics::Direct3D::Dxc::IDxcUtils| {
|
||||
library.CreateBlob(bytes.as_ptr().cast(), bytes.len() as u32,
|
||||
windows::Win32::Graphics::Direct3D::Dxc::DXC_CP(0))
|
||||
}).ok()
|
||||
&windows::Win32::Graphics::Direct3D::Dxc::CLSID_DxcLibrary,
|
||||
)
|
||||
.and_then(
|
||||
|library: windows::Win32::Graphics::Direct3D::Dxc::IDxcUtils| {
|
||||
library.CreateBlob(
|
||||
bytes.as_ptr().cast(),
|
||||
bytes.len() as u32,
|
||||
windows::Win32::Graphics::Direct3D::Dxc::DXC_CP(0),
|
||||
)
|
||||
},
|
||||
)
|
||||
.ok()
|
||||
}) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(blob.into())
|
||||
Some(blob.cast().ok()?)
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Option<Vec<u8>> {
|
||||
|
|
|
@ -3,7 +3,7 @@ name = "librashader-capi"
|
|||
edition = "2021"
|
||||
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.1.3"
|
||||
version = "0.3.0"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -16,25 +16,52 @@ crate-type = [ "cdylib", "staticlib" ]
|
|||
|
||||
[features]
|
||||
default = ["runtime-all" ]
|
||||
runtime-all = ["runtime-opengl", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan"]
|
||||
runtime-all = ["runtime-opengl", "runtime-d3d9", "runtime-d3d11", "runtime-d3d12", "runtime-vulkan", "runtime-metal"]
|
||||
runtime-opengl = ["gl", "librashader/runtime-gl"]
|
||||
runtime-d3d11 = ["windows", "librashader/runtime-d3d11", "windows/Win32_Graphics_Direct3D11"]
|
||||
runtime-d3d12 = ["windows", "librashader/runtime-d3d12", "windows/Win32_Graphics_Direct3D12"]
|
||||
runtime-d3d9 = ["windows", "librashader/runtime-d3d9", "windows/Win32_Graphics_Direct3D9"]
|
||||
|
||||
runtime-vulkan = ["ash", "librashader/runtime-vk"]
|
||||
runtime-metal = ["__cbindgen_internal_objc", "librashader/runtime-metal"]
|
||||
|
||||
reflect-unstable = []
|
||||
|
||||
__cbindgen_internal = ["runtime-all"]
|
||||
|
||||
# make runtime-metal depend on this, so its automatically implied.
|
||||
# this will make cbindgen generate __OBJC__ ifdefs for metal functions.
|
||||
__cbindgen_internal_objc = ["objc2-metal", "objc2"]
|
||||
|
||||
[dependencies]
|
||||
librashader = { path = "../librashader", version = "0.1.3", features = ["internal"] }
|
||||
thiserror = "1.0.37"
|
||||
paste = "1.0.9"
|
||||
gl = { version = "0.14.0", optional = true }
|
||||
rustc-hash = "1.1.0"
|
||||
ash = { version = "0.37.2+1.3.238", optional = true }
|
||||
spirv_cross = { package = "librashader-spirv-cross", version = "0.23" }
|
||||
rustc-hash = "2.0.0"
|
||||
ash = { version = "0.38", optional = true }
|
||||
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1" }
|
||||
sptr = "0.3.2"
|
||||
|
||||
[dependencies.librashader]
|
||||
path = "../librashader"
|
||||
version = "0.3.0"
|
||||
default-features = false
|
||||
features = ["reflect", "presets", "preprocess"]
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.44.0"
|
||||
workspace = true
|
||||
optional = true
|
||||
|
||||
[target.'cfg(target_vendor="apple")'.dependencies]
|
||||
objc2-metal = { version = "0.2.0" , features = [ "all" ], optional = true }
|
||||
objc2 = { version = "0.5.0", features = ["apple"] , optional = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-pc-windows-msvc", "x86_64-unknown-linux-gnu"]
|
||||
targets = [ "x86_64-pc-windows-msvc",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-apple-darwin",
|
||||
"aarch64-apple-darwin",
|
||||
"aarch64-apple-ios",
|
||||
"i686-pc-windows-msvc",
|
||||
"i686-unknown-linux-gnu", ]
|
||||
features = ["librashader/docsrs"]
|
||||
|
|
|
@ -5,4 +5,4 @@ pub fn main() {
|
|||
println!("cargo:rustc-link-arg=/DELAYLOAD:dxcompiler.dll");
|
||||
println!("cargo:rustc-link-arg=/DELAYLOAD:d3d12.dll");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,28 +34,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
after_includes = """
|
||||
#if defined(_WIN32) && defined(LIBRA_RUNTIME_D3D11)
|
||||
#include <d3d11.h>
|
||||
#else
|
||||
typedef void ID3D11Device;
|
||||
typedef void ID3D11DeviceContext;
|
||||
typedef void ID3D11RenderTargetView;
|
||||
typedef void ID3D11ShaderResourceView;
|
||||
#endif
|
||||
#if defined(LIBRA_RUNTIME_VULKAN)
|
||||
#include <vulkan\\vulkan.h>
|
||||
#else
|
||||
typedef int32_t VkFormat;
|
||||
typedef uint64_t VkImage;
|
||||
typedef void* VkPhysicalDevice;
|
||||
typedef void* VkInstance;
|
||||
typedef void* VkCommandBuffer;
|
||||
#endif
|
||||
#if defined(_WIN32) && defined(LIBRA_RUNTIME_D3D12)
|
||||
#include <d3d12.h>
|
||||
#else
|
||||
typedef void ID3D12GraphicsCommandList;
|
||||
typedef void ID3D12Device;
|
||||
typedef void ID3D12Resource;
|
||||
typedef void D3D12_CPU_DESCRIPTOR_HANDLE;
|
||||
#endif
|
||||
#if defined(_WIN32) && defined(LIBRA_RUNTIME_D3D9)
|
||||
#include <D3D9.h>
|
||||
#endif
|
||||
#if defined(__APPLE__) && defined(LIBRA_RUNTIME_METAL) && defined(__OBJC__)
|
||||
#import <Metal/Metal.h>
|
||||
#endif
|
||||
#if defined(LIBRA_RUNTIME_VULKAN)
|
||||
#include <vulkan/vulkan.h>
|
||||
#endif
|
||||
"""
|
||||
|
||||
|
@ -64,20 +54,20 @@ typedef void D3D12_CPU_DESCRIPTOR_HANDLE;
|
|||
"feature = runtime-vulkan" = "LIBRA_RUNTIME_VULKAN"
|
||||
"feature = runtime-d3d11" = "LIBRA_RUNTIME_D3D11"
|
||||
"feature = runtime-d3d12" = "LIBRA_RUNTIME_D3D12"
|
||||
"feature = runtime-d3d9" = "LIBRA_RUNTIME_D3D9"
|
||||
"feature = runtime-metal" = "LIBRA_RUNTIME_METAL"
|
||||
"feature = __cbindgen_internal_objc" = "__OBJC__"
|
||||
|
||||
"target_os = windows" = "_WIN32"
|
||||
"target_vendor = apple" = "__APPLE__"
|
||||
|
||||
[parse]
|
||||
parse_deps = true
|
||||
include = ["librashader",
|
||||
"librashader-presets",
|
||||
"librashader-preprocess",
|
||||
"librashader-reflect",
|
||||
"librashader-runtime-gl",
|
||||
"librashader-runtime-vk",
|
||||
"librashader-runtime-d3d11",
|
||||
"librashader-runtime-d3d12",
|
||||
]
|
||||
expand = ["librashader-capi"]
|
||||
parse_deps = false
|
||||
include = ["librashader"]
|
||||
|
||||
[parse.expand]
|
||||
crates = ["librashader-capi"]
|
||||
features = ["__cbindgen_internal"]
|
||||
|
||||
[struct]
|
||||
|
||||
|
@ -99,6 +89,20 @@ include = [
|
|||
"PFN_libra_preset_print",
|
||||
"PFN_libra_preset_get_runtime_params",
|
||||
"PFN_libra_preset_free_runtime_params",
|
||||
"PFN_libra_preset_create_with_context",
|
||||
|
||||
"PFN_libra_preset_ctx_create",
|
||||
"PFN_libra_preset_ctx_free",
|
||||
"PFN_libra_preset_ctx_set_core_name",
|
||||
"PFN_libra_preset_ctx_set_content_dir",
|
||||
"PFN_libra_preset_ctx_set_param",
|
||||
"PFN_libra_preset_ctx_set_core_rotation",
|
||||
"PFN_libra_preset_ctx_set_user_rotation",
|
||||
"PFN_libra_preset_ctx_set_screen_orientation",
|
||||
"PFN_libra_preset_ctx_set_allow_rotation",
|
||||
"PFN_libra_preset_ctx_set_view_aspect_orientation",
|
||||
"PFN_libra_preset_ctx_set_core_aspect_orientation",
|
||||
"PFN_libra_preset_ctx_set_runtime",
|
||||
|
||||
# error
|
||||
"PFN_libra_error_errno",
|
||||
|
@ -137,6 +141,14 @@ include = [
|
|||
"PFN_libra_d3d11_filter_chain_get_active_pass_count",
|
||||
"PFN_libra_d3d11_filter_chain_free",
|
||||
|
||||
# d3d11
|
||||
"PFN_libra_d3d9_filter_chain_create",
|
||||
"PFN_libra_d3d9_filter_chain_frame",
|
||||
"PFN_libra_d3d9_filter_chain_set_param",
|
||||
"PFN_libra_d3d9_filter_chain_get_param",
|
||||
"PFN_libra_d3d9_filter_chain_set_active_pass_count",
|
||||
"PFN_libra_d3d9_filter_chain_get_active_pass_count",
|
||||
"PFN_libra_d3d9_filter_chain_free",
|
||||
|
||||
# d3d12
|
||||
"PFN_libra_d3d12_filter_chain_create",
|
||||
|
@ -147,17 +159,37 @@ include = [
|
|||
"PFN_libra_d3d12_filter_chain_set_active_pass_count",
|
||||
"PFN_libra_d3d12_filter_chain_get_active_pass_count",
|
||||
"PFN_libra_d3d12_filter_chain_free",
|
||||
|
||||
# metal
|
||||
"PFN_libra_mtl_filter_chain_create",
|
||||
"PFN_libra_mtl_filter_chain_create_deferred",
|
||||
"PFN_libra_mtl_filter_chain_frame",
|
||||
"PFN_libra_mtl_filter_chain_set_param",
|
||||
"PFN_libra_mtl_filter_chain_get_param",
|
||||
"PFN_libra_mtl_filter_chain_set_active_pass_count",
|
||||
"PFN_libra_mtl_filter_chain_get_active_pass_count",
|
||||
"PFN_libra_mtl_filter_chain_free",
|
||||
]
|
||||
|
||||
exclude = ["Option_ID3D11DeviceContext"]
|
||||
exclude = [
|
||||
"Option_ID3D11DeviceContext",
|
||||
"PMTLCommandQueue",
|
||||
"PMTLCommandBuffer",
|
||||
"PMTLTexture"
|
||||
]
|
||||
|
||||
[export.rename]
|
||||
"LibrashaderError" = "_libra_error"
|
||||
"ShaderPreset" = "_shader_preset"
|
||||
|
||||
"WildcardContext" = "_preset_ctx"
|
||||
|
||||
"FilterChainGL" = "_filter_chain_gl"
|
||||
"FilterChainVulkan" = "_filter_chain_vk"
|
||||
"FilterChainD3D11" = "_filter_chain_d3d11"
|
||||
"FilterChainD3D12" = "_filter_chain_d3d12"
|
||||
"FilterChainD3D9" = "_filter_chain_d3d9"
|
||||
"FilterChainMetal" = "_filter_chain_mtl"
|
||||
|
||||
# vulkan renames
|
||||
"PhysicalDevice" = "VkPhysicalDevice"
|
||||
|
@ -174,10 +206,19 @@ exclude = ["Option_ID3D11DeviceContext"]
|
|||
"ID3D11RenderTargetView" = "ID3D11RenderTargetView *"
|
||||
"ID3D11ShaderResourceView" = "ID3D11ShaderResourceView *"
|
||||
|
||||
# hack to get proper pointer indirection for COM pointers
|
||||
"IDirect3DDevice9" = "IDirect3DDevice9 *"
|
||||
"IDirect3DSurface9" = "IDirect3DSurface9 *"
|
||||
"IDirect3DTexture9" = "IDirect3DTexture9 *"
|
||||
|
||||
# hack to force cbindgen to not generate option type for nullable ID3D11DeviceContext.
|
||||
"Option_ID3D11DeviceContext" = "ID3D11DeviceContext *"
|
||||
|
||||
# hack to get proper pointer indirection for COM pointers
|
||||
"ID3D12Device" = "ID3D12Device *"
|
||||
"ID3D12Resource" = "ID3D12Resource *"
|
||||
"ID3D12GraphicsCommandList" = "ID3D12GraphicsCommandList *"
|
||||
"ID3D12GraphicsCommandList" = "ID3D12GraphicsCommandList *"
|
||||
|
||||
"PMTLCommandQueue" = "id<MTLCommandQueue>"
|
||||
"PMTLCommandBuffer" = "id<MTLCommandBuffer>"
|
||||
"PMTLTexture" = "id<MTLTexture>"
|
|
@ -1,5 +1,6 @@
|
|||
//! Binding types for the librashader C API.
|
||||
use crate::error::LibrashaderError;
|
||||
use librashader::presets::context::{Orientation, VideoDriver, WildcardContext};
|
||||
use librashader::presets::ShaderPreset;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::NonNull;
|
||||
|
@ -7,31 +8,122 @@ use std::ptr::NonNull;
|
|||
/// A handle to a shader preset object.
|
||||
pub type libra_shader_preset_t = Option<NonNull<ShaderPreset>>;
|
||||
|
||||
/// A handle to a preset wildcard context object.
|
||||
pub type libra_preset_ctx_t = Option<NonNull<WildcardContext>>;
|
||||
|
||||
/// A handle to a librashader error object.
|
||||
pub type libra_error_t = Option<NonNull<LibrashaderError>>;
|
||||
|
||||
/// An enum representing orientation for use in preset contexts.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum LIBRA_PRESET_CTX_ORIENTATION {
|
||||
Vertical = 0,
|
||||
Horizontal,
|
||||
}
|
||||
impl From<LIBRA_PRESET_CTX_ORIENTATION> for Orientation {
|
||||
fn from(value: LIBRA_PRESET_CTX_ORIENTATION) -> Self {
|
||||
match value {
|
||||
LIBRA_PRESET_CTX_ORIENTATION::Vertical => Orientation::Vertical,
|
||||
LIBRA_PRESET_CTX_ORIENTATION::Horizontal => Orientation::Horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An enum representing graphics runtimes (video drivers) for use in preset contexts.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum LIBRA_PRESET_CTX_RUNTIME {
|
||||
None = 0,
|
||||
GlCore,
|
||||
Vulkan,
|
||||
D3D11,
|
||||
D3D12,
|
||||
Metal,
|
||||
}
|
||||
|
||||
impl From<LIBRA_PRESET_CTX_RUNTIME> for VideoDriver {
|
||||
fn from(value: LIBRA_PRESET_CTX_RUNTIME) -> Self {
|
||||
match value {
|
||||
LIBRA_PRESET_CTX_RUNTIME::None => VideoDriver::None,
|
||||
LIBRA_PRESET_CTX_RUNTIME::GlCore => VideoDriver::GlCore,
|
||||
LIBRA_PRESET_CTX_RUNTIME::Vulkan => VideoDriver::Vulkan,
|
||||
LIBRA_PRESET_CTX_RUNTIME::D3D11 => VideoDriver::Direct3D11,
|
||||
LIBRA_PRESET_CTX_RUNTIME::D3D12 => VideoDriver::Direct3D12,
|
||||
LIBRA_PRESET_CTX_RUNTIME::Metal => VideoDriver::Metal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-opengl")]
|
||||
use librashader::runtime::gl::FilterChain as FilterChainGL;
|
||||
|
||||
/// A handle to a OpenGL filter chain.
|
||||
#[cfg(feature = "runtime-opengl")]
|
||||
#[doc(cfg(feature = "runtime-opengl"))]
|
||||
pub type libra_gl_filter_chain_t = Option<NonNull<librashader::runtime::gl::capi::FilterChainGL>>;
|
||||
pub type libra_gl_filter_chain_t = Option<NonNull<FilterChainGL>>;
|
||||
|
||||
/// A handle to a Direct3D 11 filter chain.
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d11")
|
||||
))]
|
||||
use librashader::runtime::d3d11::FilterChain as FilterChainD3D11;
|
||||
|
||||
/// A handle to a Direct3D 11 filter chain.
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
|
||||
pub type libra_d3d11_filter_chain_t =
|
||||
Option<NonNull<librashader::runtime::d3d11::capi::FilterChainD3D11>>;
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d11")
|
||||
))]
|
||||
pub type libra_d3d11_filter_chain_t = Option<NonNull<FilterChainD3D11>>;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d12")
|
||||
))]
|
||||
use librashader::runtime::d3d12::FilterChain as FilterChainD3D12;
|
||||
/// A handle to a Direct3D 12 filter chain.
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
|
||||
pub type libra_d3d12_filter_chain_t =
|
||||
Option<NonNull<librashader::runtime::d3d12::capi::FilterChainD3D12>>;
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d12")
|
||||
))]
|
||||
pub type libra_d3d12_filter_chain_t = Option<NonNull<FilterChainD3D12>>;
|
||||
|
||||
/// A handle to a Direct3D 9 filter chain.
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d9")
|
||||
))]
|
||||
use librashader::runtime::d3d9::FilterChain as FilterChainD3D9;
|
||||
|
||||
/// A handle to a Direct3D 11 filter chain.
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d9")
|
||||
))]
|
||||
pub type libra_d3d9_filter_chain_t = Option<NonNull<FilterChainD3D9>>;
|
||||
|
||||
#[cfg(feature = "runtime-vulkan")]
|
||||
use librashader::runtime::vk::FilterChain as FilterChainVulkan;
|
||||
/// A handle to a Vulkan filter chain.
|
||||
#[cfg(feature = "runtime-vulkan")]
|
||||
#[doc(cfg(feature = "runtime-vulkan"))]
|
||||
pub type libra_vk_filter_chain_t =
|
||||
Option<NonNull<librashader::runtime::vk::capi::FilterChainVulkan>>;
|
||||
pub type libra_vk_filter_chain_t = Option<NonNull<FilterChainVulkan>>;
|
||||
|
||||
#[cfg(all(target_os = "macos", feature = "runtime-metal"))]
|
||||
use librashader::runtime::mtl::FilterChain as FilterChainMetal;
|
||||
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(
|
||||
target_vendor = "apple",
|
||||
feature = "runtime-metal",
|
||||
feature = "__cbindgen_internal_objc"
|
||||
)
|
||||
))]
|
||||
pub type libra_mtl_filter_chain_t = Option<NonNull<FilterChainMetal>>;
|
||||
|
||||
/// Defines the output viewport for a rendered frame.
|
||||
#[repr(C)]
|
||||
|
@ -87,3 +179,39 @@ macro_rules! config_struct {
|
|||
pub(crate) use config_set_field;
|
||||
pub(crate) use config_struct;
|
||||
pub(crate) use config_version_set;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deny(deprecated)]
|
||||
#[deprecated = "Forward declarations for cbindgen, do not use."]
|
||||
mod __cbindgen_opaque_forward_declarations {
|
||||
macro_rules! typedef_struct {
|
||||
($($(#[$($attrss:tt)*])* $name:ident;)*) => {
|
||||
$($(#[$($attrss)*])*
|
||||
#[allow(unused)]
|
||||
#[doc(hidden)]
|
||||
#[deny(deprecated)]
|
||||
#[deprecated]
|
||||
pub struct $name;
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
typedef_struct! {
|
||||
/// Opaque struct for a preset context.
|
||||
WildcardContext;
|
||||
/// Opaque struct for a shader preset.
|
||||
ShaderPreset;
|
||||
/// Opaque struct for an OpenGL filter chain.
|
||||
FilterChainGL;
|
||||
/// Opaque struct for a Direct3D 11 filter chain.
|
||||
FilterChainD3D11;
|
||||
/// Opaque struct for a Direct3D 12 filter chain.
|
||||
FilterChainD3D12;
|
||||
/// Opaque struct for a Direct3D 9 filter chain.
|
||||
FilterChainD3D9;
|
||||
/// Opaque struct for a Vulkan filter chain.
|
||||
FilterChainVulkan;
|
||||
/// Opaque struct for a Metal filter chain.
|
||||
FilterChainMetal;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::mem::MaybeUninit;
|
|||
use std::ptr::NonNull;
|
||||
use thiserror::Error;
|
||||
|
||||
/// The error type for librashader.
|
||||
/// The error type for librashader C API.
|
||||
#[non_exhaustive]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LibrashaderError {
|
||||
|
@ -37,10 +37,18 @@ pub enum LibrashaderError {
|
|||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
|
||||
#[error("There was an error in the D3D12 filter chain.")]
|
||||
D3D12FilterError(#[from] librashader::runtime::d3d12::error::FilterChainError),
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d9"))]
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
|
||||
#[error("There was an error in the D3D9 filter chain.")]
|
||||
D3D9FilterError(#[from] librashader::runtime::d3d9::error::FilterChainError),
|
||||
#[cfg(feature = "runtime-vulkan")]
|
||||
#[doc(cfg(feature = "runtime-vulkan"))]
|
||||
#[error("There was an error in the Vulkan filter chain.")]
|
||||
VulkanFilterError(#[from] librashader::runtime::vk::error::FilterChainError),
|
||||
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
|
||||
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
|
||||
#[error("There was an error in the D3D12 filter chain.")]
|
||||
MetalFilterError(#[from] librashader::runtime::mtl::error::FilterChainError),
|
||||
}
|
||||
|
||||
/// Error codes for librashader error types.
|
||||
|
@ -67,7 +75,7 @@ pub type PFN_libra_error_errno = extern "C" fn(error: libra_error_t) -> LIBRA_ER
|
|||
/// - `error` must be valid and initialized.
|
||||
pub unsafe extern "C" fn libra_error_errno(error: libra_error_t) -> LIBRA_ERRNO {
|
||||
let Some(error) = error else {
|
||||
return LIBRA_ERRNO::UNKNOWN_ERROR
|
||||
return LIBRA_ERRNO::UNKNOWN_ERROR;
|
||||
};
|
||||
|
||||
unsafe { error.as_ref().get_code() }
|
||||
|
@ -82,9 +90,7 @@ pub type PFN_libra_error_print = extern "C" fn(error: libra_error_t) -> i32;
|
|||
/// ## Safety
|
||||
/// - `error` must be a valid and initialized instance of `libra_error_t`.
|
||||
pub unsafe extern "C" fn libra_error_print(error: libra_error_t) -> i32 {
|
||||
let Some(error) = error else {
|
||||
return 1
|
||||
};
|
||||
let Some(error) = error else { return 1 };
|
||||
unsafe {
|
||||
let error = error.as_ref();
|
||||
println!("{error:?}: {error}");
|
||||
|
@ -130,9 +136,7 @@ pub unsafe extern "C" fn libra_error_write(
|
|||
error: libra_error_t,
|
||||
out: *mut MaybeUninit<*mut c_char>,
|
||||
) -> i32 {
|
||||
let Some(error) = error else {
|
||||
return 1
|
||||
};
|
||||
let Some(error) = error else { return 1 };
|
||||
if out.is_null() {
|
||||
return 1;
|
||||
}
|
||||
|
@ -140,7 +144,7 @@ pub unsafe extern "C" fn libra_error_write(
|
|||
unsafe {
|
||||
let error = error.as_ref();
|
||||
let Ok(cstring) = CString::new(format!("{error:?}: {error}")) else {
|
||||
return 1
|
||||
return 1;
|
||||
};
|
||||
|
||||
out.write(MaybeUninit::new(cstring.into_raw()))
|
||||
|
@ -189,8 +193,12 @@ impl LibrashaderError {
|
|||
LibrashaderError::D3D11FilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
|
||||
LibrashaderError::D3D12FilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d9"))]
|
||||
LibrashaderError::D3D9FilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
|
||||
#[cfg(feature = "runtime-vulkan")]
|
||||
LibrashaderError::VulkanFilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
|
||||
#[cfg(all(target_vendor = "apple", feature = "runtime-metal"))]
|
||||
LibrashaderError::MetalFilterError(_) => LIBRA_ERRNO::RUNTIME_ERROR,
|
||||
}
|
||||
}
|
||||
pub(crate) const fn ok() -> libra_error_t {
|
||||
|
@ -204,12 +212,12 @@ impl LibrashaderError {
|
|||
|
||||
macro_rules! assert_non_null {
|
||||
($value:ident) => {
|
||||
if $value.is_null() || !$value.is_aligned() {
|
||||
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
|
||||
return $crate::error::LibrashaderError::InvalidParameter(stringify!($value)).export();
|
||||
}
|
||||
};
|
||||
(noexport $value:ident) => {
|
||||
if $value.is_null() || !$value.is_aligned() {
|
||||
if $value.is_null() || !$crate::ffi::ptr_is_aligned($value) {
|
||||
return Err($crate::error::LibrashaderError::InvalidParameter(
|
||||
stringify!($value),
|
||||
));
|
||||
|
|
|
@ -1,6 +1,31 @@
|
|||
macro_rules! ffi_body {
|
||||
(nopanic $body:block) => {
|
||||
{
|
||||
let result: Result<(), $crate::error::LibrashaderError> = try {
|
||||
$body
|
||||
};
|
||||
|
||||
let Err(e) = result else {
|
||||
return $crate::error::LibrashaderError::ok()
|
||||
};
|
||||
e.export()
|
||||
}
|
||||
};
|
||||
($body:block) => {
|
||||
{
|
||||
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
|
||||
$crate::ffi::ffi_body!(nopanic $body)
|
||||
}));
|
||||
|
||||
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
|
||||
}
|
||||
};
|
||||
(nopanic |$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
|
||||
{
|
||||
$($crate::error::assert_non_null!($ref_capture);)*
|
||||
$(let $ref_capture = unsafe { &*$ref_capture };)*
|
||||
$($crate::error::assert_non_null!($mut_capture);)*
|
||||
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
|
||||
let result: Result<(), $crate::error::LibrashaderError> = try {
|
||||
$body
|
||||
};
|
||||
|
@ -13,8 +38,15 @@ macro_rules! ffi_body {
|
|||
};
|
||||
(|$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
|
||||
{
|
||||
$($crate::error::assert_non_null!($ref_capture);)*
|
||||
$(let $ref_capture = unsafe { &*$ref_capture };)*
|
||||
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
|
||||
$crate::ffi::ffi_body!(nopanic |$($ref_capture),*|; mut |$($mut_capture),*| $body)
|
||||
}));
|
||||
|
||||
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
|
||||
}
|
||||
};
|
||||
(nopanic mut |$($mut_capture:ident),*| $body:block) => {
|
||||
{
|
||||
$($crate::error::assert_non_null!($mut_capture);)*
|
||||
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
|
||||
let result: Result<(), $crate::error::LibrashaderError> = try {
|
||||
|
@ -28,20 +60,15 @@ macro_rules! ffi_body {
|
|||
}
|
||||
};
|
||||
(mut |$($mut_capture:ident),*| $body:block) => {
|
||||
{
|
||||
$($crate::error::assert_non_null!($mut_capture);)*
|
||||
$(let $mut_capture = unsafe { &mut *$mut_capture };)*
|
||||
let result: Result<(), $crate::error::LibrashaderError> = try {
|
||||
$body
|
||||
};
|
||||
{
|
||||
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
|
||||
$crate::ffi::ffi_body!(nopanic mut |$($mut_capture),*| $body)
|
||||
}));
|
||||
|
||||
let Err(e) = result else {
|
||||
return $crate::error::LibrashaderError::ok()
|
||||
};
|
||||
e.export()
|
||||
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
|
||||
}
|
||||
};
|
||||
(|$($ref_capture:ident),*| $body:block) => {
|
||||
(nopanic |$($ref_capture:ident),*| $body:block) => {
|
||||
{
|
||||
$($crate::error::assert_non_null!($ref_capture);)*
|
||||
$(let $ref_capture = unsafe { &*$ref_capture };)*
|
||||
|
@ -54,24 +81,20 @@ macro_rules! ffi_body {
|
|||
};
|
||||
e.export()
|
||||
}
|
||||
}
|
||||
};
|
||||
(|$($ref_capture:ident),*| $body:block) => {
|
||||
{
|
||||
let result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
|
||||
$crate::ffi::ffi_body!(nopanic |$($ref_capture),*| $body)
|
||||
}));
|
||||
|
||||
result.unwrap_or_else(|e| $crate::error::LibrashaderError::UnknownError(e).export())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! extern_fn {
|
||||
($(#[$($attrss:tt)*])* fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) $body:block) => {
|
||||
::paste::paste! {
|
||||
/// Function pointer definition for
|
||||
#[doc = ::std::stringify!($func_name)]
|
||||
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
$(#[$($attrss)*])*
|
||||
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
|
||||
$crate::ffi::ffi_body!($body)
|
||||
}
|
||||
};
|
||||
|
||||
// raw doesn't wrap in ffi_body
|
||||
($(#[$($attrss:tt)*])* raw fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) $body:block) => {
|
||||
::paste::paste! {
|
||||
/// Function pointer definition for
|
||||
|
@ -86,6 +109,21 @@ macro_rules! extern_fn {
|
|||
}
|
||||
};
|
||||
|
||||
// ffi_body but panic-safe
|
||||
($(#[$($attrss:tt)*])* fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) $body:block) => {
|
||||
::paste::paste! {
|
||||
/// Function pointer definition for
|
||||
#[doc = ::std::stringify!($func_name)]
|
||||
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
$(#[$($attrss)*])*
|
||||
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
|
||||
$crate::ffi::ffi_body!($body)
|
||||
}
|
||||
};
|
||||
|
||||
($(#[$($attrss:tt)*])* fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) |$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
|
||||
::paste::paste! {
|
||||
/// Function pointer definition for
|
||||
|
@ -126,7 +164,83 @@ macro_rules! extern_fn {
|
|||
$crate::ffi::ffi_body!(|$($ref_capture),*| $body)
|
||||
}
|
||||
};
|
||||
|
||||
// nopanic variants that are UB if panics
|
||||
($(#[$($attrss:tt)*])* nopanic fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) $body:block) => {
|
||||
::paste::paste! {
|
||||
/// Function pointer definition for
|
||||
#[doc = ::std::stringify!($func_name)]
|
||||
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
$(#[$($attrss)*])*
|
||||
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
|
||||
$crate::ffi::ffi_body!(nopanic $body)
|
||||
}
|
||||
};
|
||||
|
||||
($(#[$($attrss:tt)*])* nopanic fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) |$($ref_capture:ident),*|; mut |$($mut_capture:ident),*| $body:block) => {
|
||||
::paste::paste! {
|
||||
/// Function pointer definition for
|
||||
#[doc = ::std::stringify!($func_name)]
|
||||
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
$(#[$($attrss)*])*
|
||||
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
|
||||
$crate::ffi::ffi_body!(nopanic |$($ref_capture),*|; mut |$($mut_capture),*| $body)
|
||||
}
|
||||
};
|
||||
|
||||
($(#[$($attrss:tt)*])* nopanic fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) mut |$($mut_capture:ident),*| $body:block) => {
|
||||
::paste::paste! {
|
||||
/// Function pointer definition for
|
||||
#[doc = ::std::stringify!($func_name)]
|
||||
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
$(#[$($attrss)*])*
|
||||
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
|
||||
$crate::ffi::ffi_body!(nopanic mut |$($mut_capture),*| $body)
|
||||
}
|
||||
};
|
||||
($(#[$($attrss:tt)*])* nopanic fn $func_name:ident ($($arg_name:ident : $arg_ty:ty),* $(,)?) |$($ref_capture:ident),*| $body:block) => {
|
||||
::paste::paste! {
|
||||
/// Function pointer definition for
|
||||
#[doc = ::std::stringify!($func_name)]
|
||||
pub type [<PFN_ $func_name>] = unsafe extern "C" fn($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
$(#[$($attrss)*])*
|
||||
pub unsafe extern "C" fn $func_name($($arg_name: $arg_ty,)*) -> $crate::ctypes::libra_error_t {
|
||||
$crate::ffi::ffi_body!(nopanic |$($ref_capture),*| $body)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn boxed_slice_into_raw_parts<T>(vec: Box<[T]>) -> (*mut T, usize) {
|
||||
let mut me = ManuallyDrop::new(vec);
|
||||
(me.as_mut_ptr(), me.len())
|
||||
}
|
||||
|
||||
pub unsafe fn boxed_slice_from_raw_parts<T>(ptr: *mut T, len: usize) -> Box<[T]> {
|
||||
unsafe { Box::from_raw(std::slice::from_raw_parts_mut(ptr, len)) }
|
||||
}
|
||||
|
||||
#[allow(unstable_name_collisions)]
|
||||
pub fn ptr_is_aligned<T: Sized>(ptr: *const T) -> bool {
|
||||
use sptr::Strict;
|
||||
let align = std::mem::align_of::<T>();
|
||||
if !align.is_power_of_two() {
|
||||
panic!("is_aligned_to: align is not a power-of-two");
|
||||
}
|
||||
ptr.addr() & (align - 1) == 0
|
||||
}
|
||||
|
||||
pub(crate) use extern_fn;
|
||||
pub(crate) use ffi_body;
|
||||
use std::mem::ManuallyDrop;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//! possible by linking against `librashader.h` as well as any static libraries used by `librashader`.
|
||||
//!
|
||||
//! ## Usage
|
||||
//! ⚠ Rust consumers use [librashader](https://docs.rs/librashader/) directly instead. ⚠
|
||||
//! ⚠ Rust consumers should use [librashader](https://docs.rs/librashader/) directly instead. ⚠
|
||||
//!
|
||||
//! The librashader C API is designed to be easy to use and safe. Most objects are only accessible behind an opaque pointer.
|
||||
//! Every allocated object can be freed with a corresponding `free` function **for that specific object type**.
|
||||
|
@ -54,32 +54,34 @@
|
|||
//!
|
||||
//! ## Thread safety
|
||||
//!
|
||||
//! In general, it is **safe** to create a filter chain instance from a different thread, but drawing filter passes must be
|
||||
//! synchronized externally. The exception to filter chain creation are in OpenGL, where creating the filter chain instance
|
||||
//! is safe **if and only if** the thread local OpenGL context is initialized to the same context as the drawing thread, and
|
||||
//! in Direct3D 11, where filter chain creation is unsafe if the `ID3D11Device` was created with
|
||||
//! `D3D11_CREATE_DEVICE_SINGLETHREADED`.
|
||||
//! Except for the metal runtime, it is in general, **safe** to create a filter chain instance from a different thread,
|
||||
//! but drawing filter passes must be synchronized externally. The exception to filter chain creation are in OpenGL,
|
||||
//! where creating the filter chain instance is safe **if and only if** the thread local OpenGL context is initialized
|
||||
//! to the same context as the drawing thread, and in Direct3D 11, where filter chain creation is unsafe
|
||||
//! if the `ID3D11Device` was created with `D3D11_CREATE_DEVICE_SINGLETHREADED`. Metal is entirely thread unsafe.
|
||||
//!
|
||||
//! You must ensure that only thread has access to a created filter pass **before** you call `*_frame`. `*_frame` may only be
|
||||
//! called from one thread at a time.
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
#![feature(try_blocks)]
|
||||
#![feature(pointer_is_aligned)]
|
||||
#![feature(vec_into_raw_parts)]
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
#![deny(deprecated)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod ctypes;
|
||||
pub mod error;
|
||||
mod ffi;
|
||||
pub mod presets;
|
||||
|
||||
#[cfg(feature = "reflect")]
|
||||
#[cfg(feature = "reflect-unstable")]
|
||||
#[doc(hidden)]
|
||||
pub mod reflect;
|
||||
|
||||
pub mod runtime;
|
||||
pub mod version;
|
||||
pub mod wildcard;
|
||||
|
||||
pub use version::LIBRASHADER_ABI_VERSION;
|
||||
pub use version::LIBRASHADER_API_VERSION;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! librashader preset C API (`libra_preset_*`).
|
||||
use crate::ctypes::libra_shader_preset_t;
|
||||
use crate::ctypes::{libra_preset_ctx_t, libra_shader_preset_t};
|
||||
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
|
||||
use crate::ffi::extern_fn;
|
||||
use librashader::presets::ShaderPreset;
|
||||
|
@ -55,7 +55,6 @@ extern_fn! {
|
|||
|
||||
let filename = unsafe { CStr::from_ptr(filename) };
|
||||
let filename = filename.to_str()?;
|
||||
println!("loading {filename}");
|
||||
|
||||
let preset = ShaderPreset::try_parse(filename)?;
|
||||
unsafe {
|
||||
|
@ -66,6 +65,49 @@ extern_fn! {
|
|||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Load a preset with the given wildcard context.
|
||||
///
|
||||
/// The wildcard context is immediately invalidated and must be recreated after
|
||||
/// the preset is created.
|
||||
///
|
||||
/// Path information variables `PRESET_DIR` and `PRESET` will automatically be filled in.
|
||||
/// ## Safety
|
||||
/// - `filename` must be either null or a valid, aligned pointer to a string path to the shader preset.
|
||||
/// - `context` must be either null or a valid, aligned pointer to a initialized `libra_preset_ctx_t`.
|
||||
/// - `context` is invalidated after this function returns.
|
||||
/// - `out` must be either null, or an aligned pointer to an uninitialized or invalid `libra_shader_preset_t`.
|
||||
/// ## Returns
|
||||
/// - If any parameters are null, `out` is unchanged, and this function returns `LIBRA_ERR_INVALID_PARAMETER`.
|
||||
fn libra_preset_create_with_context(
|
||||
filename: *const c_char,
|
||||
context: *mut libra_preset_ctx_t,
|
||||
out: *mut MaybeUninit<libra_shader_preset_t>
|
||||
) {
|
||||
assert_non_null!(filename);
|
||||
assert_non_null!(context);
|
||||
assert_non_null!(out);
|
||||
|
||||
let filename = unsafe { CStr::from_ptr(filename) };
|
||||
let filename = filename.to_str()?;
|
||||
|
||||
let mut context = unsafe {
|
||||
let context_ptr = &mut *context;
|
||||
let context = context_ptr.take();
|
||||
Box::from_raw(context.unwrap().as_ptr())
|
||||
};
|
||||
|
||||
context.add_path_defaults(filename);
|
||||
|
||||
let preset = ShaderPreset::try_parse_with_context(filename, *context)?;
|
||||
unsafe {
|
||||
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
|
||||
preset,
|
||||
)))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Free the preset.
|
||||
///
|
||||
|
@ -73,7 +115,7 @@ extern_fn! {
|
|||
/// null.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `preset` must be a valid and aligned pointer to a shader preset.
|
||||
/// - `preset` must be a valid and aligned pointer to a `libra_shader_preset_t`.
|
||||
fn libra_preset_free(preset: *mut libra_shader_preset_t) {
|
||||
assert_non_null!(preset);
|
||||
unsafe {
|
||||
|
@ -88,7 +130,7 @@ extern_fn! {
|
|||
/// Set the value of the parameter in the preset.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
|
||||
/// - `preset` must be null or a valid and aligned pointer to a `libra_shader_preset_t`.
|
||||
/// - `name` must be null or a valid and aligned pointer to a string.
|
||||
fn libra_preset_set_param(
|
||||
preset: *mut libra_shader_preset_t,
|
||||
|
@ -135,7 +177,7 @@ extern_fn! {
|
|||
/// Pretty print the shader preset.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
|
||||
/// - `preset` must be null or a valid and aligned pointer to a `libra_shader_preset_t`.
|
||||
fn libra_preset_print(preset: *mut libra_shader_preset_t) |preset| {
|
||||
assert_some_ptr!(preset);
|
||||
println!("{preset:#?}");
|
||||
|
@ -146,7 +188,7 @@ extern_fn! {
|
|||
/// Get a list of runtime parameters.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `preset` must be null or a valid and aligned pointer to a shader preset.
|
||||
/// - `preset` must be null or a valid and aligned pointer to a `libra_shader_preset_t`.
|
||||
/// - `out` must be an aligned pointer to a `libra_preset_parameter_list_t`.
|
||||
/// - The output struct should be treated as immutable. Mutating any struct fields
|
||||
/// in the returned struct may at best cause memory leaks, and at worse
|
||||
|
@ -176,12 +218,15 @@ extern_fn! {
|
|||
step: param.step
|
||||
})
|
||||
}
|
||||
let (parts, len, cap) = values.into_raw_parts();
|
||||
|
||||
let values = values.into_boxed_slice();
|
||||
let (parts, len) = crate::ffi::boxed_slice_into_raw_parts(values);
|
||||
|
||||
unsafe {
|
||||
out.write(MaybeUninit::new(libra_preset_param_list_t {
|
||||
parameters: parts,
|
||||
length: len as u64,
|
||||
_internal_alloc: cap as u64,
|
||||
_internal_alloc: 0,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -209,9 +254,9 @@ extern_fn! {
|
|||
/// in undefined behaviour.
|
||||
fn libra_preset_free_runtime_params(preset: libra_preset_param_list_t) {
|
||||
unsafe {
|
||||
let values = Vec::from_raw_parts(preset.parameters.cast_mut(),
|
||||
preset.length as usize,
|
||||
preset._internal_alloc as usize);
|
||||
let values =
|
||||
crate::ffi::boxed_slice_from_raw_parts(preset.parameters.cast_mut(),
|
||||
preset.length as usize).into_vec();
|
||||
|
||||
for value in values {
|
||||
let name = CString::from_raw(value.name.cast_mut());
|
||||
|
|
|
@ -6,8 +6,8 @@ use librashader::reflect::targets::SPIRV;
|
|||
use librashader::reflect::{CompileShader, ReflectShader, ShaderCompilerOutput, ShaderReflection};
|
||||
use librashader::{FilterMode, WrapMode};
|
||||
|
||||
use librashader::reflect::cross::GlslangCompilation;
|
||||
use librashader::reflect::helper::image::{Image, UVDirection, RGBA8};
|
||||
use librashader::reflect::SpirvCompilation;
|
||||
|
||||
pub(crate) struct LookupTexture {
|
||||
wrap_mode: WrapMode,
|
||||
|
@ -38,8 +38,9 @@ impl FilterReflection {
|
|||
let (passes, textures) = (preset.shaders, preset.textures);
|
||||
|
||||
let (passes, semantics) = librashader::reflect::helper::compile_preset_passes::<
|
||||
Glslang,
|
||||
SPIRV,
|
||||
GlslangCompilation,
|
||||
SpirvCompilation,
|
||||
error::LibrashaderError,
|
||||
>(passes, &textures)?;
|
||||
|
||||
|
|
|
@ -3,46 +3,31 @@ use crate::ctypes::{
|
|||
};
|
||||
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
|
||||
use crate::ffi::extern_fn;
|
||||
use librashader::runtime::d3d11::{D3D11InputView, D3D11OutputView};
|
||||
use librashader::runtime::d3d11::{FilterChain, FilterChainOptions, FrameOptions};
|
||||
use std::ffi::c_char;
|
||||
use std::ffi::CStr;
|
||||
use std::mem::{ManuallyDrop, MaybeUninit};
|
||||
use std::ops::Deref;
|
||||
use std::ptr::NonNull;
|
||||
use std::slice;
|
||||
use windows::Win32::Graphics::Direct3D11::{
|
||||
ID3D11Device, ID3D11DeviceContext, ID3D11RenderTargetView, ID3D11ShaderResourceView,
|
||||
};
|
||||
|
||||
use librashader::runtime::d3d11::capi::options::FilterChainOptionsD3D11;
|
||||
use librashader::runtime::d3d11::capi::options::FrameOptionsD3D11;
|
||||
|
||||
use crate::LIBRASHADER_API_VERSION;
|
||||
use librashader::runtime::{FilterChainParameters, Size, Viewport};
|
||||
use librashader::runtime::{FilterChainParameters, Viewport};
|
||||
|
||||
/// Direct3D 11 parameters for the source image.
|
||||
#[repr(C)]
|
||||
pub struct libra_source_image_d3d11_t {
|
||||
/// A shader resource view into the source image
|
||||
pub handle: ManuallyDrop<ID3D11ShaderResourceView>,
|
||||
/// The width of the source image.
|
||||
/// This is currently ignored.
|
||||
pub width: u32,
|
||||
/// The height of the source image.
|
||||
/// This is currently ignored.
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl TryFrom<libra_source_image_d3d11_t> for D3D11InputView {
|
||||
type Error = LibrashaderError;
|
||||
|
||||
fn try_from(value: libra_source_image_d3d11_t) -> Result<Self, Self::Error> {
|
||||
let handle = value.handle.clone();
|
||||
|
||||
Ok(D3D11InputView {
|
||||
handle: ManuallyDrop::into_inner(handle),
|
||||
size: Size::new(value.width, value.height),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for Direct3D 11 filter chain creation.
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
|
@ -58,7 +43,7 @@ pub struct filter_chain_d3d11_opt_t {
|
|||
}
|
||||
|
||||
config_struct! {
|
||||
impl FilterChainOptionsD3D11 => filter_chain_d3d11_opt_t {
|
||||
impl FilterChainOptions => filter_chain_d3d11_opt_t {
|
||||
0 => [force_no_mipmaps, disable_cache];
|
||||
}
|
||||
}
|
||||
|
@ -74,11 +59,18 @@ pub struct frame_d3d11_opt_t {
|
|||
/// The direction of rendering.
|
||||
/// -1 indicates that the frames are played in reverse order.
|
||||
pub frame_direction: i32,
|
||||
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
|
||||
pub rotation: u32,
|
||||
/// The total number of subframes ran. Default is 1.
|
||||
pub total_subframes: u32,
|
||||
/// The current sub frame. Default is 1.
|
||||
pub current_subframe: u32,
|
||||
}
|
||||
|
||||
config_struct! {
|
||||
impl FrameOptionsD3D11 => frame_d3d11_opt_t {
|
||||
impl FrameOptions => frame_d3d11_opt_t {
|
||||
0 => [clear_history, frame_direction];
|
||||
1 => [rotation, total_subframes, current_subframe]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +106,7 @@ extern_fn! {
|
|||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
unsafe {
|
||||
let chain = librashader::runtime::d3d11::capi::FilterChainD3D11::load_from_preset(
|
||||
let chain = FilterChain::load_from_preset(
|
||||
*preset,
|
||||
&device,
|
||||
options.as_ref(),
|
||||
|
@ -174,7 +166,7 @@ extern_fn! {
|
|||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
unsafe {
|
||||
let chain = librashader::runtime::d3d11::capi::FilterChainD3D11::load_from_preset_deferred(
|
||||
let chain = FilterChain::load_from_preset_deferred(
|
||||
*preset,
|
||||
&device,
|
||||
&device_context,
|
||||
|
@ -207,7 +199,7 @@ extern_fn! {
|
|||
/// function will return an error.
|
||||
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
|
||||
/// values for the model view projection matrix.
|
||||
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_gl_opt_t`
|
||||
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d11_opt_t`
|
||||
/// struct.
|
||||
/// - `out` must not be null.
|
||||
/// - `image.handle` must not be null.
|
||||
|
@ -216,7 +208,7 @@ extern_fn! {
|
|||
/// the filter chain was created with.
|
||||
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
|
||||
/// thread at a time may call this function.
|
||||
fn libra_d3d11_filter_chain_frame(
|
||||
nopanic fn libra_d3d11_filter_chain_frame(
|
||||
chain: *mut libra_d3d11_filter_chain_t,
|
||||
// cbindgen can't discover that ID3D11DeviceContext has the niche optimization
|
||||
// so ManuallyDrop<Option<ID3D11DeviceContext>> doesn't generate correct bindings.
|
||||
|
@ -245,19 +237,14 @@ extern_fn! {
|
|||
let viewport = Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output: D3D11OutputView {
|
||||
size: Size::new(viewport.width, viewport.height),
|
||||
handle: ManuallyDrop::into_inner(out.clone()),
|
||||
},
|
||||
output: ManuallyDrop::into_inner(out.clone()),
|
||||
mvp,
|
||||
};
|
||||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
|
||||
let image = image.try_into()?;
|
||||
|
||||
unsafe {
|
||||
chain.frame(device_context.as_deref(), image, &viewport, frame_count, options.as_ref())?;
|
||||
chain.frame(device_context.as_deref(), image.handle.deref(), &viewport, frame_count, options.as_ref())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,10 @@ use windows::Win32::Graphics::Direct3D12::{
|
|||
};
|
||||
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT;
|
||||
|
||||
use librashader::runtime::d3d12::capi::options::FilterChainOptionsD3D12;
|
||||
use librashader::runtime::d3d12::capi::options::FrameOptionsD3D12;
|
||||
|
||||
use crate::LIBRASHADER_API_VERSION;
|
||||
use librashader::runtime::d3d12::{D3D12InputImage, D3D12OutputView};
|
||||
use librashader::runtime::d3d12::{
|
||||
D3D12InputImage, D3D12OutputView, FilterChain, FilterChainOptions, FrameOptions,
|
||||
};
|
||||
use librashader::runtime::{FilterChainParameters, Size, Viewport};
|
||||
|
||||
/// Direct3D 12 parameters for the source image.
|
||||
|
@ -27,11 +26,11 @@ pub struct libra_source_image_d3d12_t {
|
|||
pub resource: ManuallyDrop<ID3D12Resource>,
|
||||
/// A CPU descriptor handle to a shader resource view of the image.
|
||||
pub descriptor: D3D12_CPU_DESCRIPTOR_HANDLE,
|
||||
/// The format of the image.
|
||||
/// This is currently ignored.
|
||||
pub format: DXGI_FORMAT,
|
||||
/// The width of the source image.
|
||||
/// This is currently ignored.
|
||||
pub width: u32,
|
||||
/// The height of the source image.
|
||||
/// This is currently ignored.
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
|
@ -55,11 +54,18 @@ pub struct frame_d3d12_opt_t {
|
|||
/// The direction of rendering.
|
||||
/// -1 indicates that the frames are played in reverse order.
|
||||
pub frame_direction: i32,
|
||||
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
|
||||
pub rotation: u32,
|
||||
/// The total number of subframes ran. Default is 1.
|
||||
pub total_subframes: u32,
|
||||
/// The current sub frame. Default is 1.
|
||||
pub current_subframe: u32,
|
||||
}
|
||||
|
||||
config_struct! {
|
||||
impl FrameOptionsD3D12 => frame_d3d12_opt_t {
|
||||
impl FrameOptions => frame_d3d12_opt_t {
|
||||
0 => [clear_history, frame_direction];
|
||||
1 => [rotation, total_subframes, current_subframe]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,7 +90,7 @@ pub struct filter_chain_d3d12_opt_t {
|
|||
}
|
||||
|
||||
config_struct! {
|
||||
impl FilterChainOptionsD3D12 => filter_chain_d3d12_opt_t {
|
||||
impl FilterChainOptions => filter_chain_d3d12_opt_t {
|
||||
0 => [force_hlsl_pipeline, force_no_mipmaps, disable_cache];
|
||||
}
|
||||
}
|
||||
|
@ -98,8 +104,6 @@ impl TryFrom<libra_source_image_d3d12_t> for D3D12InputImage {
|
|||
Ok(D3D12InputImage {
|
||||
resource: ManuallyDrop::into_inner(resource),
|
||||
descriptor: value.descriptor,
|
||||
size: Size::new(value.width, value.height),
|
||||
format: value.format,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +140,7 @@ extern_fn! {
|
|||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
unsafe {
|
||||
let chain = librashader::runtime::d3d12::capi::FilterChainD3D12::load_from_preset(
|
||||
let chain = FilterChain::load_from_preset(
|
||||
*preset,
|
||||
&device,
|
||||
options.as_ref(),
|
||||
|
@ -188,7 +192,7 @@ extern_fn! {
|
|||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
unsafe {
|
||||
let chain = librashader::runtime::d3d12::capi::FilterChainD3D12::load_from_preset_deferred(
|
||||
let chain = FilterChain::load_from_preset_deferred(
|
||||
*preset,
|
||||
&device,
|
||||
&command_list,
|
||||
|
@ -218,7 +222,7 @@ extern_fn! {
|
|||
/// function will return an error.
|
||||
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
|
||||
/// values for the model view projection matrix.
|
||||
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_gl_opt_t`
|
||||
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d12_opt_t`
|
||||
/// struct.
|
||||
/// - `out` must be a descriptor handle to a render target view.
|
||||
/// - `image.resource` must not be null.
|
||||
|
@ -226,7 +230,7 @@ extern_fn! {
|
|||
/// and must be associated with the `ID3D12Device` this filter chain was created with.
|
||||
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
|
||||
/// thread at a time may call this function.
|
||||
fn libra_d3d12_filter_chain_frame(
|
||||
nopanic fn libra_d3d12_filter_chain_frame(
|
||||
chain: *mut libra_d3d12_filter_chain_t,
|
||||
command_list: ManuallyDrop<ID3D12GraphicsCommandList>,
|
||||
frame_count: usize,
|
||||
|
|
259
librashader-capi/src/runtime/d3d9/filter_chain.rs
Normal file
259
librashader-capi/src/runtime/d3d9/filter_chain.rs
Normal file
|
@ -0,0 +1,259 @@
|
|||
use crate::ctypes::{
|
||||
config_struct, libra_d3d9_filter_chain_t, libra_shader_preset_t, libra_viewport_t, FromUninit,
|
||||
};
|
||||
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
|
||||
use crate::ffi::extern_fn;
|
||||
use librashader::runtime::d3d9::{FilterChain, FilterChainOptions, FrameOptions};
|
||||
use std::ffi::c_char;
|
||||
use std::ffi::CStr;
|
||||
use std::mem::{ManuallyDrop, MaybeUninit};
|
||||
use std::ptr::NonNull;
|
||||
use std::slice;
|
||||
use windows::Win32::Graphics::Direct3D9::{IDirect3DDevice9, IDirect3DSurface9, IDirect3DTexture9};
|
||||
|
||||
use crate::LIBRASHADER_API_VERSION;
|
||||
use librashader::runtime::{FilterChainParameters, Viewport};
|
||||
|
||||
/// Options for Direct3D 11 filter chain creation.
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct filter_chain_d3d9_opt_t {
|
||||
/// The librashader API version.
|
||||
pub version: LIBRASHADER_API_VERSION,
|
||||
/// Whether or not to explicitly disable mipmap
|
||||
/// generation regardless of shader preset settings.
|
||||
pub force_no_mipmaps: bool,
|
||||
/// Disable the shader object cache. Shaders will be
|
||||
/// recompiled rather than loaded from the cache.
|
||||
pub disable_cache: bool,
|
||||
}
|
||||
|
||||
config_struct! {
|
||||
impl FilterChainOptions => filter_chain_d3d9_opt_t {
|
||||
0 => [force_no_mipmaps, disable_cache];
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for each Direct3D 11 shader frame.
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct frame_d3d9_opt_t {
|
||||
/// The librashader API version.
|
||||
pub version: LIBRASHADER_API_VERSION,
|
||||
/// Whether or not to clear the history buffers.
|
||||
pub clear_history: bool,
|
||||
/// The direction of rendering.
|
||||
/// -1 indicates that the frames are played in reverse order.
|
||||
pub frame_direction: i32,
|
||||
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
|
||||
pub rotation: u32,
|
||||
/// The total number of subframes ran. Default is 1.
|
||||
pub total_subframes: u32,
|
||||
/// The current sub frame. Default is 1.
|
||||
pub current_subframe: u32,
|
||||
}
|
||||
|
||||
config_struct! {
|
||||
impl FrameOptions => frame_d3d9_opt_t {
|
||||
0 => [clear_history, frame_direction];
|
||||
1 => [rotation, total_subframes, current_subframe]
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Create the filter chain given the shader preset.
|
||||
///
|
||||
/// The shader preset is immediately invalidated and must be recreated after
|
||||
/// the filter chain is created.
|
||||
///
|
||||
/// ## Safety:
|
||||
/// - `preset` must be either null, or valid and aligned.
|
||||
/// - `options` must be either null, or valid and aligned.
|
||||
/// - `device` must not be null.
|
||||
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
|
||||
fn libra_d3d9_filter_chain_create(
|
||||
preset: *mut libra_shader_preset_t,
|
||||
device: ManuallyDrop<IDirect3DDevice9>,
|
||||
options: *const MaybeUninit<filter_chain_d3d9_opt_t>,
|
||||
out: *mut MaybeUninit<libra_d3d9_filter_chain_t>
|
||||
) {
|
||||
assert_non_null!(preset);
|
||||
let preset = unsafe {
|
||||
let preset_ptr = &mut *preset;
|
||||
let preset = preset_ptr.take();
|
||||
Box::from_raw(preset.unwrap().as_ptr())
|
||||
};
|
||||
|
||||
let options = if options.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { options.read() })
|
||||
};
|
||||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
unsafe {
|
||||
let chain = FilterChain::load_from_preset(
|
||||
*preset,
|
||||
&device,
|
||||
options.as_ref(),
|
||||
)?;
|
||||
|
||||
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
|
||||
chain,
|
||||
)))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Draw a frame with the given parameters for the given filter chain.
|
||||
///
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
|
||||
/// function will return an error.
|
||||
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
|
||||
/// values for the model view projection matrix.
|
||||
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_d3d9_opt_t`
|
||||
/// struct.
|
||||
/// - `out` must not be null.
|
||||
/// - `image` must not be null.
|
||||
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
|
||||
/// thread at a time may call this function.
|
||||
nopanic fn libra_d3d9_filter_chain_frame(
|
||||
chain: *mut libra_d3d9_filter_chain_t,
|
||||
frame_count: usize,
|
||||
image: ManuallyDrop<IDirect3DTexture9>,
|
||||
viewport: libra_viewport_t,
|
||||
out: ManuallyDrop<IDirect3DSurface9>,
|
||||
mvp: *const f32,
|
||||
options: *const MaybeUninit<frame_d3d9_opt_t>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
|
||||
let mvp = if mvp.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(<&[f32; 16]>::try_from(unsafe { slice::from_raw_parts(mvp, 16) }).unwrap())
|
||||
};
|
||||
|
||||
let options = if options.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { options.read() })
|
||||
};
|
||||
|
||||
let viewport = Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output: ManuallyDrop::into_inner(out.clone()),
|
||||
mvp,
|
||||
};
|
||||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
|
||||
|
||||
unsafe {
|
||||
chain.frame(ManuallyDrop::into_inner(image.clone()), &viewport, frame_count, options.as_ref())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Sets a parameter for the filter chain.
|
||||
///
|
||||
/// If the parameter does not exist, returns an error.
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
|
||||
/// - `param_name` must be either null or a null terminated string.
|
||||
fn libra_d3d9_filter_chain_set_param(
|
||||
chain: *mut libra_d3d9_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
value: f32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
assert_non_null!(param_name);
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(param_name);
|
||||
let name = name.to_str()?;
|
||||
|
||||
if chain.set_parameter(name, value).is_none() {
|
||||
return LibrashaderError::UnknownShaderParameter(param_name).export()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Gets a parameter for the filter chain.
|
||||
///
|
||||
/// If the parameter does not exist, returns an error.
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
|
||||
/// - `param_name` must be either null or a null terminated string.
|
||||
fn libra_d3d9_filter_chain_get_param(
|
||||
chain: *mut libra_d3d9_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
out: *mut MaybeUninit<f32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
assert_non_null!(param_name);
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(param_name);
|
||||
let name = name.to_str()?;
|
||||
|
||||
let Some(value) = chain.get_parameter(name) else {
|
||||
return LibrashaderError::UnknownShaderParameter(param_name).export()
|
||||
};
|
||||
|
||||
out.write(MaybeUninit::new(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Sets the number of active passes for this chain.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
|
||||
fn libra_d3d9_filter_chain_set_active_pass_count(
|
||||
chain: *mut libra_d3d9_filter_chain_t,
|
||||
value: u32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
chain.set_enabled_pass_count(value as usize);
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Gets the number of active passes for this chain.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
|
||||
fn libra_d3d9_filter_chain_get_active_pass_count(
|
||||
chain: *mut libra_d3d9_filter_chain_t,
|
||||
out: *mut MaybeUninit<u32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
unsafe {
|
||||
let value = chain.get_enabled_pass_count();
|
||||
out.write(MaybeUninit::new(value as u32))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Free a d3d9 filter chain.
|
||||
///
|
||||
/// The resulting value in `chain` then becomes null.
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_d3d9_filter_chain_t`.
|
||||
fn libra_d3d9_filter_chain_free(chain: *mut libra_d3d9_filter_chain_t) {
|
||||
assert_non_null!(chain);
|
||||
unsafe {
|
||||
let chain_ptr = &mut *chain;
|
||||
let chain = chain_ptr.take();
|
||||
drop(Box::from_raw(chain.unwrap().as_ptr()))
|
||||
};
|
||||
}
|
||||
}
|
5
librashader-capi/src/runtime/d3d9/mod.rs
Normal file
5
librashader-capi/src/runtime/d3d9/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
//! C API for the librashader D3D9 Runtime (`libra_d3d9_*`).
|
||||
|
||||
mod filter_chain;
|
||||
pub use filter_chain::*;
|
||||
const _: () = crate::assert_thread_safe::<librashader::runtime::d3d11::FilterChain>();
|
|
@ -3,7 +3,9 @@ use crate::ctypes::{
|
|||
};
|
||||
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
|
||||
use crate::ffi::extern_fn;
|
||||
use librashader::runtime::gl::{GLFramebuffer, GLImage};
|
||||
use librashader::runtime::gl::{
|
||||
FilterChain, FilterChainOptions, FrameOptions, GLFramebuffer, GLImage,
|
||||
};
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::{c_char, c_void, CString};
|
||||
use std::mem::MaybeUninit;
|
||||
|
@ -11,8 +13,6 @@ use std::ptr::NonNull;
|
|||
use std::slice;
|
||||
|
||||
use crate::LIBRASHADER_API_VERSION;
|
||||
use librashader::runtime::gl::capi::options::FilterChainOptionsGL;
|
||||
use librashader::runtime::gl::capi::options::FrameOptionsGL;
|
||||
use librashader::runtime::FilterChainParameters;
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
|
||||
|
@ -64,11 +64,18 @@ pub struct frame_gl_opt_t {
|
|||
/// The direction of rendering.
|
||||
/// -1 indicates that the frames are played in reverse order.
|
||||
pub frame_direction: i32,
|
||||
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
|
||||
pub rotation: u32,
|
||||
/// The total number of subframes ran. Default is 1.
|
||||
pub total_subframes: u32,
|
||||
/// The current sub frame. Default is 1.
|
||||
pub current_subframe: u32,
|
||||
}
|
||||
|
||||
config_struct! {
|
||||
impl FrameOptionsGL => frame_gl_opt_t {
|
||||
impl FrameOptions => frame_gl_opt_t {
|
||||
0 => [clear_history, frame_direction];
|
||||
1 => [rotation, total_subframes, current_subframe]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +99,7 @@ pub struct filter_chain_gl_opt_t {
|
|||
}
|
||||
|
||||
config_struct! {
|
||||
impl FilterChainOptionsGL => filter_chain_gl_opt_t {
|
||||
impl FilterChainOptions => filter_chain_gl_opt_t {
|
||||
0 => [glsl_version, use_dsa, force_no_mipmaps, disable_cache];
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +156,7 @@ extern_fn! {
|
|||
let options = options.map(FromUninit::from_uninit);
|
||||
|
||||
unsafe {
|
||||
let chain = librashader::runtime::gl::capi::FilterChainGL::load_from_preset(*preset, options.as_ref())?;
|
||||
let chain = FilterChain::load_from_preset(*preset, options.as_ref())?;
|
||||
|
||||
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
|
||||
chain,
|
||||
|
@ -172,7 +179,7 @@ extern_fn! {
|
|||
/// thread at a time may call this function. The thread `libra_gl_filter_chain_frame` is called from
|
||||
/// must have its thread-local OpenGL context initialized with the same context used to create
|
||||
/// the filter chain.
|
||||
fn libra_gl_filter_chain_frame(
|
||||
nopanic fn libra_gl_filter_chain_frame(
|
||||
chain: *mut libra_gl_filter_chain_t,
|
||||
frame_count: usize,
|
||||
image: libra_source_image_gl_t,
|
||||
|
|
|
@ -8,9 +8,33 @@ pub mod gl;
|
|||
pub mod vk;
|
||||
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d11")))]
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d11"))]
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d11")
|
||||
))]
|
||||
pub mod d3d11;
|
||||
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d9")))]
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d9")
|
||||
))]
|
||||
pub mod d3d9;
|
||||
|
||||
#[doc(cfg(all(target_os = "windows", feature = "runtime-d3d12")))]
|
||||
#[cfg(all(target_os = "windows", feature = "runtime-d3d12"))]
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(target_os = "windows", feature = "runtime-d3d12")
|
||||
))]
|
||||
pub mod d3d12;
|
||||
|
||||
#[doc(cfg(all(target_vendor = "apple", feature = "runtime-metal")))]
|
||||
#[cfg(any(
|
||||
feature = "__cbindgen_internal",
|
||||
all(
|
||||
target_vendor = "apple",
|
||||
feature = "runtime-metal",
|
||||
feature = "__cbindgen_internal_objc"
|
||||
)
|
||||
))]
|
||||
pub mod mtl;
|
||||
|
|
319
librashader-capi/src/runtime/mtl/filter_chain.rs
Normal file
319
librashader-capi/src/runtime/mtl/filter_chain.rs
Normal file
|
@ -0,0 +1,319 @@
|
|||
use crate::ctypes::{
|
||||
config_struct, libra_mtl_filter_chain_t, libra_shader_preset_t, libra_viewport_t, FromUninit,
|
||||
};
|
||||
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
|
||||
use crate::ffi::extern_fn;
|
||||
use librashader::runtime::mtl::{FilterChain, FilterChainOptions, FrameOptions};
|
||||
use std::ffi::c_char;
|
||||
use std::ffi::CStr;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::NonNull;
|
||||
use std::slice;
|
||||
|
||||
use librashader::runtime::FilterChainParameters;
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2_metal::{MTLCommandBuffer, MTLCommandQueue, MTLTexture};
|
||||
|
||||
use crate::LIBRASHADER_API_VERSION;
|
||||
|
||||
/// An alias to a `id<MTLCommandQueue>` protocol object pointer.
|
||||
pub type PMTLCommandQueue = *const ProtocolObject<dyn MTLCommandQueue>;
|
||||
|
||||
/// An alias to a `id<MTLCommandBuffer>` protocol object pointer.
|
||||
pub type PMTLCommandBuffer = *const ProtocolObject<dyn MTLCommandBuffer>;
|
||||
|
||||
/// An alias to a `id<MTLTexture>` protocol object pointer.
|
||||
pub type PMTLTexture = *const ProtocolObject<dyn MTLTexture>;
|
||||
|
||||
/// Options for each Metal shader frame.
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct frame_mtl_opt_t {
|
||||
/// The librashader API version.
|
||||
pub version: LIBRASHADER_API_VERSION,
|
||||
/// Whether or not to clear the history buffers.
|
||||
pub clear_history: bool,
|
||||
/// The direction of rendering.
|
||||
/// -1 indicates that the frames are played in reverse order.
|
||||
pub frame_direction: i32,
|
||||
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
|
||||
pub rotation: u32,
|
||||
/// The total number of subframes ran. Default is 1.
|
||||
pub total_subframes: u32,
|
||||
/// The current sub frame. Default is 1.
|
||||
pub current_subframe: u32,
|
||||
}
|
||||
|
||||
config_struct! {
|
||||
impl FrameOptions => frame_mtl_opt_t {
|
||||
0 => [clear_history, frame_direction];
|
||||
1 => [rotation, total_subframes, current_subframe]
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for filter chain creation.
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct filter_chain_mtl_opt_t {
|
||||
/// The librashader API version.
|
||||
pub version: LIBRASHADER_API_VERSION,
|
||||
/// Whether or not to explicitly disable mipmap generation regardless of shader preset settings.
|
||||
pub force_no_mipmaps: bool,
|
||||
}
|
||||
|
||||
config_struct! {
|
||||
impl FilterChainOptions => filter_chain_mtl_opt_t {
|
||||
0 => [force_no_mipmaps];
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Create the filter chain given the shader preset.
|
||||
///
|
||||
/// The shader preset is immediately invalidated and must be recreated after
|
||||
/// the filter chain is created.
|
||||
///
|
||||
/// ## Safety:
|
||||
/// - `queue` must be valid for the command buffers
|
||||
/// that `libra_mtl_filter_chain_frame` will write to.
|
||||
/// - `queue` must be a reference to a `id<MTLCommandQueue>`.
|
||||
/// - `preset` must be either null, or valid and aligned.
|
||||
/// - `options` must be either null, or valid and aligned.
|
||||
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
|
||||
fn libra_mtl_filter_chain_create(
|
||||
preset: *mut libra_shader_preset_t,
|
||||
queue: PMTLCommandQueue,
|
||||
options: *const MaybeUninit<filter_chain_mtl_opt_t>,
|
||||
out: *mut MaybeUninit<libra_mtl_filter_chain_t>
|
||||
) |queue| {
|
||||
assert_non_null!(preset);
|
||||
|
||||
let preset = unsafe {
|
||||
let preset_ptr = &mut *preset;
|
||||
let preset = preset_ptr.take();
|
||||
Box::from_raw(preset.unwrap().as_ptr())
|
||||
};
|
||||
|
||||
let options = if options.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { options.read() })
|
||||
};
|
||||
|
||||
let queue = queue.as_ref();
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
|
||||
unsafe {
|
||||
let chain = FilterChain::load_from_preset(*preset, queue, options.as_ref())?;
|
||||
|
||||
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
|
||||
chain,
|
||||
)))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Create the filter chain given the shader preset deferring and GPU-side initialization
|
||||
/// to the caller. This function therefore requires no external synchronization of the device queue.
|
||||
///
|
||||
/// The shader preset is immediately invalidated and must be recreated after
|
||||
/// the filter chain is created.
|
||||
///
|
||||
/// ## Safety:
|
||||
/// - `queue` must be valid for the command buffers
|
||||
/// that `libra_mtl_filter_chain_frame` will write to.
|
||||
/// - `queue` must be a reference to a `id<MTLCommandQueue>`.
|
||||
/// - `command_buffer` must be a valid reference to a `MTLCommandBuffer` that is not already encoding.
|
||||
/// - `preset` must be either null, or valid and aligned.
|
||||
/// - `options` must be either null, or valid and aligned.
|
||||
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
|
||||
///
|
||||
/// The provided command buffer must be ready for recording and contain no prior commands.
|
||||
/// The caller is responsible for ending the command buffer and immediately submitting it to a
|
||||
/// graphics queue. The command buffer must be completely executed before calling `libra_mtl_filter_chain_frame`.
|
||||
fn libra_mtl_filter_chain_create_deferred(
|
||||
preset: *mut libra_shader_preset_t,
|
||||
queue: PMTLCommandQueue,
|
||||
command_buffer: PMTLCommandBuffer,
|
||||
options: *const MaybeUninit<filter_chain_mtl_opt_t>,
|
||||
out: *mut MaybeUninit<libra_mtl_filter_chain_t>
|
||||
) |queue, command_buffer| {
|
||||
assert_non_null!(preset);
|
||||
|
||||
let preset = unsafe {
|
||||
let preset_ptr = &mut *preset;
|
||||
let preset = preset_ptr.take();
|
||||
Box::from_raw(preset.unwrap().as_ptr())
|
||||
};
|
||||
|
||||
let options = if options.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { options.read() })
|
||||
};
|
||||
|
||||
let options = options.map(FromUninit::from_uninit);
|
||||
|
||||
unsafe {
|
||||
let chain = FilterChain::load_from_preset_deferred(*preset,
|
||||
queue,
|
||||
command_buffer,
|
||||
options.as_ref())?;
|
||||
|
||||
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
|
||||
chain,
|
||||
)))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Records rendering commands for a frame with the given parameters for the given filter chain
|
||||
/// to the input command buffer.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `command_buffer` must be a valid reference to a `MTLCommandBuffer` that is not already encoding.
|
||||
/// - `chain` may be null, invalid, but not uninitialized. If `chain` is null or invalid, this
|
||||
/// function will return an error.
|
||||
/// - `mvp` may be null, or if it is not null, must be an aligned pointer to 16 consecutive `float`
|
||||
/// values for the model view projection matrix.
|
||||
/// - `opt` may be null, or if it is not null, must be an aligned pointer to a valid `frame_mtl_opt_t`
|
||||
/// struct.
|
||||
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
|
||||
/// thread at a time may call this function.
|
||||
nopanic fn libra_mtl_filter_chain_frame(
|
||||
chain: *mut libra_mtl_filter_chain_t,
|
||||
command_buffer: PMTLCommandBuffer,
|
||||
frame_count: usize,
|
||||
image: PMTLTexture,
|
||||
viewport: libra_viewport_t,
|
||||
output: PMTLTexture,
|
||||
mvp: *const f32,
|
||||
opt: *const MaybeUninit<frame_mtl_opt_t>
|
||||
) |command_buffer, image, output|; mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
|
||||
let mvp = if mvp.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(<&[f32; 16]>::try_from(unsafe { slice::from_raw_parts(mvp, 16) }).unwrap())
|
||||
};
|
||||
let opt = if opt.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { opt.read() })
|
||||
};
|
||||
let opt = opt.map(FromUninit::from_uninit);
|
||||
let viewport = Viewport {
|
||||
x: viewport.x,
|
||||
y: viewport.y,
|
||||
output,
|
||||
mvp,
|
||||
};
|
||||
|
||||
chain.frame(&image, &viewport, command_buffer, frame_count, opt.as_ref())?;
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Sets a parameter for the filter chain.
|
||||
///
|
||||
/// If the parameter does not exist, returns an error.
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
|
||||
/// - `param_name` must be either null or a null terminated string.
|
||||
fn libra_mtl_filter_chain_set_param(
|
||||
chain: *mut libra_mtl_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
value: f32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(param_name);
|
||||
let name = name.to_str()?;
|
||||
|
||||
if chain.set_parameter(name, value).is_none() {
|
||||
return LibrashaderError::UnknownShaderParameter(param_name).export()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Gets a parameter for the filter chain.
|
||||
///
|
||||
/// If the parameter does not exist, returns an error.
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
|
||||
/// - `param_name` must be either null or a null terminated string.
|
||||
fn libra_mtl_filter_chain_get_param(
|
||||
chain: *mut libra_mtl_filter_chain_t,
|
||||
param_name: *const c_char,
|
||||
out: *mut MaybeUninit<f32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
|
||||
unsafe {
|
||||
let name = CStr::from_ptr(param_name);
|
||||
let name = name.to_str()?;
|
||||
|
||||
let Some(value) = chain.get_parameter(name) else {
|
||||
return LibrashaderError::UnknownShaderParameter(param_name).export()
|
||||
};
|
||||
|
||||
out.write(MaybeUninit::new(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Sets the number of active passes for this chain.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
|
||||
fn libra_mtl_filter_chain_set_active_pass_count(
|
||||
chain: *mut libra_mtl_filter_chain_t,
|
||||
value: u32
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
chain.set_enabled_pass_count(value as usize);
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Gets the number of active passes for this chain.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
|
||||
fn libra_mtl_filter_chain_get_active_pass_count(
|
||||
chain: *mut libra_mtl_filter_chain_t,
|
||||
out: *mut MaybeUninit<u32>
|
||||
) mut |chain| {
|
||||
assert_some_ptr!(mut chain);
|
||||
let value = chain.get_enabled_pass_count();
|
||||
unsafe {
|
||||
out.write(MaybeUninit::new(value as u32))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Free a Metal filter chain.
|
||||
///
|
||||
/// The resulting value in `chain` then becomes null.
|
||||
/// ## Safety
|
||||
/// - `chain` must be either null or a valid and aligned pointer to an initialized `libra_mtl_filter_chain_t`.
|
||||
fn libra_mtl_filter_chain_free(
|
||||
chain: *mut libra_mtl_filter_chain_t
|
||||
) {
|
||||
assert_non_null!(chain);
|
||||
unsafe {
|
||||
let chain_ptr = &mut *chain;
|
||||
let chain = chain_ptr.take();
|
||||
drop(Box::from_raw(chain.unwrap().as_ptr()))
|
||||
};
|
||||
}
|
||||
}
|
5
librashader-capi/src/runtime/mtl/mod.rs
Normal file
5
librashader-capi/src/runtime/mtl/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
//! C API for the librashader Metal Runtime (`libra_mtl_*`).
|
||||
|
||||
mod filter_chain;
|
||||
|
||||
pub use filter_chain::*;
|
|
@ -3,20 +3,21 @@ use crate::ctypes::{
|
|||
};
|
||||
use crate::error::{assert_non_null, assert_some_ptr, LibrashaderError};
|
||||
use crate::ffi::extern_fn;
|
||||
use librashader::runtime::vk::{VulkanImage, VulkanInstance};
|
||||
use librashader::runtime::vk::{
|
||||
FilterChain, FilterChainOptions, FrameOptions, VulkanImage, VulkanInstance,
|
||||
};
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::{c_char, c_void};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::NonNull;
|
||||
use std::slice;
|
||||
|
||||
use librashader::runtime::vk::capi::options::FilterChainOptionsVulkan;
|
||||
use librashader::runtime::vk::capi::options::FrameOptionsVulkan;
|
||||
use librashader::runtime::FilterChainParameters;
|
||||
use librashader::runtime::{Size, Viewport};
|
||||
|
||||
use ash::vk;
|
||||
|
||||
use crate::LIBRASHADER_API_VERSION;
|
||||
pub use ash::vk::PFN_vkGetInstanceProcAddr;
|
||||
|
||||
/// A Vulkan instance function loader that the Vulkan filter chain needs to be initialized with.
|
||||
|
@ -82,22 +83,29 @@ impl From<libra_device_vk_t> for VulkanInstance {
|
|||
}
|
||||
}
|
||||
|
||||
/// Options for each OpenGL shader frame.
|
||||
/// Options for each Vulkan shader frame.
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct frame_vk_opt_t {
|
||||
/// The librashader API version.
|
||||
pub version: usize,
|
||||
pub version: LIBRASHADER_API_VERSION,
|
||||
/// Whether or not to clear the history buffers.
|
||||
pub clear_history: bool,
|
||||
/// The direction of rendering.
|
||||
/// -1 indicates that the frames are played in reverse order.
|
||||
pub frame_direction: i32,
|
||||
/// The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 4 = 270deg.
|
||||
pub rotation: u32,
|
||||
/// The total number of subframes ran. Default is 1.
|
||||
pub total_subframes: u32,
|
||||
/// The current sub frame. Default is 1.
|
||||
pub current_subframe: u32,
|
||||
}
|
||||
|
||||
config_struct! {
|
||||
impl FrameOptionsVulkan => frame_vk_opt_t {
|
||||
impl FrameOptions => frame_vk_opt_t {
|
||||
0 => [clear_history, frame_direction];
|
||||
1 => [rotation, total_subframes, current_subframe]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,22 +114,23 @@ config_struct! {
|
|||
#[derive(Default, Debug, Clone)]
|
||||
pub struct filter_chain_vk_opt_t {
|
||||
/// The librashader API version.
|
||||
pub version: usize,
|
||||
pub version: LIBRASHADER_API_VERSION,
|
||||
/// The number of frames in flight to keep. If zero, defaults to three.
|
||||
pub frames_in_flight: u32,
|
||||
/// Whether or not to explicitly disable mipmap generation regardless of shader preset settings.
|
||||
pub force_no_mipmaps: bool,
|
||||
/// Use explicit render pass objects It is recommended if possible to use dynamic rendering,
|
||||
/// Use dynamic rendering over explicit render pass objects.
|
||||
/// It is recommended if possible to use dynamic rendering,
|
||||
/// because render-pass mode will create new framebuffers per pass.
|
||||
pub use_render_pass: bool,
|
||||
pub use_dynamic_rendering: bool,
|
||||
/// Disable the shader object cache. Shaders will be
|
||||
/// recompiled rather than loaded from the cache.
|
||||
pub disable_cache: bool,
|
||||
}
|
||||
|
||||
config_struct! {
|
||||
impl FilterChainOptionsVulkan => filter_chain_vk_opt_t {
|
||||
0 => [frames_in_flight, force_no_mipmaps, use_render_pass, disable_cache];
|
||||
impl FilterChainOptions => filter_chain_vk_opt_t {
|
||||
0 => [frames_in_flight, force_no_mipmaps, use_dynamic_rendering, disable_cache];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,7 +142,7 @@ extern_fn! {
|
|||
///
|
||||
/// ## Safety:
|
||||
/// - The handles provided in `vulkan` must be valid for the command buffers that
|
||||
/// `libra_vk_filter_chain_frame` will write to. Namely, the VkDevice must have been
|
||||
/// `libra_vk_filter_chain_frame` will write to.
|
||||
/// created with the `VK_KHR_dynamic_rendering` extension.
|
||||
/// - `preset` must be either null, or valid and aligned.
|
||||
/// - `options` must be either null, or valid and aligned.
|
||||
|
@ -161,7 +170,7 @@ extern_fn! {
|
|||
let options = options.map(FromUninit::from_uninit);
|
||||
|
||||
unsafe {
|
||||
let chain = librashader::runtime::vk::capi::FilterChainVulkan::load_from_preset(*preset, vulkan, options.as_ref())?;
|
||||
let chain = FilterChain::load_from_preset(*preset, vulkan, options.as_ref())?;
|
||||
|
||||
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
|
||||
chain,
|
||||
|
@ -179,8 +188,7 @@ extern_fn! {
|
|||
///
|
||||
/// ## Safety:
|
||||
/// - The handles provided in `vulkan` must be valid for the command buffers that
|
||||
/// `libra_vk_filter_chain_frame` will write to. Namely, the VkDevice must have been
|
||||
/// created with the `VK_KHR_dynamic_rendering` extension.
|
||||
/// `libra_vk_filter_chain_frame` will write to.
|
||||
/// - `preset` must be either null, or valid and aligned.
|
||||
/// - `options` must be either null, or valid and aligned.
|
||||
/// - `out` must be aligned, but may be null, invalid, or uninitialized.
|
||||
|
@ -212,7 +220,7 @@ extern_fn! {
|
|||
let options = options.map(FromUninit::from_uninit);
|
||||
|
||||
unsafe {
|
||||
let chain = librashader::runtime::vk::capi::FilterChainVulkan::load_from_preset_deferred(*preset,
|
||||
let chain = FilterChain::load_from_preset_deferred(*preset,
|
||||
vulkan,
|
||||
command_buffer,
|
||||
options.as_ref())?;
|
||||
|
@ -246,7 +254,7 @@ extern_fn! {
|
|||
/// struct.
|
||||
/// - You must ensure that only one thread has access to `chain` before you call this function. Only one
|
||||
/// thread at a time may call this function.
|
||||
fn libra_vk_filter_chain_frame(
|
||||
nopanic fn libra_vk_filter_chain_frame(
|
||||
chain: *mut libra_vk_filter_chain_t,
|
||||
command_buffer: vk::CommandBuffer,
|
||||
frame_count: usize,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! C API for the librashader OpenGL Runtime (`libra_vk_*`).
|
||||
//! C API for the librashader Vulkan Runtime (`libra_vk_*`).
|
||||
|
||||
mod filter_chain;
|
||||
pub use filter_chain::*;
|
||||
|
|
|
@ -13,7 +13,11 @@ pub type LIBRASHADER_ABI_VERSION = usize;
|
|||
/// versions must remain backwards compatible.
|
||||
/// ## API Versions
|
||||
/// - API version 0: 0.1.0
|
||||
pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 0;
|
||||
/// - API version 1: 0.2.0
|
||||
/// - Added rotation, total_subframes, current_subframes to frame options
|
||||
/// - Added preset context API
|
||||
/// - Added Metal runtime API
|
||||
pub const LIBRASHADER_CURRENT_VERSION: LIBRASHADER_API_VERSION = 1;
|
||||
|
||||
/// The current version of the librashader ABI.
|
||||
/// Used by the loader to check ABI compatibility.
|
||||
|
|
263
librashader-capi/src/wildcard.rs
Normal file
263
librashader-capi/src/wildcard.rs
Normal file
|
@ -0,0 +1,263 @@
|
|||
//! librashader preset wildcard context C API (`libra_preset_ctx_*`).
|
||||
|
||||
use crate::ctypes::{libra_preset_ctx_t, LIBRA_PRESET_CTX_ORIENTATION, LIBRA_PRESET_CTX_RUNTIME};
|
||||
use crate::error::{assert_non_null, assert_some_ptr};
|
||||
|
||||
use librashader::presets::context::{
|
||||
ContextItem, PresetExtension, Rotation, ShaderExtension, WildcardContext,
|
||||
};
|
||||
use std::ffi::{c_char, CStr};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use crate::ffi::extern_fn;
|
||||
|
||||
const _: () = crate::assert_thread_safe::<WildcardContext>();
|
||||
|
||||
extern_fn! {
|
||||
/// Create a wildcard context
|
||||
///
|
||||
/// The C API does not allow directly setting certain variables
|
||||
///
|
||||
/// - `PRESET_DIR` and `PRESET` are inferred on preset creation.
|
||||
/// - `VID-DRV-SHADER-EXT` and `VID-DRV-PRESET-EXT` are always set to `slang` and `slangp` for librashader.
|
||||
/// - `VID-FINAL-ROT` is automatically calculated as the sum of `VID-USER-ROT` and `CORE-REQ-ROT` if either are present.
|
||||
///
|
||||
/// These automatically inferred variables, as well as all other variables can be overridden with
|
||||
/// `libra_preset_ctx_set_param`, but the expected string values must be provided.
|
||||
/// See https://github.com/libretro/RetroArch/pull/15023 for a list of expected string values.
|
||||
///
|
||||
/// No variables can be removed once added to the context, however subsequent calls to set the same
|
||||
/// variable will overwrite the expected variable.
|
||||
/// ## Safety
|
||||
/// - `out` must be either null, or an aligned pointer to an uninitialized or invalid `libra_preset_ctx_t`.
|
||||
/// ## Returns
|
||||
/// - If any parameters are null, `out` is unchanged, and this function returns `LIBRA_ERR_INVALID_PARAMETER`.
|
||||
fn libra_preset_ctx_create(
|
||||
out: *mut MaybeUninit<libra_preset_ctx_t>
|
||||
) {
|
||||
assert_non_null!(out);
|
||||
|
||||
unsafe {
|
||||
out.write(MaybeUninit::new(NonNull::new(Box::into_raw(Box::new(
|
||||
WildcardContext::new(),
|
||||
)))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Free the wildcard context.
|
||||
///
|
||||
/// If `context` is null, this function does nothing. The resulting value in `context` then becomes
|
||||
/// null.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `context` must be a valid and aligned pointer to a `libra_preset_ctx_t`
|
||||
fn libra_preset_ctx_free(context: *mut libra_preset_ctx_t) {
|
||||
assert_non_null!(context);
|
||||
unsafe {
|
||||
let context_ptr = &mut *context;
|
||||
let context = context_ptr.take();
|
||||
drop(Box::from_raw(context.unwrap().as_ptr()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set the core name (`CORE`) variable in the context
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
/// - `name` must be null or a valid and aligned pointer to a string.
|
||||
fn libra_preset_ctx_set_core_name(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
name: *const c_char,
|
||||
) |name|; mut |context| {
|
||||
let name = unsafe {
|
||||
CStr::from_ptr(name)
|
||||
};
|
||||
|
||||
let name = name.to_str()?;
|
||||
assert_some_ptr!(mut context);
|
||||
|
||||
context.append_item(ContextItem::CoreName(String::from(name)));
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set the content directory (`CONTENT-DIR`) variable in the context.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
/// - `name` must be null or a valid and aligned pointer to a string.
|
||||
fn libra_preset_ctx_set_content_dir(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
name: *const c_char,
|
||||
) |name|; mut |context| {
|
||||
let name = unsafe {
|
||||
CStr::from_ptr(name)
|
||||
};
|
||||
|
||||
let name = name.to_str()?;
|
||||
assert_some_ptr!(mut context);
|
||||
context.append_item(ContextItem::ContentDirectory(String::from(name)));
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set a custom string variable in context.
|
||||
///
|
||||
/// If the path contains this variable when loading a preset, it will be replaced with the
|
||||
/// provided contents.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
/// - `name` must be null or a valid and aligned pointer to a string.
|
||||
/// - `value` must be null or a valid and aligned pointer to a string.
|
||||
fn libra_preset_ctx_set_param(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
name: *const c_char,
|
||||
value: *const c_char,
|
||||
) |name, value|; mut |context| {
|
||||
let name = unsafe {
|
||||
CStr::from_ptr(name)
|
||||
};
|
||||
let name = name.to_str()?;
|
||||
|
||||
let value = unsafe {
|
||||
CStr::from_ptr(value)
|
||||
};
|
||||
let value = value.to_str()?;
|
||||
|
||||
assert_some_ptr!(mut context);
|
||||
context.append_item(ContextItem::ExternContext(String::from(name), String::from(value)));
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set the graphics runtime (`VID-DRV`) variable in the context.
|
||||
///
|
||||
/// Note that librashader only supports the following runtimes.
|
||||
///
|
||||
/// - Vulkan
|
||||
/// - GLCore
|
||||
/// - Direct3D11
|
||||
/// - Direct3D12
|
||||
///
|
||||
/// This will also set the appropriate video driver extensions.
|
||||
///
|
||||
/// For librashader, `VID-DRV-SHADER-EXT` and `VID-DRV-PRESET-EXT` are always `slang` and `slangp`.
|
||||
/// To override this, use `libra_preset_ctx_set_param`.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
/// - `name` must be null or a valid and aligned pointer to a string.
|
||||
fn libra_preset_ctx_set_runtime(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
value: LIBRA_PRESET_CTX_RUNTIME,
|
||||
) mut |context| {
|
||||
assert_some_ptr!(mut context);
|
||||
|
||||
context.append_item(ContextItem::VideoDriverPresetExtension(
|
||||
PresetExtension::Slangp,
|
||||
));
|
||||
context.append_item(ContextItem::VideoDriverShaderExtension(
|
||||
ShaderExtension::Slang,
|
||||
));
|
||||
context.append_item(ContextItem::VideoDriver(value.into()));
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set the core requested rotation (`CORE-REQ-ROT`) variable in the context.
|
||||
///
|
||||
/// Rotation is represented by quarter rotations around the unit circle.
|
||||
/// For example. `0` = 0deg, `1` = 90deg, `2` = 180deg, `3` = 270deg, `4` = 0deg.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
fn libra_preset_ctx_set_core_rotation(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
value: u32,
|
||||
) mut |context| {
|
||||
assert_some_ptr!(mut context);
|
||||
context.append_item(ContextItem::CoreRequestedRotation(Rotation::from(value)))
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set the user rotation (`VID-USER-ROT`) variable in the context.
|
||||
///
|
||||
/// Rotation is represented by quarter rotations around the unit circle.
|
||||
/// For example. `0` = 0deg, `1` = 90deg, `2` = 180deg, `3` = 270deg, `4` = 0deg.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
fn libra_preset_ctx_set_user_rotation(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
value: u32,
|
||||
) mut |context| {
|
||||
assert_some_ptr!(mut context);
|
||||
context.append_item(ContextItem::UserRotation(Rotation::from(value)))
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set the screen orientation (`SCREEN-ORIENT`) variable in the context.
|
||||
///
|
||||
/// Orientation is represented by quarter rotations around the unit circle.
|
||||
/// For example. `0` = 0deg, `1` = 90deg, `2` = 180deg, `3` = 270deg, `4` = 0deg.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
fn libra_preset_ctx_set_screen_orientation(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
value: u32,
|
||||
) mut |context| {
|
||||
assert_some_ptr!(mut context);
|
||||
context.append_item(ContextItem::ScreenOrientation(Rotation::from(value)))
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set whether or not to allow rotation (`VID-ALLOW-CORE-ROT`) variable in the context.
|
||||
///
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
fn libra_preset_ctx_set_allow_rotation(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
value: bool,
|
||||
) mut |context| {
|
||||
assert_some_ptr!(mut context);
|
||||
context.append_item(ContextItem::AllowCoreRotation(value.into()))
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set the view aspect orientation (`VIEW-ASPECT-ORIENT`) variable in the context.
|
||||
//////
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
fn libra_preset_ctx_set_view_aspect_orientation(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
value: LIBRA_PRESET_CTX_ORIENTATION,
|
||||
) mut |context| {
|
||||
assert_some_ptr!(mut context);
|
||||
context.append_item(ContextItem::ViewAspectOrientation(value.into()))
|
||||
}
|
||||
}
|
||||
|
||||
extern_fn! {
|
||||
/// Set the core aspect orientation (`CORE-ASPECT-ORIENT`) variable in the context.
|
||||
//////
|
||||
/// ## Safety
|
||||
/// - `context` must be null or a valid and aligned pointer to a `libra_preset_ctx_t`.
|
||||
fn libra_preset_ctx_set_core_aspect_orientation(
|
||||
context: *mut libra_preset_ctx_t,
|
||||
value: LIBRA_PRESET_CTX_ORIENTATION,
|
||||
) mut |context| {
|
||||
assert_some_ptr!(mut context);
|
||||
context.append_item(ContextItem::CoreAspectOrientation(value.into()))
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ name = "librashader-common"
|
|||
edition = "2021"
|
||||
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.1.3"
|
||||
version = "0.3.0"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -14,24 +14,35 @@ description = "RetroArch shaders for all."
|
|||
[features]
|
||||
default = []
|
||||
opengl = ["gl"]
|
||||
d3d9 = ["windows"]
|
||||
d3d11 = ["windows", "dxgi"]
|
||||
d3d12 = ["windows", "dxgi"]
|
||||
dxgi = ["windows"]
|
||||
vulkan = ["ash"]
|
||||
wgpu = ["wgpu-types"]
|
||||
metal = ["objc2-metal"]
|
||||
|
||||
[dependencies]
|
||||
gl = { version = "0.14.0", optional = true }
|
||||
ash = { version = "0.37.1+1.3.235", optional = true }
|
||||
|
||||
ash = { version = "0.38", optional = true }
|
||||
wgpu-types = { version = "22", optional = true }
|
||||
num-traits = "0.2.15"
|
||||
rustc-hash = "2.0.0"
|
||||
halfbrown = "0.2.4"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
optional = true
|
||||
version = "0.44.0"
|
||||
workspace = true
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Dxgi_Common",
|
||||
"Win32_Graphics_Direct3D",
|
||||
"Win32_Graphics_Direct3D9",
|
||||
"Win32_Graphics_Direct3D11",
|
||||
"Win32_Graphics_Direct3D12",
|
||||
]
|
||||
|
||||
[target.'cfg(target_vendor="apple")'.dependencies.objc2-metal]
|
||||
optional = true
|
||||
version = "0.2.0"
|
||||
features = ["MTLPixelFormat", "MTLRenderCommandEncoder", "MTLSampler"]
|
||||
|
|
77
librashader-common/src/d3d9.rs
Normal file
77
librashader-common/src/d3d9.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use crate::{FilterMode, ImageFormat, WrapMode};
|
||||
use windows::Win32::Graphics::Direct3D9;
|
||||
//
|
||||
impl From<ImageFormat> for Direct3D9::D3DFORMAT {
|
||||
fn from(format: ImageFormat) -> Self {
|
||||
match format {
|
||||
ImageFormat::Unknown => Direct3D9::D3DFMT_UNKNOWN,
|
||||
ImageFormat::R8Unorm => Direct3D9::D3DFMT_R8G8B8,
|
||||
ImageFormat::R8Uint => Direct3D9::D3DFMT_R8G8B8,
|
||||
ImageFormat::R8Sint => Direct3D9::D3DFMT_R8G8B8,
|
||||
ImageFormat::R8G8B8A8Unorm => Direct3D9::D3DFMT_A8R8G8B8,
|
||||
ImageFormat::R8G8B8A8Uint => Direct3D9::D3DFMT_A8R8G8B8,
|
||||
ImageFormat::R8G8B8A8Sint => Direct3D9::D3DFMT_A8R8G8B8,
|
||||
ImageFormat::R8G8B8A8Srgb => Direct3D9::D3DFMT_A8R8G8B8,
|
||||
ImageFormat::A2B10G10R10UnormPack32 => Direct3D9::D3DFMT_A2B10G10R10,
|
||||
ImageFormat::A2B10G10R10UintPack32 => Direct3D9::D3DFMT_A2B10G10R10,
|
||||
ImageFormat::R16Sfloat => Direct3D9::D3DFMT_R16F,
|
||||
ImageFormat::R16G16Uint => Direct3D9::D3DFMT_G16R16,
|
||||
ImageFormat::R16G16Sint => Direct3D9::D3DFMT_G16R16,
|
||||
ImageFormat::R16G16Sfloat => Direct3D9::D3DFMT_G16R16F,
|
||||
ImageFormat::R16G16B16A16Uint => Direct3D9::D3DFMT_A16B16G16R16,
|
||||
ImageFormat::R16G16B16A16Sint => Direct3D9::D3DFMT_A16B16G16R16,
|
||||
ImageFormat::R16G16B16A16Sfloat => Direct3D9::D3DFMT_A16B16G16R16F,
|
||||
ImageFormat::R32Sfloat => Direct3D9::D3DFMT_R32F,
|
||||
_ => Direct3D9::D3DFMT_UNKNOWN,
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
impl From<Direct3D9::D3DFORMAT> for ImageFormat {
|
||||
fn from(format: Direct3D9::D3DFORMAT) -> Self {
|
||||
match format {
|
||||
Direct3D9::D3DFMT_R8G8B8 => ImageFormat::R8Unorm,
|
||||
Direct3D9::D3DFMT_A8R8G8B8 => ImageFormat::R8G8B8A8Unorm,
|
||||
Direct3D9::D3DFMT_A2B10G10R10 => ImageFormat::A2B10G10R10UnormPack32,
|
||||
Direct3D9::D3DFMT_R16F => ImageFormat::R16Sfloat,
|
||||
Direct3D9::D3DFMT_G16R16 => ImageFormat::R16G16Uint,
|
||||
Direct3D9::D3DFMT_G16R16F => ImageFormat::R16G16Sfloat,
|
||||
Direct3D9::D3DFMT_A16B16G16R16 => ImageFormat::R16G16B16A16Uint,
|
||||
Direct3D9::D3DFMT_A16B16G16R16F => ImageFormat::R16G16B16A16Sfloat,
|
||||
Direct3D9::D3DFMT_R32F => ImageFormat::R32Sfloat,
|
||||
_ => ImageFormat::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WrapMode> for Direct3D9::D3DTEXTUREADDRESS {
|
||||
fn from(value: WrapMode) -> Self {
|
||||
match value {
|
||||
WrapMode::ClampToBorder => Direct3D9::D3DTADDRESS_BORDER,
|
||||
WrapMode::ClampToEdge => Direct3D9::D3DTADDRESS_CLAMP,
|
||||
WrapMode::Repeat => Direct3D9::D3DTADDRESS_WRAP,
|
||||
WrapMode::MirroredRepeat => Direct3D9::D3DTADDRESS_MIRROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FilterMode> for Direct3D9::D3DTEXTUREFILTER {
|
||||
fn from(value: FilterMode) -> Self {
|
||||
match value {
|
||||
FilterMode::Linear => Direct3D9::D3DFILTER_LINEAR,
|
||||
FilterMode::Nearest => Direct3D9::D3DFILTER_NEAREST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl FilterMode {
|
||||
// /// Get the mipmap filtering mode for the given combination.
|
||||
// pub fn d3d9_mip(&self, mip: FilterMode) -> Direct3D9::D3DTEXTUREFILTER {
|
||||
// match (self, mip) {
|
||||
// (FilterMode::Linear, FilterMode::Linear) => Direct3D9::D3DFILTER_MIPLINEAR,
|
||||
// (FilterMode::Linear, FilterMode::Nearest) => Direct3D9::D3DFILTER_LINEARMIPNEAREST,
|
||||
// (FilterMode::Nearest, FilterMode::Linear) => Direct3D9::D3DFILTER_MIPNEAREST,
|
||||
// _ => Direct3D9::D3DFILTER_MIPNEAREST
|
||||
// }
|
||||
// }
|
||||
// }
|
|
@ -8,10 +8,18 @@ pub mod gl;
|
|||
#[cfg(feature = "vulkan")]
|
||||
pub mod vk;
|
||||
|
||||
/// WGPU common conversions.
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub mod wgpu;
|
||||
|
||||
/// DXGI common conversions.
|
||||
#[cfg(all(target_os = "windows", feature = "dxgi"))]
|
||||
pub mod dxgi;
|
||||
|
||||
/// Direct3D 9 common conversions.
|
||||
#[cfg(all(target_os = "windows", feature = "d3d9"))]
|
||||
pub mod d3d9;
|
||||
|
||||
/// Direct3D 11 common conversions.
|
||||
#[cfg(all(target_os = "windows", feature = "d3d11"))]
|
||||
pub mod d3d11;
|
||||
|
@ -20,13 +28,26 @@ pub mod d3d11;
|
|||
#[cfg(all(target_os = "windows", feature = "d3d12"))]
|
||||
pub mod d3d12;
|
||||
|
||||
#[cfg(all(target_vendor = "apple", feature = "metal"))]
|
||||
pub mod metal;
|
||||
|
||||
mod viewport;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod map;
|
||||
|
||||
pub use viewport::Viewport;
|
||||
|
||||
use num_traits::AsPrimitive;
|
||||
use std::convert::Infallible;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ShaderStorage {
|
||||
Path(std::path::PathBuf),
|
||||
String(String),
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
/// Supported image formats for textures.
|
||||
|
|
5
librashader-common/src/map.rs
Normal file
5
librashader-common/src/map.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
/// Fast optimized hash map type for small sets.
|
||||
pub type FastHashMap<K, V> =
|
||||
halfbrown::SizedHashMap<K, V, core::hash::BuildHasherDefault<rustc_hash::FxHasher>, 32>;
|
||||
|
||||
pub use halfbrown;
|
94
librashader-common/src/metal.rs
Normal file
94
librashader-common/src/metal.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use crate::{FilterMode, ImageFormat, Size, WrapMode};
|
||||
use objc2_metal::{
|
||||
MTLPixelFormat, MTLSamplerAddressMode, MTLSamplerMinMagFilter, MTLSamplerMipFilter, MTLViewport,
|
||||
};
|
||||
|
||||
impl From<ImageFormat> for MTLPixelFormat {
|
||||
fn from(format: ImageFormat) -> Self {
|
||||
match format {
|
||||
ImageFormat::Unknown => MTLPixelFormat(0),
|
||||
ImageFormat::R8Unorm => MTLPixelFormat::R8Unorm,
|
||||
ImageFormat::R8Uint => MTLPixelFormat::R8Uint,
|
||||
ImageFormat::R8Sint => MTLPixelFormat::R8Sint,
|
||||
ImageFormat::R8G8Unorm => MTLPixelFormat::RG8Unorm,
|
||||
ImageFormat::R8G8Uint => MTLPixelFormat::RG8Uint,
|
||||
ImageFormat::R8G8Sint => MTLPixelFormat::RG8Sint,
|
||||
ImageFormat::R8G8B8A8Unorm => MTLPixelFormat::RGBA8Unorm,
|
||||
ImageFormat::R8G8B8A8Uint => MTLPixelFormat::RGBA8Uint,
|
||||
ImageFormat::R8G8B8A8Sint => MTLPixelFormat::RGBA8Sint,
|
||||
ImageFormat::R8G8B8A8Srgb => MTLPixelFormat::RGBA8Unorm_sRGB,
|
||||
ImageFormat::A2B10G10R10UnormPack32 => MTLPixelFormat::RGB10A2Unorm,
|
||||
ImageFormat::A2B10G10R10UintPack32 => MTLPixelFormat::RGB10A2Uint,
|
||||
ImageFormat::R16Uint => MTLPixelFormat::R16Uint,
|
||||
ImageFormat::R16Sint => MTLPixelFormat::R16Sint,
|
||||
ImageFormat::R16Sfloat => MTLPixelFormat::R16Float,
|
||||
ImageFormat::R16G16Uint => MTLPixelFormat::RG16Uint,
|
||||
ImageFormat::R16G16Sint => MTLPixelFormat::RG16Sint,
|
||||
ImageFormat::R16G16Sfloat => MTLPixelFormat::RG16Float,
|
||||
ImageFormat::R16G16B16A16Uint => MTLPixelFormat::RGBA16Uint,
|
||||
ImageFormat::R16G16B16A16Sint => MTLPixelFormat::RGBA16Sint,
|
||||
ImageFormat::R16G16B16A16Sfloat => MTLPixelFormat::RGBA16Float,
|
||||
ImageFormat::R32Uint => MTLPixelFormat::R32Uint,
|
||||
ImageFormat::R32Sint => MTLPixelFormat::R32Sint,
|
||||
ImageFormat::R32Sfloat => MTLPixelFormat::R32Float,
|
||||
ImageFormat::R32G32Uint => MTLPixelFormat::RG32Uint,
|
||||
ImageFormat::R32G32Sint => MTLPixelFormat::RG32Sint,
|
||||
ImageFormat::R32G32Sfloat => MTLPixelFormat::RG32Float,
|
||||
ImageFormat::R32G32B32A32Uint => MTLPixelFormat::RGBA32Uint,
|
||||
ImageFormat::R32G32B32A32Sint => MTLPixelFormat::RGBA32Sint,
|
||||
ImageFormat::R32G32B32A32Sfloat => MTLPixelFormat::RGBA32Float,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MTLViewport> for Size<u32> {
|
||||
fn from(value: MTLViewport) -> Self {
|
||||
Size {
|
||||
width: value.width as u32,
|
||||
height: value.height as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<u32>> for MTLViewport {
|
||||
fn from(value: Size<u32>) -> Self {
|
||||
MTLViewport {
|
||||
originX: 0.0,
|
||||
originY: 0.0,
|
||||
width: value.width as f64,
|
||||
height: value.height as f64,
|
||||
znear: -1.0,
|
||||
zfar: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WrapMode> for MTLSamplerAddressMode {
|
||||
fn from(value: WrapMode) -> Self {
|
||||
match value {
|
||||
WrapMode::ClampToBorder => MTLSamplerAddressMode::ClampToBorderColor,
|
||||
WrapMode::ClampToEdge => MTLSamplerAddressMode::ClampToEdge,
|
||||
WrapMode::Repeat => MTLSamplerAddressMode::Repeat,
|
||||
WrapMode::MirroredRepeat => MTLSamplerAddressMode::MirrorRepeat,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FilterMode> for MTLSamplerMinMagFilter {
|
||||
fn from(value: FilterMode) -> Self {
|
||||
match value {
|
||||
FilterMode::Linear => MTLSamplerMinMagFilter::Linear,
|
||||
_ => MTLSamplerMinMagFilter::Nearest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterMode {
|
||||
/// Get the mipmap filtering mode for the given combination.
|
||||
pub fn mtl_mip(&self, _mip: FilterMode) -> MTLSamplerMipFilter {
|
||||
match self {
|
||||
FilterMode::Linear => MTLSamplerMipFilter::Linear,
|
||||
FilterMode::Nearest => MTLSamplerMipFilter::Nearest,
|
||||
}
|
||||
}
|
||||
}
|
125
librashader-common/src/wgpu.rs
Normal file
125
librashader-common/src/wgpu.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use crate::{FilterMode, ImageFormat, Size, WrapMode};
|
||||
|
||||
impl From<ImageFormat> for Option<wgpu_types::TextureFormat> {
|
||||
fn from(format: ImageFormat) -> Self {
|
||||
match format {
|
||||
ImageFormat::Unknown => None,
|
||||
ImageFormat::R8Unorm => Some(wgpu_types::TextureFormat::R8Unorm),
|
||||
ImageFormat::R8Uint => Some(wgpu_types::TextureFormat::R8Uint),
|
||||
ImageFormat::R8Sint => Some(wgpu_types::TextureFormat::R8Sint),
|
||||
ImageFormat::R8G8Unorm => Some(wgpu_types::TextureFormat::Rg8Unorm),
|
||||
ImageFormat::R8G8Uint => Some(wgpu_types::TextureFormat::Rg8Uint),
|
||||
ImageFormat::R8G8Sint => Some(wgpu_types::TextureFormat::Rg8Sint),
|
||||
ImageFormat::R8G8B8A8Unorm => Some(wgpu_types::TextureFormat::Rgba8Unorm),
|
||||
ImageFormat::R8G8B8A8Uint => Some(wgpu_types::TextureFormat::Rgba8Uint),
|
||||
ImageFormat::R8G8B8A8Sint => Some(wgpu_types::TextureFormat::Rgba8Sint),
|
||||
ImageFormat::R8G8B8A8Srgb => Some(wgpu_types::TextureFormat::Rgba8UnormSrgb),
|
||||
ImageFormat::A2B10G10R10UnormPack32 => Some(wgpu_types::TextureFormat::Rgb10a2Unorm),
|
||||
ImageFormat::A2B10G10R10UintPack32 => Some(wgpu_types::TextureFormat::Rgb10a2Uint),
|
||||
ImageFormat::R16Uint => Some(wgpu_types::TextureFormat::R16Uint),
|
||||
ImageFormat::R16Sint => Some(wgpu_types::TextureFormat::R16Sint),
|
||||
ImageFormat::R16Sfloat => Some(wgpu_types::TextureFormat::R16Float),
|
||||
ImageFormat::R16G16Uint => Some(wgpu_types::TextureFormat::Rg16Uint),
|
||||
ImageFormat::R16G16Sint => Some(wgpu_types::TextureFormat::Rg16Sint),
|
||||
ImageFormat::R16G16Sfloat => Some(wgpu_types::TextureFormat::Rg16Float),
|
||||
ImageFormat::R16G16B16A16Uint => Some(wgpu_types::TextureFormat::Rgba16Uint),
|
||||
ImageFormat::R16G16B16A16Sint => Some(wgpu_types::TextureFormat::Rgba16Sint),
|
||||
ImageFormat::R16G16B16A16Sfloat => Some(wgpu_types::TextureFormat::Rgba16Float),
|
||||
ImageFormat::R32Uint => Some(wgpu_types::TextureFormat::R32Uint),
|
||||
ImageFormat::R32Sint => Some(wgpu_types::TextureFormat::R32Sint),
|
||||
ImageFormat::R32Sfloat => Some(wgpu_types::TextureFormat::R32Float),
|
||||
ImageFormat::R32G32Uint => Some(wgpu_types::TextureFormat::Rg32Uint),
|
||||
ImageFormat::R32G32Sint => Some(wgpu_types::TextureFormat::Rg32Sint),
|
||||
ImageFormat::R32G32Sfloat => Some(wgpu_types::TextureFormat::Rg32Float),
|
||||
ImageFormat::R32G32B32A32Uint => Some(wgpu_types::TextureFormat::Rgba32Uint),
|
||||
ImageFormat::R32G32B32A32Sint => Some(wgpu_types::TextureFormat::Rgba32Sint),
|
||||
ImageFormat::R32G32B32A32Sfloat => Some(wgpu_types::TextureFormat::Rgba32Float),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu_types::TextureFormat> for ImageFormat {
|
||||
fn from(format: wgpu_types::TextureFormat) -> Self {
|
||||
match format {
|
||||
wgpu_types::TextureFormat::R8Unorm => ImageFormat::R8Unorm,
|
||||
wgpu_types::TextureFormat::R8Uint => ImageFormat::R8Uint,
|
||||
wgpu_types::TextureFormat::R8Sint => ImageFormat::R8Sint,
|
||||
wgpu_types::TextureFormat::Rg8Unorm => ImageFormat::R8G8Unorm,
|
||||
wgpu_types::TextureFormat::Rg8Uint => ImageFormat::R8G8Uint,
|
||||
wgpu_types::TextureFormat::Rg8Sint => ImageFormat::R8G8Sint,
|
||||
wgpu_types::TextureFormat::Rgba8Unorm => ImageFormat::R8G8B8A8Unorm,
|
||||
wgpu_types::TextureFormat::Rgba8Uint => ImageFormat::R8G8B8A8Uint,
|
||||
wgpu_types::TextureFormat::Rgba8Sint => ImageFormat::R8G8B8A8Sint,
|
||||
wgpu_types::TextureFormat::Rgba8UnormSrgb => ImageFormat::R8G8B8A8Srgb,
|
||||
wgpu_types::TextureFormat::Rgb10a2Unorm => ImageFormat::A2B10G10R10UnormPack32,
|
||||
wgpu_types::TextureFormat::Rgb10a2Uint => ImageFormat::A2B10G10R10UintPack32,
|
||||
wgpu_types::TextureFormat::R16Uint => ImageFormat::R16Uint,
|
||||
wgpu_types::TextureFormat::R16Sint => ImageFormat::R16Sint,
|
||||
wgpu_types::TextureFormat::R16Float => ImageFormat::R16Sfloat,
|
||||
wgpu_types::TextureFormat::Rg16Uint => ImageFormat::R16G16Uint,
|
||||
wgpu_types::TextureFormat::Rg16Sint => ImageFormat::R16G16Sint,
|
||||
wgpu_types::TextureFormat::Rg16Float => ImageFormat::R16G16Sfloat,
|
||||
wgpu_types::TextureFormat::Rgba16Uint => ImageFormat::R16G16B16A16Uint,
|
||||
wgpu_types::TextureFormat::Rgba16Sint => ImageFormat::R16G16B16A16Sint,
|
||||
wgpu_types::TextureFormat::Rgba16Float => ImageFormat::R16G16B16A16Sfloat,
|
||||
wgpu_types::TextureFormat::R32Uint => ImageFormat::R32Uint,
|
||||
wgpu_types::TextureFormat::R32Sint => ImageFormat::R32Sint,
|
||||
wgpu_types::TextureFormat::R32Float => ImageFormat::R32Sfloat,
|
||||
wgpu_types::TextureFormat::Rg32Uint => ImageFormat::R32G32Uint,
|
||||
wgpu_types::TextureFormat::Rg32Sint => ImageFormat::R32G32Sint,
|
||||
wgpu_types::TextureFormat::Rg32Float => ImageFormat::R32G32Sfloat,
|
||||
wgpu_types::TextureFormat::Rgba32Uint => ImageFormat::R32G32B32A32Uint,
|
||||
wgpu_types::TextureFormat::Rgba32Sint => ImageFormat::R32G32B32A32Sint,
|
||||
wgpu_types::TextureFormat::Rgba32Float => ImageFormat::R32G32B32A32Sfloat,
|
||||
_ => ImageFormat::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<wgpu_types::TextureFormat>> for ImageFormat {
|
||||
fn from(format: Option<wgpu_types::TextureFormat>) -> Self {
|
||||
let Some(format) = format else {
|
||||
return ImageFormat::Unknown;
|
||||
};
|
||||
ImageFormat::from(format)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu_types::Extent3d> for Size<u32> {
|
||||
fn from(value: wgpu_types::Extent3d) -> Self {
|
||||
Size {
|
||||
width: value.width,
|
||||
height: value.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FilterMode> for wgpu_types::FilterMode {
|
||||
fn from(value: FilterMode) -> Self {
|
||||
match value {
|
||||
FilterMode::Linear => wgpu_types::FilterMode::Linear,
|
||||
FilterMode::Nearest => wgpu_types::FilterMode::Nearest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WrapMode> for wgpu_types::AddressMode {
|
||||
fn from(value: WrapMode) -> Self {
|
||||
match value {
|
||||
WrapMode::ClampToBorder => wgpu_types::AddressMode::ClampToBorder,
|
||||
WrapMode::ClampToEdge => wgpu_types::AddressMode::ClampToEdge,
|
||||
WrapMode::Repeat => wgpu_types::AddressMode::Repeat,
|
||||
WrapMode::MirroredRepeat => wgpu_types::AddressMode::MirrorRepeat,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<u32>> for wgpu_types::Extent3d {
|
||||
fn from(value: Size<u32>) -> Self {
|
||||
wgpu_types::Extent3d {
|
||||
width: value.width,
|
||||
height: value.height,
|
||||
depth_or_array_layers: 1,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ name = "librashader-preprocess"
|
|||
edition = "2021"
|
||||
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.1.3"
|
||||
version = "0.3.0"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -14,8 +14,7 @@ description = "RetroArch shaders for all."
|
|||
[dependencies]
|
||||
thiserror = "1.0.37"
|
||||
nom = "7.1.1"
|
||||
librashader-common = { path = "../librashader-common", version = "0.1.3" }
|
||||
rustc-hash = "1.1.0"
|
||||
librashader-common = { path = "../librashader-common", version = "0.3.0" }
|
||||
encoding_rs = "0.8.31"
|
||||
|
||||
[features]
|
||||
|
@ -23,6 +22,5 @@ default = [ "line_directives" ]
|
|||
line_directives = []
|
||||
|
||||
[dev-dependencies]
|
||||
librashader-presets = "0.1.0-rc.3"
|
||||
glob = "0.3.1"
|
||||
rayon = "1.6.1"
|
||||
|
|
|
@ -25,7 +25,7 @@ fn read_file(path: impl AsRef<Path>) -> Result<String, PreprocessError> {
|
|||
let buf = e.into_bytes();
|
||||
let decoder = WINDOWS_1252.new_decoder();
|
||||
let Some(len) = decoder.max_utf8_buffer_length_without_replacement(buf.len()) else {
|
||||
return Err(PreprocessError::EncodingError(path.to_path_buf()))
|
||||
return Err(PreprocessError::EncodingError(path.to_path_buf()));
|
||||
};
|
||||
|
||||
let mut latin1_string = String::with_capacity(len);
|
||||
|
|
|
@ -15,9 +15,8 @@ mod stage;
|
|||
|
||||
use crate::include::read_source;
|
||||
pub use error::*;
|
||||
use librashader_common::map::FastHashMap;
|
||||
use librashader_common::ImageFormat;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::path::Path;
|
||||
|
||||
/// The source file for a single shader pass.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -32,7 +31,7 @@ pub struct ShaderSource {
|
|||
pub name: Option<String>,
|
||||
|
||||
/// The list of shader parameters found in the shader source.
|
||||
pub parameters: FxHashMap<String, ShaderParameter>,
|
||||
pub parameters: FastHashMap<String, ShaderParameter>,
|
||||
|
||||
/// The image format the shader expects.
|
||||
pub format: ImageFormat,
|
||||
|
@ -58,8 +57,8 @@ pub struct ShaderParameter {
|
|||
impl ShaderSource {
|
||||
/// Load the source file at the given path, resolving includes relative to the location of the
|
||||
/// source file.
|
||||
pub fn load(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
|
||||
load_shader_source(path)
|
||||
pub fn load(file: &librashader_common::ShaderStorage) -> Result<ShaderSource, PreprocessError> {
|
||||
load_shader_source(file)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,11 +77,17 @@ impl SourceOutput for String {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_shader_source(path: impl AsRef<Path>) -> Result<ShaderSource, PreprocessError> {
|
||||
let source = read_source(path)?;
|
||||
pub(crate) fn load_shader_source(
|
||||
file: &librashader_common::ShaderStorage,
|
||||
) -> Result<ShaderSource, PreprocessError> {
|
||||
let source = match file {
|
||||
librashader_common::ShaderStorage::Path(path) => read_source(path)?,
|
||||
librashader_common::ShaderStorage::String(s) => s.to_string(),
|
||||
};
|
||||
|
||||
let meta = pragma::parse_pragma_meta(&source)?;
|
||||
let text = stage::process_stages(&source)?;
|
||||
let parameters = FxHashMap::from_iter(meta.parameters.into_iter().map(|p| (p.id.clone(), p)));
|
||||
let parameters = FastHashMap::from_iter(meta.parameters.into_iter().map(|p| (p.id.clone(), p)));
|
||||
|
||||
Ok(ShaderSource {
|
||||
vertex: text.vertex,
|
||||
|
@ -100,9 +105,9 @@ mod test {
|
|||
|
||||
#[test]
|
||||
pub fn load_file() {
|
||||
let result = load_shader_source(
|
||||
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
|
||||
)
|
||||
let result = load_shader_source(&librashader_common::ShaderStorage::Path(
|
||||
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang".into(),
|
||||
))
|
||||
.unwrap();
|
||||
eprintln!("{:#}", result.vertex)
|
||||
}
|
||||
|
|
|
@ -132,4 +132,16 @@ mod test {
|
|||
step: 0.25
|
||||
}, parse_parameter_string(r#"#pragma parameter exc "orizontal correction hack (games where players stay at center)" 0.0 -10.0 10.0 0.25"#).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_parameter_pragma_test() {
|
||||
assert_eq!(ShaderParameter {
|
||||
id: "HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR".to_string(),
|
||||
description: " Scanline Dir Multiplier".to_string(),
|
||||
initial: 100.0,
|
||||
minimum: 25.0,
|
||||
maximum: 1600.0,
|
||||
step: 25.0
|
||||
}, parse_parameter_string(r#"#pragma parameter HSM_CORE_RES_SAMPLING_MULT_SCANLINE_DIR " Scanline Dir Multiplier" 100 25 1600 25"#).unwrap())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,10 @@ pub(crate) fn process_stages(source: &str) -> Result<ShaderOutput, PreprocessErr
|
|||
continue;
|
||||
}
|
||||
|
||||
if line.starts_with("#pragma name ") || line.starts_with("#pragma format ") {
|
||||
if line.starts_with("#pragma name ")
|
||||
|| line.starts_with("#pragma format ")
|
||||
|| line.starts_with("#pragma parameter ")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
use glob::glob;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use librashader_presets::ShaderPreset;
|
||||
use rayon::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn preprocess_all_slang_presets_parsed() {
|
||||
for entry in glob("../test/slang-shaders/**/*.slangp").unwrap() {
|
||||
if let Ok(path) = entry {
|
||||
if let Ok(preset) = ShaderPreset::try_parse(&path) {
|
||||
preset.shaders.into_par_iter().for_each(|shader| {
|
||||
if let Err(e) = ShaderSource::load(&shader.name) {
|
||||
println!(
|
||||
"Failed to preprocess shader {} from preset {}: {:?}",
|
||||
shader.name.display(),
|
||||
path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ name = "librashader-presets"
|
|||
edition = "2021"
|
||||
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.1.3"
|
||||
version = "0.3.0"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -15,8 +15,14 @@ description = "RetroArch shaders for all."
|
|||
thiserror = "1.0.37"
|
||||
nom = "7.1.1"
|
||||
nom_locate = "4.0.0"
|
||||
librashader-common = { path = "../librashader-common", version = "0.1.3" }
|
||||
librashader-common = { path = "../librashader-common", version = "0.3.0" }
|
||||
num-traits = "0.2"
|
||||
once_cell = "1"
|
||||
# we don't need unicode
|
||||
regex = { version = "1", default-features = false, features = ["perf"] }
|
||||
|
||||
rustversion = "1.0"
|
||||
os_str_bytes = { version = "6", features = ["conversions"] }
|
||||
|
||||
[features]
|
||||
parse_legacy_glsl = []
|
||||
|
|
463
librashader-presets/src/context.rs
Normal file
463
librashader-presets/src/context.rs
Normal file
|
@ -0,0 +1,463 @@
|
|||
// pub use librashader_presets_context::*;
|
||||
|
||||
//! Shader preset wildcard replacement context handling.
|
||||
//!
|
||||
//! Implements wildcard replacement of shader paths specified in
|
||||
//! [RetroArch#15023](https://github.com/libretro/RetroArch/pull/15023).
|
||||
use librashader_common::map::FastHashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::bytes::Regex;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::ops::Add;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
/// Valid video driver or runtime. This list is non-exhaustive.
|
||||
#[repr(u32)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum VideoDriver {
|
||||
/// None (`null`)
|
||||
None = 0,
|
||||
/// OpenGL Core (`glcore`)
|
||||
GlCore,
|
||||
/// Legacy OpenGL (`gl`)
|
||||
Gl,
|
||||
/// Vulkan (`vulkan`)
|
||||
Vulkan,
|
||||
/// Direct3D 9 (`d3d9_hlsl`)
|
||||
Direct3D9Hlsl,
|
||||
/// Direct3D 11 (`d3d11`)
|
||||
Direct3D11,
|
||||
/// Direct3D12 (`d3d12`)
|
||||
Direct3D12,
|
||||
/// Metal (`metal`)
|
||||
Metal,
|
||||
}
|
||||
|
||||
impl Display for VideoDriver {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VideoDriver::None => f.write_str("null"),
|
||||
VideoDriver::GlCore => f.write_str("glcore"),
|
||||
VideoDriver::Gl => f.write_str("gl"),
|
||||
VideoDriver::Vulkan => f.write_str("vulkan"),
|
||||
VideoDriver::Direct3D11 => f.write_str("d3d11"),
|
||||
VideoDriver::Direct3D9Hlsl => f.write_str("d3d9_hlsl"),
|
||||
VideoDriver::Direct3D12 => f.write_str("d3d12"),
|
||||
VideoDriver::Metal => f.write_str("metal"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Valid extensions for shader extensions.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum ShaderExtension {
|
||||
/// `.slang`
|
||||
Slang = 0,
|
||||
/// `.glsl`
|
||||
Glsl,
|
||||
/// `.cg`
|
||||
Cg,
|
||||
}
|
||||
|
||||
impl Display for ShaderExtension {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ShaderExtension::Slang => f.write_str("slang"),
|
||||
ShaderExtension::Glsl => f.write_str("glsl"),
|
||||
ShaderExtension::Cg => f.write_str("cg"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Valid extensions for shader presets
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum PresetExtension {
|
||||
/// `.slangp`
|
||||
Slangp = 0,
|
||||
/// `.glslp`
|
||||
Glslp,
|
||||
/// `.cgp`
|
||||
Cgp,
|
||||
}
|
||||
|
||||
impl Display for PresetExtension {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PresetExtension::Slangp => f.write_str("slangp"),
|
||||
PresetExtension::Glslp => f.write_str("glslp"),
|
||||
PresetExtension::Cgp => f.write_str("cgp"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotation of the viewport.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Rotation {
|
||||
/// Zero
|
||||
Zero = 0,
|
||||
/// 90 degrees
|
||||
Right = 1,
|
||||
/// 180 degrees
|
||||
Straight = 2,
|
||||
/// 270 degrees
|
||||
Reflex = 3,
|
||||
}
|
||||
|
||||
impl From<u32> for Rotation {
|
||||
fn from(value: u32) -> Self {
|
||||
let value = value % 4;
|
||||
match value {
|
||||
0 => Rotation::Zero,
|
||||
1 => Rotation::Right,
|
||||
2 => Rotation::Straight,
|
||||
3 => Rotation::Reflex,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Rotation {
|
||||
type Output = Rotation;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let lhs = self as u32;
|
||||
let out = lhs + rhs as u32;
|
||||
Rotation::from(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rotation {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Rotation::Zero => f.write_str("0"),
|
||||
Rotation::Right => f.write_str("90"),
|
||||
Rotation::Straight => f.write_str("180"),
|
||||
Rotation::Reflex => f.write_str("270"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Orientation of aspect ratios
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Orientation {
|
||||
/// Vertical orientation.
|
||||
Vertical = 0,
|
||||
/// Horizontal orientation.
|
||||
Horizontal,
|
||||
}
|
||||
|
||||
impl Display for Orientation {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Orientation::Vertical => f.write_str("VERT"),
|
||||
Orientation::Horizontal => f.write_str("HORZ"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An item representing a variable that can be replaced in a path preset.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ContextItem {
|
||||
/// The content directory of the game (`CONTENT-DIR`)
|
||||
ContentDirectory(String),
|
||||
/// The name of the libretro core (`CORE`)
|
||||
CoreName(String),
|
||||
/// The filename of the game (`GAME`)
|
||||
GameName(String),
|
||||
/// The name of the preset (`PRESET`)
|
||||
Preset(String),
|
||||
/// The name of the preset directory (`PRESET_DIR`)
|
||||
PresetDirectory(String),
|
||||
/// The video driver (runtime) (`VID-DRV`)
|
||||
VideoDriver(VideoDriver),
|
||||
/// The extension of shader types supported by the driver (`VID-DRV-SHADER-EXT`)
|
||||
VideoDriverShaderExtension(ShaderExtension),
|
||||
/// The extension of shader presets supported by the driver (`VID-DRV-PRESET-EXT`)
|
||||
VideoDriverPresetExtension(PresetExtension),
|
||||
/// The rotation that the core is requesting (`CORE-REQ-ROT`)
|
||||
CoreRequestedRotation(Rotation),
|
||||
/// Whether or not to allow core-requested rotation (`VID-ALLOW-CORE-ROT`)
|
||||
AllowCoreRotation(bool),
|
||||
/// The rotation the user is requesting (`VID-USER-ROT`)
|
||||
UserRotation(Rotation),
|
||||
/// The final rotation (`VID-FINAL-ROT`) calculated by the sum of `VID-USER-ROT` and `CORE-REQ-ROT`
|
||||
FinalRotation(Rotation),
|
||||
/// The user-adjusted screen orientation (`SCREEN-ORIENT`)
|
||||
ScreenOrientation(Rotation),
|
||||
/// The orientation of the viewport aspect ratio (`VIEW-ASPECT-ORIENT`)
|
||||
ViewAspectOrientation(Orientation),
|
||||
/// The orientation of the aspect ratio requested by the core (`CORE-ASPECT-ORIENT`)
|
||||
CoreAspectOrientation(Orientation),
|
||||
/// An external, arbitrary context variable.
|
||||
ExternContext(String, String),
|
||||
}
|
||||
|
||||
impl ContextItem {
|
||||
fn toggle_str(v: bool) -> &'static str {
|
||||
if v {
|
||||
"ON"
|
||||
} else {
|
||||
"OFF"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(&self) -> &str {
|
||||
match self {
|
||||
ContextItem::ContentDirectory(_) => "CONTENT-DIR",
|
||||
ContextItem::CoreName(_) => "CORE",
|
||||
ContextItem::GameName(_) => "GAME",
|
||||
ContextItem::Preset(_) => "PRESET",
|
||||
ContextItem::PresetDirectory(_) => "PRESET_DIR",
|
||||
ContextItem::VideoDriver(_) => "VID-DRV",
|
||||
ContextItem::CoreRequestedRotation(_) => "CORE-REQ-ROT",
|
||||
ContextItem::AllowCoreRotation(_) => "VID-ALLOW-CORE-ROT",
|
||||
ContextItem::UserRotation(_) => "VID-USER-ROT",
|
||||
ContextItem::FinalRotation(_) => "VID-FINAL-ROT",
|
||||
ContextItem::ScreenOrientation(_) => "SCREEN-ORIENT",
|
||||
ContextItem::ViewAspectOrientation(_) => "VIEW-ASPECT-ORIENT",
|
||||
ContextItem::CoreAspectOrientation(_) => "CORE-ASPECT-ORIENT",
|
||||
ContextItem::VideoDriverShaderExtension(_) => "VID-DRV-SHADER-EXT",
|
||||
ContextItem::VideoDriverPresetExtension(_) => "VID-DRV-PRESET-EXT",
|
||||
ContextItem::ExternContext(key, _) => key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ContextItem {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ContextItem::ContentDirectory(v) => f.write_str(v),
|
||||
ContextItem::CoreName(v) => f.write_str(v),
|
||||
ContextItem::GameName(v) => f.write_str(v),
|
||||
ContextItem::Preset(v) => f.write_str(v),
|
||||
ContextItem::PresetDirectory(v) => f.write_str(v),
|
||||
ContextItem::VideoDriver(v) => f.write_fmt(format_args!("{}", v)),
|
||||
ContextItem::CoreRequestedRotation(v) => {
|
||||
f.write_fmt(format_args!("{}-{}", self.key(), v))
|
||||
}
|
||||
ContextItem::AllowCoreRotation(v) => f.write_fmt(format_args!(
|
||||
"{}-{}",
|
||||
self.key(),
|
||||
ContextItem::toggle_str(*v)
|
||||
)),
|
||||
ContextItem::UserRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
|
||||
ContextItem::FinalRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
|
||||
ContextItem::ScreenOrientation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
|
||||
ContextItem::ViewAspectOrientation(v) => {
|
||||
f.write_fmt(format_args!("{}-{}", self.key(), v))
|
||||
}
|
||||
ContextItem::CoreAspectOrientation(v) => {
|
||||
f.write_fmt(format_args!("{}-{}", self.key(), v))
|
||||
}
|
||||
ContextItem::VideoDriverShaderExtension(v) => f.write_fmt(format_args!("{}", v)),
|
||||
ContextItem::VideoDriverPresetExtension(v) => f.write_fmt(format_args!("{}", v)),
|
||||
ContextItem::ExternContext(_, v) => f.write_fmt(format_args!("{}", v)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A preset wildcard context.
|
||||
///
|
||||
/// Any items added after will have higher priority
|
||||
/// when passed to the shader preset parser.
|
||||
///
|
||||
/// When passed to the preset parser, the preset parser
|
||||
/// will automatically add inferred items at lowest priority.
|
||||
///
|
||||
/// Any items added by the user will override the automatically
|
||||
/// inferred items.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WildcardContext(VecDeque<ContextItem>);
|
||||
|
||||
impl WildcardContext {
|
||||
/// Create a new wildcard context.
|
||||
pub fn new() -> Self {
|
||||
Self(VecDeque::new())
|
||||
}
|
||||
|
||||
/// Prepend an item to the context builder.
|
||||
pub fn prepend_item(&mut self, item: ContextItem) {
|
||||
self.0.push_front(item);
|
||||
}
|
||||
|
||||
/// Append an item to the context builder.
|
||||
/// The new item will take precedence over all items added before it.
|
||||
pub fn append_item(&mut self, item: ContextItem) {
|
||||
self.0.push_back(item);
|
||||
}
|
||||
|
||||
/// Prepend sensible defaults for the given video driver.
|
||||
///
|
||||
/// Any values added, either previously or afterwards will not be overridden.
|
||||
pub fn add_video_driver_defaults(&mut self, video_driver: VideoDriver) {
|
||||
self.prepend_item(ContextItem::VideoDriverPresetExtension(
|
||||
PresetExtension::Slangp,
|
||||
));
|
||||
self.prepend_item(ContextItem::VideoDriverShaderExtension(
|
||||
ShaderExtension::Slang,
|
||||
));
|
||||
self.prepend_item(ContextItem::VideoDriver(video_driver));
|
||||
}
|
||||
|
||||
/// Prepend default entries from the path of the preset.
|
||||
///
|
||||
/// Any values added, either previously or afterwards will not be overridden.
|
||||
pub fn add_path_defaults(&mut self, path: impl AsRef<Path>) {
|
||||
let path = path.as_ref();
|
||||
if let Some(preset_name) = path.file_stem() {
|
||||
let preset_name = preset_name.to_string_lossy();
|
||||
self.prepend_item(ContextItem::Preset(preset_name.into()))
|
||||
}
|
||||
|
||||
if let Some(preset_dir_name) = path.parent().and_then(|p| {
|
||||
if !p.is_dir() {
|
||||
return None;
|
||||
};
|
||||
p.file_name()
|
||||
}) {
|
||||
let preset_dir_name = preset_dir_name.to_string_lossy();
|
||||
self.prepend_item(ContextItem::PresetDirectory(preset_dir_name.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the context into a string hashmap.
|
||||
///
|
||||
/// This is a one way conversion, and will normalize rotation context items
|
||||
/// into `VID-FINAL-ROT`.
|
||||
pub fn to_hashmap(mut self) -> FastHashMap<String, String> {
|
||||
let mut map = FastHashMap::default();
|
||||
let last_user_rot = self
|
||||
.0
|
||||
.iter()
|
||||
.rfind(|i| matches!(i, ContextItem::UserRotation(_)));
|
||||
let last_core_rot = self
|
||||
.0
|
||||
.iter()
|
||||
.rfind(|i| matches!(i, ContextItem::CoreRequestedRotation(_)));
|
||||
|
||||
let final_rot = match (last_core_rot, last_user_rot) {
|
||||
(Some(ContextItem::UserRotation(u)), None) => Some(ContextItem::FinalRotation(*u)),
|
||||
(None, Some(ContextItem::CoreRequestedRotation(c))) => {
|
||||
Some(ContextItem::FinalRotation(*c))
|
||||
}
|
||||
(Some(ContextItem::UserRotation(u)), Some(ContextItem::CoreRequestedRotation(c))) => {
|
||||
Some(ContextItem::FinalRotation(*u + *c))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(final_rot) = final_rot {
|
||||
self.prepend_item(final_rot);
|
||||
}
|
||||
|
||||
for item in self.0 {
|
||||
map.insert(String::from(item.key()), item.to_string());
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
#[rustversion::since(1.74)]
|
||||
pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, String>) {
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
|
||||
if context.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Don't want to do any extra work if there's no match.
|
||||
if !WILDCARD_REGEX.is_match(path.as_os_str().as_encoded_bytes()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut new_path = PathBuf::with_capacity(path.capacity());
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Component::Normal(path) => {
|
||||
let haystack = path.as_encoded_bytes();
|
||||
|
||||
let replaced =
|
||||
WILDCARD_REGEX.replace_all(haystack, |caps: ®ex::bytes::Captures| {
|
||||
let Some(name) = caps.get(1) else {
|
||||
return caps[0].to_vec();
|
||||
};
|
||||
|
||||
let Ok(key) = std::str::from_utf8(name.as_bytes()) else {
|
||||
return caps[0].to_vec();
|
||||
};
|
||||
if let Some(replacement) = context.get(key) {
|
||||
return OsString::from(replacement.to_string()).into_encoded_bytes();
|
||||
}
|
||||
return caps[0].to_vec();
|
||||
});
|
||||
|
||||
// SAFETY: The original source is valid encoded bytes, and our replacement is
|
||||
// valid encoded bytes. This upholds the safety requirements of `from_encoded_bytes_unchecked`.
|
||||
new_path.push(unsafe { OsStr::from_encoded_bytes_unchecked(&replaced.as_ref()) })
|
||||
}
|
||||
_ => new_path.push(component),
|
||||
}
|
||||
}
|
||||
|
||||
// If no wildcards are found within the path, or the path after replacing the wildcards does not exist on disk, the path returned will be unaffected.
|
||||
if let Ok(true) = new_path.try_exists() {
|
||||
*path = new_path;
|
||||
}
|
||||
}
|
||||
|
||||
#[rustversion::before(1.74)]
|
||||
pub(crate) fn apply_context(path: &mut PathBuf, context: &FastHashMap<String, String>) {
|
||||
use os_str_bytes::RawOsStr;
|
||||
static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
|
||||
if context.is_empty() {
|
||||
return;
|
||||
}
|
||||
let path_str = RawOsStr::new(path.as_os_str());
|
||||
let path_bytes = path_str.to_raw_bytes();
|
||||
// Don't want to do any extra work if there's no match.
|
||||
if !WILDCARD_REGEX.is_match(&path_bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut new_path = PathBuf::with_capacity(path.capacity());
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Component::Normal(path) => {
|
||||
let haystack = RawOsStr::new(path);
|
||||
let haystack = haystack.to_raw_bytes();
|
||||
|
||||
let replaced =
|
||||
WILDCARD_REGEX.replace_all(&haystack, |caps: ®ex::bytes::Captures| {
|
||||
let Some(name) = caps.get(1) else {
|
||||
return caps[0].to_vec();
|
||||
};
|
||||
|
||||
let Ok(key) = std::str::from_utf8(name.as_bytes()) else {
|
||||
return caps[0].to_vec();
|
||||
};
|
||||
if let Some(replacement) = context.get(key) {
|
||||
return RawOsStr::from_str(replacement).to_raw_bytes().to_vec();
|
||||
}
|
||||
return caps[0].to_vec();
|
||||
});
|
||||
|
||||
// SAFETY: The original source is valid encoded bytes, and our replacement is
|
||||
// valid encoded bytes. This upholds the safety requirements of `from_encoded_bytes_unchecked`.
|
||||
new_path.push(RawOsStr::assert_cow_from_raw_bytes(&replaced.as_ref()).to_os_str())
|
||||
}
|
||||
_ => new_path.push(component),
|
||||
}
|
||||
}
|
||||
|
||||
// If no wildcards are found within the path, or the path after replacing the wildcards does not exist on disk, the path returned will be unaffected.
|
||||
if let Ok(true) = new_path.try_exists() {
|
||||
*path = new_path;
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ pub enum ParsePresetError {
|
|||
#[error("shader presets must be resolved against an absolute path")]
|
||||
RootPathWasNotAbsolute,
|
||||
/// An IO error occurred when reading the shader preset.
|
||||
#[error("the file was not found during resolution")]
|
||||
#[error("io error on file {0:?}: {1}")]
|
||||
IOError(PathBuf, std::io::Error),
|
||||
/// The shader preset did not contain valid UTF-8 bytes.
|
||||
#[error("expected utf8 bytes but got invalid utf8")]
|
||||
|
|
312
librashader-presets/src/extract_if.rs
Normal file
312
librashader-presets/src/extract_if.rs
Normal file
|
@ -0,0 +1,312 @@
|
|||
//! This is a stable polyfill for [`Vec::extract_if`](https://github.com/rust-lang/rust/issues/43244).
|
||||
|
||||
use core::ptr;
|
||||
use core::slice;
|
||||
|
||||
/// Polyfill trait for [`Vec::extract_if`](https://github.com/rust-lang/rust/issues/43244).
|
||||
pub(crate) trait MakeExtractIf<T> {
|
||||
/// Creates an iterator which uses a closure to determine if an element should be removed.
|
||||
///
|
||||
/// If the closure returns true, then the element is removed and yielded.
|
||||
/// If the closure returns false, the element will remain in the vector and will not be yielded
|
||||
/// by the iterator.
|
||||
///
|
||||
/// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating
|
||||
/// or the iteration short-circuits, then the remaining elements will be retained.
|
||||
///
|
||||
/// Note that `extract_if` also lets you mutate every element in the filter closure,
|
||||
/// regardless of whether you choose to keep or remove it.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Splitting an array into evens and odds, reusing the original allocation:
|
||||
///
|
||||
/// ```
|
||||
/// use vec_extract_if_polyfill::MakeExtractIf;
|
||||
/// let mut numbers = vec![1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15];
|
||||
///
|
||||
/// let evens = numbers.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
|
||||
/// let odds = numbers;
|
||||
///
|
||||
/// assert_eq!(evens, vec![2, 4, 6, 8, 14]);
|
||||
/// assert_eq!(odds, vec![1, 3, 5, 9, 11, 13, 15]);
|
||||
/// ```
|
||||
fn extract_if<F>(&mut self, filter: F) -> ExtractIf<T, F>
|
||||
where
|
||||
F: FnMut(&mut T) -> bool;
|
||||
}
|
||||
|
||||
impl<T> MakeExtractIf<T> for Vec<T> {
|
||||
fn extract_if<F>(&mut self, filter: F) -> ExtractIf<T, F>
|
||||
where
|
||||
F: FnMut(&mut T) -> bool,
|
||||
{
|
||||
let old_len = self.len();
|
||||
|
||||
// Guard against us getting leaked (leak amplification)
|
||||
unsafe {
|
||||
self.set_len(0);
|
||||
}
|
||||
|
||||
ExtractIf {
|
||||
vec: self,
|
||||
idx: 0,
|
||||
del: 0,
|
||||
old_len,
|
||||
pred: filter,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// An iterator which uses a closure to determine if an element should be removed.
|
||||
///
|
||||
/// This struct is created by [`Vec::extract_if`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use vec_extract_if_polyfill::MakeExtractIf;
|
||||
///
|
||||
/// let mut v = vec![0, 1, 2];
|
||||
/// let iter = v.extract_if(|x| *x % 2 == 0);
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
||||
pub struct ExtractIf<'a, T, F>
|
||||
where
|
||||
F: FnMut(&mut T) -> bool,
|
||||
{
|
||||
vec: &'a mut Vec<T>,
|
||||
/// The index of the item that will be inspected by the next call to `next`.
|
||||
idx: usize,
|
||||
/// The number of items that have been drained (removed) thus far.
|
||||
del: usize,
|
||||
/// The original length of `vec` prior to draining.
|
||||
old_len: usize,
|
||||
/// The filter test predicate.
|
||||
pred: F,
|
||||
}
|
||||
|
||||
impl<T, F> Iterator for ExtractIf<'_, T, F>
|
||||
where
|
||||
F: FnMut(&mut T) -> bool,
|
||||
{
|
||||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<T> {
|
||||
unsafe {
|
||||
while self.idx < self.old_len {
|
||||
let i = self.idx;
|
||||
let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len);
|
||||
let drained = (self.pred)(&mut v[i]);
|
||||
// Update the index *after* the predicate is called. If the index
|
||||
// is updated prior and the predicate panics, the element at this
|
||||
// index would be leaked.
|
||||
self.idx += 1;
|
||||
if drained {
|
||||
self.del += 1;
|
||||
return Some(ptr::read(&v[i]));
|
||||
} else if self.del > 0 {
|
||||
let del = self.del;
|
||||
let src: *const T = &v[i];
|
||||
let dst: *mut T = &mut v[i - del];
|
||||
ptr::copy_nonoverlapping(src, dst, 1);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(0, Some(self.old_len - self.idx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> Drop for ExtractIf<'_, T, F>
|
||||
where
|
||||
F: FnMut(&mut T) -> bool,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if self.idx < self.old_len && self.del > 0 {
|
||||
// This is a pretty messed up state, and there isn't really an
|
||||
// obviously right thing to do. We don't want to keep trying
|
||||
// to execute `pred`, so we just backshift all the unprocessed
|
||||
// elements and tell the vec that they still exist. The backshift
|
||||
// is required to prevent a double-drop of the last successfully
|
||||
// drained item prior to a panic in the predicate.
|
||||
let ptr = self.vec.as_mut_ptr();
|
||||
let src = ptr.add(self.idx);
|
||||
let dst = src.sub(self.del);
|
||||
let tail_len = self.old_len - self.idx;
|
||||
src.copy_to(dst, tail_len);
|
||||
}
|
||||
self.vec.set_len(self.old_len - self.del);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::extract_if::MakeExtractIf;
|
||||
#[test]
|
||||
fn drain_filter_empty() {
|
||||
let mut vec: Vec<i32> = vec![];
|
||||
|
||||
{
|
||||
let mut iter = vec.extract_if(|_| true);
|
||||
assert_eq!(iter.size_hint(), (0, Some(0)));
|
||||
assert_eq!(iter.next(), None);
|
||||
assert_eq!(iter.size_hint(), (0, Some(0)));
|
||||
assert_eq!(iter.next(), None);
|
||||
assert_eq!(iter.size_hint(), (0, Some(0)));
|
||||
}
|
||||
assert_eq!(vec.len(), 0);
|
||||
assert_eq!(vec, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drain_filter_zst() {
|
||||
let mut vec = vec![(), (), (), (), ()];
|
||||
let initial_len = vec.len();
|
||||
let mut count = 0;
|
||||
{
|
||||
let mut iter = vec.extract_if(|_| true);
|
||||
assert_eq!(iter.size_hint(), (0, Some(initial_len)));
|
||||
while let Some(_) = iter.next() {
|
||||
count += 1;
|
||||
assert_eq!(iter.size_hint(), (0, Some(initial_len - count)));
|
||||
}
|
||||
assert_eq!(iter.size_hint(), (0, Some(0)));
|
||||
assert_eq!(iter.next(), None);
|
||||
assert_eq!(iter.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
assert_eq!(count, initial_len);
|
||||
assert_eq!(vec.len(), 0);
|
||||
assert_eq!(vec, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drain_filter_false() {
|
||||
let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
let initial_len = vec.len();
|
||||
let mut count = 0;
|
||||
{
|
||||
let mut iter = vec.extract_if(|_| false);
|
||||
assert_eq!(iter.size_hint(), (0, Some(initial_len)));
|
||||
for _ in iter.by_ref() {
|
||||
count += 1;
|
||||
}
|
||||
assert_eq!(iter.size_hint(), (0, Some(0)));
|
||||
assert_eq!(iter.next(), None);
|
||||
assert_eq!(iter.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(vec.len(), initial_len);
|
||||
assert_eq!(vec, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drain_filter_true() {
|
||||
let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
let initial_len = vec.len();
|
||||
let mut count = 0;
|
||||
{
|
||||
let mut iter = vec.extract_if(|_| true);
|
||||
assert_eq!(iter.size_hint(), (0, Some(initial_len)));
|
||||
while let Some(_) = iter.next() {
|
||||
count += 1;
|
||||
assert_eq!(iter.size_hint(), (0, Some(initial_len - count)));
|
||||
}
|
||||
assert_eq!(iter.size_hint(), (0, Some(0)));
|
||||
assert_eq!(iter.next(), None);
|
||||
assert_eq!(iter.size_hint(), (0, Some(0)));
|
||||
}
|
||||
|
||||
assert_eq!(count, initial_len);
|
||||
assert_eq!(vec.len(), 0);
|
||||
assert_eq!(vec, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drain_filter_complex() {
|
||||
{
|
||||
// [+xxx++++++xxxxx++++x+x++]
|
||||
let mut vec = vec![
|
||||
1, 2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36,
|
||||
37, 39,
|
||||
];
|
||||
|
||||
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
|
||||
assert_eq!(removed.len(), 10);
|
||||
assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]);
|
||||
|
||||
assert_eq!(vec.len(), 14);
|
||||
assert_eq!(
|
||||
vec,
|
||||
vec![1, 7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35, 37, 39]
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// [xxx++++++xxxxx++++x+x++]
|
||||
let mut vec = vec![
|
||||
2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36, 37,
|
||||
39,
|
||||
];
|
||||
|
||||
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
|
||||
assert_eq!(removed.len(), 10);
|
||||
assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]);
|
||||
|
||||
assert_eq!(vec.len(), 13);
|
||||
assert_eq!(vec, vec![7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35, 37, 39]);
|
||||
}
|
||||
|
||||
{
|
||||
// [xxx++++++xxxxx++++x+x]
|
||||
let mut vec = vec![
|
||||
2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36,
|
||||
];
|
||||
|
||||
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
|
||||
assert_eq!(removed.len(), 10);
|
||||
assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]);
|
||||
|
||||
assert_eq!(vec.len(), 11);
|
||||
assert_eq!(vec, vec![7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35]);
|
||||
}
|
||||
|
||||
{
|
||||
// [xxxxxxxxxx+++++++++++]
|
||||
let mut vec = vec![
|
||||
2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19,
|
||||
];
|
||||
|
||||
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
|
||||
assert_eq!(removed.len(), 10);
|
||||
assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]);
|
||||
|
||||
assert_eq!(vec.len(), 10);
|
||||
assert_eq!(vec, vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19]);
|
||||
}
|
||||
|
||||
{
|
||||
// [+++++++++++xxxxxxxxxx]
|
||||
let mut vec = vec![
|
||||
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
|
||||
];
|
||||
|
||||
let removed = vec.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
|
||||
assert_eq!(removed.len(), 10);
|
||||
assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]);
|
||||
|
||||
assert_eq!(vec.len(), 10);
|
||||
assert_eq!(vec, vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,10 +7,16 @@
|
|||
//! as input to create a filter chain.
|
||||
//!
|
||||
//! Re-exported as [`librashader::presets`](https://docs.rs/librashader/latest/librashader/presets/index.html).
|
||||
#![feature(drain_filter)]
|
||||
|
||||
#![allow(stable_features)]
|
||||
#![allow(unstable_name_collisions)]
|
||||
|
||||
pub mod context;
|
||||
mod error;
|
||||
mod extract_if;
|
||||
mod parse;
|
||||
mod preset;
|
||||
|
||||
pub use context::WildcardContext;
|
||||
pub use error::*;
|
||||
pub use preset::*;
|
||||
|
|
404
librashader-presets/src/parse/context.rs
Normal file
404
librashader-presets/src/parse/context.rs
Normal file
|
@ -0,0 +1,404 @@
|
|||
//! Shader preset wildcard replacement context handling.
|
||||
//!
|
||||
//! Implements wildcard replacement of shader paths specified in
|
||||
//! [RetroArch#15023](https://github.com/libretro/RetroArch/pull/15023).
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::bytes::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::ops::Add;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
/// Valid video driver or runtime. This list is non-exhaustive.
|
||||
#[repr(u32)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum VideoDriver {
|
||||
/// None (`null`)
|
||||
None = 0,
|
||||
/// OpenGL Core (`glcore`)
|
||||
GlCore,
|
||||
/// Legacy OpenGL (`gl`)
|
||||
Gl,
|
||||
/// Vulkan (`vulkan`)
|
||||
Vulkan,
|
||||
/// Direct3D 9 (`d3d9_hlsl`)
|
||||
Direct3D9Hlsl,
|
||||
/// Direct3D 11 (`d3d11`)
|
||||
Direct3D11,
|
||||
/// Direct3D12 (`d3d12`)
|
||||
Direct3D12,
|
||||
/// Metal (`metal`)
|
||||
Metal,
|
||||
}
|
||||
|
||||
impl Display for VideoDriver {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VideoDriver::None => f.write_str("null"),
|
||||
VideoDriver::GlCore => f.write_str("glcore"),
|
||||
VideoDriver::Gl => f.write_str("gl"),
|
||||
VideoDriver::Vulkan => f.write_str("vulkan"),
|
||||
VideoDriver::Direct3D11 => f.write_str("d3d11"),
|
||||
VideoDriver::Direct3D9Hlsl => f.write_str("d3d9_hlsl"),
|
||||
VideoDriver::Direct3D12 => f.write_str("d3d12"),
|
||||
VideoDriver::Metal => f.write_str("metal"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Valid extensions for shader extensions.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum ShaderExtension {
|
||||
/// `.slang`
|
||||
Slang = 0,
|
||||
/// `.glsl`
|
||||
Glsl,
|
||||
/// `.cg`
|
||||
Cg,
|
||||
}
|
||||
|
||||
impl Display for ShaderExtension {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ShaderExtension::Slang => f.write_str("slang"),
|
||||
ShaderExtension::Glsl => f.write_str("glsl"),
|
||||
ShaderExtension::Cg => f.write_str("cg"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Valid extensions for shader presets
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum PresetExtension {
|
||||
/// `.slangp`
|
||||
Slangp = 0,
|
||||
/// `.glslp`
|
||||
Glslp,
|
||||
/// `.cgp`
|
||||
Cgp,
|
||||
}
|
||||
|
||||
impl Display for PresetExtension {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PresetExtension::Slangp => f.write_str("slangp"),
|
||||
PresetExtension::Glslp => f.write_str("glslp"),
|
||||
PresetExtension::Cgp => f.write_str("cgp"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotation of the viewport.
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Rotation {
|
||||
/// Zero
|
||||
Zero = 0,
|
||||
/// 90 degrees
|
||||
Right = 1,
|
||||
/// 180 degrees
|
||||
Straight = 2,
|
||||
/// 270 degrees
|
||||
Reflex = 3,
|
||||
}
|
||||
|
||||
impl From<u32> for Rotation {
|
||||
fn from(value: u32) -> Self {
|
||||
let value = value % 4;
|
||||
match value {
|
||||
0 => Rotation::Zero,
|
||||
1 => Rotation::Right,
|
||||
2 => Rotation::Straight,
|
||||
3 => Rotation::Reflex,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Rotation {
|
||||
type Output = Rotation;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let lhs = self as u32;
|
||||
let out = lhs + rhs as u32;
|
||||
Rotation::from(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rotation {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Rotation::Zero => f.write_str("0"),
|
||||
Rotation::Right => f.write_str("90"),
|
||||
Rotation::Straight => f.write_str("180"),
|
||||
Rotation::Reflex => f.write_str("270"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Orientation of aspect ratios
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Orientation {
|
||||
/// Vertical orientation.
|
||||
Vertical = 0,
|
||||
/// Horizontal orientation.
|
||||
Horizontal,
|
||||
}
|
||||
|
||||
impl Display for Orientation {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Orientation::Vertical => f.write_str("VERT"),
|
||||
Orientation::Horizontal => f.write_str("HORZ"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An item representing a variable that can be replaced in a path preset.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ContextItem {
|
||||
/// The content directory of the game (`CONTENT-DIR`)
|
||||
ContentDirectory(String),
|
||||
/// The name of the libretro core (`CORE`)
|
||||
CoreName(String),
|
||||
/// The filename of the game (`GAME`)
|
||||
GameName(String),
|
||||
/// The name of the preset (`PRESET`)
|
||||
Preset(String),
|
||||
/// The name of the preset directory (`PRESET_DIR`)
|
||||
PresetDirectory(String),
|
||||
/// The video driver (runtime) (`VID-DRV`)
|
||||
VideoDriver(VideoDriver),
|
||||
/// The extension of shader types supported by the driver (`VID-DRV-SHADER-EXT`)
|
||||
VideoDriverShaderExtension(ShaderExtension),
|
||||
/// The extension of shader presets supported by the driver (`VID-DRV-PRESET-EXT`)
|
||||
VideoDriverPresetExtension(PresetExtension),
|
||||
/// The rotation that the core is requesting (`CORE-REQ-ROT`)
|
||||
CoreRequestedRotation(Rotation),
|
||||
/// Whether or not to allow core-requested rotation (`VID-ALLOW-CORE-ROT`)
|
||||
AllowCoreRotation(bool),
|
||||
/// The rotation the user is requesting (`VID-USER-ROT`)
|
||||
UserRotation(Rotation),
|
||||
/// The final rotation (`VID-FINAL-ROT`) calculated by the sum of `VID-USER-ROT` and `CORE-REQ-ROT`
|
||||
FinalRotation(Rotation),
|
||||
/// The user-adjusted screen orientation (`SCREEN-ORIENT`)
|
||||
ScreenOrientation(Rotation),
|
||||
/// The orientation of the viewport aspect ratio (`VIEW-ASPECT-ORIENT`)
|
||||
ViewAspectOrientation(Orientation),
|
||||
/// The orientation of the aspect ratio requested by the core (`CORE-ASPECT-ORIENT`)
|
||||
CoreAspectOrientation(Orientation),
|
||||
/// An external, arbitrary context variable.
|
||||
ExternContext(String, String),
|
||||
}
|
||||
|
||||
impl ContextItem {
|
||||
fn toggle_str(v: bool) -> &'static str {
|
||||
if v {
|
||||
"ON"
|
||||
} else {
|
||||
"OFF"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(&self) -> &str {
|
||||
match self {
|
||||
ContextItem::ContentDirectory(_) => "CONTENT-DIR",
|
||||
ContextItem::CoreName(_) => "CORE",
|
||||
ContextItem::GameName(_) => "GAME",
|
||||
ContextItem::Preset(_) => "PRESET",
|
||||
ContextItem::PresetDirectory(_) => "PRESET_DIR",
|
||||
ContextItem::VideoDriver(_) => "VID-DRV",
|
||||
ContextItem::CoreRequestedRotation(_) => "CORE-REQ-ROT",
|
||||
ContextItem::AllowCoreRotation(_) => "VID-ALLOW-CORE-ROT",
|
||||
ContextItem::UserRotation(_) => "VID-USER-ROT",
|
||||
ContextItem::FinalRotation(_) => "VID-FINAL-ROT",
|
||||
ContextItem::ScreenOrientation(_) => "SCREEN-ORIENT",
|
||||
ContextItem::ViewAspectOrientation(_) => "VIEW-ASPECT-ORIENT",
|
||||
ContextItem::CoreAspectOrientation(_) => "CORE-ASPECT-ORIENT",
|
||||
ContextItem::VideoDriverShaderExtension(_) => "VID-DRV-SHADER-EXT",
|
||||
ContextItem::VideoDriverPresetExtension(_) => "VID-DRV-PRESET-EXT",
|
||||
ContextItem::ExternContext(key, _) => key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ContextItem {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ContextItem::ContentDirectory(v) => f.write_str(v),
|
||||
ContextItem::CoreName(v) => f.write_str(v),
|
||||
ContextItem::GameName(v) => f.write_str(v),
|
||||
ContextItem::Preset(v) => f.write_str(v),
|
||||
ContextItem::PresetDirectory(v) => f.write_str(v),
|
||||
ContextItem::VideoDriver(v) => f.write_fmt(format_args!("{}", v)),
|
||||
ContextItem::CoreRequestedRotation(v) => {
|
||||
f.write_fmt(format_args!("{}-{}", self.key(), v))
|
||||
}
|
||||
ContextItem::AllowCoreRotation(v) => f.write_fmt(format_args!(
|
||||
"{}-{}",
|
||||
self.key(),
|
||||
ContextItem::toggle_str(*v)
|
||||
)),
|
||||
ContextItem::UserRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
|
||||
ContextItem::FinalRotation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
|
||||
ContextItem::ScreenOrientation(v) => f.write_fmt(format_args!("{}-{}", self.key(), v)),
|
||||
ContextItem::ViewAspectOrientation(v) => {
|
||||
f.write_fmt(format_args!("{}-{}", self.key(), v))
|
||||
}
|
||||
ContextItem::CoreAspectOrientation(v) => {
|
||||
f.write_fmt(format_args!("{}-{}", self.key(), v))
|
||||
}
|
||||
ContextItem::VideoDriverShaderExtension(v) => f.write_fmt(format_args!("{}", v)),
|
||||
ContextItem::VideoDriverPresetExtension(v) => f.write_fmt(format_args!("{}", v)),
|
||||
ContextItem::ExternContext(_, v) => f.write_fmt(format_args!("{}", v)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A preset wildcard context.
|
||||
///
|
||||
/// Any items added after will have higher priority
|
||||
/// when passed to the shader preset parser.
|
||||
///
|
||||
/// When passed to the preset parser, the preset parser
|
||||
/// will automatically add inferred items at lowest priority.
|
||||
///
|
||||
/// Any items added by the user will override the automatically
|
||||
/// inferred items.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WildcardContext(VecDeque<ContextItem>);
|
||||
|
||||
impl WildcardContext {
|
||||
/// Create a new wildcard context.
|
||||
pub fn new() -> Self {
|
||||
Self(VecDeque::new())
|
||||
}
|
||||
|
||||
/// Prepend an item to the context builder.
|
||||
pub fn prepend_item(&mut self, item: ContextItem) {
|
||||
self.0.push_front(item);
|
||||
}
|
||||
|
||||
/// Append an item to the context builder.
|
||||
/// The new item will take precedence over all items added before it.
|
||||
pub fn append_item(&mut self, item: ContextItem) {
|
||||
self.0.push_back(item);
|
||||
}
|
||||
|
||||
/// Prepend sensible defaults for the given video driver.
|
||||
///
|
||||
/// Any values added, either previously or afterwards will not be overridden.
|
||||
pub fn add_video_driver_defaults(&mut self, video_driver: VideoDriver) {
|
||||
self.prepend_item(ContextItem::VideoDriverPresetExtension(
|
||||
PresetExtension::Slangp,
|
||||
));
|
||||
self.prepend_item(ContextItem::VideoDriverShaderExtension(
|
||||
ShaderExtension::Slang,
|
||||
));
|
||||
self.prepend_item(ContextItem::VideoDriver(video_driver));
|
||||
}
|
||||
|
||||
/// Prepend default entries from the path of the preset.
|
||||
///
|
||||
/// Any values added, either previously or afterwards will not be overridden.
|
||||
pub fn add_path_defaults(&mut self, path: impl AsRef<Path>) {
|
||||
let path = path.as_ref();
|
||||
if let Some(preset_name) = path.file_stem() {
|
||||
let preset_name = preset_name.to_string_lossy();
|
||||
self.prepend_item(ContextItem::Preset(preset_name.into()))
|
||||
}
|
||||
|
||||
if let Some(preset_dir_name) = path.parent().and_then(|p| {
|
||||
if !p.is_dir() {
|
||||
return None;
|
||||
};
|
||||
p.file_name()
|
||||
}) {
|
||||
let preset_dir_name = preset_dir_name.to_string_lossy();
|
||||
self.prepend_item(ContextItem::PresetDirectory(preset_dir_name.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_hashmap(mut self) -> FxHashMap<String, String> {
|
||||
let mut map = FxHashMap::default();
|
||||
let last_user_rot = self
|
||||
.0
|
||||
.iter()
|
||||
.rfind(|i| matches!(i, ContextItem::UserRotation(_)));
|
||||
let last_core_rot = self
|
||||
.0
|
||||
.iter()
|
||||
.rfind(|i| matches!(i, ContextItem::CoreRequestedRotation(_)));
|
||||
|
||||
let final_rot = match (last_core_rot, last_user_rot) {
|
||||
(Some(ContextItem::UserRotation(u)), None) => Some(ContextItem::FinalRotation(*u)),
|
||||
(None, Some(ContextItem::CoreRequestedRotation(c))) => {
|
||||
Some(ContextItem::FinalRotation(*c))
|
||||
}
|
||||
(Some(ContextItem::UserRotation(u)), Some(ContextItem::CoreRequestedRotation(c))) => {
|
||||
Some(ContextItem::FinalRotation(*u + *c))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(final_rot) = final_rot {
|
||||
self.prepend_item(final_rot);
|
||||
}
|
||||
|
||||
for item in self.0 {
|
||||
map.insert(String::from(item.key()), item.to_string());
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_context(path: &mut PathBuf, context: &FxHashMap<String, String>) {
|
||||
static WILDCARD_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new("\\$([A-Z-_]+)\\$").unwrap());
|
||||
if context.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Don't want to do any extra work if there's no match.
|
||||
if !WILDCARD_REGEX.is_match(path.as_os_str().as_encoded_bytes()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut new_path = PathBuf::with_capacity(path.capacity());
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Component::Normal(path) => {
|
||||
let haystack = path.as_encoded_bytes();
|
||||
let replaced =
|
||||
WILDCARD_REGEX.replace_all(haystack, |caps: ®ex::bytes::Captures| {
|
||||
let Some(name) = caps.get(1) else {
|
||||
return caps[0].to_vec();
|
||||
};
|
||||
|
||||
let Ok(key) = std::str::from_utf8(name.as_bytes()) else {
|
||||
return caps[0].to_vec();
|
||||
};
|
||||
if let Some(replacement) = context.get(key) {
|
||||
return OsString::from(replacement.to_string()).into_encoded_bytes();
|
||||
}
|
||||
return caps[0].to_vec();
|
||||
});
|
||||
|
||||
// SAFETY: The original source is valid encoded bytes, and our replacement is
|
||||
// valid encoded bytes. This upholds the safety requirements of `from_encoded_bytes_unchecked`.
|
||||
new_path.push(unsafe { OsStr::from_encoded_bytes_unchecked(&replaced.as_ref()) })
|
||||
}
|
||||
_ => new_path.push(component),
|
||||
}
|
||||
}
|
||||
|
||||
// If no wildcards are found within the path, or the path after replacing the wildcards does not exist on disk, the path returned will be unaffected.
|
||||
if let Ok(true) = new_path.try_exists() {
|
||||
*path = new_path;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ mod value;
|
|||
pub(crate) type Span<'a> = LocatedSpan<&'a str>;
|
||||
pub(crate) use token::Token;
|
||||
|
||||
use crate::context::{VideoDriver, WildcardContext};
|
||||
use crate::error::ParsePresetError;
|
||||
use crate::parse::preset::resolve_values;
|
||||
use crate::parse::value::parse_preset;
|
||||
|
@ -21,8 +22,38 @@ pub(crate) fn remove_if<T>(values: &mut Vec<T>, f: impl FnMut(&T) -> bool) -> Op
|
|||
|
||||
impl ShaderPreset {
|
||||
/// Try to parse the shader preset at the given path.
|
||||
///
|
||||
/// This will add path defaults to the wildcard resolution context.
|
||||
pub fn try_parse(path: impl AsRef<Path>) -> Result<ShaderPreset, ParsePresetError> {
|
||||
let values = parse_preset(path)?;
|
||||
let mut context = WildcardContext::new();
|
||||
context.add_path_defaults(path.as_ref());
|
||||
let values = parse_preset(path, WildcardContext::new())?;
|
||||
Ok(resolve_values(values))
|
||||
}
|
||||
|
||||
/// Try to parse the shader preset at the given path.
|
||||
///
|
||||
/// This will add path and driver defaults to the wildcard resolution context.
|
||||
pub fn try_parse_with_driver_context(
|
||||
path: impl AsRef<Path>,
|
||||
driver: VideoDriver,
|
||||
) -> Result<ShaderPreset, ParsePresetError> {
|
||||
let mut context = WildcardContext::new();
|
||||
context.add_path_defaults(path.as_ref());
|
||||
context.add_video_driver_defaults(driver);
|
||||
let values = parse_preset(path, context)?;
|
||||
Ok(resolve_values(values))
|
||||
}
|
||||
|
||||
/// Try to parse the shader preset at the given path, with the exact provided context.
|
||||
///
|
||||
/// This function does not change any of the values in the provided context, except calculating `VID-FINAL-ROT`
|
||||
/// if `CORE-REQ-ROT` and `VID-USER-ROT` is present.
|
||||
pub fn try_parse_with_context(
|
||||
path: impl AsRef<Path>,
|
||||
context: WildcardContext,
|
||||
) -> Result<ShaderPreset, ParsePresetError> {
|
||||
let values = parse_preset(path, context)?;
|
||||
Ok(resolve_values(values))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::extract_if::MakeExtractIf;
|
||||
use crate::parse::remove_if;
|
||||
use crate::parse::value::Value;
|
||||
use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig};
|
||||
|
||||
pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
||||
let textures: Vec<TextureConfig> = values
|
||||
.drain_filter(|f| matches!(*f, Value::Texture { .. }))
|
||||
.extract_if(|f| matches!(*f, Value::Texture { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Texture {
|
||||
name,
|
||||
|
@ -27,7 +28,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
|||
})
|
||||
.collect();
|
||||
let parameters: Vec<ParameterConfig> = values
|
||||
.drain_filter(|f| matches!(*f, Value::Parameter { .. }))
|
||||
.extract_if(|f| matches!(*f, Value::Parameter { .. }))
|
||||
.map(|value| {
|
||||
if let Value::Parameter(name, value) = value {
|
||||
ParameterConfig { name, value }
|
||||
|
@ -64,7 +65,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
|||
|v| matches!(*v, Value::Shader(shader_index, _) if shader_index == shader),
|
||||
) {
|
||||
let shader_values: Vec<Value> = values
|
||||
.drain_filter(|v| v.shader_index() == Some(shader))
|
||||
.extract_if(|v| v.shader_index() == Some(shader))
|
||||
.collect();
|
||||
let scale_type = shader_values.iter().find_map(|f| match f {
|
||||
Value::ScaleType(_, value) => Some(*value),
|
||||
|
@ -114,7 +115,7 @@ pub fn resolve_values(mut values: Vec<Value>) -> ShaderPreset {
|
|||
|
||||
let shader = ShaderPassConfig {
|
||||
id,
|
||||
name,
|
||||
name: librashader_common::ShaderStorage::Path(name),
|
||||
alias: shader_values.iter().find_map(|f| match f {
|
||||
Value::Alias(_, value) => Some(value.to_string()),
|
||||
_ => None,
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use std::ops::RangeFrom;
|
||||
use crate::error::ParsePresetError;
|
||||
use crate::parse::Span;
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{is_not, take_until};
|
||||
use nom::character::complete::{char, line_ending, multispace1, not_line_ending};
|
||||
use std::ops::RangeFrom;
|
||||
|
||||
use nom::combinator::{eof, map_res, value};
|
||||
use nom::error::{ErrorKind, ParseError};
|
||||
|
||||
use nom::sequence::delimited;
|
||||
use nom::{bytes::complete::tag, character::complete::multispace0, IResult, InputIter, InputLength, InputTake, Slice, AsChar};
|
||||
use nom::{
|
||||
bytes::complete::tag, character::complete::multispace0, AsChar, IResult, InputIter,
|
||||
InputLength, InputTake, Slice,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Token<'a> {
|
||||
|
@ -51,9 +54,10 @@ fn parse_assignment(input: Span) -> IResult<Span, ()> {
|
|||
}
|
||||
|
||||
fn unbalanced_quote<I>(input: I) -> IResult<I, ()>
|
||||
where I: Slice<RangeFrom<usize>> + InputIter + InputLength,
|
||||
<I as InputIter>::Item: AsChar,
|
||||
I: Copy
|
||||
where
|
||||
I: Slice<RangeFrom<usize>> + InputIter + InputLength,
|
||||
<I as InputIter>::Item: AsChar,
|
||||
I: Copy,
|
||||
{
|
||||
if let Ok((input, _)) = eof::<_, ()>(input) {
|
||||
Ok((input, ()))
|
||||
|
@ -167,7 +171,7 @@ pub fn do_lex(input: &str) -> Result<Vec<Token>, ParsePresetError> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::parse::token::{do_lex, single_comment};
|
||||
use crate::parse::token::single_comment;
|
||||
|
||||
#[test]
|
||||
fn parses_single_line_comment() {
|
||||
|
|
|
@ -10,16 +10,20 @@ use nom::IResult;
|
|||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
use crate::parse::token::do_lex;
|
||||
use librashader_common::map::FastHashMap;
|
||||
use librashader_common::{FilterMode, WrapMode};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::context::{apply_context, WildcardContext};
|
||||
use crate::extract_if::MakeExtractIf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
ShaderCount(i32),
|
||||
FeedbackPass(i32),
|
||||
FeedbackPass(#[allow(unused)] i32),
|
||||
Shader(i32, PathBuf),
|
||||
ScaleX(i32, ScaleFactor),
|
||||
ScaleY(i32, ScaleFactor),
|
||||
|
@ -148,9 +152,11 @@ fn parse_indexed_key<'a>(key: &'static str, input: Span<'a>) -> IResult<Span<'a>
|
|||
|
||||
pub const SHADER_MAX_REFERENCE_DEPTH: usize = 16;
|
||||
|
||||
// prereq: root_path must be contextualized
|
||||
fn load_child_reference_strings(
|
||||
root_references: Vec<PathBuf>,
|
||||
root_path: impl AsRef<Path>,
|
||||
context: &FastHashMap<String, String>,
|
||||
) -> Result<Vec<(PathBuf, String)>, ParsePresetError> {
|
||||
let root_path = root_path.as_ref();
|
||||
|
||||
|
@ -159,13 +165,14 @@ fn load_child_reference_strings(
|
|||
let root_references = vec![(root_path.to_path_buf(), root_references)];
|
||||
let mut root_references = VecDeque::from(root_references);
|
||||
// search needs to be depth first to allow for overrides.
|
||||
while let Some((reference_root, referenced_paths)) = root_references.pop_front() {
|
||||
while let Some((mut reference_root, referenced_paths)) = root_references.pop_front() {
|
||||
if reference_depth > SHADER_MAX_REFERENCE_DEPTH {
|
||||
return Err(ParsePresetError::ExceededReferenceDepth);
|
||||
}
|
||||
// enter the current root
|
||||
reference_depth += 1;
|
||||
// canonicalize current root
|
||||
apply_context(&mut reference_root, context);
|
||||
let reference_root = reference_root
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(reference_root.to_path_buf(), e))?;
|
||||
|
@ -174,8 +181,10 @@ fn load_child_reference_strings(
|
|||
// println!("Resolving {referenced_paths:?} against {reference_root:?}.");
|
||||
|
||||
for path in referenced_paths {
|
||||
let mut path = reference_root
|
||||
.join(path.clone())
|
||||
let mut path = reference_root.join(path.clone());
|
||||
apply_context(&mut path, context);
|
||||
|
||||
let mut path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.clone(), e))?;
|
||||
// println!("Opening {:?}", path);
|
||||
|
@ -187,7 +196,7 @@ fn load_child_reference_strings(
|
|||
|
||||
let mut new_tokens = do_lex(&reference_contents)?;
|
||||
let new_references: Vec<PathBuf> = new_tokens
|
||||
.drain_filter(|token| *token.key.fragment() == "#reference")
|
||||
.extract_if(|token| *token.key.fragment() == "#reference")
|
||||
.map(|value| PathBuf::from(*value.value.fragment()))
|
||||
.collect();
|
||||
|
||||
|
@ -202,8 +211,16 @@ fn load_child_reference_strings(
|
|||
Ok(reference_strings.into())
|
||||
}
|
||||
|
||||
pub fn parse_preset(path: impl AsRef<Path>) -> Result<Vec<Value>, ParsePresetError> {
|
||||
pub(crate) fn parse_preset(
|
||||
path: impl AsRef<Path>,
|
||||
context: WildcardContext,
|
||||
) -> Result<Vec<Value>, ParsePresetError> {
|
||||
let path = path.as_ref();
|
||||
let mut path = path.to_path_buf();
|
||||
let context = context.to_hashmap();
|
||||
|
||||
apply_context(&mut path, &context);
|
||||
|
||||
let path = path
|
||||
.canonicalize()
|
||||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
@ -214,12 +231,14 @@ pub fn parse_preset(path: impl AsRef<Path>) -> Result<Vec<Value>, ParsePresetErr
|
|||
.map_err(|e| ParsePresetError::IOError(path.to_path_buf(), e))?;
|
||||
|
||||
let tokens = super::token::do_lex(&contents)?;
|
||||
parse_values(tokens, path)
|
||||
parse_values(tokens, path, context)
|
||||
}
|
||||
|
||||
// prereq: root_path must be contextualized
|
||||
pub fn parse_values(
|
||||
mut tokens: Vec<Token>,
|
||||
root_path: impl AsRef<Path>,
|
||||
context: FastHashMap<String, String>,
|
||||
) -> Result<Vec<Value>, ParsePresetError> {
|
||||
let mut root_path = root_path.as_ref().to_path_buf();
|
||||
if root_path.is_relative() {
|
||||
|
@ -232,12 +251,14 @@ pub fn parse_values(
|
|||
}
|
||||
|
||||
let references: Vec<PathBuf> = tokens
|
||||
.drain_filter(|token| *token.key.fragment() == "#reference")
|
||||
.extract_if(|token| *token.key.fragment() == "#reference")
|
||||
.map(|value| PathBuf::from(*value.value.fragment()))
|
||||
.collect();
|
||||
|
||||
// unfortunately we need to lex twice because there's no way to know the references ahead of time.
|
||||
let child_strings = load_child_reference_strings(references, &root_path)?;
|
||||
// the returned references should have context applied
|
||||
|
||||
let child_strings = load_child_reference_strings(references, &root_path, &context)?;
|
||||
let mut all_tokens: Vec<(&Path, Vec<Token>)> = Vec::new();
|
||||
|
||||
for (path, string) in child_strings.iter() {
|
||||
|
@ -254,7 +275,7 @@ pub fn parse_values(
|
|||
// collect all possible parameter names.
|
||||
let mut parameter_names: Vec<&str> = Vec::new();
|
||||
for (_, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| *token.key.fragment() == "parameters") {
|
||||
for token in tokens.extract_if(|token| *token.key.fragment() == "parameters") {
|
||||
let parameter_name_string: &str = token.value.fragment();
|
||||
for parameter_name in parameter_name_string.split(';') {
|
||||
parameter_names.push(parameter_name);
|
||||
|
@ -265,7 +286,7 @@ pub fn parse_values(
|
|||
// collect all possible texture names.
|
||||
let mut texture_names: Vec<&str> = Vec::new();
|
||||
for (_, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| *token.key.fragment() == "textures") {
|
||||
for token in tokens.extract_if(|token| *token.key.fragment() == "textures") {
|
||||
let texture_name_string: &str = token.value.fragment();
|
||||
for texture_name in texture_name_string.split(';') {
|
||||
texture_names.push(texture_name);
|
||||
|
@ -276,7 +297,7 @@ pub fn parse_values(
|
|||
let mut values = Vec::new();
|
||||
// resolve shader paths.
|
||||
for (path, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| parse_indexed_key("shader", token.key).is_ok()) {
|
||||
for token in tokens.extract_if(|token| parse_indexed_key("shader", token.key).is_ok()) {
|
||||
let (_, index) = parse_indexed_key("shader", token.key).map_err(|e| match e {
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => {
|
||||
let input: Span = e.input;
|
||||
|
@ -307,7 +328,7 @@ pub fn parse_values(
|
|||
// resolve texture paths
|
||||
let mut textures = Vec::new();
|
||||
for (path, tokens) in all_tokens.iter_mut() {
|
||||
for token in tokens.drain_filter(|token| texture_names.contains(token.key.fragment())) {
|
||||
for token in tokens.extract_if(|token| texture_names.contains(token.key.fragment())) {
|
||||
let mut relative_path = path.to_path_buf();
|
||||
relative_path.push(*token.value.fragment());
|
||||
relative_path
|
||||
|
@ -530,7 +551,6 @@ pub fn parse_values(
|
|||
param_val,
|
||||
));
|
||||
}
|
||||
|
||||
// very last resort, assume undeclared texture (must have extension)
|
||||
else if Path::new(token.value.fragment()).extension().is_some()
|
||||
&& ["_mipmap", "_linear", "_wrap_mode", "_repeat_mode"]
|
||||
|
@ -595,13 +615,14 @@ pub fn parse_values(
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::parse::value::parse_preset;
|
||||
use crate::WildcardContext;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
pub fn parse_basic() {
|
||||
let root =
|
||||
PathBuf::from("../test/slang-shaders/bezel/Mega_Bezel/Presets/Base_CRT_Presets/MBZ__3__STD__MEGATRON-NTSC.slangp");
|
||||
let basic = parse_preset(root);
|
||||
let basic = parse_preset(root, WildcardContext::new());
|
||||
eprintln!("{basic:?}");
|
||||
assert!(basic.is_ok());
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ pub struct ShaderPassConfig {
|
|||
/// The index of the shader pass relative to its parent preset.
|
||||
pub id: i32,
|
||||
/// The fully qualified path to the shader pass source file.
|
||||
pub name: PathBuf,
|
||||
pub name: librashader_common::ShaderStorage,
|
||||
/// The alias of the shader pass if available.
|
||||
pub alias: Option<String>,
|
||||
/// The filtering mode that this shader pass should expect.
|
||||
|
@ -63,6 +63,8 @@ pub enum ScaleType {
|
|||
Absolute,
|
||||
/// Scale by the size of the viewport.
|
||||
Viewport,
|
||||
/// Scale by the size of the original input quad.
|
||||
Original,
|
||||
}
|
||||
|
||||
/// The scaling factor for framebuffer scaling.
|
||||
|
@ -119,6 +121,7 @@ impl FromStr for ScaleType {
|
|||
"source" => Ok(ScaleType::Input),
|
||||
"viewport" => Ok(ScaleType::Viewport),
|
||||
"absolute" => Ok(ScaleType::Absolute),
|
||||
"original" => Ok(ScaleType::Original),
|
||||
_ => Err(ParsePresetError::InvalidScaleType(s.to_string())),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use glob::glob;
|
||||
use librashader_presets::context::{ContextItem, VideoDriver, WildcardContext};
|
||||
use librashader_presets::ShaderPreset;
|
||||
|
||||
#[test]
|
||||
|
@ -15,7 +16,19 @@ fn parses_all_slang_presets() {
|
|||
#[test]
|
||||
fn parses_problematic() {
|
||||
let path = "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_NDS_DREZ/NDS-[DREZ]-[Native]-[ADV]-[Guest]-[Night].slangp";
|
||||
ShaderPreset::try_parse(path)
|
||||
.expect(&format!("Failed to parse {}", path));
|
||||
|
||||
ShaderPreset::try_parse(path).expect(&format!("Failed to parse {}", path));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_wildcard() {
|
||||
let path =
|
||||
"../test/shaders_slang/bezel/Mega_Bezel/resource/wildcard-examples/Preset-01-Core.slangp";
|
||||
let mut context = WildcardContext::new();
|
||||
|
||||
context.add_video_driver_defaults(VideoDriver::Vulkan);
|
||||
|
||||
context.append_item(ContextItem::CoreName(String::from("image display")));
|
||||
|
||||
ShaderPreset::try_parse_with_context(path, context)
|
||||
.expect(&format!("Failed to parse {}", path));
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ name = "librashader-reflect"
|
|||
edition = "2021"
|
||||
|
||||
license = "MPL-2.0 OR GPL-3.0-only"
|
||||
version = "0.1.3"
|
||||
version = "0.3.0"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
repository = "https://github.com/SnowflakePowered/librashader"
|
||||
readme = "../README.md"
|
||||
|
@ -12,28 +12,39 @@ keywords = ["shader", "retroarch", "SPIR-V"]
|
|||
description = "RetroArch shaders for all."
|
||||
|
||||
[dependencies]
|
||||
shaderc = { version = "0.8.2", features = [] }
|
||||
glslang = "0.3"
|
||||
bytemuck = "1.13.0"
|
||||
|
||||
thiserror = "1.0.37"
|
||||
bitflags = "1.3.2"
|
||||
rustc-hash = "1.1.0"
|
||||
bitflags = "2.4.2"
|
||||
|
||||
librashader-common = { path = "../librashader-common", version = "0.1.3" }
|
||||
librashader-preprocess = { path = "../librashader-preprocess", version = "0.1.3" }
|
||||
librashader-presets = { path = "../librashader-presets", version = "0.1.3" }
|
||||
librashader-common = { path = "../librashader-common", version = "0.3.0" }
|
||||
librashader-preprocess = { path = "../librashader-preprocess", version = "0.3.0" }
|
||||
librashader-presets = { path = "../librashader-presets", version = "0.3.0" }
|
||||
|
||||
spirv_cross = { package = "librashader-spirv-cross", version = "0.23", optional = true }
|
||||
spirv-to-dxil = { version = "0.3", optional = true }
|
||||
naga = { version = "0.11.0", features = ["glsl-in", "spv-in", "spv-out", "glsl-out", "wgsl-out"], optional = true }
|
||||
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1", optional = true }
|
||||
|
||||
rspirv = { version = "0.11.0+1.5.4", optional = true }
|
||||
naga = { version = "22", optional = true }
|
||||
rspirv = { version = "0.12.0", optional = true }
|
||||
spirv = { version = "0.3.0", optional = true}
|
||||
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
|
||||
indexmap = { version = "2.1.0", features = [] }
|
||||
matches = { version = "0.1.10", features = [] }
|
||||
rustc-hash = "2.0.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.spirv-to-dxil]
|
||||
version = "0.4.7"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["cross", "serialize"]
|
||||
unstable-naga = [ "naga", "rspirv" ]
|
||||
standalone = ["shaderc/build-from-source"]
|
||||
dxil = ["cross", "spirv-to-dxil"]
|
||||
cross = [ "spirv_cross", "spirv_cross/glsl", "spirv_cross/hlsl" ]
|
||||
default = ["cross", "naga", "serialize", "wgsl", "msl"]
|
||||
dxil = ["spirv_cross/hlsl", "spirv-to-dxil"]
|
||||
wgsl = ["cross", "naga/wgsl-out", "spirv", "rspirv"]
|
||||
cross = [ "spirv_cross", "spirv_cross/glsl", "spirv_cross/hlsl", "spirv_cross/msl" ]
|
||||
naga = [ "rspirv", "spirv", "naga/spv-in", "naga/spv-out", "naga/wgsl-out", "naga/msl-out" ]
|
||||
serialize = [ "serde" ]
|
||||
msl = [ "spirv_cross/msl", "naga/msl-out" ]
|
||||
|
||||
unstable-naga-in = ["naga/glsl-in"]
|
BIN
librashader-reflect/basic.spv
Normal file
BIN
librashader-reflect/basic.spv
Normal file
Binary file not shown.
|
@ -1,58 +0,0 @@
|
|||
use crate::back::targets::{GLSL, HLSL};
|
||||
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
|
||||
use crate::error::ShaderReflectError;
|
||||
use crate::front::GlslangCompilation;
|
||||
use crate::reflect::cross::{CompiledProgram, GlslReflect, HlslReflect};
|
||||
use crate::reflect::ReflectShader;
|
||||
|
||||
/// The GLSL version to target.
|
||||
pub use spirv_cross::glsl::Version as GlslVersion;
|
||||
|
||||
/// The HLSL shader model version to target.
|
||||
pub use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
|
||||
|
||||
/// The context for a GLSL compilation via spirv-cross.
|
||||
pub struct CrossGlslContext {
|
||||
/// A map of bindings of sampler names to binding locations.
|
||||
pub sampler_bindings: Vec<(String, u32)>,
|
||||
/// The compiled program artifact after compilation.
|
||||
pub artifact: CompiledProgram<spirv_cross::glsl::Target>,
|
||||
}
|
||||
|
||||
impl FromCompilation<GlslangCompilation> for GLSL {
|
||||
type Target = GLSL;
|
||||
type Options = GlslVersion;
|
||||
type Context = CrossGlslContext;
|
||||
type Output = impl CompileShader<Self::Target, Options = GlslVersion, Context = Self::Context>
|
||||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: GlslangCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: GlslReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The context for a HLSL compilation via spirv-cross.
|
||||
pub struct CrossHlslContext {
|
||||
/// The compiled HLSL program.
|
||||
pub artifact: CompiledProgram<spirv_cross::hlsl::Target>,
|
||||
}
|
||||
|
||||
impl FromCompilation<GlslangCompilation> for HLSL {
|
||||
type Target = HLSL;
|
||||
type Options = Option<HlslShaderModel>;
|
||||
type Context = CrossHlslContext;
|
||||
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
|
||||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: GlslangCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: HlslReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
use crate::back::spirv::WriteSpirV;
|
||||
use crate::back::targets::{OutputTarget, DXIL};
|
||||
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
|
||||
use crate::error::{ShaderCompileError, ShaderReflectError};
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::cross::glsl::GlslReflect;
|
||||
use crate::reflect::cross::SpirvCross;
|
||||
use crate::reflect::ReflectShader;
|
||||
pub use spirv_to_dxil::DxilObject;
|
||||
pub use spirv_to_dxil::ShaderModel;
|
||||
use spirv_to_dxil::{
|
||||
PushConstantBufferConfig, RuntimeConfig, RuntimeDataBufferConfig, ShaderStage, ValidatorVersion,
|
||||
};
|
||||
|
||||
use crate::back::targets::{OutputTarget, DXIL};
|
||||
use crate::error::{ShaderCompileError, ShaderReflectError};
|
||||
use crate::front::GlslangCompilation;
|
||||
use crate::reflect::cross::GlslReflect;
|
||||
use crate::reflect::ReflectShader;
|
||||
|
||||
impl OutputTarget for DXIL {
|
||||
type Output = DxilObject;
|
||||
}
|
||||
|
||||
impl FromCompilation<GlslangCompilation> for DXIL {
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for DXIL {
|
||||
type Target = DXIL;
|
||||
type Options = Option<ShaderModel>;
|
||||
type Context = ();
|
||||
|
@ -24,17 +24,15 @@ impl FromCompilation<GlslangCompilation> for DXIL {
|
|||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: GlslangCompilation,
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
let reflect = GlslReflect::try_from(&compile)?;
|
||||
let vertex = compile.vertex;
|
||||
let fragment = compile.fragment;
|
||||
Ok(CompilerBackend {
|
||||
// we can just reuse WriteSpirV as the backend.
|
||||
backend: WriteSpirV {
|
||||
reflect,
|
||||
vertex,
|
||||
fragment,
|
||||
vertex: compile.vertex,
|
||||
fragment: compile.fragment,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -59,6 +57,7 @@ impl CompileShader<DXIL> for WriteSpirV {
|
|||
register_space: 0,
|
||||
base_shader_register: 1,
|
||||
},
|
||||
shader_model_max: sm,
|
||||
..RuntimeConfig::default()
|
||||
};
|
||||
|
||||
|
@ -68,7 +67,6 @@ impl CompileShader<DXIL> for WriteSpirV {
|
|||
None,
|
||||
"main",
|
||||
ShaderStage::Vertex,
|
||||
sm,
|
||||
ValidatorVersion::None,
|
||||
&config,
|
||||
)
|
||||
|
@ -79,7 +77,6 @@ impl CompileShader<DXIL> for WriteSpirV {
|
|||
None,
|
||||
"main",
|
||||
ShaderStage::Fragment,
|
||||
sm,
|
||||
ValidatorVersion::None,
|
||||
&config,
|
||||
)
|
||||
|
|
35
librashader-reflect/src/back/glsl.rs
Normal file
35
librashader-reflect/src/back/glsl.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use crate::back::targets::GLSL;
|
||||
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
|
||||
use crate::error::ShaderReflectError;
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::cross::{CompiledProgram, SpirvCross};
|
||||
use crate::reflect::ReflectShader;
|
||||
|
||||
/// The GLSL version to target.
|
||||
pub use spirv_cross::glsl::Version as GlslVersion;
|
||||
|
||||
use crate::reflect::cross::glsl::GlslReflect;
|
||||
|
||||
/// The context for a GLSL compilation via spirv-cross.
|
||||
pub struct CrossGlslContext {
|
||||
/// A map of bindings of sampler names to binding locations.
|
||||
pub sampler_bindings: Vec<(String, u32)>,
|
||||
/// The compiled program artifact after compilation.
|
||||
pub artifact: CompiledProgram<spirv_cross::glsl::Target>,
|
||||
}
|
||||
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for GLSL {
|
||||
type Target = GLSL;
|
||||
type Options = GlslVersion;
|
||||
type Context = CrossGlslContext;
|
||||
type Output = impl CompileShader<Self::Target, Options = GlslVersion, Context = Self::Context>
|
||||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: GlslReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
148
librashader-reflect/src/back/hlsl.rs
Normal file
148
librashader-reflect/src/back/hlsl.rs
Normal file
|
@ -0,0 +1,148 @@
|
|||
use crate::back::targets::HLSL;
|
||||
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
|
||||
use crate::error::ShaderReflectError;
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::cross::hlsl::HlslReflect;
|
||||
use crate::reflect::cross::{CompiledProgram, SpirvCross};
|
||||
use crate::reflect::ReflectShader;
|
||||
|
||||
/// The HLSL shader model version to target.
|
||||
pub use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
|
||||
|
||||
/// Buffer assignment information
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HlslBufferAssignment {
|
||||
/// The name of the buffer
|
||||
pub name: String,
|
||||
/// The id of the buffer
|
||||
pub id: u32,
|
||||
}
|
||||
|
||||
/// Buffer assignment information
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct HlslBufferAssignments {
|
||||
/// Buffer assignment information for UBO
|
||||
pub ubo: Option<HlslBufferAssignment>,
|
||||
/// Buffer assignment information for Push
|
||||
pub push: Option<HlslBufferAssignment>,
|
||||
}
|
||||
|
||||
impl HlslBufferAssignments {
|
||||
fn find_mangled_id(mangled_name: &str) -> Option<u32> {
|
||||
if !mangled_name.starts_with("_") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(next_underscore) = mangled_name[1..].find("_") else {
|
||||
return None;
|
||||
};
|
||||
|
||||
mangled_name[1..next_underscore + 1].parse().ok()
|
||||
}
|
||||
|
||||
fn find_mangled_name(buffer_name: &str, uniform_name: &str, mangled_name: &str) -> bool {
|
||||
// name prependded
|
||||
if mangled_name[buffer_name.len()..].starts_with("_")
|
||||
&& &mangled_name[buffer_name.len() + 1..] == uniform_name
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the mangled name matches.
|
||||
pub fn contains_uniform(&self, uniform_name: &str, mangled_name: &str) -> bool {
|
||||
let is_likely_id_mangled = mangled_name.starts_with("_");
|
||||
if !mangled_name.ends_with(uniform_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(ubo) = &self.ubo {
|
||||
if is_likely_id_mangled {
|
||||
if let Some(id) = Self::find_mangled_id(mangled_name) {
|
||||
if id == ubo.id {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// name prependded
|
||||
if Self::find_mangled_name(&ubo.name, uniform_name, mangled_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(push) = &self.push {
|
||||
if is_likely_id_mangled {
|
||||
if let Some(id) = Self::find_mangled_id(mangled_name) {
|
||||
if id == push.id {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// name prependded
|
||||
if Self::find_mangled_name(&push.name, uniform_name, mangled_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Sometimes SPIRV-cross will assign variables to "global"
|
||||
if Self::find_mangled_name("global", uniform_name, mangled_name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// The context for a HLSL compilation via spirv-cross.
|
||||
pub struct CrossHlslContext {
|
||||
/// The compiled HLSL program.
|
||||
pub artifact: CompiledProgram<spirv_cross::hlsl::Target>,
|
||||
pub vertex_buffers: HlslBufferAssignments,
|
||||
pub fragment_buffers: HlslBufferAssignments,
|
||||
}
|
||||
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for HLSL {
|
||||
type Target = HLSL;
|
||||
type Options = Option<HlslShaderModel>;
|
||||
type Context = CrossHlslContext;
|
||||
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
|
||||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: HlslReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::back::hlsl::HlslBufferAssignments;
|
||||
|
||||
#[test]
|
||||
pub fn mangled_id_test() {
|
||||
assert_eq!(HlslBufferAssignments::find_mangled_id("_19_MVP"), Some(19));
|
||||
assert_eq!(HlslBufferAssignments::find_mangled_id("_19"), None);
|
||||
assert_eq!(HlslBufferAssignments::find_mangled_id("_19_"), Some(19));
|
||||
assert_eq!(HlslBufferAssignments::find_mangled_id("19_"), None);
|
||||
assert_eq!(HlslBufferAssignments::find_mangled_id("_19MVP"), None);
|
||||
assert_eq!(
|
||||
HlslBufferAssignments::find_mangled_id("_19_29_MVP"),
|
||||
Some(19)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn mangled_name_test() {
|
||||
assert!(HlslBufferAssignments::find_mangled_name(
|
||||
"params",
|
||||
"MVP",
|
||||
"params_MVP"
|
||||
));
|
||||
}
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
pub mod cross;
|
||||
#[cfg(feature = "dxil")]
|
||||
#[cfg(all(target_os = "windows", feature = "dxil"))]
|
||||
pub mod dxil;
|
||||
mod spirv;
|
||||
pub mod glsl;
|
||||
pub mod hlsl;
|
||||
pub mod msl;
|
||||
pub mod spirv;
|
||||
pub mod targets;
|
||||
pub mod wgsl;
|
||||
|
||||
use crate::back::targets::OutputTarget;
|
||||
use crate::error::{ShaderCompileError, ShaderReflectError};
|
||||
|
@ -40,26 +43,26 @@ pub trait CompileShader<T: OutputTarget> {
|
|||
///
|
||||
/// This trait is automatically implemented for reflected outputs that have [`FromCompilation`](crate::back::FromCompilation) implement
|
||||
/// for a given target that also implement [`CompileShader`](crate::back::CompileShader) for that target.
|
||||
pub trait CompileReflectShader<T: OutputTarget, C>:
|
||||
pub trait CompileReflectShader<T: OutputTarget, C, S>:
|
||||
CompileShader<
|
||||
T,
|
||||
Options = <T as FromCompilation<C>>::Options,
|
||||
Context = <T as FromCompilation<C>>::Context,
|
||||
Options = <T as FromCompilation<C, S>>::Options,
|
||||
Context = <T as FromCompilation<C, S>>::Context,
|
||||
> + ReflectShader
|
||||
where
|
||||
T: FromCompilation<C>,
|
||||
T: FromCompilation<C, S>,
|
||||
{
|
||||
}
|
||||
|
||||
impl<T, C, O> CompileReflectShader<T, C> for O
|
||||
impl<T, C, O, S> CompileReflectShader<T, C, S> for O
|
||||
where
|
||||
T: OutputTarget,
|
||||
T: FromCompilation<C>,
|
||||
T: FromCompilation<C, S>,
|
||||
O: ReflectShader,
|
||||
O: CompileShader<
|
||||
T,
|
||||
Options = <T as FromCompilation<C>>::Options,
|
||||
Context = <T as FromCompilation<C>>::Context,
|
||||
Options = <T as FromCompilation<C, S>>::Options,
|
||||
Context = <T as FromCompilation<C, S>>::Context,
|
||||
>,
|
||||
{
|
||||
}
|
||||
|
@ -80,8 +83,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A trait for reflectable compilations that can be transformed into an object ready for reflection or compilation.
|
||||
pub trait FromCompilation<T> {
|
||||
/// A trait for reflectable compilations that can be transformed
|
||||
/// into an object ready for reflection or compilation.
|
||||
///
|
||||
/// `T` is the compiled reflectable form of the shader.
|
||||
/// `S` is the semantics under which the shader is reflected.
|
||||
///
|
||||
/// librashader currently supports two semantics, [`SpirvCross`](crate::reflect::cross::SpirvCross)
|
||||
pub trait FromCompilation<T, S> {
|
||||
/// The target that the transformed object is expected to compile for.
|
||||
type Target: OutputTarget;
|
||||
/// Options provided to the compiler.
|
||||
|
@ -114,3 +123,14 @@ where
|
|||
self.backend.reflect(pass_number, semantics)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::front::{Glslang, ShaderInputCompiler};
|
||||
use librashader_preprocess::ShaderSource;
|
||||
|
||||
pub fn test() {
|
||||
let result = ShaderSource::load("../test/basic.slang").unwrap();
|
||||
let _cross = Glslang::compile(&result).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
70
librashader-reflect/src/back/msl.rs
Normal file
70
librashader-reflect/src/back/msl.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use crate::back::targets::MSL;
|
||||
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
|
||||
use crate::error::ShaderReflectError;
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::cross::msl::MslReflect;
|
||||
use crate::reflect::cross::{CompiledProgram, SpirvCross};
|
||||
use crate::reflect::naga::{Naga, NagaReflect};
|
||||
use crate::reflect::ReflectShader;
|
||||
use naga::back::msl::TranslationInfo;
|
||||
use naga::Module;
|
||||
|
||||
/// The HLSL shader model version to target.
|
||||
pub use spirv_cross::msl::Version as MslVersion;
|
||||
|
||||
/// Compiler options for MSL
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MslNagaCompileOptions {
|
||||
// pub write_pcb_as_ubo: bool,
|
||||
pub sampler_bind_group: u32,
|
||||
}
|
||||
|
||||
/// The context for a MSL compilation via spirv-cross.
|
||||
pub struct CrossMslContext {
|
||||
/// The compiled HLSL program.
|
||||
pub artifact: CompiledProgram<spirv_cross::msl::Target>,
|
||||
}
|
||||
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for MSL {
|
||||
type Target = MSL;
|
||||
type Options = Option<self::MslVersion>;
|
||||
type Context = CrossMslContext;
|
||||
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
|
||||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: MslReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The naga module for a shader after compilation
|
||||
pub struct NagaMslModule {
|
||||
pub translation_info: TranslationInfo,
|
||||
pub module: Module,
|
||||
}
|
||||
|
||||
pub struct NagaMslContext {
|
||||
pub vertex: NagaMslModule,
|
||||
pub fragment: NagaMslModule,
|
||||
pub next_free_binding: u32,
|
||||
}
|
||||
|
||||
impl FromCompilation<SpirvCompilation, Naga> for MSL {
|
||||
type Target = MSL;
|
||||
type Options = Option<self::MslVersion>;
|
||||
type Context = NagaMslContext;
|
||||
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
|
||||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: NagaReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
use crate::back::targets::SPIRV;
|
||||
use crate::back::{CompileShader, CompilerBackend, FromCompilation, ShaderCompilerOutput};
|
||||
use crate::error::{ShaderCompileError, ShaderReflectError};
|
||||
use crate::front::GlslangCompilation;
|
||||
use crate::reflect::cross::GlslReflect;
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::cross::glsl::GlslReflect;
|
||||
use crate::reflect::cross::SpirvCross;
|
||||
use crate::reflect::naga::{Naga, NagaLoweringOptions, NagaReflect};
|
||||
use crate::reflect::semantics::ShaderSemantics;
|
||||
use crate::reflect::{ReflectShader, ShaderReflection};
|
||||
use naga::Module;
|
||||
|
||||
pub(crate) struct WriteSpirV {
|
||||
// rely on GLSL to provide out reflection but we don't actually need the AST.
|
||||
|
@ -13,7 +16,7 @@ pub(crate) struct WriteSpirV {
|
|||
pub(crate) fragment: Vec<u32>,
|
||||
}
|
||||
|
||||
impl FromCompilation<GlslangCompilation> for SPIRV {
|
||||
impl FromCompilation<SpirvCompilation, SpirvCross> for SPIRV {
|
||||
type Target = SPIRV;
|
||||
type Options = Option<()>;
|
||||
type Context = ();
|
||||
|
@ -21,7 +24,7 @@ impl FromCompilation<GlslangCompilation> for SPIRV {
|
|||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: GlslangCompilation,
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
let reflect = GlslReflect::try_from(&compile)?;
|
||||
let vertex = compile.vertex;
|
||||
|
@ -61,3 +64,30 @@ impl CompileShader<SPIRV> for WriteSpirV {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The context for a SPIRV compilation via Naga
|
||||
pub struct NagaSpirvContext {
|
||||
pub fragment: Module,
|
||||
pub vertex: Module,
|
||||
}
|
||||
|
||||
impl FromCompilation<SpirvCompilation, Naga> for SPIRV {
|
||||
type Target = SPIRV;
|
||||
type Options = NagaSpirvOptions;
|
||||
type Context = NagaSpirvContext;
|
||||
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
|
||||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: NagaReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NagaSpirvOptions {
|
||||
pub lowering: NagaLoweringOptions,
|
||||
pub version: (u8, u8),
|
||||
}
|
||||
|
|
|
@ -5,35 +5,54 @@ pub trait OutputTarget {
|
|||
}
|
||||
|
||||
/// Shader compiler target for GLSL.
|
||||
#[derive(Debug)]
|
||||
pub struct GLSL;
|
||||
/// Shader compiler target for HLSL.
|
||||
#[derive(Debug)]
|
||||
pub struct HLSL;
|
||||
/// Shader compiler target for SPIR-V.
|
||||
#[derive(Debug)]
|
||||
pub struct SPIRV;
|
||||
/// Shader compiler target for MSL.
|
||||
#[derive(Debug)]
|
||||
pub struct MSL;
|
||||
/// Shader compiler target for DXIL.
|
||||
///
|
||||
/// The resulting DXIL object is always unvalidated and
|
||||
/// must be validated using platform APIs before usage.
|
||||
#[derive(Debug)]
|
||||
pub struct DXIL;
|
||||
|
||||
/// Shader compiler target for WGSL.
|
||||
///
|
||||
/// The resulting WGSL will split sampler2Ds into
|
||||
/// split textures and shaders. Shaders for each texture binding
|
||||
/// will be in descriptor set 1.
|
||||
#[derive(Debug)]
|
||||
pub struct WGSL;
|
||||
impl OutputTarget for GLSL {
|
||||
type Output = String;
|
||||
}
|
||||
impl OutputTarget for HLSL {
|
||||
type Output = String;
|
||||
}
|
||||
impl OutputTarget for WGSL {
|
||||
type Output = String;
|
||||
}
|
||||
impl OutputTarget for MSL {
|
||||
type Output = String;
|
||||
}
|
||||
impl OutputTarget for SPIRV {
|
||||
type Output = Vec<u32>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::back::targets::GLSL;
|
||||
use crate::back::FromCompilation;
|
||||
use crate::front::GlslangCompilation;
|
||||
use crate::front::SpirvCompilation;
|
||||
#[allow(dead_code)]
|
||||
pub fn test_compile(value: GlslangCompilation) {
|
||||
pub fn test_compile(value: SpirvCompilation) {
|
||||
let _x = GLSL::from_compilation(value).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
95
librashader-reflect/src/back/wgsl.rs
Normal file
95
librashader-reflect/src/back/wgsl.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use crate::back::targets::WGSL;
|
||||
use crate::back::{CompileShader, CompilerBackend, FromCompilation};
|
||||
use crate::error::ShaderReflectError;
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::naga::{Naga, NagaLoweringOptions, NagaReflect};
|
||||
use crate::reflect::ReflectShader;
|
||||
use naga::Module;
|
||||
|
||||
/// The context for a WGSL compilation via Naga
|
||||
pub struct NagaWgslContext {
|
||||
pub fragment: Module,
|
||||
pub vertex: Module,
|
||||
}
|
||||
|
||||
impl FromCompilation<SpirvCompilation, Naga> for WGSL {
|
||||
type Target = WGSL;
|
||||
type Options = NagaLoweringOptions;
|
||||
type Context = NagaWgslContext;
|
||||
type Output = impl CompileShader<Self::Target, Options = Self::Options, Context = Self::Context>
|
||||
+ ReflectShader;
|
||||
|
||||
fn from_compilation(
|
||||
compile: SpirvCompilation,
|
||||
) -> Result<CompilerBackend<Self::Output>, ShaderReflectError> {
|
||||
Ok(CompilerBackend {
|
||||
backend: NagaReflect::try_from(&compile)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::back::targets::WGSL;
|
||||
use crate::back::{CompileShader, FromCompilation};
|
||||
use crate::reflect::naga::NagaLoweringOptions;
|
||||
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
|
||||
use crate::reflect::ReflectShader;
|
||||
use bitflags::Flags;
|
||||
use librashader_common::map::FastHashMap;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
|
||||
#[test]
|
||||
pub fn test_into() {
|
||||
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
|
||||
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
|
||||
let result = ShaderSource::load("../test/basic.slang").unwrap();
|
||||
|
||||
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
|
||||
|
||||
for (_index, param) in result.parameters.iter().enumerate() {
|
||||
uniform_semantics.insert(
|
||||
param.1.id.clone(),
|
||||
UniformSemantic::Unique(Semantic {
|
||||
semantics: UniqueSemantics::FloatParameter,
|
||||
index: (),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
|
||||
|
||||
let mut wgsl = WGSL::from_compilation(compilation).unwrap();
|
||||
|
||||
wgsl.reflect(
|
||||
0,
|
||||
&ShaderSemantics {
|
||||
uniform_semantics,
|
||||
texture_semantics: Default::default(),
|
||||
},
|
||||
)
|
||||
.expect("");
|
||||
|
||||
let compiled = wgsl
|
||||
.compile(NagaLoweringOptions {
|
||||
write_pcb_as_ubo: true,
|
||||
sampler_bind_group: 1,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
println!("{}", compiled.fragment);
|
||||
|
||||
// println!("{}", compiled.fragment);
|
||||
// let mut loader = rspirv::dr::Loader::new();
|
||||
// rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
|
||||
// let module = loader.module();
|
||||
//
|
||||
// let outputs: Vec<&Instruction> = module
|
||||
// .types_global_values
|
||||
// .iter()
|
||||
// .filter(|i| i.class.opcode == Op::Variable)
|
||||
// .collect();
|
||||
//
|
||||
// println!("{outputs:#?}");
|
||||
}
|
||||
}
|
|
@ -6,26 +6,46 @@ use thiserror::Error;
|
|||
#[derive(Error, Debug)]
|
||||
pub enum ShaderCompileError {
|
||||
/// Compile error from naga.
|
||||
#[cfg(feature = "unstable-naga")]
|
||||
#[cfg(feature = "unstable-naga-in")]
|
||||
#[error("shader")]
|
||||
NagaCompileError(Vec<naga::front::glsl::Error>),
|
||||
|
||||
/// Compilation error from shaderc (glslang).
|
||||
#[error("shaderc")]
|
||||
ShaderCCompileError(#[from] shaderc::Error),
|
||||
/// Compilation error from glslang.
|
||||
#[error("error when compiling with glslang: {0}")]
|
||||
GlslangError(#[from] glslang::error::GlslangError),
|
||||
|
||||
/// Error when initializing the shaderc compiler.
|
||||
#[error("shaderc init")]
|
||||
ShaderCInitError,
|
||||
/// Error when initializing the glslang compiler.
|
||||
#[error("error when initializing glslang")]
|
||||
CompilerInitError,
|
||||
|
||||
/// Error when transpiling from spirv-cross.
|
||||
#[error("cross")]
|
||||
#[error("spirv-cross error: {0:?}")]
|
||||
SpirvCrossCompileError(#[from] spirv_cross::ErrorCode),
|
||||
|
||||
/// Error when transpiling from spirv-to-dxil
|
||||
#[cfg(feature = "dxil")]
|
||||
#[error("spirv-to-dxil")]
|
||||
#[cfg(all(target_os = "windows", feature = "dxil"))]
|
||||
#[error("spirv-to-dxil error: {0:?}")]
|
||||
SpirvToDxilCompileError(#[from] spirv_to_dxil::SpirvToDxilError),
|
||||
|
||||
/// Error when transpiling from naga
|
||||
#[cfg(all(feature = "wgsl", feature = "naga"))]
|
||||
#[error("naga error when compiling wgsl: {0:?}")]
|
||||
NagaWgslError(#[from] naga::back::wgsl::Error),
|
||||
|
||||
/// Error when transpiling from naga
|
||||
#[cfg(feature = "naga")]
|
||||
#[error("naga error when compiling spirv: {0:?}")]
|
||||
NagaSpvError(#[from] naga::back::spv::Error),
|
||||
|
||||
/// Error when transpiling from naga
|
||||
#[cfg(all(feature = "naga", feature = "msl"))]
|
||||
#[error("naga error when compiling msl: {0:?}")]
|
||||
NagaMslError(#[from] naga::back::msl::Error),
|
||||
|
||||
/// Error when transpiling from naga
|
||||
#[cfg(any(feature = "naga", feature = "wgsl"))]
|
||||
#[error("naga validation error: {0}")]
|
||||
NagaValidationError(#[from] naga::WithSpan<naga::valid::ValidationError>),
|
||||
}
|
||||
|
||||
/// The error kind encountered when reflecting shader semantics.
|
||||
|
@ -34,6 +54,8 @@ pub enum SemanticsErrorKind {
|
|||
/// The number of uniform buffers was invalid. Only one UBO is permitted.
|
||||
InvalidUniformBufferCount(usize),
|
||||
/// The number of push constant blocks was invalid. Only one push constant block is permitted.
|
||||
InvalidPushBufferCount(usize),
|
||||
/// The size of the push constant block was invalid.
|
||||
InvalidPushBufferSize(u32),
|
||||
/// The location of a varying was invalid.
|
||||
InvalidLocation(u32),
|
||||
|
@ -43,12 +65,16 @@ pub enum SemanticsErrorKind {
|
|||
InvalidInputCount(usize),
|
||||
/// The number of outputs declared was invalid.
|
||||
InvalidOutputCount(usize),
|
||||
/// Expected a binding but there was none.
|
||||
MissingBinding,
|
||||
/// The declared binding point was invalid.
|
||||
InvalidBinding(u32),
|
||||
/// The declared resource type was invalid.
|
||||
InvalidResourceType,
|
||||
/// The range of a struct member was invalid.
|
||||
InvalidRange(u32),
|
||||
/// The number of entry points in the shader was invalid.
|
||||
InvalidEntryPointCount(usize),
|
||||
/// The requested uniform or texture name was not provided semantics.
|
||||
UnknownSemantics(String),
|
||||
/// The type of the requested uniform was not compatible with the provided semantics.
|
||||
|
@ -59,29 +85,24 @@ pub enum SemanticsErrorKind {
|
|||
#[non_exhaustive]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ShaderReflectError {
|
||||
/// Compile error from naga.
|
||||
#[cfg(feature = "unstable-naga")]
|
||||
#[error("shader")]
|
||||
NagaCompileError(#[from] naga::front::spv::Error),
|
||||
|
||||
/// Reflection error from spirv-cross.
|
||||
#[error("spirv")]
|
||||
#[error("spirv cross error: {0}")]
|
||||
SpirvCrossError(#[from] spirv_cross::ErrorCode),
|
||||
/// Error when validating vertex shader semantics.
|
||||
#[error("error when verifying vertex semantics")]
|
||||
#[error("error when verifying vertex semantics: {0:?}")]
|
||||
VertexSemanticError(SemanticsErrorKind),
|
||||
/// Error when validating fragment shader semantics.
|
||||
#[error("error when verifying texture semantics")]
|
||||
#[error("error when verifying texture semantics {0:?}")]
|
||||
FragmentSemanticError(SemanticsErrorKind),
|
||||
/// The vertex and fragment shader must have the same UBO binding location.
|
||||
#[error("vertex and fragment shader must have same binding")]
|
||||
#[error("vertex and fragment shader must have same UBO binding. declared {vertex} in vertex, got {fragment} in fragment")]
|
||||
MismatchedUniformBuffer { vertex: u32, fragment: u32 },
|
||||
/// The filter chain was found to be non causal. A pass tried to access the target output
|
||||
/// in the future.
|
||||
#[error("filter chain is non causal")]
|
||||
#[error("filter chain is non causal: tried to access target {target} in pass {pass}")]
|
||||
NonCausalFilterChain { pass: usize, target: usize },
|
||||
/// The offset of the given uniform did not match up in both the vertex and fragment shader.
|
||||
#[error("mismatched offset")]
|
||||
#[error("the offset of {semantic} was declared as {expected} but found as {received} in pass {pass}")]
|
||||
MismatchedOffset {
|
||||
semantic: String,
|
||||
expected: usize,
|
||||
|
@ -90,7 +111,7 @@ pub enum ShaderReflectError {
|
|||
pass: usize,
|
||||
},
|
||||
/// The size of the given uniform did not match up in both the vertex and fragment shader.
|
||||
#[error("mismatched component")]
|
||||
#[error("the size of {semantic} was found declared as {vertex} in vertex shader but found as {fragment} in fragment in pass {pass}")]
|
||||
MismatchedSize {
|
||||
semantic: String,
|
||||
vertex: u32,
|
||||
|
@ -98,11 +119,19 @@ pub enum ShaderReflectError {
|
|||
pass: usize,
|
||||
},
|
||||
/// The binding number is already in use.
|
||||
#[error("the binding is already in use")]
|
||||
#[error("binding {0} is already in use")]
|
||||
BindingInUse(u32),
|
||||
/// Error when transpiling from naga
|
||||
#[cfg(feature = "naga")]
|
||||
#[error("naga spirv error: {0}")]
|
||||
NagaInputError(#[from] naga::front::spv::Error),
|
||||
/// Error when transpiling from naga
|
||||
#[cfg(feature = "naga")]
|
||||
#[error("naga validation error: {0}")]
|
||||
NagaReflectError(#[from] naga::WithSpan<naga::valid::ValidationError>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-naga")]
|
||||
#[cfg(feature = "unstable-naga-in")]
|
||||
impl From<Vec<naga::front::glsl::Error>> for ShaderCompileError {
|
||||
fn from(err: Vec<naga::front::glsl::Error>) -> Self {
|
||||
ShaderCompileError::NagaCompileError(err)
|
||||
|
|
63
librashader-reflect/src/front/glslang.rs
Normal file
63
librashader-reflect/src/front/glslang.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use crate::error::ShaderCompileError;
|
||||
use glslang::{CompilerOptions, ShaderInput};
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use rspirv::binary::Assemble;
|
||||
use rspirv::dr::Builder;
|
||||
|
||||
use crate::front::spirv_passes::{link_input_outputs, load_module};
|
||||
use crate::front::{ShaderInputCompiler, SpirvCompilation};
|
||||
|
||||
/// glslang compiler
|
||||
pub struct Glslang;
|
||||
|
||||
impl ShaderInputCompiler<SpirvCompilation> for Glslang {
|
||||
fn compile(source: &ShaderSource) -> Result<SpirvCompilation, ShaderCompileError> {
|
||||
compile_spirv(source)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compile_spirv(source: &ShaderSource) -> Result<SpirvCompilation, ShaderCompileError> {
|
||||
let compiler = glslang::Compiler::acquire().ok_or(ShaderCompileError::CompilerInitError)?;
|
||||
let options = CompilerOptions {
|
||||
source_language: glslang::SourceLanguage::GLSL,
|
||||
target: glslang::Target::Vulkan {
|
||||
version: glslang::VulkanVersion::Vulkan1_0,
|
||||
spirv_version: glslang::SpirvVersion::SPIRV1_0,
|
||||
},
|
||||
version_profile: None,
|
||||
};
|
||||
|
||||
let vertex = glslang::ShaderSource::from(source.vertex.as_str());
|
||||
let vertex = ShaderInput::new(&vertex, glslang::ShaderStage::Vertex, &options, None)?;
|
||||
let vertex = compiler.create_shader(vertex)?;
|
||||
|
||||
let fragment = glslang::ShaderSource::from(source.fragment.as_str());
|
||||
let fragment = ShaderInput::new(&fragment, glslang::ShaderStage::Fragment, &options, None)?;
|
||||
let fragment = compiler.create_shader(fragment)?;
|
||||
|
||||
let vertex = vertex.compile()?;
|
||||
let fragment = fragment.compile()?;
|
||||
|
||||
let vertex = load_module(&vertex);
|
||||
let fragment = load_module(&fragment);
|
||||
let mut fragment = Builder::new_from_module(fragment);
|
||||
|
||||
let mut pass = link_input_outputs::LinkInputs::new(&vertex, &mut fragment);
|
||||
pass.do_pass();
|
||||
|
||||
let vertex = vertex.assemble();
|
||||
let fragment = fragment.module().assemble();
|
||||
|
||||
Ok(SpirvCompilation { vertex, fragment })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::front::glslang::compile_spirv;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
#[test]
|
||||
pub fn compile_shader() {
|
||||
let result = ShaderSource::load("../test/basic.slang").unwrap();
|
||||
let _spirv = compile_spirv(&result).unwrap();
|
||||
}
|
||||
}
|
|
@ -1,24 +1,49 @@
|
|||
use crate::error::ShaderCompileError;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub(crate) mod spirv_passes;
|
||||
|
||||
#[cfg(feature = "unstable-naga")]
|
||||
mod naga;
|
||||
mod glslang;
|
||||
|
||||
mod shaderc;
|
||||
/// The output of a shader compiler that is reflectable.
|
||||
pub trait ShaderReflectObject: Sized {
|
||||
/// The compiler that produces this reflect object.
|
||||
type Compiler;
|
||||
}
|
||||
|
||||
pub use crate::front::shaderc::GlslangCompilation;
|
||||
|
||||
#[cfg(feature = "unstable-naga")]
|
||||
pub use crate::front::naga::NagaCompilation;
|
||||
pub use crate::front::glslang::Glslang;
|
||||
|
||||
/// Trait for types that can compile shader sources into a compilation unit.
|
||||
pub trait ShaderCompilation: Sized {
|
||||
pub trait ShaderInputCompiler<O: ShaderReflectObject>: Sized {
|
||||
/// Compile the input shader source file into a compilation unit.
|
||||
fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError>;
|
||||
fn compile(source: &ShaderSource) -> Result<O, ShaderCompileError>;
|
||||
}
|
||||
|
||||
impl<T: for<'a> TryFrom<&'a ShaderSource, Error = ShaderCompileError>> ShaderCompilation for T {
|
||||
fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
|
||||
source.try_into()
|
||||
/// Marker trait for types that are the reflectable outputs of a shader compilation.
|
||||
impl ShaderReflectObject for SpirvCompilation {
|
||||
type Compiler = Glslang;
|
||||
}
|
||||
|
||||
/// A reflectable shader compilation via glslang.
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpirvCompilation {
|
||||
pub(crate) vertex: Vec<u32>,
|
||||
pub(crate) fragment: Vec<u32>,
|
||||
}
|
||||
|
||||
impl SpirvCompilation {
|
||||
/// Tries to compile SPIR-V from the provided shader source.
|
||||
pub fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
|
||||
glslang::compile_spirv(source)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ShaderSource> for SpirvCompilation {
|
||||
type Error = ShaderCompileError;
|
||||
|
||||
/// Tries to compile SPIR-V from the provided shader source.
|
||||
fn try_from(source: &ShaderSource) -> Result<Self, Self::Error> {
|
||||
Glslang::compile(source)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
use crate::error::ShaderCompileError;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use naga::front::glsl::{Options, Parser};
|
||||
use naga::{Module, ShaderStage};
|
||||
|
||||
/// A reflectable shader compilation via naga.
|
||||
#[derive(Debug)]
|
||||
pub struct NagaCompilation {
|
||||
pub(crate) vertex: Module,
|
||||
pub(crate) fragment: Module,
|
||||
}
|
||||
|
||||
impl TryFrom<&ShaderSource> for NagaCompilation {
|
||||
type Error = ShaderCompileError;
|
||||
|
||||
/// Tries to compile SPIR-V from the provided shader source.
|
||||
fn try_from(source: &ShaderSource) -> Result<Self, Self::Error> {
|
||||
compile_spirv(source)
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_spirv(source: &ShaderSource) -> Result<NagaCompilation, ShaderCompileError> {
|
||||
let mut parser = Parser::default();
|
||||
let vertex = parser.parse(&Options::from(ShaderStage::Vertex), &source.vertex)?;
|
||||
let fragment = parser.parse(&Options::from(ShaderStage::Fragment), &source.fragment)?;
|
||||
Ok(NagaCompilation { vertex, fragment })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::front::naga::compile_spirv;
|
||||
use crate::front::shaderc::GlslangCompilation;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use naga::back::glsl::{PipelineOptions, Version};
|
||||
use naga::back::spv::{Capability, WriterFlags};
|
||||
use naga::front::glsl::{Options, Parser};
|
||||
use naga::front::spv::Options as SpvOptions;
|
||||
use naga::valid::{Capabilities, ValidationFlags};
|
||||
use naga::{FastHashSet, ShaderStage};
|
||||
use rspirv::binary::Disassemble;
|
||||
|
||||
#[test]
|
||||
pub fn compile_naga_test() {
|
||||
let result = ShaderSource::load(
|
||||
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let fragment_source = result.fragment;
|
||||
let mut parser = Parser::default();
|
||||
println!("{fragment_source}");
|
||||
let _fragment = parser
|
||||
.parse(&Options::from(ShaderStage::Fragment), &fragment_source)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn compile_shader() {
|
||||
let result = ShaderSource::load(
|
||||
"../test/slang-shaders/blurs/shaders/royale/blur3x3-last-pass.slang",
|
||||
)
|
||||
.unwrap();
|
||||
let spirv = compile_spirv(&result).unwrap();
|
||||
eprintln!("{spirv:?}")
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn compile_shader_roundtrip() {
|
||||
let result = ShaderSource::load("../test/basic.slang").unwrap();
|
||||
let cross = GlslangCompilation::compile(&result).unwrap();
|
||||
let naga_fragment =
|
||||
naga::front::spv::parse_u8_slice(cross.fragment.as_binary_u8(), &SpvOptions::default())
|
||||
.unwrap();
|
||||
println!("{:#?}", naga_fragment.constants);
|
||||
println!("{:#?}", naga_fragment.global_variables);
|
||||
println!("{:#?}", naga_fragment.types);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn naga_playground() {
|
||||
let result = ShaderSource::load("../test/basic.slang").unwrap();
|
||||
let spirv = GlslangCompilation::compile(&result).unwrap();
|
||||
|
||||
let module =
|
||||
naga::front::spv::parse_u8_slice(spirv.fragment.as_binary_u8(), &SpvOptions::default())
|
||||
.unwrap();
|
||||
|
||||
let capability = FastHashSet::from_iter([Capability::Shader]);
|
||||
let mut writer = naga::back::spv::Writer::new(&naga::back::spv::Options {
|
||||
lang_version: (1, 0),
|
||||
flags: WriterFlags::all(),
|
||||
binding_map: Default::default(),
|
||||
capabilities: Some(capability),
|
||||
bounds_check_policies: Default::default(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut validator =
|
||||
naga::valid::Validator::new(ValidationFlags::empty(), Capabilities::all());
|
||||
let info = validator.validate(&module).unwrap();
|
||||
let mut spv_out = Vec::new();
|
||||
let pipe = naga::back::spv::PipelineOptions {
|
||||
shader_stage: ShaderStage::Fragment,
|
||||
entry_point: "main".to_string(),
|
||||
};
|
||||
writer
|
||||
.write(&module, &info, Some(&pipe), &mut spv_out)
|
||||
.unwrap();
|
||||
|
||||
let mut glsl_out = String::new();
|
||||
let opts = naga::back::glsl::Options {
|
||||
version: Version::Desktop(330),
|
||||
writer_flags: naga::back::glsl::WriterFlags::all(),
|
||||
binding_map: Default::default(),
|
||||
};
|
||||
let pipe = PipelineOptions {
|
||||
shader_stage: ShaderStage::Fragment,
|
||||
entry_point: "main".to_string(),
|
||||
multiview: None,
|
||||
};
|
||||
let mut glsl_naga = naga::back::glsl::Writer::new(
|
||||
&mut glsl_out,
|
||||
&module,
|
||||
&info,
|
||||
&opts,
|
||||
&pipe,
|
||||
Default::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
glsl_naga.write().unwrap();
|
||||
|
||||
let wgsl =
|
||||
naga::back::wgsl::write_string(&module, &info, naga::back::wgsl::WriterFlags::all())
|
||||
.unwrap();
|
||||
|
||||
let mut loader = rspirv::dr::Loader::new();
|
||||
rspirv::binary::parse_words(&spv_out, &mut loader).unwrap();
|
||||
let module = loader.module();
|
||||
println!("--- spirv --");
|
||||
println!("{:#}", module.disassemble());
|
||||
println!("--- cross glsl --");
|
||||
|
||||
let loaded = spirv_cross::spirv::Module::from_words(&spv_out);
|
||||
let mut ast = spirv_cross::spirv::Ast::<spirv_cross::glsl::Target>::parse(&loaded).unwrap();
|
||||
println!("{:#}", ast.compile().unwrap());
|
||||
println!("--- naga glsl---");
|
||||
println!("{glsl_out:#}");
|
||||
println!("--- naga wgsl---");
|
||||
println!("{wgsl:#}")
|
||||
}
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
use crate::error::ShaderCompileError;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use shaderc::{CompileOptions, Limit, ShaderKind};
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A reflectable shader compilation via glslang (shaderc).
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GlslangCompilation {
|
||||
pub(crate) vertex: Vec<u32>,
|
||||
pub(crate) fragment: Vec<u32>,
|
||||
}
|
||||
|
||||
impl GlslangCompilation {
|
||||
/// Tries to compile SPIR-V from the provided shader source.
|
||||
pub fn compile(source: &ShaderSource) -> Result<Self, ShaderCompileError> {
|
||||
compile_spirv(source)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ShaderSource> for GlslangCompilation {
|
||||
type Error = ShaderCompileError;
|
||||
|
||||
/// Tries to compile SPIR-V from the provided shader source.
|
||||
fn try_from(source: &ShaderSource) -> Result<Self, Self::Error> {
|
||||
GlslangCompilation::compile(source)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_shaderc_options() -> Result<CompileOptions<'static>, ShaderCompileError> {
|
||||
let mut options = CompileOptions::new().ok_or(ShaderCompileError::ShaderCInitError)?;
|
||||
options.set_include_callback(|_, _, _, _| {
|
||||
Err("RetroArch shaders must already have includes be preprocessed".into())
|
||||
});
|
||||
options.set_limit(Limit::MaxLights, 32);
|
||||
options.set_limit(Limit::MaxClipPlanes, 6);
|
||||
options.set_limit(Limit::MaxTextureUnits, 32);
|
||||
options.set_limit(Limit::MaxTextureCoords, 32);
|
||||
options.set_limit(Limit::MaxVertexAttribs, 64);
|
||||
options.set_limit(Limit::MaxVertexUniformComponents, 4096);
|
||||
options.set_limit(Limit::MaxVaryingFloats, 64);
|
||||
options.set_limit(Limit::MaxVertexTextureImageUnits, 32);
|
||||
options.set_limit(Limit::MaxCombinedTextureImageUnits, 80);
|
||||
options.set_limit(Limit::MaxTextureImageUnits, 32);
|
||||
options.set_limit(Limit::MaxFragmentUniformComponents, 4096);
|
||||
options.set_limit(Limit::MaxDrawBuffers, 32);
|
||||
options.set_limit(Limit::MaxVertexUniformVectors, 128);
|
||||
options.set_limit(Limit::MaxVaryingVectors, 8);
|
||||
options.set_limit(Limit::MaxFragmentUniformVectors, 16);
|
||||
options.set_limit(Limit::MaxVertexOutputVectors, 16);
|
||||
options.set_limit(Limit::MaxFragmentInputVectors, 15);
|
||||
options.set_limit(Limit::MinProgramTexelOffset, -8);
|
||||
options.set_limit(Limit::MaxProgramTexelOffset, 7);
|
||||
options.set_limit(Limit::MaxClipDistances, 8);
|
||||
options.set_limit(Limit::MaxComputeWorkGroupCountX, 65535);
|
||||
options.set_limit(Limit::MaxComputeWorkGroupCountY, 65535);
|
||||
options.set_limit(Limit::MaxComputeWorkGroupCountZ, 65535);
|
||||
options.set_limit(Limit::MaxComputeWorkGroupSizeX, 1024);
|
||||
options.set_limit(Limit::MaxComputeWorkGroupSizeY, 1024);
|
||||
options.set_limit(Limit::MaxComputeWorkGroupSizeZ, 64);
|
||||
options.set_limit(Limit::MaxComputeUniformComponents, 1024);
|
||||
options.set_limit(Limit::MaxComputeTextureImageUnits, 16);
|
||||
options.set_limit(Limit::MaxComputeImageUniforms, 8);
|
||||
options.set_limit(Limit::MaxComputeAtomicCounters, 8);
|
||||
options.set_limit(Limit::MaxComputeAtomicCounterBuffers, 1);
|
||||
options.set_limit(Limit::MaxVaryingComponents, 60);
|
||||
options.set_limit(Limit::MaxVertexOutputComponents, 64);
|
||||
options.set_limit(Limit::MaxGeometryInputComponents, 64);
|
||||
options.set_limit(Limit::MaxGeometryOutputComponents, 128);
|
||||
options.set_limit(Limit::MaxFragmentInputComponents, 128);
|
||||
options.set_limit(Limit::MaxImageUnits, 8);
|
||||
options.set_limit(Limit::MaxCombinedImageUnitsAndFragmentOutputs, 8);
|
||||
options.set_limit(Limit::MaxCombinedShaderOutputResources, 8);
|
||||
options.set_limit(Limit::MaxImageSamples, 0);
|
||||
options.set_limit(Limit::MaxVertexImageUniforms, 0);
|
||||
options.set_limit(Limit::MaxTessControlImageUniforms, 0);
|
||||
options.set_limit(Limit::MaxTessEvaluationImageUniforms, 0);
|
||||
options.set_limit(Limit::MaxGeometryImageUniforms, 0);
|
||||
options.set_limit(Limit::MaxFragmentImageUniforms, 8);
|
||||
options.set_limit(Limit::MaxCombinedImageUniforms, 8);
|
||||
options.set_limit(Limit::MaxGeometryTextureImageUnits, 16);
|
||||
options.set_limit(Limit::MaxGeometryOutputVertices, 256);
|
||||
options.set_limit(Limit::MaxGeometryTotalOutputComponents, 1024);
|
||||
options.set_limit(Limit::MaxGeometryUniformComponents, 1024);
|
||||
options.set_limit(Limit::MaxGeometryVaryingComponents, 64);
|
||||
options.set_limit(Limit::MaxTessControlInputComponents, 128);
|
||||
options.set_limit(Limit::MaxTessControlOutputComponents, 128);
|
||||
options.set_limit(Limit::MaxTessControlTextureImageUnits, 16);
|
||||
options.set_limit(Limit::MaxTessControlUniformComponents, 1024);
|
||||
options.set_limit(Limit::MaxTessControlTotalOutputComponents, 4096);
|
||||
options.set_limit(Limit::MaxTessEvaluationInputComponents, 128);
|
||||
options.set_limit(Limit::MaxTessEvaluationOutputComponents, 128);
|
||||
options.set_limit(Limit::MaxTessEvaluationTextureImageUnits, 16);
|
||||
options.set_limit(Limit::MaxTessEvaluationUniformComponents, 1024);
|
||||
options.set_limit(Limit::MaxTessPatchComponents, 120);
|
||||
options.set_limit(Limit::MaxPatchVertices, 32);
|
||||
options.set_limit(Limit::MaxTessGenLevel, 64);
|
||||
options.set_limit(Limit::MaxViewports, 16);
|
||||
options.set_limit(Limit::MaxVertexAtomicCounters, 0);
|
||||
options.set_limit(Limit::MaxTessControlAtomicCounters, 0);
|
||||
options.set_limit(Limit::MaxTessEvaluationAtomicCounters, 0);
|
||||
options.set_limit(Limit::MaxGeometryAtomicCounters, 0);
|
||||
options.set_limit(Limit::MaxFragmentAtomicCounters, 8);
|
||||
options.set_limit(Limit::MaxCombinedAtomicCounters, 8);
|
||||
options.set_limit(Limit::MaxAtomicCounterBindings, 1);
|
||||
options.set_limit(Limit::MaxVertexAtomicCounterBuffers, 0);
|
||||
options.set_limit(Limit::MaxTessControlAtomicCounterBuffers, 0);
|
||||
options.set_limit(Limit::MaxTessEvaluationAtomicCounterBuffers, 0);
|
||||
options.set_limit(Limit::MaxGeometryAtomicCounterBuffers, 0);
|
||||
options.set_limit(Limit::MaxFragmentAtomicCounterBuffers, 1);
|
||||
options.set_limit(Limit::MaxCombinedAtomicCounterBuffers, 1);
|
||||
options.set_limit(Limit::MaxAtomicCounterBufferSize, 16384);
|
||||
options.set_limit(Limit::MaxTransformFeedbackBuffers, 4);
|
||||
options.set_limit(Limit::MaxTransformFeedbackInterleavedComponents, 64);
|
||||
options.set_limit(Limit::MaxCullDistances, 8);
|
||||
options.set_limit(Limit::MaxCombinedClipAndCullDistances, 8);
|
||||
options.set_limit(Limit::MaxSamples, 4);
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
pub(crate) fn compile_spirv(
|
||||
source: &ShaderSource,
|
||||
) -> Result<GlslangCompilation, ShaderCompileError> {
|
||||
let compiler = shaderc::Compiler::new().ok_or(ShaderCompileError::ShaderCInitError)?;
|
||||
let name = source.name.as_deref().unwrap_or("shader.slang");
|
||||
let options = get_shaderc_options()?;
|
||||
|
||||
let vertex = compiler.compile_into_spirv(
|
||||
&source.vertex,
|
||||
ShaderKind::Vertex,
|
||||
name,
|
||||
"main",
|
||||
Some(&options),
|
||||
)?;
|
||||
let fragment = compiler.compile_into_spirv(
|
||||
&source.fragment,
|
||||
ShaderKind::Fragment,
|
||||
name,
|
||||
"main",
|
||||
Some(&options),
|
||||
)?;
|
||||
|
||||
// shaderc has a GIL so Send is unsafe.
|
||||
let vertex = Vec::from(vertex.as_binary());
|
||||
let fragment = Vec::from(fragment.as_binary());
|
||||
|
||||
Ok(GlslangCompilation { vertex, fragment })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::front::shaderc::compile_spirv;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
#[test]
|
||||
pub fn compile_shader() {
|
||||
let result = ShaderSource::load("../test/basic.slang").unwrap();
|
||||
let _spirv = compile_spirv(&result).unwrap();
|
||||
}
|
||||
}
|
151
librashader-reflect/src/front/spirv_passes/link_input_outputs.rs
Normal file
151
librashader-reflect/src/front/spirv_passes/link_input_outputs.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
use rspirv::dr::{Builder, Module, Operand};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use spirv::{Decoration, Op, StorageClass};
|
||||
|
||||
pub struct LinkInputs<'a> {
|
||||
pub frag_builder: &'a mut Builder,
|
||||
pub inputs: FxHashSet<spirv::Word>,
|
||||
}
|
||||
|
||||
impl<'a> LinkInputs<'a> {
|
||||
fn find_location(module: &Module, id: spirv::Word) -> Option<u32> {
|
||||
module.annotations.iter().find_map(|op| {
|
||||
if op.class.opcode != Op::Decorate {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(Operand::Decoration(Decoration::Location)) = op.operands.get(1) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(&Operand::IdRef(target)) = op.operands.get(0) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if target != id {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(&Operand::LiteralBit32(binding)) = op.operands.get(2) else {
|
||||
return None;
|
||||
};
|
||||
return Some(binding);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(vert: &'a Module, frag: &'a mut Builder) -> Self {
|
||||
let mut inputs = FxHashSet::default();
|
||||
let mut bindings = FxHashMap::default();
|
||||
for global in frag.module_ref().types_global_values.iter() {
|
||||
if global.class.opcode == spirv::Op::Variable
|
||||
&& global.operands[0] == Operand::StorageClass(StorageClass::Input)
|
||||
{
|
||||
if let Some(id) = global.result_id {
|
||||
let Some(location) = Self::find_location(frag.module_ref(), id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
inputs.insert(id);
|
||||
bindings.insert(location, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for global in vert.types_global_values.iter() {
|
||||
if global.class.opcode == spirv::Op::Variable
|
||||
&& global.operands[0] == Operand::StorageClass(StorageClass::Output)
|
||||
{
|
||||
if let Some(id) = global.result_id {
|
||||
let Some(location) = Self::find_location(vert, id) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(frag_ref) = bindings.get(&location) {
|
||||
// if something is bound to the same location in the vertex shader,
|
||||
// we're good.
|
||||
inputs.remove(&frag_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
frag_builder: frag,
|
||||
inputs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_pass(&mut self) {
|
||||
let functions = &self.frag_builder.module_ref().functions;
|
||||
|
||||
// literally if it has any reference at all we can keep it
|
||||
for function in functions {
|
||||
for param in &function.parameters {
|
||||
for op in ¶m.operands {
|
||||
if let Some(word) = op.id_ref_any() {
|
||||
if self.inputs.contains(&word) {
|
||||
self.inputs.remove(&word);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for block in &function.blocks {
|
||||
for inst in &block.instructions {
|
||||
for op in &inst.operands {
|
||||
if let Some(word) = op.id_ref_any() {
|
||||
if self.inputs.contains(&word) {
|
||||
self.inputs.remove(&word);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ok well guess we dont
|
||||
|
||||
self.frag_builder.module_mut().debug_names.retain(|instr| {
|
||||
for op in &instr.operands {
|
||||
if let Some(word) = op.id_ref_any() {
|
||||
if self.inputs.contains(&word) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
self.frag_builder.module_mut().annotations.retain(|instr| {
|
||||
for op in &instr.operands {
|
||||
if let Some(word) = op.id_ref_any() {
|
||||
if self.inputs.contains(&word) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
for entry_point in self.frag_builder.module_mut().entry_points.iter_mut() {
|
||||
entry_point.operands.retain(|op| {
|
||||
if let Some(word) = op.id_ref_any() {
|
||||
if self.inputs.contains(&word) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})
|
||||
}
|
||||
|
||||
self.frag_builder
|
||||
.module_mut()
|
||||
.types_global_values
|
||||
.retain(|instr| {
|
||||
let Some(id) = instr.result_id else {
|
||||
return true;
|
||||
};
|
||||
|
||||
!self.inputs.contains(&id)
|
||||
});
|
||||
}
|
||||
}
|
1078
librashader-reflect/src/front/spirv_passes/lower_samplers.rs
Normal file
1078
librashader-reflect/src/front/spirv_passes/lower_samplers.rs
Normal file
File diff suppressed because it is too large
Load diff
10
librashader-reflect/src/front/spirv_passes/mod.rs
Normal file
10
librashader-reflect/src/front/spirv_passes/mod.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
pub mod link_input_outputs;
|
||||
pub mod lower_samplers;
|
||||
|
||||
// Load SPIR-V as an rspirv module
|
||||
pub(crate) fn load_module(words: &[u32]) -> rspirv::dr::Module {
|
||||
let mut loader = rspirv::dr::Loader::new();
|
||||
rspirv::binary::parse_words(words, &mut loader).unwrap();
|
||||
let module = loader.module();
|
||||
module
|
||||
}
|
|
@ -13,10 +13,11 @@
|
|||
//! use librashader_presets::ShaderPreset;
|
||||
//! use librashader_reflect::back::{CompileReflectShader, FromCompilation};
|
||||
//! use librashader_reflect::back::targets::SPIRV;
|
||||
//! use librashader_reflect::front::GlslangCompilation;
|
||||
//! use librashader_reflect::front::{Glslang, ShaderInputCompiler, SpirvCompilation};
|
||||
//! use librashader_reflect::reflect::cross::SpirvCross;
|
||||
//! use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
|
||||
//! use librashader_reflect::reflect::semantics::ShaderSemantics;
|
||||
//! type Artifact = impl CompileReflectShader<SPIRV, GlslangCompilation>;
|
||||
//! type Artifact = impl CompileReflectShader<SPIRV, SpirvCompilation, SpirvCross>;
|
||||
//! type ShaderPassMeta = ShaderPassArtifact<Artifact>;
|
||||
//!
|
||||
//! // Compile single shader
|
||||
|
@ -24,28 +25,24 @@
|
|||
//! source: &ShaderSource,
|
||||
//! ) -> Result<Artifact, Box<dyn Error>>
|
||||
//! {
|
||||
//! let compilation = GlslangCompilation::compile(&source)?;
|
||||
//! let spirv = SPIRV::from_compilation(artifact)?;
|
||||
//! let compilation = SpirvCompilation::try_from(&source);
|
||||
//! let spirv = SPIRV::from_compilation(compilation)?;
|
||||
//! Ok(spirv)
|
||||
//! }
|
||||
//!
|
||||
//! // Compile preset
|
||||
//! pub fn compile_preset(preset: ShaderPreset) -> Result<(Vec<ShaderPassMeta>, ShaderSemantics), Box<dyn Error>>
|
||||
//! {
|
||||
//! let (passes, semantics) = SPIRV::compile_preset_passes::<GlslangCompilation, Box<dyn Error>>(
|
||||
//! let (passes, semantics) = SPIRV::compile_preset_passes::<SpirvCompilation, SpirvCross, Box<dyn Error>>(
|
||||
//! preset.shaders, &preset.textures)?;
|
||||
//! Ok((passes, semantics))
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## What's with all the traits?
|
||||
//! librashader-reflect is designed to be compiler-agnostic. In the future, we will allow usage of
|
||||
//! [naga](https://docs.rs/naga/latest/naga/index.html), a pure-Rust shader compiler, when it has
|
||||
//! matured enough to support [the features librashader needs](https://github.com/gfx-rs/naga/issues/1012).
|
||||
//!
|
||||
//! In the meanwhile, the only supported compilation type is [GlslangCompilation](crate::front::GlslangCompilation),
|
||||
//! which does transpilation via [shaderc](https://github.com/google/shaderc) and [SPIRV-Cross](https://github.com/KhronosGroup/SPIRV-Cross).
|
||||
#![feature(type_alias_impl_trait)]
|
||||
//! librashader-reflect is designed to be compiler-agnostic. [naga](https://docs.rs/naga/latest/naga/index.html),
|
||||
//! a pure-Rust shader compiler, as well as SPIRV-Cross via [SpirvCompilation](crate::front::SpirvCompilation)
|
||||
//! is supported.
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
#![feature(let_chains)]
|
||||
|
||||
|
|
139
librashader-reflect/src/reflect/cross/glsl.rs
Normal file
139
librashader-reflect/src/reflect/cross/glsl.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use crate::back::glsl::CrossGlslContext;
|
||||
use crate::back::targets::GLSL;
|
||||
use crate::back::{CompileShader, ShaderCompilerOutput};
|
||||
use crate::error::ShaderCompileError;
|
||||
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
|
||||
use spirv_cross::spirv::Decoration;
|
||||
use spirv_cross::ErrorCode;
|
||||
|
||||
pub(crate) type GlslReflect = CrossReflect<spirv_cross::glsl::Target>;
|
||||
|
||||
impl CompileShader<GLSL> for CrossReflect<spirv_cross::glsl::Target> {
|
||||
type Options = spirv_cross::glsl::Version;
|
||||
type Context = CrossGlslContext;
|
||||
|
||||
fn compile(
|
||||
mut self,
|
||||
version: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
|
||||
let mut options: spirv_cross::glsl::CompilerOptions = Default::default();
|
||||
options.version = version;
|
||||
options.fragment.default_float_precision = spirv_cross::glsl::Precision::High;
|
||||
options.fragment.default_int_precision = spirv_cross::glsl::Precision::High;
|
||||
options.enable_420_pack_extension = false;
|
||||
|
||||
self.vertex.set_compiler_options(&options)?;
|
||||
self.fragment.set_compiler_options(&options)?;
|
||||
|
||||
let vertex_resources = self.vertex.get_shader_resources()?;
|
||||
let fragment_resources = self.fragment.get_shader_resources()?;
|
||||
|
||||
for res in &vertex_resources.stage_outputs {
|
||||
// let location = self.vertex.get_decoration(res.id, Decoration::Location)?;
|
||||
// self.vertex
|
||||
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
|
||||
self.vertex.unset_decoration(res.id, Decoration::Location)?;
|
||||
}
|
||||
for res in &fragment_resources.stage_inputs {
|
||||
// let location = self.fragment.get_decoration(res.id, Decoration::Location)?;
|
||||
// self.fragment
|
||||
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::Location)?;
|
||||
}
|
||||
|
||||
if vertex_resources.push_constant_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one push constant buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
for res in &vertex_resources.push_constant_buffers {
|
||||
self.vertex.set_name(res.id, "LIBRA_PUSH_VERTEX_INSTANCE")?;
|
||||
self.vertex
|
||||
.set_name(res.base_type_id, "LIBRA_PUSH_VERTEX")?;
|
||||
}
|
||||
|
||||
// todo: options
|
||||
let _flatten = false;
|
||||
|
||||
if vertex_resources.uniform_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one uniform buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
for res in &vertex_resources.uniform_buffers {
|
||||
// if flatten {
|
||||
// self.vertex.flatten_buffer_block(res.id)?;
|
||||
// }
|
||||
self.vertex.set_name(res.id, "LIBRA_UBO_VERTEX_INSTANCE")?;
|
||||
self.vertex.set_name(res.base_type_id, "LIBRA_UBO_VERTEX")?;
|
||||
self.vertex
|
||||
.unset_decoration(res.id, Decoration::DescriptorSet)?;
|
||||
self.vertex.unset_decoration(res.id, Decoration::Binding)?;
|
||||
}
|
||||
|
||||
if fragment_resources.push_constant_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one push constant buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
for res in &fragment_resources.push_constant_buffers {
|
||||
self.fragment
|
||||
.set_name(res.id, "LIBRA_PUSH_FRAGMENT_INSTANCE")?;
|
||||
self.fragment
|
||||
.set_name(res.base_type_id, "LIBRA_PUSH_FRAGMENT")?;
|
||||
}
|
||||
|
||||
if fragment_resources.uniform_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one uniform buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
for res in &fragment_resources.uniform_buffers {
|
||||
// if flatten {
|
||||
// self.fragment.flatten_buffer_block(res.id)?;
|
||||
// }
|
||||
self.fragment
|
||||
.set_name(res.id, "LIBRA_UBO_FRAGMENT_INSTANCE")?;
|
||||
self.fragment
|
||||
.set_name(res.base_type_id, "LIBRA_UBO_FRAGMENT")?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::DescriptorSet)?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::Binding)?;
|
||||
}
|
||||
|
||||
let mut texture_fixups = Vec::new();
|
||||
for res in fragment_resources.sampled_images {
|
||||
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::DescriptorSet)?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::Binding)?;
|
||||
let mut name = res.name;
|
||||
name.push('\0');
|
||||
texture_fixups.push((name, binding));
|
||||
}
|
||||
|
||||
Ok(ShaderCompilerOutput {
|
||||
vertex: self.vertex.compile()?,
|
||||
fragment: self.fragment.compile()?,
|
||||
context: CrossGlslContext {
|
||||
sampler_bindings: texture_fixups,
|
||||
artifact: CompiledProgram {
|
||||
vertex: CompiledAst(self.vertex),
|
||||
fragment: CompiledAst(self.fragment),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
119
librashader-reflect/src/reflect/cross/hlsl.rs
Normal file
119
librashader-reflect/src/reflect/cross/hlsl.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use crate::back::hlsl::{CrossHlslContext, HlslBufferAssignment, HlslBufferAssignments};
|
||||
use crate::back::targets::HLSL;
|
||||
use crate::back::{CompileShader, ShaderCompilerOutput};
|
||||
use crate::error::ShaderCompileError;
|
||||
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
|
||||
use spirv_cross::hlsl::ShaderModel as HlslShaderModel;
|
||||
use spirv_cross::spirv::Decoration;
|
||||
use spirv_cross::ErrorCode;
|
||||
|
||||
pub(crate) type HlslReflect = CrossReflect<spirv_cross::hlsl::Target>;
|
||||
|
||||
impl CompileShader<HLSL> for CrossReflect<spirv_cross::hlsl::Target> {
|
||||
type Options = Option<HlslShaderModel>;
|
||||
type Context = CrossHlslContext;
|
||||
|
||||
fn compile(
|
||||
mut self,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<String, CrossHlslContext>, ShaderCompileError> {
|
||||
let sm = options.unwrap_or(HlslShaderModel::V5_0);
|
||||
let mut options = spirv_cross::hlsl::CompilerOptions::default();
|
||||
options.shader_model = sm;
|
||||
|
||||
self.vertex.set_compiler_options(&options)?;
|
||||
self.fragment.set_compiler_options(&options)?;
|
||||
|
||||
// todo: options
|
||||
|
||||
let vertex_resources = self.vertex.get_shader_resources()?;
|
||||
let fragment_resources = self.fragment.get_shader_resources()?;
|
||||
|
||||
let mut vertex_buffer_assignment = HlslBufferAssignments::default();
|
||||
let mut fragment_buffer_assignment = HlslBufferAssignments::default();
|
||||
|
||||
if vertex_resources.uniform_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one uniform buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(buf) = vertex_resources.uniform_buffers.first() {
|
||||
vertex_buffer_assignment.ubo = Some(HlslBufferAssignment {
|
||||
name: buf.name.clone(),
|
||||
id: buf.id,
|
||||
})
|
||||
}
|
||||
|
||||
if vertex_resources.push_constant_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one push constant buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(buf) = vertex_resources.push_constant_buffers.first() {
|
||||
vertex_buffer_assignment.push = Some(HlslBufferAssignment {
|
||||
name: buf.name.clone(),
|
||||
id: buf.id,
|
||||
})
|
||||
}
|
||||
|
||||
if fragment_resources.uniform_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one uniform buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(buf) = fragment_resources.uniform_buffers.first() {
|
||||
fragment_buffer_assignment.ubo = Some(HlslBufferAssignment {
|
||||
name: buf.name.clone(),
|
||||
id: buf.id,
|
||||
})
|
||||
}
|
||||
|
||||
if fragment_resources.push_constant_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one push constant buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(buf) = fragment_resources.push_constant_buffers.first() {
|
||||
fragment_buffer_assignment.push = Some(HlslBufferAssignment {
|
||||
name: buf.name.clone(),
|
||||
id: buf.id,
|
||||
})
|
||||
}
|
||||
|
||||
if sm == HlslShaderModel::V3_0 {
|
||||
for res in &fragment_resources.sampled_images {
|
||||
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
|
||||
self.fragment
|
||||
.set_name(res.id, &format!("LIBRA_SAMPLER2D_{binding}"))?;
|
||||
// self.fragment
|
||||
// .unset_decoration(res.id, Decoration::Binding)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ShaderCompilerOutput {
|
||||
vertex: self.vertex.compile()?,
|
||||
fragment: self.fragment.compile()?,
|
||||
context: CrossHlslContext {
|
||||
artifact: CompiledProgram {
|
||||
vertex: CompiledAst(self.vertex),
|
||||
fragment: CompiledAst(self.fragment),
|
||||
},
|
||||
|
||||
vertex_buffers: vertex_buffer_assignment,
|
||||
fragment_buffers: fragment_buffer_assignment,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,28 +1,48 @@
|
|||
use crate::error::{SemanticsErrorKind, ShaderCompileError, ShaderReflectError};
|
||||
use crate::front::GlslangCompilation;
|
||||
#[doc(hidden)]
|
||||
pub mod glsl;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod hlsl;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod msl;
|
||||
|
||||
use crate::error::{SemanticsErrorKind, ShaderReflectError};
|
||||
use crate::front::SpirvCompilation;
|
||||
use crate::reflect::semantics::{
|
||||
BindingMeta, BindingStage, MemberOffset, PushReflection, ShaderReflection, ShaderSemantics,
|
||||
TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo, UboReflection,
|
||||
BindingMeta, BindingStage, BufferReflection, MemberOffset, ShaderReflection, ShaderSemantics,
|
||||
TextureBinding, TextureSemanticMap, TextureSemantics, TextureSizeMeta, TypeInfo,
|
||||
UniformMemberBlock, UniqueSemanticMap, UniqueSemantics, ValidateTypeSemantics, VariableMeta,
|
||||
MAX_BINDINGS_COUNT, MAX_PUSH_BUFFER_SIZE,
|
||||
};
|
||||
use crate::reflect::{align_uniform_size, ReflectShader};
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
|
||||
use spirv_cross::spirv::{Ast, Decoration, Module, Resource, ShaderResources, Type};
|
||||
use spirv_cross::{glsl, hlsl, ErrorCode};
|
||||
use spirv_cross::ErrorCode;
|
||||
|
||||
use crate::back::cross::{CrossGlslContext, CrossHlslContext, HlslShaderModel};
|
||||
use crate::back::targets::{GLSL, HLSL};
|
||||
use crate::back::{CompileShader, ShaderCompilerOutput};
|
||||
use crate::reflect::helper::{SemanticErrorBlame, TextureData, UboData};
|
||||
|
||||
/// Reflect shaders under SPIRV-Cross semantics.
|
||||
///
|
||||
/// SPIRV-Cross supports GLSL, HLSL, SPIR-V, and MSL targets.
|
||||
#[derive(Debug)]
|
||||
pub struct SpirvCross;
|
||||
|
||||
// This is "probably" OK.
|
||||
unsafe impl<T: Send + spirv_cross::spirv::Target> Send for CrossReflect<T> {}
|
||||
unsafe impl<T: Send + spirv_cross::spirv::Target> Send for CrossReflect<T>
|
||||
where
|
||||
Ast<T>: spirv_cross::spirv::Compile<T>,
|
||||
Ast<T>: spirv_cross::spirv::Parse<T>,
|
||||
{
|
||||
}
|
||||
|
||||
pub(crate) struct CrossReflect<T>
|
||||
where
|
||||
T: spirv_cross::spirv::Target,
|
||||
Ast<T>: spirv_cross::spirv::Compile<T>,
|
||||
Ast<T>: spirv_cross::spirv::Parse<T>,
|
||||
{
|
||||
vertex: Ast<T>,
|
||||
fragment: Ast<T>,
|
||||
|
@ -49,15 +69,28 @@ where
|
|||
pub fragment: CompiledAst<T>,
|
||||
}
|
||||
|
||||
pub(crate) type HlslReflect = CrossReflect<hlsl::Target>;
|
||||
pub(crate) type GlslReflect = CrossReflect<glsl::Target>;
|
||||
|
||||
impl ValidateTypeSemantics<Type> for UniqueSemantics {
|
||||
fn validate_type(&self, ty: &Type) -> Option<TypeInfo> {
|
||||
let (Type::Float { ref array, vecsize, columns, .. }
|
||||
| Type::Int { ref array, vecsize, columns, .. }
|
||||
| Type::UInt { ref array, vecsize, columns, .. }) = *ty else {
|
||||
return None
|
||||
let (Type::Float {
|
||||
ref array,
|
||||
vecsize,
|
||||
columns,
|
||||
..
|
||||
}
|
||||
| Type::Int {
|
||||
ref array,
|
||||
vecsize,
|
||||
columns,
|
||||
..
|
||||
}
|
||||
| Type::UInt {
|
||||
ref array,
|
||||
vecsize,
|
||||
columns,
|
||||
..
|
||||
}) = *ty
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if !array.is_empty() {
|
||||
|
@ -68,7 +101,10 @@ impl ValidateTypeSemantics<Type> for UniqueSemantics {
|
|||
UniqueSemantics::MVP => {
|
||||
matches!(ty, Type::Float { .. }) && vecsize == 4 && columns == 4
|
||||
}
|
||||
UniqueSemantics::FrameCount => {
|
||||
UniqueSemantics::FrameCount
|
||||
| UniqueSemantics::Rotation
|
||||
| UniqueSemantics::TotalSubFrames
|
||||
| UniqueSemantics::CurrentSubFrame => {
|
||||
matches!(ty, Type::UInt { .. }) && vecsize == 1 && columns == 1
|
||||
}
|
||||
UniqueSemantics::FrameDirection => {
|
||||
|
@ -93,8 +129,14 @@ impl ValidateTypeSemantics<Type> for UniqueSemantics {
|
|||
|
||||
impl ValidateTypeSemantics<Type> for TextureSemantics {
|
||||
fn validate_type(&self, ty: &Type) -> Option<TypeInfo> {
|
||||
let Type::Float { ref array, vecsize, columns, .. } = *ty else {
|
||||
return None
|
||||
let Type::Float {
|
||||
ref array,
|
||||
vecsize,
|
||||
columns,
|
||||
..
|
||||
} = *ty
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if !array.is_empty() {
|
||||
|
@ -112,7 +154,7 @@ impl ValidateTypeSemantics<Type> for TextureSemantics {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> TryFrom<&GlslangCompilation> for CrossReflect<T>
|
||||
impl<T> TryFrom<&SpirvCompilation> for CrossReflect<T>
|
||||
where
|
||||
T: spirv_cross::spirv::Target,
|
||||
Ast<T>: spirv_cross::spirv::Compile<T>,
|
||||
|
@ -120,7 +162,7 @@ where
|
|||
{
|
||||
type Error = ShaderReflectError;
|
||||
|
||||
fn try_from(value: &GlslangCompilation) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: &SpirvCompilation) -> Result<Self, Self::Error> {
|
||||
let vertex_module = Module::from_words(&value.vertex);
|
||||
let fragment_module = Module::from_words(&value.fragment);
|
||||
|
||||
|
@ -186,6 +228,7 @@ where
|
|||
));
|
||||
}
|
||||
|
||||
// Ensure that vertex attributes use location 0 and 1
|
||||
let vert_mask = vertex_res.stage_inputs.iter().try_fold(0, |mask, input| {
|
||||
Ok::<u32, ErrorCode>(
|
||||
mask | 1 << self.vertex.get_decoration(input.id, Decoration::Location)?,
|
||||
|
@ -205,9 +248,7 @@ where
|
|||
|
||||
if vertex_res.push_constant_buffers.len() > 1 {
|
||||
return Err(ShaderReflectError::VertexSemanticError(
|
||||
SemanticsErrorKind::InvalidUniformBufferCount(
|
||||
vertex_res.push_constant_buffers.len(),
|
||||
),
|
||||
SemanticsErrorKind::InvalidPushBufferCount(vertex_res.push_constant_buffers.len()),
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -219,7 +260,7 @@ where
|
|||
|
||||
if fragment_res.push_constant_buffers.len() > 1 {
|
||||
return Err(ShaderReflectError::FragmentSemanticError(
|
||||
SemanticsErrorKind::InvalidUniformBufferCount(
|
||||
SemanticsErrorKind::InvalidPushBufferCount(
|
||||
fragment_res.push_constant_buffers.len(),
|
||||
),
|
||||
));
|
||||
|
@ -295,7 +336,7 @@ where
|
|||
|
||||
if let Some(parameter) = semantics.uniform_semantics.get_unique_semantic(&name) {
|
||||
let Some(typeinfo) = parameter.semantics.validate_type(&range_type) else {
|
||||
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)))
|
||||
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
|
||||
};
|
||||
|
||||
match ¶meter.semantics {
|
||||
|
@ -310,7 +351,7 @@ where
|
|||
expected,
|
||||
received: offset,
|
||||
ty: offset_type,
|
||||
pass: pass_number
|
||||
pass: pass_number,
|
||||
});
|
||||
}
|
||||
if meta.size != typeinfo.size {
|
||||
|
@ -345,7 +386,7 @@ where
|
|||
expected,
|
||||
received: offset,
|
||||
ty: offset_type,
|
||||
pass: pass_number
|
||||
pass: pass_number,
|
||||
});
|
||||
}
|
||||
if meta.size != typeinfo.size * typeinfo.columns {
|
||||
|
@ -372,7 +413,7 @@ where
|
|||
}
|
||||
} else if let Some(texture) = semantics.uniform_semantics.get_texture_semantic(&name) {
|
||||
let Some(_typeinfo) = texture.semantics.validate_type(&range_type) else {
|
||||
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)))
|
||||
return Err(blame.error(SemanticsErrorKind::InvalidTypeForSemantic(name)));
|
||||
};
|
||||
|
||||
if let TextureSemantics::PassOutput = texture.semantics {
|
||||
|
@ -394,7 +435,7 @@ where
|
|||
expected,
|
||||
received: offset,
|
||||
ty: offset_type,
|
||||
pass: pass_number
|
||||
pass: pass_number,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -428,7 +469,7 @@ where
|
|||
&mut self,
|
||||
vertex_ubo: Option<&Resource>,
|
||||
fragment_ubo: Option<&Resource>,
|
||||
) -> Result<Option<UboReflection>, ShaderReflectError> {
|
||||
) -> Result<Option<BufferReflection<u32>>, ShaderReflectError> {
|
||||
if let Some(vertex_ubo) = vertex_ubo {
|
||||
self.vertex
|
||||
.set_decoration(vertex_ubo.id, Decoration::Binding, 0)?;
|
||||
|
@ -454,7 +495,7 @@ where
|
|||
}
|
||||
|
||||
let size = std::cmp::max(vertex_ubo.size, fragment_ubo.size);
|
||||
Ok(Some(UboReflection {
|
||||
Ok(Some(BufferReflection {
|
||||
binding: vertex_ubo.binding,
|
||||
size: align_uniform_size(size),
|
||||
stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT,
|
||||
|
@ -463,7 +504,7 @@ where
|
|||
(Some(vertex_ubo), None) => {
|
||||
let vertex_ubo =
|
||||
Self::get_ubo_data(&self.vertex, vertex_ubo, SemanticErrorBlame::Vertex)?;
|
||||
Ok(Some(UboReflection {
|
||||
Ok(Some(BufferReflection {
|
||||
binding: vertex_ubo.binding,
|
||||
size: align_uniform_size(vertex_ubo.size),
|
||||
stage_mask: BindingStage::VERTEX,
|
||||
|
@ -472,7 +513,7 @@ where
|
|||
(None, Some(fragment_ubo)) => {
|
||||
let fragment_ubo =
|
||||
Self::get_ubo_data(&self.fragment, fragment_ubo, SemanticErrorBlame::Fragment)?;
|
||||
Ok(Some(UboReflection {
|
||||
Ok(Some(BufferReflection {
|
||||
binding: fragment_ubo.binding,
|
||||
size: align_uniform_size(fragment_ubo.size),
|
||||
stage_mask: BindingStage::FRAGMENT,
|
||||
|
@ -488,8 +529,15 @@ where
|
|||
semantics: &ShaderSemantics,
|
||||
meta: &mut BindingMeta,
|
||||
) -> Result<(), ShaderReflectError> {
|
||||
let Some(semantic) = semantics.texture_semantics.get_texture_semantic(texture.name) else {
|
||||
return Err(SemanticErrorBlame::Fragment.error(SemanticsErrorKind::UnknownSemantics(texture.name.to_string())))
|
||||
let Some(semantic) = semantics
|
||||
.texture_semantics
|
||||
.get_texture_semantic(texture.name)
|
||||
else {
|
||||
return Err(
|
||||
SemanticErrorBlame::Fragment.error(SemanticsErrorKind::UnknownSemantics(
|
||||
texture.name.to_string(),
|
||||
)),
|
||||
);
|
||||
};
|
||||
|
||||
if semantic.semantics == TextureSemantics::PassOutput && semantic.index >= pass_number {
|
||||
|
@ -541,7 +589,7 @@ where
|
|||
&mut self,
|
||||
vertex_pcb: Option<&Resource>,
|
||||
fragment_pcb: Option<&Resource>,
|
||||
) -> Result<Option<PushReflection>, ShaderReflectError> {
|
||||
) -> Result<Option<BufferReflection<Option<u32>>>, ShaderReflectError> {
|
||||
if let Some(vertex_pcb) = vertex_pcb {
|
||||
self.vertex
|
||||
.set_decoration(vertex_pcb.id, Decoration::Binding, 1)?;
|
||||
|
@ -565,7 +613,8 @@ where
|
|||
|
||||
let size = std::cmp::max(vertex_size, fragment_size);
|
||||
|
||||
Ok(Some(PushReflection {
|
||||
Ok(Some(BufferReflection {
|
||||
binding: None,
|
||||
size: align_uniform_size(size),
|
||||
stage_mask: BindingStage::VERTEX | BindingStage::FRAGMENT,
|
||||
}))
|
||||
|
@ -573,7 +622,8 @@ where
|
|||
(Some(vertex_push), None) => {
|
||||
let vertex_size =
|
||||
Self::get_push_size(&self.vertex, vertex_push, SemanticErrorBlame::Vertex)?;
|
||||
Ok(Some(PushReflection {
|
||||
Ok(Some(BufferReflection {
|
||||
binding: None,
|
||||
size: align_uniform_size(vertex_size),
|
||||
stage_mask: BindingStage::VERTEX,
|
||||
}))
|
||||
|
@ -584,7 +634,8 @@ where
|
|||
fragment_push,
|
||||
SemanticErrorBlame::Fragment,
|
||||
)?;
|
||||
Ok(Some(PushReflection {
|
||||
Ok(Some(BufferReflection {
|
||||
binding: None,
|
||||
size: align_uniform_size(fragment_size),
|
||||
stage_mask: BindingStage::FRAGMENT,
|
||||
}))
|
||||
|
@ -691,184 +742,30 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl CompileShader<GLSL> for CrossReflect<glsl::Target> {
|
||||
type Options = glsl::Version;
|
||||
type Context = CrossGlslContext;
|
||||
|
||||
fn compile(
|
||||
mut self,
|
||||
version: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
|
||||
let mut options: glsl::CompilerOptions = Default::default();
|
||||
options.version = version;
|
||||
options.fragment.default_float_precision = glsl::Precision::High;
|
||||
options.fragment.default_int_precision = glsl::Precision::High;
|
||||
options.enable_420_pack_extension = false;
|
||||
|
||||
self.vertex.set_compiler_options(&options)?;
|
||||
self.fragment.set_compiler_options(&options)?;
|
||||
|
||||
let vertex_resources = self.vertex.get_shader_resources()?;
|
||||
let fragment_resources = self.fragment.get_shader_resources()?;
|
||||
|
||||
for res in &vertex_resources.stage_inputs {
|
||||
self.vertex.unset_decoration(res.id, Decoration::Location)?;
|
||||
}
|
||||
for res in &vertex_resources.stage_outputs {
|
||||
// let location = self.vertex.get_decoration(res.id, Decoration::Location)?;
|
||||
// self.vertex
|
||||
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
|
||||
self.vertex.unset_decoration(res.id, Decoration::Location)?;
|
||||
}
|
||||
for res in &fragment_resources.stage_inputs {
|
||||
// let location = self.fragment.get_decoration(res.id, Decoration::Location)?;
|
||||
// self.fragment
|
||||
// .set_name(res.id, &format!("LIBRA_VARYING_{location}"))?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::Location)?;
|
||||
}
|
||||
|
||||
if vertex_resources.push_constant_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one push constant buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
for res in &vertex_resources.push_constant_buffers {
|
||||
self.vertex.set_name(res.id, "LIBRA_PUSH_VERTEX_INSTANCE")?;
|
||||
self.vertex
|
||||
.set_name(res.base_type_id, "LIBRA_PUSH_VERTEX")?;
|
||||
}
|
||||
|
||||
// todo: options
|
||||
let _flatten = false;
|
||||
|
||||
if vertex_resources.uniform_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one uniform buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
for res in &vertex_resources.uniform_buffers {
|
||||
// if flatten {
|
||||
// self.vertex.flatten_buffer_block(res.id)?;
|
||||
// }
|
||||
self.vertex.set_name(res.id, "LIBRA_UBO_VERTEX_INSTANCE")?;
|
||||
self.vertex.set_name(res.base_type_id, "LIBRA_UBO_VERTEX")?;
|
||||
self.vertex
|
||||
.unset_decoration(res.id, Decoration::DescriptorSet)?;
|
||||
self.vertex.unset_decoration(res.id, Decoration::Binding)?;
|
||||
}
|
||||
|
||||
if fragment_resources.push_constant_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one push constant buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
for res in &fragment_resources.push_constant_buffers {
|
||||
self.fragment
|
||||
.set_name(res.id, "LIBRA_PUSH_FRAGMENT_INSTANCE")?;
|
||||
self.fragment
|
||||
.set_name(res.base_type_id, "LIBRA_PUSH_FRAGMENT")?;
|
||||
}
|
||||
|
||||
if fragment_resources.uniform_buffers.len() > 1 {
|
||||
return Err(ShaderCompileError::SpirvCrossCompileError(
|
||||
ErrorCode::CompilationError(String::from(
|
||||
"Cannot have more than one uniform buffer",
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
for res in &fragment_resources.uniform_buffers {
|
||||
// if flatten {
|
||||
// self.fragment.flatten_buffer_block(res.id)?;
|
||||
// }
|
||||
self.fragment
|
||||
.set_name(res.id, "LIBRA_UBO_FRAGMENT_INSTANCE")?;
|
||||
self.fragment
|
||||
.set_name(res.base_type_id, "LIBRA_UBO_FRAGMENT")?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::DescriptorSet)?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::Binding)?;
|
||||
}
|
||||
|
||||
let mut texture_fixups = Vec::new();
|
||||
for res in fragment_resources.sampled_images {
|
||||
let binding = self.fragment.get_decoration(res.id, Decoration::Binding)?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::DescriptorSet)?;
|
||||
self.fragment
|
||||
.unset_decoration(res.id, Decoration::Binding)?;
|
||||
let mut name = res.name;
|
||||
name.push('\0');
|
||||
texture_fixups.push((name, binding));
|
||||
}
|
||||
|
||||
Ok(ShaderCompilerOutput {
|
||||
vertex: self.vertex.compile()?,
|
||||
fragment: self.fragment.compile()?,
|
||||
context: CrossGlslContext {
|
||||
sampler_bindings: texture_fixups,
|
||||
artifact: CompiledProgram {
|
||||
vertex: CompiledAst(self.vertex),
|
||||
fragment: CompiledAst(self.fragment),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CompileShader<HLSL> for CrossReflect<hlsl::Target> {
|
||||
type Options = Option<HlslShaderModel>;
|
||||
type Context = CrossHlslContext;
|
||||
|
||||
fn compile(
|
||||
mut self,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<String, CrossHlslContext>, ShaderCompileError> {
|
||||
let sm = options.unwrap_or(HlslShaderModel::V5_0);
|
||||
let mut options = hlsl::CompilerOptions::default();
|
||||
options.shader_model = sm;
|
||||
|
||||
self.vertex.set_compiler_options(&options)?;
|
||||
self.fragment.set_compiler_options(&options)?;
|
||||
|
||||
Ok(ShaderCompilerOutput {
|
||||
vertex: self.vertex.compile()?,
|
||||
fragment: self.fragment.compile()?,
|
||||
context: CrossHlslContext {
|
||||
artifact: CompiledProgram {
|
||||
vertex: CompiledAst(self.vertex),
|
||||
fragment: CompiledAst(self.fragment),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::reflect::cross::CrossReflect;
|
||||
use crate::reflect::ReflectShader;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::back::CompileShader;
|
||||
use crate::front::GlslangCompilation;
|
||||
use crate::back::hlsl::CrossHlslContext;
|
||||
use crate::back::targets::HLSL;
|
||||
use crate::back::{CompileShader, ShaderCompilerOutput};
|
||||
use crate::front::{Glslang, ShaderInputCompiler};
|
||||
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
|
||||
use librashader_common::map::FastHashMap;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use spirv_cross::glsl;
|
||||
use spirv_cross::glsl::{CompilerOptions, Version};
|
||||
use spirv_cross::hlsl::ShaderModel;
|
||||
use spirv_cross::{glsl, hlsl};
|
||||
|
||||
#[test]
|
||||
pub fn test_into() {
|
||||
let result = ShaderSource::load("../test/basic.slang").unwrap();
|
||||
let mut uniform_semantics: FxHashMap<String, UniformSemantic> = Default::default();
|
||||
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
|
||||
"../test/basic.slang".into(),
|
||||
))
|
||||
.unwrap();
|
||||
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
|
||||
|
||||
for (_index, param) in result.parameters.iter().enumerate() {
|
||||
uniform_semantics.insert(
|
||||
|
@ -879,9 +776,9 @@ mod test {
|
|||
}),
|
||||
);
|
||||
}
|
||||
let spirv = GlslangCompilation::compile(&result).unwrap();
|
||||
let mut reflect = CrossReflect::<glsl::Target>::try_from(&spirv).unwrap();
|
||||
let _shader_reflection = reflect
|
||||
let spirv = Glslang::compile(&result).unwrap();
|
||||
let mut reflect = CrossReflect::<hlsl::Target>::try_from(&spirv).unwrap();
|
||||
let shader_reflection = reflect
|
||||
.reflect(
|
||||
0,
|
||||
&ShaderSemantics {
|
||||
|
@ -890,12 +787,22 @@ mod test {
|
|||
},
|
||||
)
|
||||
.unwrap();
|
||||
let mut opts = CompilerOptions::default();
|
||||
opts.version = Version::V4_60;
|
||||
opts.enable_420_pack_extension = false;
|
||||
let compiled = reflect.compile(Version::V3_30).unwrap();
|
||||
// eprintln!("{shader_reflection:#?}");
|
||||
eprintln!("{:#}", compiled.fragment)
|
||||
let mut opts = hlsl::CompilerOptions::default();
|
||||
opts.shader_model = ShaderModel::V3_0;
|
||||
|
||||
let compiled: ShaderCompilerOutput<String, CrossHlslContext> =
|
||||
<CrossReflect<hlsl::Target> as CompileShader<HLSL>>::compile(
|
||||
reflect,
|
||||
Some(ShaderModel::V3_0),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
println!("{:?}", shader_reflection.meta);
|
||||
println!("{}", compiled.fragment);
|
||||
println!("{}", compiled.vertex);
|
||||
|
||||
// // eprintln!("{shader_reflection:#?}");
|
||||
// eprintln!("{}", compiled.fragment)
|
||||
// let mut loader = rspirv::dr::Loader::new();
|
||||
// rspirv::binary::parse_words(spirv.fragment.as_binary(), &mut loader).unwrap();
|
||||
// let module = loader.module();
|
158
librashader-reflect/src/reflect/cross/msl.rs
Normal file
158
librashader-reflect/src/reflect/cross/msl.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
use crate::back::msl::CrossMslContext;
|
||||
use crate::back::targets::MSL;
|
||||
use crate::back::{CompileShader, ShaderCompilerOutput};
|
||||
use crate::error::ShaderCompileError;
|
||||
use crate::reflect::cross::{CompiledAst, CompiledProgram, CrossReflect};
|
||||
use spirv_cross::msl;
|
||||
use spirv_cross::msl::{ResourceBinding, ResourceBindingLocation};
|
||||
use spirv_cross::spirv::{Ast, Decoration, ExecutionModel};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub(crate) type MslReflect = CrossReflect<spirv_cross::msl::Target>;
|
||||
|
||||
impl CompileShader<MSL> for CrossReflect<spirv_cross::msl::Target> {
|
||||
type Options = Option<spirv_cross::msl::Version>;
|
||||
type Context = CrossMslContext;
|
||||
|
||||
fn compile(
|
||||
mut self,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<String, CrossMslContext>, ShaderCompileError> {
|
||||
let version = options.unwrap_or(msl::Version::V2_0);
|
||||
let mut vert_options = spirv_cross::msl::CompilerOptions::default();
|
||||
let mut frag_options = spirv_cross::msl::CompilerOptions::default();
|
||||
|
||||
vert_options.version = version;
|
||||
frag_options.version = version;
|
||||
|
||||
fn set_bindings(
|
||||
ast: &Ast<msl::Target>,
|
||||
stage: ExecutionModel,
|
||||
binding_map: &mut BTreeMap<ResourceBindingLocation, ResourceBinding>,
|
||||
) -> Result<(), ShaderCompileError> {
|
||||
let resources = ast.get_shader_resources()?;
|
||||
for resource in &resources.push_constant_buffers {
|
||||
let location = ResourceBindingLocation {
|
||||
stage,
|
||||
desc_set: msl::PUSH_CONSTANT_DESCRIPTOR_SET,
|
||||
binding: msl::PUSH_CONSTANT_BINDING,
|
||||
};
|
||||
let overridden = ResourceBinding {
|
||||
buffer_id: ast.get_decoration(resource.id, Decoration::Binding)?,
|
||||
texture_id: 0,
|
||||
sampler_id: 0,
|
||||
base_type: None,
|
||||
count: 0, // no arrays allowed in slang shaders, otherwise we'd have to get the type and get the array length
|
||||
};
|
||||
|
||||
binding_map.insert(location, overridden);
|
||||
}
|
||||
|
||||
for resource in resources
|
||||
.uniform_buffers
|
||||
.iter()
|
||||
.chain(resources.sampled_images.iter())
|
||||
{
|
||||
let binding = ast.get_decoration(resource.id, Decoration::Binding)?;
|
||||
let location = ResourceBindingLocation {
|
||||
stage,
|
||||
desc_set: ast.get_decoration(resource.id, Decoration::DescriptorSet)?,
|
||||
binding,
|
||||
};
|
||||
|
||||
let overridden = ResourceBinding {
|
||||
buffer_id: binding,
|
||||
texture_id: binding,
|
||||
sampler_id: binding,
|
||||
base_type: None,
|
||||
count: 0, // no arrays allowed in slang shaders, otherwise we'd have to get the type and get the array length
|
||||
};
|
||||
|
||||
binding_map.insert(location, overridden);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
set_bindings(
|
||||
&self.vertex,
|
||||
ExecutionModel::Vertex,
|
||||
&mut vert_options.resource_binding_overrides,
|
||||
)?;
|
||||
|
||||
set_bindings(
|
||||
&self.fragment,
|
||||
ExecutionModel::Fragment,
|
||||
&mut frag_options.resource_binding_overrides,
|
||||
)?;
|
||||
|
||||
self.vertex.set_compiler_options(&vert_options)?;
|
||||
self.fragment.set_compiler_options(&frag_options)?;
|
||||
|
||||
Ok(ShaderCompilerOutput {
|
||||
vertex: self.vertex.compile()?,
|
||||
fragment: self.fragment.compile()?,
|
||||
context: CrossMslContext {
|
||||
artifact: CompiledProgram {
|
||||
vertex: CompiledAst(self.vertex),
|
||||
fragment: CompiledAst(self.fragment),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::back::targets::{MSL, WGSL};
|
||||
use crate::back::{CompileShader, FromCompilation};
|
||||
use crate::reflect::cross::SpirvCross;
|
||||
use crate::reflect::naga::{Naga, NagaLoweringOptions};
|
||||
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
|
||||
use crate::reflect::ReflectShader;
|
||||
use bitflags::Flags;
|
||||
use librashader_common::map::FastHashMap;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use rustc_hash::FxHashMap;
|
||||
use spirv_cross::msl;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
pub fn test_into() {
|
||||
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
|
||||
// let result = ShaderSource::load("../test/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
|
||||
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
|
||||
"../test/basic.slang".into(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
|
||||
|
||||
for (_index, param) in result.parameters.iter().enumerate() {
|
||||
uniform_semantics.insert(
|
||||
param.1.id.clone(),
|
||||
UniformSemantic::Unique(Semantic {
|
||||
semantics: UniqueSemantics::FloatParameter,
|
||||
index: (),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
|
||||
|
||||
let mut msl =
|
||||
<MSL as FromCompilation<_, SpirvCross>>::from_compilation(compilation).unwrap();
|
||||
|
||||
msl.reflect(
|
||||
0,
|
||||
&ShaderSemantics {
|
||||
uniform_semantics,
|
||||
texture_semantics: Default::default(),
|
||||
},
|
||||
)
|
||||
.expect("");
|
||||
|
||||
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
|
||||
|
||||
println!("{}", compiled.vertex);
|
||||
}
|
||||
}
|
|
@ -12,8 +12,9 @@ pub mod presets;
|
|||
|
||||
mod helper;
|
||||
|
||||
#[cfg(feature = "unstable-naga")]
|
||||
mod naga;
|
||||
/// Reflection via naga.
|
||||
#[cfg(feature = "naga")]
|
||||
pub mod naga;
|
||||
|
||||
/// A trait for compilation outputs that can provide reflection information.
|
||||
pub trait ReflectShader {
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
use crate::error::{SemanticsErrorKind, ShaderReflectError};
|
||||
use crate::front::GlslangCompilation;
|
||||
use crate::front::NagaCompilation;
|
||||
|
||||
use naga::front::spv::Options;
|
||||
use naga::{GlobalVariable, Module, StructMember, Type, TypeInner};
|
||||
use std::convert::Infallible;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NagaReflect {
|
||||
vertex: Module,
|
||||
fragment: Module,
|
||||
}
|
||||
|
||||
impl TryFrom<NagaCompilation> for NagaReflect {
|
||||
type Error = ShaderReflectError;
|
||||
|
||||
fn try_from(value: NagaCompilation) -> Result<Self, Self::Error> {
|
||||
Ok(NagaReflect {
|
||||
vertex: value.vertex,
|
||||
fragment: value.fragment,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&GlslangCompilation> for NagaReflect {
|
||||
type Error = ShaderReflectError;
|
||||
|
||||
fn try_from(value: &GlslangCompilation) -> Result<Self, Self::Error> {
|
||||
let ops = Options::default();
|
||||
let vertex =
|
||||
naga::front::spv::Parser::new(value.vertex.clone().into_iter(), &ops).parse()?;
|
||||
let fragment =
|
||||
naga::front::spv::Parser::new(value.fragment.clone().into_iter(), &ops).parse()?;
|
||||
Ok(NagaReflect { vertex, fragment })
|
||||
}
|
||||
}
|
||||
|
||||
struct UboData {
|
||||
// id: u32,
|
||||
// descriptor_set: u32,
|
||||
binding: u32,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
struct Ubo {
|
||||
members: Vec<StructMember>,
|
||||
span: u32,
|
||||
}
|
||||
|
||||
impl TryFrom<naga::Type> for Ubo {
|
||||
type Error = Infallible;
|
||||
|
||||
fn try_from(value: Type) -> Result<Self, Infallible> {
|
||||
match value.inner {
|
||||
TypeInner::Struct { members, span } => Ok(Ubo { members, span }),
|
||||
// todo: make this programmer error
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NagaReflect {
|
||||
// pub fn get_ubo_data(arena: Arena, variable: GlobalVariable, blame: SemanticErrorBlame) -> Result<UboData, ShaderReflectError> {
|
||||
// let binding = match variable.binding {
|
||||
// Some(ResourceBinding { group: 0, binding }) => binding,
|
||||
// Some(ResourceBinding { group, .. }) => return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(group))),
|
||||
// None => return Err(blame.error(SemanticsErrorKind::InvalidDescriptorSet(u32::MAX))),
|
||||
// };
|
||||
//
|
||||
// if binding >= MAX_BINDINGS_COUNT {
|
||||
// return Err(blame.error(SemanticsErrorKind::InvalidBinding(binding)));
|
||||
// }
|
||||
//
|
||||
// match variable.ty.as {
|
||||
// Handle { .. } => {}
|
||||
// }
|
||||
// Ok(UboData {
|
||||
// binding,
|
||||
//
|
||||
// })
|
||||
// }
|
||||
pub fn reflect_ubos(
|
||||
vertex: GlobalVariable,
|
||||
fragment: GlobalVariable,
|
||||
) -> Result<(), ShaderReflectError> {
|
||||
match (vertex.binding, fragment.binding) {
|
||||
// todo: should emit for both but whatever
|
||||
(None, None) | (Some(_), None) | (None, Some(_)) => {
|
||||
ShaderReflectError::VertexSemanticError(SemanticsErrorKind::InvalidDescriptorSet(
|
||||
u32::MAX,
|
||||
))
|
||||
}
|
||||
(Some(_vert), Some(_frag)) => {
|
||||
todo!();
|
||||
}
|
||||
};
|
||||
|
||||
todo!();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use rspirv::dr::Instruction;
|
||||
use rspirv::spirv::Op;
|
||||
|
||||
#[test]
|
||||
pub fn test_into() {
|
||||
let result = ShaderSource::load("../test/slang-shaders/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang").unwrap();
|
||||
let compilation = crate::front::GlslangCompilation::try_from(&result).unwrap();
|
||||
|
||||
let mut loader = rspirv::dr::Loader::new();
|
||||
rspirv::binary::parse_words(compilation.vertex.as_binary(), &mut loader).unwrap();
|
||||
let module = loader.module();
|
||||
|
||||
let outputs: Vec<&Instruction> = module
|
||||
.types_global_values
|
||||
.iter()
|
||||
.filter(|i| i.class.opcode == Op::Variable)
|
||||
.collect();
|
||||
|
||||
println!("{outputs:#?}");
|
||||
}
|
||||
}
|
1052
librashader-reflect/src/reflect/naga/mod.rs
Normal file
1052
librashader-reflect/src/reflect/naga/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
212
librashader-reflect/src/reflect/naga/msl.rs
Normal file
212
librashader-reflect/src/reflect/naga/msl.rs
Normal file
|
@ -0,0 +1,212 @@
|
|||
use crate::back::msl::{MslVersion, NagaMslContext, NagaMslModule};
|
||||
use crate::back::targets::MSL;
|
||||
use crate::back::{CompileShader, ShaderCompilerOutput};
|
||||
use crate::error::ShaderCompileError;
|
||||
use crate::reflect::naga::{NagaLoweringOptions, NagaReflect};
|
||||
use naga::back::msl::{
|
||||
BindSamplerTarget, BindTarget, EntryPointResources, Options, PipelineOptions, TranslationInfo,
|
||||
};
|
||||
use naga::valid::{Capabilities, ValidationFlags};
|
||||
use naga::{Module, TypeInner};
|
||||
use spirv_cross::msl::Version;
|
||||
|
||||
fn msl_version_to_naga_msl(version: MslVersion) -> (u8, u8) {
|
||||
match version {
|
||||
Version::V1_0 => (1, 0),
|
||||
Version::V1_1 => (1, 1),
|
||||
Version::V1_2 => (1, 2),
|
||||
Version::V2_0 => (2, 0),
|
||||
Version::V2_1 => (2, 1),
|
||||
Version::V2_2 => (2, 2),
|
||||
Version::V2_3 => (2, 3),
|
||||
_ => (0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
impl CompileShader<MSL> for NagaReflect {
|
||||
type Options = Option<crate::back::msl::MslVersion>;
|
||||
type Context = NagaMslContext;
|
||||
|
||||
fn compile(
|
||||
mut self,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
|
||||
// https://github.com/libretro/RetroArch/blob/434e94c782af2e4d4277a24b7ed8e5fc54870088/gfx/drivers_shader/slang_process.cpp#L524
|
||||
|
||||
let lang_version = msl_version_to_naga_msl(options.unwrap_or(MslVersion::V2_0));
|
||||
|
||||
let mut vert_options = Options {
|
||||
lang_version,
|
||||
per_entry_point_map: Default::default(),
|
||||
inline_samplers: vec![],
|
||||
spirv_cross_compatibility: true,
|
||||
fake_missing_bindings: false,
|
||||
bounds_check_policies: Default::default(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
};
|
||||
|
||||
let mut frag_options = vert_options.clone();
|
||||
|
||||
fn write_msl(
|
||||
module: &Module,
|
||||
options: Options,
|
||||
) -> Result<(String, TranslationInfo), ShaderCompileError> {
|
||||
let mut valid =
|
||||
naga::valid::Validator::new(ValidationFlags::all(), Capabilities::empty());
|
||||
let info = valid.validate(&module)?;
|
||||
|
||||
let pipeline_options = PipelineOptions {
|
||||
allow_and_force_point_size: false,
|
||||
vertex_pulling_transform: false,
|
||||
vertex_buffer_mappings: vec![],
|
||||
};
|
||||
|
||||
let msl = naga::back::msl::write_string(&module, &info, &options, &pipeline_options)?;
|
||||
Ok(msl)
|
||||
}
|
||||
|
||||
fn generate_bindings(module: &Module) -> EntryPointResources {
|
||||
let mut resources = EntryPointResources::default();
|
||||
let binding_map = &mut resources.resources;
|
||||
// Don't set PCB because they'll be gone after lowering..
|
||||
// resources.push_constant_buffer = Some(1u8);
|
||||
|
||||
for (_, variable) in module.global_variables.iter() {
|
||||
let Some(binding) = &variable.binding else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(ty) = module.types.get_handle(variable.ty) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match ty.inner {
|
||||
TypeInner::Sampler { .. } => {
|
||||
binding_map.insert(
|
||||
binding.clone(),
|
||||
BindTarget {
|
||||
buffer: None,
|
||||
texture: None,
|
||||
sampler: Some(BindSamplerTarget::Resource(binding.binding as u8)),
|
||||
binding_array_size: None,
|
||||
mutable: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
TypeInner::Struct { .. } => {
|
||||
binding_map.insert(
|
||||
binding.clone(),
|
||||
BindTarget {
|
||||
buffer: Some(binding.binding as u8),
|
||||
texture: None,
|
||||
sampler: None,
|
||||
binding_array_size: None,
|
||||
mutable: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
TypeInner::Image { .. } => {
|
||||
binding_map.insert(
|
||||
binding.clone(),
|
||||
BindTarget {
|
||||
buffer: None,
|
||||
texture: Some(binding.binding as u8),
|
||||
sampler: None,
|
||||
binding_array_size: None,
|
||||
mutable: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
resources
|
||||
}
|
||||
|
||||
self.do_lowering(&NagaLoweringOptions {
|
||||
write_pcb_as_ubo: true,
|
||||
sampler_bind_group: 1,
|
||||
});
|
||||
|
||||
frag_options
|
||||
.per_entry_point_map
|
||||
.insert(String::from("main"), generate_bindings(&self.fragment));
|
||||
vert_options
|
||||
.per_entry_point_map
|
||||
.insert(String::from("main"), generate_bindings(&self.vertex));
|
||||
|
||||
let fragment = write_msl(&self.fragment, frag_options)?;
|
||||
let vertex = write_msl(&self.vertex, vert_options)?;
|
||||
|
||||
let vertex_binding = self.get_next_binding(0);
|
||||
Ok(ShaderCompilerOutput {
|
||||
vertex: vertex.0,
|
||||
fragment: fragment.0,
|
||||
context: NagaMslContext {
|
||||
fragment: NagaMslModule {
|
||||
translation_info: fragment.1,
|
||||
module: self.fragment,
|
||||
},
|
||||
vertex: NagaMslModule {
|
||||
translation_info: vertex.1,
|
||||
module: self.vertex,
|
||||
},
|
||||
next_free_binding: vertex_binding,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::back::targets::MSL;
|
||||
use crate::back::{CompileShader, FromCompilation};
|
||||
use crate::reflect::naga::{Naga, NagaLoweringOptions};
|
||||
use crate::reflect::semantics::{Semantic, ShaderSemantics, UniformSemantic, UniqueSemantics};
|
||||
use crate::reflect::ReflectShader;
|
||||
use bitflags::Flags;
|
||||
use librashader_common::map::FastHashMap;
|
||||
use librashader_preprocess::ShaderSource;
|
||||
use spirv_cross::msl;
|
||||
|
||||
#[test]
|
||||
pub fn test_into() {
|
||||
let result = ShaderSource::load(&librashader_common::ShaderStorage::Path(
|
||||
"../test/basic.slang".into(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
|
||||
|
||||
for (_index, param) in result.parameters.iter().enumerate() {
|
||||
uniform_semantics.insert(
|
||||
param.1.id.clone(),
|
||||
UniformSemantic::Unique(Semantic {
|
||||
semantics: UniqueSemantics::FloatParameter,
|
||||
index: (),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let compilation = crate::front::SpirvCompilation::try_from(&result).unwrap();
|
||||
|
||||
let mut msl = <MSL as FromCompilation<_, Naga>>::from_compilation(compilation).unwrap();
|
||||
|
||||
msl.reflect(
|
||||
0,
|
||||
&ShaderSemantics {
|
||||
uniform_semantics,
|
||||
texture_semantics: Default::default(),
|
||||
},
|
||||
)
|
||||
.expect("");
|
||||
|
||||
let compiled = msl.compile(Some(msl::Version::V2_0)).unwrap();
|
||||
|
||||
println!(
|
||||
"{:?}",
|
||||
compiled.context.fragment.translation_info.entry_point_names
|
||||
);
|
||||
}
|
||||
}
|
54
librashader-reflect/src/reflect/naga/spirv.rs
Normal file
54
librashader-reflect/src/reflect/naga/spirv.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use crate::back::spirv::{NagaSpirvContext, NagaSpirvOptions};
|
||||
use crate::back::targets::SPIRV;
|
||||
use crate::back::{CompileShader, ShaderCompilerOutput};
|
||||
use crate::error::ShaderCompileError;
|
||||
use crate::reflect::naga::NagaReflect;
|
||||
use naga::back::spv::PipelineOptions;
|
||||
use naga::valid::{Capabilities, ValidationFlags};
|
||||
use naga::Module;
|
||||
|
||||
impl CompileShader<SPIRV> for NagaReflect {
|
||||
type Options = NagaSpirvOptions;
|
||||
type Context = NagaSpirvContext;
|
||||
|
||||
fn compile(
|
||||
mut self,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<Vec<u32>, Self::Context>, ShaderCompileError> {
|
||||
fn write_spv(
|
||||
module: &Module,
|
||||
stage: naga::ShaderStage,
|
||||
version: (u8, u8),
|
||||
) -> Result<Vec<u32>, ShaderCompileError> {
|
||||
let mut valid =
|
||||
naga::valid::Validator::new(ValidationFlags::all(), Capabilities::empty());
|
||||
let info = valid.validate(&module)?;
|
||||
let mut options = naga::back::spv::Options::default();
|
||||
options.lang_version = version;
|
||||
|
||||
let spv = naga::back::spv::write_vec(
|
||||
&module,
|
||||
&info,
|
||||
&options,
|
||||
Some(&PipelineOptions {
|
||||
shader_stage: stage,
|
||||
entry_point: "main".to_string(),
|
||||
}),
|
||||
)?;
|
||||
Ok(spv)
|
||||
}
|
||||
|
||||
self.do_lowering(&options.lowering);
|
||||
|
||||
let fragment = write_spv(&self.fragment, naga::ShaderStage::Fragment, options.version)?;
|
||||
let vertex = write_spv(&self.vertex, naga::ShaderStage::Vertex, options.version)?;
|
||||
Ok(ShaderCompilerOutput {
|
||||
vertex,
|
||||
fragment,
|
||||
context: NagaSpirvContext {
|
||||
fragment: self.fragment,
|
||||
vertex: self.vertex,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
41
librashader-reflect/src/reflect/naga/wgsl.rs
Normal file
41
librashader-reflect/src/reflect/naga/wgsl.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use crate::back::targets::WGSL;
|
||||
use crate::back::wgsl::NagaWgslContext;
|
||||
use crate::back::{CompileShader, ShaderCompilerOutput};
|
||||
use crate::error::ShaderCompileError;
|
||||
use crate::reflect::naga::{NagaLoweringOptions, NagaReflect};
|
||||
use naga::back::wgsl::WriterFlags;
|
||||
use naga::valid::{Capabilities, ModuleInfo, ValidationFlags, Validator};
|
||||
use naga::Module;
|
||||
|
||||
impl CompileShader<WGSL> for NagaReflect {
|
||||
type Options = NagaLoweringOptions;
|
||||
type Context = NagaWgslContext;
|
||||
|
||||
fn compile(
|
||||
mut self,
|
||||
options: Self::Options,
|
||||
) -> Result<ShaderCompilerOutput<String, Self::Context>, ShaderCompileError> {
|
||||
fn write_wgsl(module: &Module, info: &ModuleInfo) -> Result<String, ShaderCompileError> {
|
||||
let wgsl = naga::back::wgsl::write_string(&module, &info, WriterFlags::empty())?;
|
||||
Ok(wgsl)
|
||||
}
|
||||
|
||||
self.do_lowering(&options);
|
||||
|
||||
let mut valid = Validator::new(ValidationFlags::all(), Capabilities::empty());
|
||||
|
||||
let vertex_info = valid.validate(&self.vertex)?;
|
||||
let fragment_info = valid.validate(&self.fragment)?;
|
||||
|
||||
let fragment = write_wgsl(&self.fragment, &fragment_info)?;
|
||||
let vertex = write_wgsl(&self.vertex, &vertex_info)?;
|
||||
Ok(ShaderCompilerOutput {
|
||||
vertex,
|
||||
fragment,
|
||||
context: NagaWgslContext {
|
||||
fragment: self.fragment,
|
||||
vertex: self.vertex,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
use crate::back::targets::OutputTarget;
|
||||
use crate::back::{CompilerBackend, FromCompilation};
|
||||
use crate::error::{ShaderCompileError, ShaderReflectError};
|
||||
use crate::front::ShaderCompilation;
|
||||
use crate::front::{ShaderInputCompiler, ShaderReflectObject};
|
||||
use crate::reflect::semantics::{
|
||||
Semantic, ShaderSemantics, TextureSemantics, UniformSemantic, UniqueSemantics,
|
||||
};
|
||||
use librashader_common::map::FastHashMap;
|
||||
use librashader_preprocess::{PreprocessError, ShaderSource};
|
||||
use librashader_presets::{ShaderPassConfig, TextureConfig};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// Artifacts of a reflected and compiled shader pass.
|
||||
///
|
||||
|
@ -18,11 +18,12 @@ use rustc_hash::FxHashMap;
|
|||
/// ```rust
|
||||
/// #![feature(type_alias_impl_trait)]
|
||||
/// use librashader_reflect::back::CompileReflectShader;
|
||||
/// use librashader_reflect::back::targets::SPIRV;
|
||||
/// use librashader_reflect::front::GlslangCompilation;
|
||||
/// use librashader_reflect::back::targets::{GLSL, SPIRV};
|
||||
/// use librashader_reflect::front::SpirvCompilation;
|
||||
/// use librashader_reflect::reflect::cross::SpirvCross;
|
||||
/// use librashader_reflect::reflect::presets::ShaderPassArtifact;
|
||||
///
|
||||
/// type VulkanPassMeta = ShaderPassArtifact<impl CompileReflectShader<SPIRV, GlslangCompilation>>;
|
||||
/// type VulkanPassMeta = ShaderPassArtifact<impl CompileReflectShader<SPIRV, SpirvCompilation, SpirvCross>>;
|
||||
/// ```
|
||||
///
|
||||
/// This allows a runtime to not name the backing type of the compiled artifact if not necessary.
|
||||
|
@ -35,57 +36,59 @@ impl<T: OutputTarget> CompilePresetTarget for T {}
|
|||
pub trait CompilePresetTarget: OutputTarget {
|
||||
/// Compile passes of a shader preset given the applicable
|
||||
/// shader output target, compilation type, and resulting error.
|
||||
fn compile_preset_passes<C, E>(
|
||||
fn compile_preset_passes<I, R, E>(
|
||||
passes: Vec<ShaderPassConfig>,
|
||||
textures: &[TextureConfig],
|
||||
) -> Result<
|
||||
(
|
||||
Vec<ShaderPassArtifact<<Self as FromCompilation<C>>::Output>>,
|
||||
Vec<ShaderPassArtifact<<Self as FromCompilation<I, R>>::Output>>,
|
||||
ShaderSemantics,
|
||||
),
|
||||
E,
|
||||
>
|
||||
where
|
||||
I: ShaderReflectObject,
|
||||
Self: Sized,
|
||||
Self: FromCompilation<C>,
|
||||
C: ShaderCompilation,
|
||||
Self: FromCompilation<I, R>,
|
||||
I::Compiler: ShaderInputCompiler<I>,
|
||||
E: From<PreprocessError>,
|
||||
E: From<ShaderReflectError>,
|
||||
E: From<ShaderCompileError>,
|
||||
{
|
||||
compile_preset_passes::<Self, C, E>(passes, textures)
|
||||
compile_preset_passes::<Self, I, R, E>(passes, textures)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile passes of a shader preset given the applicable
|
||||
/// shader output target, compilation type, and resulting error.
|
||||
fn compile_preset_passes<T, C, E>(
|
||||
fn compile_preset_passes<T, I, R, E>(
|
||||
passes: Vec<ShaderPassConfig>,
|
||||
textures: &[TextureConfig],
|
||||
) -> Result<
|
||||
(
|
||||
Vec<ShaderPassArtifact<<T as FromCompilation<C>>::Output>>,
|
||||
Vec<ShaderPassArtifact<<T as FromCompilation<I, R>>::Output>>,
|
||||
ShaderSemantics,
|
||||
),
|
||||
E,
|
||||
>
|
||||
where
|
||||
I: ShaderReflectObject,
|
||||
T: OutputTarget,
|
||||
T: FromCompilation<C>,
|
||||
C: ShaderCompilation,
|
||||
T: FromCompilation<I, R>,
|
||||
I::Compiler: ShaderInputCompiler<I>,
|
||||
E: From<PreprocessError>,
|
||||
E: From<ShaderReflectError>,
|
||||
E: From<ShaderCompileError>,
|
||||
{
|
||||
let mut uniform_semantics: FxHashMap<String, UniformSemantic> = Default::default();
|
||||
let mut texture_semantics: FxHashMap<String, Semantic<TextureSemantics>> = Default::default();
|
||||
let mut uniform_semantics: FastHashMap<String, UniformSemantic> = Default::default();
|
||||
let mut texture_semantics: FastHashMap<String, Semantic<TextureSemantics>> = Default::default();
|
||||
|
||||
let passes = passes
|
||||
.into_iter()
|
||||
.map(|shader| {
|
||||
let source: ShaderSource = ShaderSource::load(&shader.name)?;
|
||||
|
||||
let compiled = C::compile(&source)?;
|
||||
let compiled = I::Compiler::compile(&source)?;
|
||||
let reflect = T::from_compilation(compiled)?;
|
||||
|
||||
for parameter in source.parameters.values() {
|
||||
|
@ -116,8 +119,8 @@ where
|
|||
|
||||
/// Insert the available semantics for the input pass config into the provided semantic maps.
|
||||
fn insert_pass_semantics(
|
||||
uniform_semantics: &mut FxHashMap<String, UniformSemantic>,
|
||||
texture_semantics: &mut FxHashMap<String, Semantic<TextureSemantics>>,
|
||||
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
|
||||
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
|
||||
config: &ShaderPassConfig,
|
||||
) {
|
||||
let Some(alias) = &config.alias else {
|
||||
|
@ -167,8 +170,8 @@ fn insert_pass_semantics(
|
|||
/// Insert the available semantics for the input texture config into the provided semantic maps.
|
||||
fn insert_lut_semantics(
|
||||
textures: &[TextureConfig],
|
||||
uniform_semantics: &mut FxHashMap<String, UniformSemantic>,
|
||||
texture_semantics: &mut FxHashMap<String, Semantic<TextureSemantics>>,
|
||||
uniform_semantics: &mut FastHashMap<String, UniformSemantic>,
|
||||
texture_semantics: &mut FastHashMap<String, Semantic<TextureSemantics>>,
|
||||
) {
|
||||
for (index, texture) in textures.iter().enumerate() {
|
||||
texture_semantics.insert(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use bitflags::bitflags;
|
||||
use rustc_hash::FxHashMap;
|
||||
use librashader_common::map::FastHashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// The maximum number of bindings allowed in a shader.
|
||||
|
@ -40,11 +40,18 @@ pub enum UniqueSemantics {
|
|||
/// The frame count, possibly with shader-defined modulo.
|
||||
FrameCount = 3,
|
||||
// int, frame direction
|
||||
/// The frame direction.
|
||||
/// The direction in time where frames are rendered
|
||||
FrameDirection = 4,
|
||||
//int, rotation (glUniform1i(uni->rotation, retroarch_get_rotation());)
|
||||
/// The rotation index (0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg)
|
||||
Rotation = 5,
|
||||
/// Total number of subframes.
|
||||
TotalSubFrames = 6,
|
||||
/// The current subframe (default 1)
|
||||
CurrentSubFrame = 7,
|
||||
/// A user defined float parameter.
|
||||
// float, user defined parameter, array
|
||||
FloatParameter = 5,
|
||||
FloatParameter = 8,
|
||||
}
|
||||
|
||||
impl UniqueSemantics {
|
||||
|
@ -64,6 +71,9 @@ impl UniqueSemantics {
|
|||
UniqueSemantics::FinalViewport => UniformType::Vec4,
|
||||
UniqueSemantics::FrameCount => UniformType::Unsigned,
|
||||
UniqueSemantics::FrameDirection => UniformType::Signed,
|
||||
UniqueSemantics::Rotation => UniformType::Unsigned,
|
||||
UniqueSemantics::TotalSubFrames => UniformType::Unsigned,
|
||||
UniqueSemantics::CurrentSubFrame => UniformType::Unsigned,
|
||||
UniqueSemantics::FloatParameter => UniformType::Float,
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +172,7 @@ pub struct Semantic<T, I = usize> {
|
|||
|
||||
bitflags! {
|
||||
/// The pipeline stage for which a uniform is bound.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
|
||||
pub struct BindingStage: u8 {
|
||||
const NONE = 0b00000000;
|
||||
const VERTEX = 0b00000001;
|
||||
|
@ -169,26 +180,17 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reflection information for the Uniform Buffer
|
||||
/// Reflection information for the Uniform Buffer or Push Constant Block
|
||||
#[derive(Debug)]
|
||||
pub struct UboReflection {
|
||||
/// The binding point for this UBO.
|
||||
pub binding: u32,
|
||||
/// The size of the UBO buffer. UBO sizes returned by reflection is always aligned to a 16 byte boundary.
|
||||
pub struct BufferReflection<T> {
|
||||
/// The binding point for this buffer, if applicable
|
||||
pub binding: T,
|
||||
/// The size of the buffer. Buffer sizes returned by reflection is always aligned to a 16 byte boundary.
|
||||
pub size: u32,
|
||||
/// The mask indicating for which stages the UBO should be bound.
|
||||
pub stage_mask: BindingStage,
|
||||
}
|
||||
|
||||
/// Reflection information for the Push Constant Block
|
||||
#[derive(Debug)]
|
||||
pub struct PushReflection {
|
||||
/// The size of the Push Constant range. The size returned by reflection is always aligned to a 16 byte boundary.
|
||||
pub size: u32,
|
||||
/// The mask indicating for which stages the Push Constant range should be bound.
|
||||
pub stage_mask: BindingStage,
|
||||
}
|
||||
|
||||
/// The offset of a uniform member.
|
||||
///
|
||||
/// A uniform can be bound to both the UBO, or as a Push Constant.
|
||||
|
@ -279,9 +281,9 @@ pub struct TextureBinding {
|
|||
#[derive(Debug)]
|
||||
pub struct ShaderReflection {
|
||||
/// Reflection information about the UBO for this shader.
|
||||
pub ubo: Option<UboReflection>,
|
||||
pub ubo: Option<BufferReflection<u32>>,
|
||||
/// Reflection information about the Push Constant range for this shader.
|
||||
pub push_constant: Option<PushReflection>,
|
||||
pub push_constant: Option<BufferReflection<Option<u32>>>,
|
||||
/// Metadata about the bindings required for this shader.
|
||||
pub meta: BindingMeta,
|
||||
}
|
||||
|
@ -319,7 +321,7 @@ pub trait TextureSemanticMap {
|
|||
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>>;
|
||||
}
|
||||
|
||||
impl TextureSemanticMap for FxHashMap<String, UniformSemantic> {
|
||||
impl TextureSemanticMap for FastHashMap<String, UniformSemantic> {
|
||||
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
|
||||
match self.get(name) {
|
||||
None => {
|
||||
|
@ -351,7 +353,7 @@ impl TextureSemanticMap for FxHashMap<String, UniformSemantic> {
|
|||
}
|
||||
}
|
||||
|
||||
impl TextureSemanticMap for FxHashMap<String, Semantic<TextureSemantics>> {
|
||||
impl TextureSemanticMap for FastHashMap<String, Semantic<TextureSemantics>> {
|
||||
fn get_texture_semantic(&self, name: &str) -> Option<Semantic<TextureSemantics>> {
|
||||
match self.get(name) {
|
||||
None => {
|
||||
|
@ -361,7 +363,9 @@ impl TextureSemanticMap for FxHashMap<String, Semantic<TextureSemantics>> {
|
|||
{
|
||||
if semantics.is_indexed() {
|
||||
let index = &name[semantics.texture_name().len()..];
|
||||
let Ok(index) = usize::from_str(index) else {return None};
|
||||
let Ok(index) = usize::from_str(index) else {
|
||||
return None;
|
||||
};
|
||||
return Some(Semantic {
|
||||
semantics: *semantics,
|
||||
index,
|
||||
|
@ -386,7 +390,7 @@ pub trait UniqueSemanticMap {
|
|||
fn get_unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>>;
|
||||
}
|
||||
|
||||
impl UniqueSemanticMap for FxHashMap<String, UniformSemantic> {
|
||||
impl UniqueSemanticMap for FastHashMap<String, UniformSemantic> {
|
||||
fn get_unique_semantic(&self, name: &str) -> Option<Semantic<UniqueSemantics, ()>> {
|
||||
match self.get(name) {
|
||||
// existing uniforms in the semantic map have priority
|
||||
|
@ -411,6 +415,18 @@ impl UniqueSemanticMap for FxHashMap<String, UniformSemantic> {
|
|||
semantics: UniqueSemantics::FrameDirection,
|
||||
index: (),
|
||||
}),
|
||||
"Rotation" => Some(Semantic {
|
||||
semantics: UniqueSemantics::Rotation,
|
||||
index: (),
|
||||
}),
|
||||
"TotalSubFrames" => Some(Semantic {
|
||||
semantics: UniqueSemantics::TotalSubFrames,
|
||||
index: (),
|
||||
}),
|
||||
"CurrentSubFrame" => Some(Semantic {
|
||||
semantics: UniqueSemantics::TotalSubFrames,
|
||||
index: (),
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
Some(UniformSemantic::Unique(variable)) => Some(*variable),
|
||||
|
@ -432,9 +448,9 @@ pub enum UniformSemantic {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct ShaderSemantics {
|
||||
/// A map of uniform names to filter chain semantics.
|
||||
pub uniform_semantics: FxHashMap<String, UniformSemantic>,
|
||||
pub uniform_semantics: FastHashMap<String, UniformSemantic>,
|
||||
/// A map of texture names to filter chain semantics.
|
||||
pub texture_semantics: FxHashMap<String, Semantic<TextureSemantics>>,
|
||||
pub texture_semantics: FastHashMap<String, Semantic<TextureSemantics>>,
|
||||
}
|
||||
|
||||
/// The binding of a uniform after the shader has been linked.
|
||||
|
@ -467,11 +483,11 @@ impl From<Semantic<TextureSemantics>> for UniformBinding {
|
|||
#[derive(Debug, Default)]
|
||||
pub struct BindingMeta {
|
||||
/// A map of parameter names to uniform binding metadata.
|
||||
pub parameter_meta: FxHashMap<String, VariableMeta>,
|
||||
pub parameter_meta: FastHashMap<String, VariableMeta>,
|
||||
/// A map of unique semantics to uniform binding metadata.
|
||||
pub unique_meta: FxHashMap<UniqueSemantics, VariableMeta>,
|
||||
pub unique_meta: FastHashMap<UniqueSemantics, VariableMeta>,
|
||||
/// A map of texture semantics to texture binding points.
|
||||
pub texture_meta: FxHashMap<Semantic<TextureSemantics>, TextureBinding>,
|
||||
pub texture_meta: FastHashMap<Semantic<TextureSemantics>, TextureBinding>,
|
||||
/// A map of texture semantics to texture size uniform binding metadata.
|
||||
pub texture_size_meta: FxHashMap<Semantic<TextureSemantics>, TextureSizeMeta>,
|
||||
pub texture_size_meta: FastHashMap<Semantic<TextureSemantics>, TextureSizeMeta>,
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue